Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge mpan feature branch into main #1521

Merged
merged 14 commits into from
Feb 13, 2025
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Braintree iOS SDK Release Notes

## unreleased
* BraintreeApplePay
* Add `BTApplePayCardNonce.isDeviceToken` for MPAN identification

## 6.28.0 (2025-02-05)
* BraintreeVenmo
* Allow universal links to be set without a return URL scheme (fixes #1505)
Expand Down
22 changes: 20 additions & 2 deletions Demo/Application/Features/ApplePayViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import PassKit
class ApplePayViewController: PaymentButtonBaseViewController {

lazy var applePayClient = BTApplePayClient(apiClient: apiClient)
// swiftlint:disable:next force_unwrapping
let managementURL = URL(string: "https://www.merchant.com/update-payment")!

override func viewDidLoad() {
super.viewDidLoad()
Expand Down Expand Up @@ -36,15 +38,31 @@ class ApplePayViewController: PaymentButtonBaseViewController {
}

let paymentRequest = self.constructPaymentRequest(with: request)
// swiftlint:disable:next force_unwrapping
let paymentAuthorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)!
guard let paymentAuthorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) else {
self.progressBlock("Could not create PKPaymentAuthorizationViewController")
return
}
paymentAuthorizationViewController.delegate = self

if #available(iOS 16.0, *) {
paymentRequest.recurringPaymentRequest = self.recurringPaymentRequest()
}

self.progressBlock("Presenting Apple Pay Sheet")
self.present(paymentAuthorizationViewController, animated: true)
}
}

@available(iOS 16.0, *)
private func recurringPaymentRequest() -> PKRecurringPaymentRequest {
let recurringPaymentRequest = PKRecurringPaymentRequest(
paymentDescription: "Payment description.",
regularBilling: PKRecurringPaymentSummaryItem(label: "Payment label", amount: 10.99),
managementURL: managementURL
)
return recurringPaymentRequest
}

private func constructPaymentRequest(with paymentRequest: PKPaymentRequest) -> PKPaymentRequest {
paymentRequest.requiredBillingContactFields = [PKContactField.name]

Expand Down
6 changes: 6 additions & 0 deletions Sources/BraintreeApplePay/BTApplePayCardNonce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ import BraintreeCore
/// The BIN data for the card number associated with this nonce.
public let binData: BTBinData

/// This Boolean indicates whether this tokenized card is a device-specific account number (DPAN) or merchant/cloud token (MPAN). Available on iOS 16+.
/// If `isDeviceToken` is `false`, then token type is MPAN
public var isDeviceToken: Bool

init?(json: BTJSON) {
guard let nonce = json["nonce"].asString() else { return nil }

let cardType = json["details"]["cardType"].asString() ?? "ApplePayCard"
let isDefault = json["default"].isTrue

self.isDeviceToken = json["details"]["isDeviceToken"].asBool() ?? true

binData = BTBinData(json: json["binData"])
super.init(nonce: nonce, type: cardType, isDefault: isDefault)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,44 @@ class BTApplePayCardNonce_Tests: XCTestCase {
XCTAssertEqual(applePayNonce?.type, "fake-card-type")
}

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)
}

func testInitWithJSON_setsDefaultProperties() {
let applePayCard = BTJSON(
value: [
Expand Down