Skip to content

Commit

Permalink
fix: Push Provisioning issues on iOS due to Apple bugs (#1305)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliecruzan-stripe authored Feb 21, 2023
1 parent 42d64fe commit 32e07e5
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 39 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Fixes

- Fixed an issue on iOS where `canAddCardToWallet` would always return a `details.status` of `UNSUPPORTED_DEVICE` on iPads. [#1305](https://github.com/stripe/stripe-react-native/pull/1305)
- Fixed an issue on iOS where `canAddCardToWallet` would always return a `{canAddCard: false}` if the card in question had been provsioned on the current device, but **had not yet been provisioned** on a paired Watch. [#1305](https://github.com/stripe/stripe-react-native/pull/1305)

## 0.24.0 - 2023-02-17

### Breaking changes
Expand Down
14 changes: 9 additions & 5 deletions ios/PaymentPassFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ internal class PaymentPassFinder: NSObject {
case PAIRED_DEVICE
}

class func findPassWithLast4(last4: String, hasPairedAppleWatch: Bool, completion: @escaping ((Bool, [PassLocation]) -> Void)) {
class func findPassWith(
primaryAccountIdentifier: String,
hasPairedAppleWatch: Bool,
completion: @escaping ((Bool, [PassLocation]) -> Void)
) {
let existingPassOnDevice: PKPass? = {
if #available(iOS 13.4, *) {
return PKPassLibrary().passes(of: PKPassType.secureElement)
.first(where: { $0.secureElementPass?.primaryAccountNumberSuffix == last4 && $0.secureElementPass?.passActivationState != .deactivated && !$0.isRemotePass })
.first(where: { $0.secureElementPass?.primaryAccountIdentifier == primaryAccountIdentifier && $0.secureElementPass?.passActivationState != .deactivated && !$0.isRemotePass })
} else {
return PKPassLibrary().passes(of: PKPassType.payment)
.first(where: { $0.paymentPass?.primaryAccountNumberSuffix == last4 && $0.paymentPass?.passActivationState != .deactivated && !$0.isRemotePass })
.first(where: { $0.paymentPass?.primaryAccountIdentifier == primaryAccountIdentifier && $0.paymentPass?.passActivationState != .deactivated && !$0.isRemotePass })
}
}()

Expand All @@ -41,10 +45,10 @@ internal class PaymentPassFinder: NSObject {
let existingPassOnPairedDevices: PKPass? = {
if #available(iOS 13.4, *) {
return PKPassLibrary().remoteSecureElementPasses
.first(where: { $0.secureElementPass?.primaryAccountNumberSuffix == last4 && $0.secureElementPass?.passActivationState != .deactivated })
.first(where: { $0.secureElementPass?.primaryAccountIdentifier == primaryAccountIdentifier && $0.secureElementPass?.passActivationState != .deactivated })
} else {
return PKPassLibrary().remotePaymentPasses()
.first(where: { $0.paymentPass?.primaryAccountNumberSuffix == last4 && $0.paymentPass?.passActivationState != .deactivated })
.first(where: { $0.paymentPass?.primaryAccountIdentifier == primaryAccountIdentifier && $0.paymentPass?.passActivationState != .deactivated })
}
}()

Expand Down
2 changes: 1 addition & 1 deletion ios/PushProvisioning/AddToWalletButtonView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class AddToWalletButtonView: UIView {
}

@objc func beginPushProvisioning() {
if (!PushProvisioningUtils.canAddPaymentPass(primaryAccountIdentifier: cardDetails?["primaryAccountIdentifier"] as? String ?? "", isTestMode: self.testEnv)) {
if (!PushProvisioningUtils.canAddPaymentPass(isTestMode: self.testEnv)) {
onCompleteAction!(
Errors.createError(
ErrorType.Failed,
Expand Down
27 changes: 8 additions & 19 deletions ios/PushProvisioning/PushProvisioningUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,18 @@ import Stripe

internal class PushProvisioningUtils {
class func canAddCardToWallet(
last4: String,
primaryAccountIdentifier: String,
testEnv: Bool,
hasPairedAppleWatch: Bool,
completion: @escaping (_ canAddCard: Bool, _ status: AddCardToWalletStatus?) -> Void
) {
if (!PKAddPassesViewController.canAddPasses()) {
if (!canAddPaymentPass(isTestMode: testEnv)) {
completion(false, AddCardToWalletStatus.UNSUPPORTED_DEVICE)
}

let canAddCard = canAddPaymentPass(
primaryAccountIdentifier: primaryAccountIdentifier,
isTestMode: testEnv)

if (!canAddCard) {
completion(canAddCard, AddCardToWalletStatus.MISSING_CONFIGURATION)
} else {
PaymentPassFinder.findPassWithLast4(last4: last4, hasPairedAppleWatch: hasPairedAppleWatch) { canAddCardToADevice, passLocations in
PaymentPassFinder.findPassWith(
primaryAccountIdentifier: primaryAccountIdentifier,
hasPairedAppleWatch: hasPairedAppleWatch)
{ canAddCardToADevice, passLocations in
var status: AddCardToWalletStatus? = nil
if (!canAddCardToADevice) {
status = AddCardToWalletStatus.CARD_ALREADY_EXISTS
Expand All @@ -41,16 +35,12 @@ internal class PushProvisioningUtils {
}
}

class func canAddPaymentPass(primaryAccountIdentifier: String, isTestMode: Bool) -> Bool {
class func canAddPaymentPass(isTestMode: Bool) -> Bool {
if (isTestMode) {
return STPFakeAddPaymentPassViewController.canAddPaymentPass()
}

if #available(iOS 13.4, *) {
return PKPassLibrary().canAddSecureElementPass(primaryAccountIdentifier: primaryAccountIdentifier)
} else {
return PKAddPaymentPassViewController.canAddPaymentPass()
}

return PKAddPaymentPassViewController.canAddPaymentPass()
}

class func getPassLocation(last4: String) -> PaymentPassFinder.PassLocation? {
Expand Down Expand Up @@ -79,7 +69,6 @@ internal class PushProvisioningUtils {

enum AddCardToWalletStatus: String {
case UNSUPPORTED_DEVICE
case MISSING_CONFIGURATION
case CARD_ALREADY_EXISTS
case CARD_EXISTS_ON_CURRENT_DEVICE
case CARD_EXISTS_ON_PAIRED_DEVICE
Expand Down
5 changes: 0 additions & 5 deletions ios/StripeSdk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1022,12 +1022,7 @@ class StripeSdk: RCTEventEmitter, STPBankSelectionViewControllerDelegate, UIAdap
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
}
PushProvisioningUtils.canAddCardToWallet(
last4: last4,
primaryAccountIdentifier: params["primaryAccountIdentifier"] as? String ?? "",
testEnv: params["testEnv"] as? Bool ?? false,
hasPairedAppleWatch: params["hasPairedAppleWatch"] as? Bool ?? false)
Expand Down
15 changes: 7 additions & 8 deletions ios/Tests/PushProvisioningTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,33 @@ import XCTest

class PushProvisioningTests: XCTestCase {
func testCanAddCardToWalletInTestMode() throws {
PushProvisioningUtils.canAddCardToWallet(last4: "4242",
primaryAccountIdentifier: "",
testEnv: true, hasPairedAppleWatch: false) { canAddCard, status in
PushProvisioningUtils.canAddCardToWallet(primaryAccountIdentifier: "",
testEnv: true,
hasPairedAppleWatch: false) { canAddCard, status in
XCTAssertEqual(canAddCard, true)
XCTAssertEqual(status, nil)
}
}

func testCanAddCardToWalletInLiveMode() throws {
PushProvisioningUtils.canAddCardToWallet(last4: "4242",
primaryAccountIdentifier: "",
PushProvisioningUtils.canAddCardToWallet(primaryAccountIdentifier: "",
testEnv: false,
hasPairedAppleWatch: false) { canAddCard, status in
XCTAssertEqual(canAddCard, false)
XCTAssertEqual(status, PushProvisioningUtils.AddCardToWalletStatus.MISSING_CONFIGURATION)
XCTAssertEqual(status, PushProvisioningUtils.AddCardToWalletStatus.UNSUPPORTED_DEVICE)
}
}

func testCanAddPaymentPassInTestMode() throws {
XCTAssertEqual(
PushProvisioningUtils.canAddPaymentPass(primaryAccountIdentifier: "", isTestMode: true),
PushProvisioningUtils.canAddPaymentPass(isTestMode: true),
true
)
}

func testCanAddPaymentPassInLiveMode() throws {
XCTAssertEqual(
PushProvisioningUtils.canAddPaymentPass(primaryAccountIdentifier: "", isTestMode: false),
PushProvisioningUtils.canAddPaymentPass(isTestMode: false),
false
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/types/PushProvisioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type IsCardInWalletResult =
export type CanAddCardToWalletParams = {
/** The `primary_account_identifier` value from the issued card. Can be an empty string. */
primaryAccountIdentifier: string | null;
/** Last 4 digits of the card number. */
/** Last 4 digits of the card number. Required for Android. */
cardLastFour: string;
/** iOS only. Set this to `true` until shipping through TestFlight || App Store. If false, you must be using live cards, and have the proper iOS entitlement set up. See https://stripe.com/docs/issuing/cards/digital-wallets?platform=react-native#requesting-access-for-ios */
testEnv?: boolean;
Expand Down

0 comments on commit 32e07e5

Please sign in to comment.