diff --git a/CHANGELOG.md b/CHANGELOG.md index f9502210cd94d..48d6b1a6e757b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Package-specific changes not released in any SDK will be added here just before ### 📚 3rd party library updates +- Updated `@stripe/stripe-react-native` from `0.13.1` to `0.18.1` on iOS. ([#19055](https://github.com/expo/expo/pull/19055) by [@tsapeta](https://github.com/tsapeta)) + ### 🛠 Breaking changes ### 🎉 New features diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 935beb7521543..dd8717078686c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -623,8 +623,8 @@ PODS: - ABI45_0_0React-RCTImage - ABI45_0_0stripe-react-native (0.6.0): - ABI45_0_0React-Core - - Stripe (~> 22.4.0) - - StripeFinancialConnections (~> 22.4.0) + - Stripe (~> 22.7.0) + - StripeFinancialConnections (~> 22.7.0) - ABI45_0_0UMAppLoader (3.1.0) - ABI45_0_0Yoga (1.14.0) - ABI46_0_0EASClient (0.3.0): @@ -1269,8 +1269,8 @@ PODS: - ABI46_0_0React-RCTImage - ABI46_0_0stripe-react-native (0.13.1): - ABI46_0_0React-Core - - Stripe (~> 22.4.0) - - StripeFinancialConnections (~> 22.4.0) + - Stripe (~> 22.7.0) + - StripeFinancialConnections (~> 22.7.0) - ABI46_0_0UMAppLoader (3.1.0) - ABI46_0_0Yoga (1.14.0) - Amplitude (6.0.0) @@ -2027,27 +2027,27 @@ PODS: - RNScreens (3.15.0): - React-Core - React-RCTImage - - Stripe (22.4.0): - - Stripe/Stripe3DS2 (= 22.4.0) - - StripeApplePay (= 22.4.0) - - StripeCore (= 22.4.0) - - StripeUICore (= 22.4.0) - - stripe-react-native (0.13.1): + - Stripe (22.7.1): + - Stripe/Stripe3DS2 (= 22.7.1) + - StripeApplePay (= 22.7.1) + - StripeCore (= 22.7.1) + - StripeUICore (= 22.7.1) + - stripe-react-native (0.18.1): - React-Core - - Stripe (~> 22.4.0) - - StripeFinancialConnections (~> 22.4.0) - - Stripe/Stripe3DS2 (22.4.0): - - StripeApplePay (= 22.4.0) - - StripeCore (= 22.4.0) - - StripeUICore (= 22.4.0) - - StripeApplePay (22.4.0): - - StripeCore (= 22.4.0) - - StripeCore (22.4.0) - - StripeFinancialConnections (22.4.0): - - StripeCore (= 22.4.0) - - StripeUICore (= 22.4.0) - - StripeUICore (22.4.0): - - StripeCore (= 22.4.0) + - Stripe (~> 22.7.0) + - StripeFinancialConnections (~> 22.7.0) + - Stripe/Stripe3DS2 (22.7.1): + - StripeApplePay (= 22.7.1) + - StripeCore (= 22.7.1) + - StripeUICore (= 22.7.1) + - StripeApplePay (22.7.1): + - StripeCore (= 22.7.1) + - StripeCore (22.7.1) + - StripeFinancialConnections (22.7.1): + - StripeCore (= 22.7.1) + - StripeUICore (= 22.7.1) + - StripeUICore (22.7.1): + - StripeCore (= 22.7.1) - UMAppLoader (3.1.0) - Yoga (1.14.0) - ZXingObjC/Core (3.6.5) @@ -3223,7 +3223,7 @@ SPEC CHECKSUMS: ABI45_0_0RNGestureHandler: c6a3399e797896b51aaafa5ffd9ef3217a965554 ABI45_0_0RNReanimated: 7dbc4ffd288bfbb8657e5d28d8c633108a4fbc7a ABI45_0_0RNScreens: 09474fa5b21fada696fcfda7029ea1de53af363b - ABI45_0_0stripe-react-native: 165947b4b857c03da141aae92470620c0d2e742c + ABI45_0_0stripe-react-native: 48734eb853a923bbf041ceab5ac065c9a2c08d49 ABI45_0_0UMAppLoader: 9f4f8ab793e254030a3e1986ce2d75b89308168b ABI45_0_0Yoga: 59ff587f79c3e37f52418bef27055ecf8e0bce1c ABI46_0_0EASClient: c419fef100fbdac83e755c790d6b427594d4263e @@ -3330,7 +3330,7 @@ SPEC CHECKSUMS: ABI46_0_0RNGestureHandler: 42f00c4ce0ac12c4c51081de65e2ba831dfd5cb7 ABI46_0_0RNReanimated: c68969af1eae15d09ee5b8b3b61407feb5db9884 ABI46_0_0RNScreens: f521a4613dc2207aab4fcd72b5bb7cef74104a39 - ABI46_0_0stripe-react-native: 7f12b5c30e713ef7d444295e7021acb13ccff2fb + ABI46_0_0stripe-react-native: 599bc13001fb6eae9a687e1eb587c720d04ee744 ABI46_0_0UMAppLoader: 271ab738d153f711d91da6b1b7a3a981d668dcfe ABI46_0_0Yoga: 898c6ba86e91d19b0810efa11e8a6c7e5559a64d Amplitude: cc34fcd8dfffc3470bc2e05f3a4abb0178f6d963 @@ -3483,12 +3483,12 @@ SPEC CHECKSUMS: RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50 RNReanimated: 5c8c17e26787fd8984cd5accdc70fef2ca70aafd RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7 - Stripe: a925dfa9a7e51aa8f782f428abc10cb828b45a85 - stripe-react-native: 01069cba9dfaaf108df9c65a266ff753f311ddc8 - StripeApplePay: 369857bafe8baf02e31b47478db1574febd2a2f3 - StripeCore: a94d2823817c97c79fce60884ab9027a2da798c1 - StripeFinancialConnections: 269658b79f639c2d6b7d513235d469418b04c2dd - StripeUICore: a8e24a6c91a5c99075c7a490d45695fb912876e8 + Stripe: fb29a476e4866fec4ef22fb76207363dd32795aa + stripe-react-native: 5663bf9de94bff6b3d92c16d32784433b5e94cf7 + StripeApplePay: 09955cdf3f49b367af2feadd9c5b3bddb35446c0 + StripeCore: 39ea580c26ccc324fb9671288a2ae21114e54dca + StripeFinancialConnections: 0e1d638388572d52ce829416fbc7b0af2bde3865 + StripeUICore: eed17e95a4517fc02482e250a6422c2a81a14ce8 UMAppLoader: 6185e8c45922f187002b85afae097961c4781df4 Yoga: 7ab6e3ee4ce47d7b789d1cb520163833e515f452 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb diff --git a/ios/vendored/sdk45/@stripe/stripe-react-native/ABI45_0_0stripe-react-native.podspec.json b/ios/vendored/sdk45/@stripe/stripe-react-native/ABI45_0_0stripe-react-native.podspec.json index dbbdd689a4c77..2605a544fbcf8 100644 --- a/ios/vendored/sdk45/@stripe/stripe-react-native/ABI45_0_0stripe-react-native.podspec.json +++ b/ios/vendored/sdk45/@stripe/stripe-react-native/ABI45_0_0stripe-react-native.podspec.json @@ -16,10 +16,10 @@ "dependencies": { "ABI45_0_0React-Core": [], "Stripe": [ - "~> 22.4.0" + "~> 22.7.0" ], "StripeFinancialConnections": [ - "~> 22.4.0" + "~> 22.7.0" ] }, "pod_target_xcconfig": { diff --git a/ios/vendored/sdk46/@stripe/stripe-react-native/ABI46_0_0stripe-react-native.podspec.json b/ios/vendored/sdk46/@stripe/stripe-react-native/ABI46_0_0stripe-react-native.podspec.json index 7a75a480dfc41..fa591732f831f 100644 --- a/ios/vendored/sdk46/@stripe/stripe-react-native/ABI46_0_0stripe-react-native.podspec.json +++ b/ios/vendored/sdk46/@stripe/stripe-react-native/ABI46_0_0stripe-react-native.podspec.json @@ -16,10 +16,10 @@ "dependencies": { "ABI46_0_0React-Core": [], "Stripe": [ - "~> 22.4.0" + "~> 22.7.0" ], "StripeFinancialConnections": [ - "~> 22.4.0" + "~> 22.7.0" ] }, "pod_target_xcconfig": { diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/ApplePayUtils.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/ApplePayUtils.swift new file mode 100644 index 0000000000000..e8dc6fb183f82 --- /dev/null +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/ApplePayUtils.swift @@ -0,0 +1,159 @@ +// +// ApplePayUtils.swift +// stripe-react-native +// +// Created by Charles Cruzan on 6/27/22. +// + +import Foundation +import Stripe + +class ApplePayUtils { + + @available(iOS 15.0, *) + internal class func createDeferredPaymentSummaryItem(item: [String : Any]) throws -> PKPaymentSummaryItem { + let label = item["label"] as? String ?? "" + let amount = NSDecimalNumber(string: item["amount"] as? String ?? "") + + let deferredItem = PKDeferredPaymentSummaryItem( + label: label, + amount: amount + ) + guard let date = item["deferredDate"] as? Double else { + throw ApplePayUtilsError.missingParameter(label, "deferredDate") + } + deferredItem.deferredDate = Date(timeIntervalSince1970: date) + return deferredItem + } + + @available(iOS 15.0, *) + internal class func createRecurringPaymentSummaryItem(item: [String : Any]) throws -> PKPaymentSummaryItem { + let label = item["label"] as? String ?? "" + let amount = NSDecimalNumber(string: item["amount"] as? String ?? "") + + let recurringItem = PKRecurringPaymentSummaryItem( + label: label, + amount: amount + ) + guard let intervalCount = item["intervalCount"] as? Int else { + throw ApplePayUtilsError.missingParameter(label, "intervalCount") + } + recurringItem.intervalCount = intervalCount + recurringItem.intervalUnit = try mapToIntervalUnit(intervalString: item["intervalUnit"] as? String) + if let startDate = item["startDate"] as? Double { + recurringItem.startDate = Date(timeIntervalSince1970: startDate) + } + if let endDate = item["endDate"] as? Double { + recurringItem.endDate = Date(timeIntervalSince1970: endDate) + } + return recurringItem + } + + internal class func mapToIntervalUnit(intervalString: String?) throws -> NSCalendar.Unit { + switch intervalString { + case "minute": + return NSCalendar.Unit.minute + case "hour": + return NSCalendar.Unit.hour + case "day": + return NSCalendar.Unit.day + case "month": + return NSCalendar.Unit.month + case "year": + return NSCalendar.Unit.year + default: + throw ApplePayUtilsError.invalidTimeInterval(intervalString ?? "null") + } + } + + internal class func createImmediatePaymentSummaryItem(item: [String : Any]) -> PKPaymentSummaryItem { + let label = item["label"] as? String ?? "" + let amount = NSDecimalNumber(string: item["amount"] as? String ?? "") + + return PKPaymentSummaryItem( + label: label, + amount: amount, + type: item["isPending"] as? Bool ?? false ? + PKPaymentSummaryItemType.pending : PKPaymentSummaryItemType.final + ) + } + + public class func buildPaymentSummaryItems(items: [[String : Any]]?) throws -> [PKPaymentSummaryItem] { + var paymentSummaryItems: [PKPaymentSummaryItem] = [] + if let items = items { + for item in items { + let paymentSummaryItem = try buildPaymentSummaryItem(item: item) + paymentSummaryItems.append(paymentSummaryItem) + } + } + + return paymentSummaryItems + } + + internal class func buildPaymentSummaryItem(item: [String : Any]) throws -> PKPaymentSummaryItem { + switch item["paymentType"] as? String { + case "Deferred": + if #available(iOS 15.0, *) { + return try createDeferredPaymentSummaryItem(item: item) + } else { + return createImmediatePaymentSummaryItem(item: item) + } + case "Recurring": + if #available(iOS 15.0, *) { + return try createRecurringPaymentSummaryItem(item: item) + } else { + return createImmediatePaymentSummaryItem(item: item) + } + case "Immediate": + return createImmediatePaymentSummaryItem(item: item) + default: + throw ApplePayUtilsError.invalidCartSummaryItemType(item["paymentType"] as? String ?? "null") + } + } + + public class func buildPaymentSheetApplePayConfig( + merchantIdentifier: String?, + merchantCountryCode: String?, + paymentSummaryItems: [[String : Any]]? + ) throws -> PaymentSheet.ApplePayConfiguration { + guard let merchantId = merchantIdentifier else { + throw ApplePayUtilsError.missingMerchantId + } + guard let countryCode = merchantCountryCode else { + throw ApplePayUtilsError.missingCountryCode + } + let paymentSummaryItems = try ApplePayUtils.buildPaymentSummaryItems( + items: paymentSummaryItems + ) + return PaymentSheet.ApplePayConfiguration.init( + merchantId: merchantId, + merchantCountryCode: countryCode, + paymentSummaryItems:paymentSummaryItems.count > 0 ? paymentSummaryItems : nil + ) + } +} + +enum ApplePayUtilsError : Error, Equatable { + case invalidCartSummaryItemType(String) + case missingParameter(String, String) + case invalidTimeInterval(String) + case missingMerchantId + case missingCountryCode +} + +extension ApplePayUtilsError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidCartSummaryItemType(let type): + return "Failed to ceate Apple Pay summary item. Expected `type` to be one of 'Immediate', 'Recurring', or 'Deferred', but received: \(type)" + case .missingParameter(let label, let parameter): + return "Failed to create Apple Pay summary item with label: \(label). The \(parameter) item parameter is required, but none was provided." + case .invalidTimeInterval(let providedInterval): + return "Failed to create Apple Pay summary item. \(providedInterval) is not a valid timeInterval, must be one of: minute, hour, day, month, or year." + case .missingMerchantId: + return "`merchantIdentifier` is required, but none was found. Ensure you are passing this to initStripe your StripeProvider." + case .missingCountryCode: + return "`merchantCountryCode` is a required param, but was not provided." + } + } +} diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFieldView.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFieldView.swift index ebbe37c8f8238..0f9284381c300 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFieldView.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFieldView.swift @@ -132,13 +132,16 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) { if onCardChange != nil { - let brand = STPCardValidator.brand(forNumber: textField.cardParams.number ?? "") - let validExpiryDate = STPCardValidator.validationState(forExpirationYear: textField.cardParams.expYear?.stringValue ?? "", inMonth: textField.cardParams.expMonth?.stringValue ?? "") - let validCVC = STPCardValidator.validationState(forCVC: textField.cardParams.cvc ?? "", cardBrand: brand) - let validNumber = STPCardValidator.validationState(forNumber: textField.cardParams.number ?? "", validatingCardBrand: true) + let brand = STPCardValidator.brand(forNumber: textField.cardNumber ?? "") + let validExpiryDate = STPCardValidator.validationState( + forExpirationYear: textField.formattedExpirationYear ?? "", + inMonth: textField.formattedExpirationMonth ?? "" + ) + let validCVC = STPCardValidator.validationState(forCVC: textField.cvc ?? "", cardBrand: brand) + let validNumber = STPCardValidator.validationState(forNumber: textField.cardNumber ?? "", validatingCardBrand: true) var cardData: [String: Any?] = [ - "expiryMonth": textField.cardParams.expMonth ?? NSNull(), - "expiryYear": textField.cardParams.expYear ?? NSNull(), + "expiryMonth": textField.expirationMonth, + "expiryYear": textField.expirationYear, "complete": textField.isValid, "brand": Mappers.mapFromCardBrand(brand) ?? NSNull(), "last4": textField.cardParams.last4 ?? "", @@ -150,8 +153,8 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { cardData["postalCode"] = textField.postalCode ?? "" } if (dangerouslyGetFullCardDetails) { - cardData["number"] = textField.cardParams.number ?? "" - cardData["cvc"] = textField.cardParams.cvc ?? "" + cardData["number"] = textField.cardNumber ?? "" + cardData["cvc"] = textField.cvc ?? "" } onCardChange!(cardData as [AnyHashable : Any]) } diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFormView.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFormView.swift index fdc2b4488bdcb..f0bc39b2ad3b2 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFormView.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/CardFormView.swift @@ -75,6 +75,26 @@ class CardFormView: UIView, STPCardFormViewDelegate { if let backgroundColor = cardStyle["backgroundColor"] as? String { cardForm?.backgroundColor = UIColor(hexString: backgroundColor) } + /** + The following reveals a bug in STPCardFormView where there's a extra space in the layer, + and thus must remain commented out for now. + + if let borderWidth = cardStyle["borderWidth"] as? Int { + cardForm?.layer.borderWidth = CGFloat(borderWidth) + } else { + cardForm?.layer.borderWidth = CGFloat(0) + } + + */ + if let borderColor = cardStyle["borderColor"] as? String { + cardForm?.layer.borderColor = UIColor(hexString: borderColor).cgColor + } + if let borderRadius = cardStyle["borderRadius"] as? Int { + cardForm?.layer.cornerRadius = CGFloat(borderRadius) + } + if let cursorColor = cardStyle["cursorColor"] as? String { + cardForm?.tintColor = UIColor(hexString: cursorColor) + } // if let disabledBackgroundColor = cardStyle["disabledBackgroundColor"] as? String { // cardForm?.disabledBackgroundColor = UIColor(hexString: disabledBackgroundColor) // } diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Errors.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Errors.swift index 66083042b52dc..056c58dc16ef1 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Errors.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Errors.swift @@ -1,4 +1,5 @@ import Stripe +@_spi(STP) import StripeCore enum ErrorType { static let Failed = "Failed" @@ -50,7 +51,7 @@ class Errors { class func createError (_ code: String, _ error: NSError?) -> NSDictionary { let value: NSDictionary = [ "code": code, - "message": error?.userInfo[STPError.errorMessageKey] ?? NSNull(), + "message": error?.userInfo[STPError.errorMessageKey] ?? error?.localizedDescription ?? NSNull(), "localizedMessage": error?.localizedDescription ?? NSNull(), "declineCode": error?.userInfo[STPError.stripeDeclineCodeKey] ?? NSNull(), "stripeErrorCode": error?.userInfo[STPError.stripeErrorCodeKey] ?? NSNull(), @@ -84,5 +85,15 @@ class Errors { return ["error": value] } + + class func createError(_ code: String, _ error: Error) -> NSDictionary { + if let stripeError = error as? StripeError { + return createError(code, NSError.stp_error(from: stripeError)) + } + + return createError(code, error as NSError) + } + + static let MISSING_INIT_ERROR = Errors.createError(ErrorType.Failed, "Stripe has not been initialized. Initialize Stripe in your app with the StripeProvider component or the initStripe method.") } diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/FinancialConnections.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/FinancialConnections.swift new file mode 100644 index 0000000000000..8b44a8866e030 --- /dev/null +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/FinancialConnections.swift @@ -0,0 +1,258 @@ +// +// FinancialConnections.swift +// stripe-react-native +// +// Created by Charles Cruzan on 7/12/22. +// + +import Foundation +import StripeFinancialConnections +import Stripe + +class FinancialConnections { + + internal static func present( + withClientSecret: String, + resolve: @escaping RCTPromiseResolveBlock + ) -> Void { + DispatchQueue.main.async { + FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).present( + from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()), + completion: { result in + switch result { + case .completed(session: let session): + resolve([ "session": mapFromSessionResult(session) ]) + case .canceled: + resolve(Errors.createError(ErrorType.Canceled, "The flow has been canceled.")) + case .failed(let error): + resolve(Errors.createError(ErrorType.Failed, error)) + } + }) + } + } + + internal static func presentForToken( + withClientSecret: String, + resolve: @escaping RCTPromiseResolveBlock + ) -> Void { + DispatchQueue.main.async { + FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).presentForToken( + from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()), + completion: { result in + switch result { + case .completed(result: let result): + resolve( + [ + "session": mapFromSessionResult(result.session), + "token" : mapFromTokenResult(result.token) + ] + ) + case .canceled: + resolve(Errors.createError(ErrorType.Canceled, "The flow has been canceled.")) + case .failed(let error): + resolve(Errors.createError(ErrorType.Failed, error)) + } + }) + } + } + + internal static func mapFromSessionResult( + _ session: StripeAPI.FinancialConnectionsSession + ) -> NSDictionary { + return [ + "id": session.id, + "clientSecret": session.clientSecret, + "livemode": session.livemode, + "accounts": mapFromAccountsList(accounts: session.accounts) + ] + } + + internal static func mapFromTokenResult( + _ token: StripeAPI.BankAccountToken? + ) -> NSDictionary { + return [ + "bankAccount": mapFromBankAccount(bankAccount: token?.bankAccount) ?? NSNull(), + "livemode": token?.livemode ?? false, + "id": token?.id ?? NSNull(), + "used": token?.used ?? false, + "type": Mappers.mapFromTokenType(STPTokenType.bankAccount) ?? NSNull(), + "created": NSNull(), // Doesn't exist on StripeAPI.BankAccountToken + ] + } + + internal static func mapFromBankAccount( + bankAccount: StripeAPI.BankAccountToken.BankAccount? + ) -> NSDictionary? { + guard let bankAccount = bankAccount else { + return nil + } + // return Mappers.mapFromBankAccount(bankAccount) Cannot use this since it expects an STPBankAccount + return [ + "id": bankAccount.id, + "bankName": bankAccount.bankName ?? NSNull(), + "accountHolderName": bankAccount.accountHolderName ?? NSNull(), + "accountHolderType": NSNull(), // Doesn't exist on StripeAPI.BankAccountToken + "currency": bankAccount.currency, + "country": bankAccount.country, + "routingNumber": bankAccount.routingNumber ?? NSNull(), + "fingerprint": bankAccount.fingerprint ?? NSNull(), + "last4": bankAccount.last4, + "status": bankAccount.status.prefix(1).uppercased() + bankAccount.status.lowercased().dropFirst(), // stripe-ios returns a string, not STPBankAccountStatus + ] + } + + internal static func mapFromAccountsList( + accounts: StripeAPI.FinancialConnectionsSession.AccountList + ) -> [[String: Any]] { + var result = [[String: Any]]() + + for account in accounts.data { + result.append([ + "id": account.id, + "livemode": account.livemode, + "displayName": account.displayName ?? NSNull(), + "status": mapFromStatus(account.status), + "institutionName": account.institutionName, + "last4": account.last4 ?? NSNull(), + "created": account.created * 1000, + "balance": mapFromAccountBalance(balance: account.balance) ?? NSNull(), + "balanceRefresh": mapFromAccountBalanceRefresh(balanceRefresh: account.balanceRefresh) ?? NSNull(), + "category": mapFromCategory(account.category), + "subcategory": mapFromSubcategory(account.subcategory), + "permissions": account.permissions?.map { mapFromPermission($0) } ?? NSNull(), + "supportedPaymentMethodTypes": account.supportedPaymentMethodTypes.map { mapFromSupportedPaymentMethodTypes($0) }, + ]) + } + + return result + } + + internal static func mapFromAccountBalance( + balance: StripeAPI.FinancialConnectionsAccount.Balance? + ) -> NSDictionary? { + guard let balance = balance else { + return nil + } + + return [ + "asOf": balance.asOf * 1000, + "type": mapFromBalanceType(balance.type), +// TODO: Protected by internal on iOS only. PR is out to fix + "cash": ["available": NSNull()], // balance.cash?.available + "credit": ["used": NSNull()], // balance.credit?.used + "current": balance.current, + ] + } + + internal static func mapFromAccountBalanceRefresh( + balanceRefresh: StripeAPI.FinancialConnectionsAccount.BalanceRefresh? + ) -> NSDictionary? { + guard let balanceRefresh = balanceRefresh else { + return nil + } + + return [ + "status": mapFromBalanceRefreshStatus(balanceRefresh.status), + "lastAttemptedAt": balanceRefresh.lastAttemptedAt * 1000, + ] + } + + internal static func mapFromStatus( _ status: StripeAPI.FinancialConnectionsAccount.Status) -> String { + switch status { + case .active: + return "active" + case .inactive: + return "inactive" + case .disconnected: + return "disconnected" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromCategory( _ category: StripeAPI.FinancialConnectionsAccount.Category) -> String { + switch category { + case .cash: + return "cash" + case .credit: + return "credit" + case .investment: + return "investment" + case .other: + return "other" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromSubcategory( _ subcategory: StripeAPI.FinancialConnectionsAccount.Subcategory) -> String { + switch subcategory { + case .savings: + return "savings" + case .mortgage: + return "mortgage" + case .checking: + return "checking" + case .creditCard: + return "creditCard" + case .lineOfCredit: + return "lineOfCredit" + case .other: + return "other" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromPermission( _ permission: StripeAPI.FinancialConnectionsAccount.Permissions) -> String { + switch permission { + case .transactions: + return "transactions" + case .ownership: + return "ownership" + case .paymentMethod: + return "paymentMethod" + case .accountNumbers: + return "accountNumbers" + case .balances: + return "balances" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromSupportedPaymentMethodTypes( _ type: StripeAPI.FinancialConnectionsAccount.SupportedPaymentMethodTypes) -> String { + switch type { + case .usBankAccount: + return "usBankAccount" + case .link: + return "link" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromBalanceType( _ type: StripeAPI.FinancialConnectionsAccount.Balance.ModelType) -> String { + switch type { + case .cash: + return "cash" + case .credit: + return "credit" + case .unparsable: + return "unparsable" + } + } + + internal static func mapFromBalanceRefreshStatus( _ status: StripeAPI.FinancialConnectionsAccount.BalanceRefresh.Status) -> String { + switch status { + case .succeeded: + return "succeeded" + case .pending: + return "pending" + case .failed: + return "failed" + case .unparsable: + return "unparsable" + } + } +} diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Mappers.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Mappers.swift index 483300415663a..25af05059d9f1 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Mappers.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/Mappers.swift @@ -16,17 +16,6 @@ class Mappers { } } - class func mapToPaymentSummaryItemType(type: String?) -> PKPaymentSummaryItemType { - if let type = type { - switch type { - case "pending": return PKPaymentSummaryItemType.pending - case "final": return PKPaymentSummaryItemType.final - default: return PKPaymentSummaryItemType.final - } - } - return PKPaymentSummaryItemType.final - } - class func mapFromBankAccountHolderType(_ type: STPBankAccountHolderType?) -> String? { if let type = type { switch type { @@ -61,18 +50,21 @@ class Mappers { } class func mapFromBankAccount(_ bankAccount: STPBankAccount?) -> NSDictionary? { - if (bankAccount == nil) { + guard let bankAccount = bankAccount else { return nil } + let result: NSDictionary = [ - "id": bankAccount?.stripeID ?? NSNull(), - "bankName": bankAccount?.bankName ?? NSNull(), - "accountHolderName": bankAccount?.accountHolderName ?? NSNull(), - "accountHolderType": mapFromBankAccountHolderType(bankAccount?.accountHolderType) ?? NSNull(), - "country": bankAccount?.country ?? NSNull(), - "currency": bankAccount?.currency ?? NSNull(), - "routingNumber": bankAccount?.routingNumber ?? NSNull(), - "status": mapFromBankAccountStatus(bankAccount?.status) ?? NSNull(), + "id": bankAccount.stripeID, + "bankName": bankAccount.bankName ?? NSNull(), + "accountHolderName": bankAccount.accountHolderName ?? NSNull(), + "accountHolderType": mapFromBankAccountHolderType(bankAccount.accountHolderType) ?? NSNull(), + "country": bankAccount.country ?? NSNull(), + "currency": bankAccount.currency ?? NSNull(), + "routingNumber": bankAccount.routingNumber ?? NSNull(), + "status": mapFromBankAccountStatus(bankAccount.status) ?? NSNull(), + "fingerprint": bankAccount.fingerprint ?? NSNull(), + "last4": bankAccount.last4 ?? NSNull() ] return result } @@ -171,8 +163,12 @@ class Mappers { let amount = NSDecimalNumber(string: method["amount"] as? String ?? "") let identifier = method["identifier"] as! String let detail = method["detail"] as? String ?? "" - let type = Mappers.mapToPaymentSummaryItemType(type: method["type"] as? String) - let pm = PKShippingMethod.init(label: label, amount: amount, type: type) + let pm = PKShippingMethod.init( + label: label, + amount: amount, + type: method["isPending"] as? Bool ?? false + ? PKPaymentSummaryItemType.pending : PKPaymentSummaryItemType.final + ) pm.identifier = identifier pm.detail = detail shippingMethodsList.append(pm) @@ -283,6 +279,7 @@ class Mappers { case STPPaymentMethodType.klarna: return "Klarna" case STPPaymentMethodType.USBankAccount: return "USBankAccount" case STPPaymentMethodType.payPal: return "PayPal" + case STPPaymentMethodType.affirm: return "Affirm" case STPPaymentMethodType.unknown: return "Unknown" default: return "Unknown" } @@ -312,6 +309,7 @@ class Mappers { case "WeChatPay": return STPPaymentMethodType.weChatPay case "USBankAccount": return STPPaymentMethodType.USBankAccount case "PayPal": return STPPaymentMethodType.payPal + case "Affirm": return STPPaymentMethodType.affirm default: return STPPaymentMethodType.unknown } } diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/PaymentMethodFactory.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/PaymentMethodFactory.swift index f78b838d9c7ae..4370cbb17b728 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/PaymentMethodFactory.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/PaymentMethodFactory.swift @@ -53,6 +53,8 @@ class PaymentMethodFactory { return try createUSBankAccountPaymentMethodParams() case STPPaymentMethodType.payPal: return try createPayPalPaymentMethodParams() + case STPPaymentMethodType.affirm: + return try createAffirmPaymentMethodParams() // case STPPaymentMethodType.weChatPay: // return try createWeChatPayPaymentMethodParams() default: @@ -102,6 +104,8 @@ class PaymentMethodFactory { return try createUSBankAccountPaymentMethodOptions() case STPPaymentMethodType.payPal: return nil + case STPPaymentMethodType.affirm: + return nil default: throw PaymentMethodError.paymentNotSupported } @@ -361,6 +365,11 @@ class PaymentMethodFactory { private func createPayPalPaymentMethodParams() throws -> STPPaymentMethodParams { return STPPaymentMethodParams(payPal: STPPaymentMethodPayPalParams(), billingDetails: billingDetailsParams, metadata: nil) } + + private func createAffirmPaymentMethodParams() throws -> STPPaymentMethodParams { + let params = STPPaymentMethodAffirmParams() + return STPPaymentMethodParams(affirm: params, metadata: nil) + } } enum PaymentMethodError: Error { diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.m b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.m index 74c4ccd940e04..a8c2f16c5dbe8 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.m +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.m @@ -121,10 +121,24 @@ @interface RCT_EXTERN_MODULE(StripeSdk, RCTEventEmitter) resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject ) +RCT_EXTERN_METHOD( + canAddCardToWallet:(NSDictionary *)params + resolver: (RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) RCT_EXTERN_METHOD( isCardInWallet:(NSDictionary *)params resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject ) - +RCT_EXTERN_METHOD( + collectBankAccountToken:(NSString *)clientSecret + resolver: (RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) +RCT_EXTERN_METHOD( + collectFinancialConnectionsAccounts:(NSString *)clientSecret + resolver: (RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) @end diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.swift index cb03088f0984b..41ddbcfcc14e8 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/StripeSdk.swift @@ -1,5 +1,6 @@ import PassKit import Stripe +import StripeFinancialConnections @objc(StripeSdk) class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionViewControllerDelegate, UIAdaptivePresentationControllerDelegate { @@ -85,12 +86,15 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } } - if params["applePay"] as? Bool == true { - if let merchantIdentifier = self.merchantIdentifier, let merchantCountryCode = params["merchantCountryCode"] as? String { - configuration.applePay = .init(merchantId: merchantIdentifier, - merchantCountryCode: merchantCountryCode) - } else { - resolve(Errors.createError(ErrorType.Failed, "Either merchantIdentifier or merchantCountryCode is missing")) + if let applePayParams = params["applePay"] as? NSDictionary { + do { + configuration.applePay = try ApplePayUtils.buildPaymentSheetApplePayConfig( + merchantIdentifier: self.merchantIdentifier, + merchantCountryCode: applePayParams["merchantCountryCode"] as? String, + paymentSummaryItems: applePayParams["paymentSummaryItems"] as? [[String : Any]] + ) + } catch { + resolve(Errors.createError(ErrorType.Failed, error.localizedDescription)) return } } @@ -145,15 +149,14 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.createError(ErrorType.Failed, error as NSError)) case .success(let paymentSheetFlowController): self.paymentSheetFlowController = paymentSheetFlowController + var result: NSDictionary? = nil if let paymentOption = stripeSdk?.paymentSheetFlowController?.paymentOption { - let option: NSDictionary = [ + result = [ "label": paymentOption.label, "image": paymentOption.image.pngData()?.base64EncodedString() ?? "" ] - resolve(Mappers.createResult("paymentOption", option)) - } else { - resolve(Mappers.createResult("paymentOption", nil)) } + resolve(Mappers.createResult("paymentOption", result)) } } @@ -285,6 +288,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi if (paymentMethodType == .payPal) { resolve(Errors.createError(ErrorType.Failed, "PayPal is not yet supported through SetupIntents.")) + return } var err: NSDictionary? = nil @@ -344,17 +348,16 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.createError(ErrorType.Failed, "You can use this method only after either onDidSetShippingMethod or onDidSetShippingContact events emitted")) return } - var paymentSummaryItems: [PKPaymentSummaryItem] = [] - if let items = summaryItems as? [[String : Any]] { - for item in items { - let label = item["label"] as? String ?? "" - let amount = NSDecimalNumber(string: item["amount"] as? String ?? "") - let type = Mappers.mapToPaymentSummaryItemType(type: item["type"] as? String) - paymentSummaryItems.append(PKPaymentSummaryItem(label: label, amount: amount, type: type)) - } + + var paymentSummaryItems : [PKPaymentSummaryItem] = [] + do { + paymentSummaryItems = try ApplePayUtils.buildPaymentSummaryItems(items: summaryItems as? [[String : Any]]) + } catch { + resolve(Errors.createError(ErrorType.Failed, error.localizedDescription)) + return } + var shippingAddressErrors: [Error] = [] - for item in errorAddressFields { let field = item["field"] as! String let message = item["message"] as? String ?? field + " error" @@ -513,18 +516,14 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi paymentRequest.shippingMethods = Mappers.mapToShippingMethods(shippingMethods: shippingMethods) - var paymentSummaryItems: [PKPaymentSummaryItem] = [] - - if let items = summaryItems as? [[String : Any]] { - for item in items { - let label = item["label"] as? String ?? "" - let amount = NSDecimalNumber(string: item["amount"] as? String ?? "") - let type = Mappers.mapToPaymentSummaryItemType(type: item["type"] as? String) - paymentSummaryItems.append(PKPaymentSummaryItem(label: label, amount: amount, type: type)) - } + do { + paymentRequest.paymentSummaryItems = try ApplePayUtils + .buildPaymentSummaryItems(items: summaryItems as? [[String : Any]]) + } catch { + resolve(Errors.createError(ErrorType.Failed, error.localizedDescription)) + return } - paymentRequest.paymentSummaryItems = paymentSummaryItems if let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self) { DispatchQueue.main.async { applePayContext.presentApplePay(completion: nil) @@ -572,12 +571,10 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi STPAPIClient.shared.createPaymentMethod(with: paymentMethodParams) { paymentMethod, error in if let createError = error { resolve(Errors.createError(ErrorType.Failed, createError.localizedDescription)) - return - } - - if let paymentMethod = paymentMethod { - let method = Mappers.mapFromPaymentMethod(paymentMethod) - resolve(Mappers.createResult("paymentMethod", method)) + } else { + resolve( + Mappers.createResult("paymentMethod", Mappers.mapFromPaymentMethod(paymentMethod)) + ) } } } else { @@ -763,10 +760,11 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi if let intent = intent { if (intent.status == .requiresPaymentMethod) { resolve(Errors.createError(ErrorType.Canceled, "Bank account collection was canceled.")) + } else { + resolve( + Mappers.createResult("paymentIntent", Mappers.mapFromPaymentIntent(paymentIntent: intent)) + ) } - resolve( - Mappers.createResult("paymentIntent", Mappers.mapFromPaymentIntent(paymentIntent: intent)) - ) } else { resolve(Errors.createError(ErrorType.Unknown, "There was unexpected error while collecting bank account information.")) } @@ -787,10 +785,11 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi if let intent = intent { if (intent.status == .requiresPaymentMethod) { resolve(Errors.createError(ErrorType.Canceled, "Bank account collection was canceled.")) + } else { + resolve( + Mappers.createResult("setupIntent", Mappers.mapFromSetupIntent(setupIntent: intent)) + ) } - resolve( - Mappers.createResult("setupIntent", Mappers.mapFromSetupIntent(setupIntent: intent)) - ) } else { resolve(Errors.createError(ErrorType.Unknown, "There was unexpected error while collecting bank account information.")) } @@ -802,7 +801,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi @objc(confirmPayment:data:options:resolver:rejecter:) func confirmPayment( paymentIntentClientSecret: String, - params: NSDictionary, + params: NSDictionary?, options: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock @@ -810,13 +809,13 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi self.confirmPaymentResolver = resolve self.confirmPaymentClientSecret = paymentIntentClientSecret - let paymentMethodData = params["paymentMethodData"] as? NSDictionary - let type = Mappers.mapToPaymentMethodType(type: params["paymentMethodType"] as? String) - guard let paymentMethodType = type else { - resolve(Errors.createError(ErrorType.Failed, "You must provide paymentMethodType")) + let paymentMethodData = params?["paymentMethodData"] as? NSDictionary + let (missingPaymentMethodError, paymentMethodType) = getPaymentMethodType(params: params) + if (missingPaymentMethodError != nil) { + resolve(missingPaymentMethodError) return } - + if (paymentMethodType == .FPX) { let testOfflineBank = paymentMethodData?["testOfflineBank"] as? Bool if (testOfflineBank == false || testOfflineBank == nil) { @@ -833,10 +832,24 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi STPPaymentHandler.shared().confirmPayment(paymentIntentParams, with: self, completion: onCompleteConfirmPayment) } } + + func getPaymentMethodType( + params: NSDictionary? + ) -> (NSDictionary?, STPPaymentMethodType?) { + if let params = params { + guard let paymentMethodType = Mappers.mapToPaymentMethodType(type: params["paymentMethodType"] as? String) else { + return (Errors.createError(ErrorType.Failed, "You must provide paymentMethodType"), nil) + } + return (nil, paymentMethodType) + } else { + // If params aren't provided, it means we expect that the payment method was attached on the server side + return (nil, nil) + } + } func createPaymentIntentParams( paymentIntentClientSecret: String, - paymentMethodType: STPPaymentMethodType, + paymentMethodType: STPPaymentMethodType?, paymentMethodData: NSDictionary?, options: NSDictionary ) -> (NSDictionary?, STPPaymentIntentParams) { @@ -848,6 +861,8 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi if (paymentMethodType == .USBankAccount && paymentMethodData == nil) { return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret, paymentMethodType: .USBankAccount) } else { + guard let paymentMethodType = paymentMethodType else { return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) } + let paymentMethodId = paymentMethodData?["paymentMethodId"] as? String let parameters = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) @@ -891,7 +906,6 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } else { resolve(Errors.createError(ErrorType.Unknown, error?.localizedDescription)) } - return } @@ -916,7 +930,6 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } else { resolve(Errors.createError(ErrorType.Unknown, error?.localizedDescription)) } - return } @@ -995,7 +1008,26 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } } } - + + @objc(canAddCardToWallet:resolver:rejecter:) + func canAddCardToWallet( + params: NSDictionary, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + guard let last4 = params["cardLastFour"] as? String else { + resolve(Errors.createError(ErrorType.Failed, "You must provide `cardLastFour`")) + return + } + let (canAddCard, status) = PushProvisioningUtils.canAddCardToWallet(last4: last4, + primaryAccountIdentifier: params["primaryAccountIdentifier"] as? String ?? "", + testEnv: params["testEnv"] as? Bool ?? false) + resolve([ + "canAddCard": canAddCard, + "details": ["status": status?.rawValue], + ]) + } + @objc(isCardInWallet:resolver:rejecter:) func isCardInWallet( params: NSDictionary, @@ -1006,17 +1038,35 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.createError(ErrorType.Failed, "You must provide `cardLastFour`")) return } - - let existingPass: PKPass? = { - if #available(iOS 13.4, *) { - return PKPassLibrary().passes(of: PKPassType.secureElement).first(where: {$0.secureElementPass?.primaryAccountNumberSuffix == last4}) - } else { - return PKPassLibrary().passes(of: PKPassType.payment).first(where: {$0.paymentPass?.primaryAccountNumberSuffix == last4}) - } - }() - resolve(["isInWallet": existingPass != nil]) + resolve(["isInWallet": PushProvisioningUtils.passExistsWith(last4: last4)]) } - + + @objc(collectBankAccountToken:resolver:rejecter:) + func collectBankAccountToken( + clientSecret: String, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + if (STPAPIClient.shared.publishableKey == nil) { + resolve(Errors.MISSING_INIT_ERROR) + return + } + FinancialConnections.presentForToken(withClientSecret: clientSecret, resolve: resolve) + } + + @objc(collectFinancialConnectionsAccounts:resolver:rejecter:) + func collectFinancialConnectionsAccounts( + clientSecret: String, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + if (STPAPIClient.shared.publishableKey == nil) { + resolve(Errors.MISSING_INIT_ERROR) + return + } + FinancialConnections.present(withClientSecret: clientSecret, resolve: resolve) + } + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { confirmPaymentResolver?(Errors.createError(ErrorType.Canceled, "FPX Payment has been canceled")) } diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/AddToWalletButtonView.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/AddToWalletButtonView.swift index 1fb8470d4e300..59b9dc6b2c4fc 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/AddToWalletButtonView.swift +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/AddToWalletButtonView.swift @@ -33,10 +33,6 @@ class AddToWalletButtonView: UIView { } } - func canAddPaymentPass() -> Bool { - return self.testEnv ? STPFakeAddPaymentPassViewController.canAddPaymentPass() : PKAddPaymentPassViewController.canAddPaymentPass() - } - override func didSetProps(_ changedProps: [String]!) { if let addToWalletButton = addToWalletButton { addToWalletButton.removeFromSuperview() @@ -53,7 +49,7 @@ class AddToWalletButtonView: UIView { } @objc func beginPushProvisioning() { - if (!canAddPaymentPass()) { + if (!PushProvisioningUtils.canAddPaymentPass(primaryAccountIdentifier: cardDetails?["primaryAccountIdentifier"] as? String ?? "", isTestMode: self.testEnv)) { onCompleteAction!( Errors.createError( ErrorType.Failed, diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/PushProvisioningUtils.swift b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/PushProvisioningUtils.swift new file mode 100644 index 0000000000000..ea5569b76e261 --- /dev/null +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/ios/pushprovisioning/PushProvisioningUtils.swift @@ -0,0 +1,64 @@ +// +// PushProvisioningUtils.swift +// stripe-react-native +// +// Created by Charles Cruzan on 6/9/22. +// + +import Foundation +import Stripe + +internal class PushProvisioningUtils { + class func canAddCardToWallet( + last4: String, + primaryAccountIdentifier: String, + testEnv: Bool + ) -> (canAddCard: Bool, status: AddCardToWalletStatus?) { + if (!PKAddPassesViewController.canAddPasses()) { + return (false, AddCardToWalletStatus.UNSUPPORTED_DEVICE) + } + + var status : AddCardToWalletStatus? = nil + var canAddCard = PushProvisioningUtils.canAddPaymentPass( + primaryAccountIdentifier: primaryAccountIdentifier, + isTestMode: testEnv) + + if (!canAddCard) { + status = AddCardToWalletStatus.MISSING_CONFIGURATION + } else if (PushProvisioningUtils.passExistsWith(last4: last4)) { + canAddCard = false + status = AddCardToWalletStatus.CARD_ALREADY_EXISTS + } + + return (canAddCard, status) + } + + class func canAddPaymentPass(primaryAccountIdentifier: String, isTestMode: Bool) -> Bool { + if (isTestMode) { + return STPFakeAddPaymentPassViewController.canAddPaymentPass() + } + + if #available(iOS 13.4, *) { + return PKPassLibrary().canAddSecureElementPass(primaryAccountIdentifier: primaryAccountIdentifier) + } else { + return PKAddPaymentPassViewController.canAddPaymentPass() + } + } + + class func passExistsWith(last4: String) -> Bool { + let existingPass: PKPass? = { + if #available(iOS 13.4, *) { + return PKPassLibrary().passes(of: PKPassType.secureElement).first(where: {$0.secureElementPass?.primaryAccountNumberSuffix == last4}) + } else { + return PKPassLibrary().passes(of: PKPassType.payment).first(where: {$0.paymentPass?.primaryAccountNumberSuffix == last4}) + } + }() + return existingPass != nil + } + + enum AddCardToWalletStatus: String { + case UNSUPPORTED_DEVICE + case MISSING_CONFIGURATION + case CARD_ALREADY_EXISTS + } +} diff --git a/ios/vendored/unversioned/@stripe/stripe-react-native/stripe-react-native.podspec.json b/ios/vendored/unversioned/@stripe/stripe-react-native/stripe-react-native.podspec.json index 46a1642dc376b..941e8b5e1ee69 100644 --- a/ios/vendored/unversioned/@stripe/stripe-react-native/stripe-react-native.podspec.json +++ b/ios/vendored/unversioned/@stripe/stripe-react-native/stripe-react-native.podspec.json @@ -1,6 +1,6 @@ { "name": "stripe-react-native", - "version": "0.13.1", + "version": "0.18.1", "summary": "Stripe SDK for React Native", "homepage": "https://github.com/stripe/stripe-react-native/#readme", "license": "MIT", @@ -10,18 +10,26 @@ }, "source": { "git": "https://github.com/stripe/stripe-react-native.git", - "tag": "0.13.1" + "tag": "0.18.1" }, "source_files": "ios/**/*.{h,m,mm,swift}", + "exclude_files": "ios/Tests/", "dependencies": { "React-Core": [], "Stripe": [ - "~> 22.4.0" + "~> 22.7.0" ], "StripeFinancialConnections": [ - "~> 22.4.0" + "~> 22.7.0" ] }, + "testspecs": [ + { + "name": "Tests", + "test_type": "unit", + "source_files": "ios/Tests/**/*.{m,swift}" + } + ], "pod_target_xcconfig": { "HEADER_SEARCH_PATHS": "\"${PODS_ROOT}/Stripe/Stripe3DS2\" \"${PODS_ROOT}/Headers/Public/Stripe\"" } diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 661ffb1cea93a..47c456806e65e 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -9,7 +9,7 @@ "@react-native-firebase/app": "~15.4.0", "@react-native-picker/picker": "2.4.2", "@react-native-segmented-control/segmented-control": "2.4.0", - "@stripe/stripe-react-native": "0.13.1", + "@stripe/stripe-react-native": "0.18.1", "expo-analytics-amplitude": "~11.3.0", "expo-app-auth": "~11.1.0", "expo-app-loader-provider": "~8.0.0",