From d57a4be63d88de6e6f2b100bc19289b5ecd0a8af Mon Sep 17 00:00:00 2001 From: stechiu Date: Thu, 13 Feb 2025 17:18:32 -0800 Subject: [PATCH 1/5] Exposed isDeviceToken. Added unit tests --- CHANGELOG.md | 4 ++ .../BraintreeApplePay/BTApplePayCardNonce.m | 1 + .../BraintreeApplePay/BTApplePayCardNonce.h | 5 +++ .../BTApplePayCardNonce_Tests.swift | 38 +++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3e17c050..fc9e7b1bd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Braintree iOS SDK Release Notes +## unreleased +* BraintreeApplePay + * Add `BTApplePayCardNonce.isDeviceToken` for MPAN identification + ## 5.26.0 (2024-05-07) * Updated expiring pinned vendor SSL certificates diff --git a/Sources/BraintreeApplePay/BTApplePayCardNonce.m b/Sources/BraintreeApplePay/BTApplePayCardNonce.m index 74670e1435..74f9f9c630 100644 --- a/Sources/BraintreeApplePay/BTApplePayCardNonce.m +++ b/Sources/BraintreeApplePay/BTApplePayCardNonce.m @@ -8,6 +8,7 @@ @implementation BTApplePayCardNonce - (instancetype)initWithJSON:(BTJSON *)json { NSString *cardType = [json[@"details"][@"cardType"] asString] ?: @"ApplePayCard"; + _isDeviceToken = [json[@"details"][@"isDeviceToken"] isBool] ?: TRUE; self = [super initWithNonce:[json[@"nonce"] asString] type:cardType isDefault:[json[@"default"] isTrue]]; if (self) { diff --git a/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h b/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h index 2c7c1b344e..3bf3f3ff58 100644 --- a/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h +++ b/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h @@ -16,6 +16,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly, strong) BTBinData *binData; +/** + This Boolean (available on iOS 16+) indicates whether this tokenized card is a device-specific account number (DPAN) or merchant/cloud token (MPAN). If `isDeviceToken` is `false`, then token type is MPAN. + */ +@property (nonatomic, assign) BOOL isDeviceToken; + /** Used to initialize a `BTApplePayCardNonce` with parameters. */ diff --git a/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift b/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift index f6bee4ef17..ab0cdd3a62 100644 --- a/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift +++ b/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift @@ -33,4 +33,42 @@ class BTApplePayCardNonce_Tests: XCTestCase { XCTAssertEqual(applePayNonce?.type, "ApplePayCard") } + func testInitWithJSON_whenApplePayTokenIsMPAN() { + let applePayCard = BTJSON( + value: [ + "consumed": false, + "binData": [ + "commercial": "yes" + ], + "details": [ + "cardType": "fake-card-type", + "isDeviceToken": false + ], + "nonce": "a-nonce" + ] as [String: Any] + ) + + let applePayNonce = BTApplePayCardNonce(json: applePayCard) + XCTAssertEqual(applePayNonce?.isDeviceToken, false) + } + + func testInitWithJSON_whenApplePayTokenIsDPAN() { + let applePayCard = BTJSON( + value: [ + "consumed": false, + "binData": [ + "commercial": "yes" + ], + "details": [ + "cardType": "fake-card-type", + "isDeviceToken": true + ], + "nonce": "a-nonce" + ] as [String: Any] + ) + + let applePayNonce = BTApplePayCardNonce(json: applePayCard) + XCTAssertEqual(applePayNonce?.isDeviceToken, true) + } + } From 48cb99c9df489bc2c077debf964230e31daec9eb Mon Sep 17 00:00:00 2001 From: Stephanie <127455800+stechiu@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:44:05 -0800 Subject: [PATCH 2/5] Update BTApplePayCardNonce.h --- .../Public/BraintreeApplePay/BTApplePayCardNonce.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h b/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h index 3bf3f3ff58..f062f2b384 100644 --- a/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h +++ b/Sources/BraintreeApplePay/Public/BraintreeApplePay/BTApplePayCardNonce.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, strong) BTBinData *binData; /** - This Boolean (available on iOS 16+) indicates whether this tokenized card is a device-specific account number (DPAN) or merchant/cloud token (MPAN). If `isDeviceToken` is `false`, then token type is MPAN. + This Boolean (available on iOS 16+) indicates whether this tokenized card is a device-specific account number (DPAN) or merchant/cloud token (MPAN). If `isDeviceToken` is `false`, then token type is MPAN. */ @property (nonatomic, assign) BOOL isDeviceToken; From ae39467c813f253cc3c0bbc8cb343ca2ba332127 Mon Sep 17 00:00:00 2001 From: stechiu Date: Wed, 19 Feb 2025 13:43:12 -0800 Subject: [PATCH 3/5] Added recurring payment --- ...aintreeDemoApplePayPassKitViewController.m | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Demo/Application/Features/Apple Pay - PassKit/BraintreeDemoApplePayPassKitViewController.m b/Demo/Application/Features/Apple Pay - PassKit/BraintreeDemoApplePayPassKitViewController.m index dd4abf8f1d..578c2b6c58 100644 --- a/Demo/Application/Features/Apple Pay - PassKit/BraintreeDemoApplePayPassKitViewController.m +++ b/Demo/Application/Features/Apple Pay - PassKit/BraintreeDemoApplePayPassKitViewController.m @@ -82,12 +82,44 @@ - (void)tappedApplePayButton { PKPaymentAuthorizationViewController *viewController = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:paymentRequest]; viewController.delegate = self; - + + if (@available(iOS 16.0, *)) { + PKRecurringPaymentSummaryItem *recurringItem = [self createRecurringPaymentSummaryItem]; + NSURL *managementURL = [NSURL URLWithString:@"https://example.com/manage"]; + + PKRecurringPaymentRequest *recurringPaymentRequest = [ + [PKRecurringPaymentRequest alloc] initWithPaymentDescription:@"Payment description." + regularBilling:recurringItem + managementURL:managementURL + ]; + } + self.progressBlock(@"Presenting Apple Pay Sheet"); [self presentViewController:viewController animated:YES completion:nil]; }]; } +- (PKRecurringPaymentSummaryItem *)createRecurringPaymentSummaryItem API_AVAILABLE(ios(15.0)){ + NSDecimalNumber *amount = [NSDecimalNumber decimalNumberWithString:@"10.99"]; + NSDate *startDate = [[NSCalendar currentCalendar] dateByAddingUnit:NSCalendarUnitMonth + value:1 + toDate:[NSDate date] + options:0]; + NSDate *endDate = [[NSCalendar currentCalendar] dateByAddingUnit:NSCalendarUnitMonth + value:12 + toDate:startDate + options:0]; + + PKRecurringPaymentSummaryItem *recurringItem = [[PKRecurringPaymentSummaryItem alloc] init]; + recurringItem.label = @"Subscription Payment"; + recurringItem.amount = amount; + recurringItem.startDate = startDate; + recurringItem.intervalUnit = NSCalendarUnitMonth; // Monthly billing cycle + recurringItem.intervalCount = 1; // Every 1 month + recurringItem.endDate = endDate; // Optional end date + + return recurringItem; +} #pragma mark PKPaymentAuthorizationViewControllerDelegate From 1650867fe2752bc309ac6934e18ee7834574e4a0 Mon Sep 17 00:00:00 2001 From: stechiu Date: Wed, 19 Feb 2025 17:28:03 -0800 Subject: [PATCH 4/5] Updated test --- .../BTApplePayCardNonce_Tests.swift | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift b/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift index ab0cdd3a62..2de3226ddd 100644 --- a/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift +++ b/UnitTests/BraintreeApplePayTests/BTApplePayCardNonce_Tests.swift @@ -34,41 +34,41 @@ class BTApplePayCardNonce_Tests: XCTestCase { } func testInitWithJSON_whenApplePayTokenIsMPAN() { - let applePayCard = BTJSON( - value: [ - "consumed": false, - "binData": [ - "commercial": "yes" - ], - "details": [ - "cardType": "fake-card-type", - "isDeviceToken": false - ], - "nonce": "a-nonce" - ] as [String: Any] - ) - - let applePayNonce = BTApplePayCardNonce(json: applePayCard) - XCTAssertEqual(applePayNonce?.isDeviceToken, false) - } - - func testInitWithJSON_whenApplePayTokenIsDPAN() { - let applePayCard = BTJSON( - value: [ - "consumed": false, - "binData": [ - "commercial": "yes" - ], - "details": [ - "cardType": "fake-card-type", - "isDeviceToken": true - ], - "nonce": "a-nonce" - ] as [String: Any] - ) + let applePayCard = BTJSON( + value: [ + "consumed": false, + "binData": [ + "commercial": "yes" + ], + "details": [ + "cardType": "fake-card-type", + "isDeviceToken": true + ], + "nonce": "a-nonce" + ] as [String: Any] + ) + + let applePayNonce = BTApplePayCardNonce(json: applePayCard) + XCTAssertEqual(applePayNonce?.isDeviceToken, true) + } - let applePayNonce = BTApplePayCardNonce(json: applePayCard) - XCTAssertEqual(applePayNonce?.isDeviceToken, true) - } + func testInitWithJSON_whenApplePayTokenIsDPAN() { + let applePayCard = BTJSON( + value: [ + "consumed": false, + "binData": [ + "commercial": "yes" + ], + "details": [ + "cardType": "fake-card-type", + "isDeviceToken": false + ], + "nonce": "a-nonce" + ] as [String: Any] + ) + + let applePayNonce = BTApplePayCardNonce(json: applePayCard) + XCTAssertEqual(applePayNonce?.isDeviceToken, false) + } } From e343e2f67a266201eaa18dbef6aa3af77b2e3290 Mon Sep 17 00:00:00 2001 From: stechiu Date: Thu, 20 Feb 2025 14:26:19 -0800 Subject: [PATCH 5/5] Changed variable type --- Sources/BraintreeApplePay/BTApplePayCardNonce.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeApplePay/BTApplePayCardNonce.m b/Sources/BraintreeApplePay/BTApplePayCardNonce.m index 74f9f9c630..e453ee0280 100644 --- a/Sources/BraintreeApplePay/BTApplePayCardNonce.m +++ b/Sources/BraintreeApplePay/BTApplePayCardNonce.m @@ -8,10 +8,10 @@ @implementation BTApplePayCardNonce - (instancetype)initWithJSON:(BTJSON *)json { NSString *cardType = [json[@"details"][@"cardType"] asString] ?: @"ApplePayCard"; - _isDeviceToken = [json[@"details"][@"isDeviceToken"] isBool] ?: TRUE; self = [super initWithNonce:[json[@"nonce"] asString] type:cardType isDefault:[json[@"default"] isTrue]]; if (self) { + _isDeviceToken = [json[@"details"][@"isDeviceToken"] isTrue]; _binData = [[BTBinData alloc] initWithJSON:json[@"binData"]]; } return self;