Skip to content

Commit b900d96

Browse files
committed
[Breaking] Adds async APIs to STPApplePayContext and ApplePayConfiguration
1 parent cd84277 commit b900d96

File tree

10 files changed

+575
-156
lines changed

10 files changed

+575
-156
lines changed

CHANGELOG_v25_draft.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## 25.0.0 2025-XX-YY
2+
### PaymentSheet
3+
* [Changed] Replaces `PaymentSheet.ApplePayConfiguration.Handlers` completion-block based `authorizationResultHandler` with an async equivalent.
4+
5+
### STPApplePayContext
6+
* [Added] Added async delegate methods.
7+
* [Changed] Replaces the `ApplePayContextDelegate.didCreatePaymentMethod` method with an async version.
8+
9+

Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,15 @@ class PlaygroundController: ObservableObject {
5757
request.paymentSummaryItems = [billing]
5858
return request
5959
},
60-
authorizationResultHandler: { result, completion in
61-
// Hardcoded order details:
62-
// In a real app, you should fetch these details from your service and call the completion() block on
63-
// the main queue.
60+
authorizationResultHandler: { result in
61+
// Hardcoded order details:
62+
// In a real app, you should fetch these details from your service
6463
result.orderDetails = PKPaymentOrderDetails(
6564
orderTypeIdentifier: "com.myapp.order",
6665
orderIdentifier: "ABC123-AAAA-1111",
6766
webServiceURL: URL(string: "https://my-backend.example.com/apple-order-tracking-backend")!,
6867
authenticationToken: "abc123")
69-
completion(result)
68+
return result
7069
}
7170
)
7271
return PaymentSheet.ApplePayConfiguration(

MIGRATING_v25_draft.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#### PaymentSheet
2+
`PaymentSheet.ApplePayConfiguration.Handlers` completion-block based `authorizationResultHandler` has been replaced by an async equivalent. You can use the following example to quickly migrate completion-block based code:
3+
4+
```
5+
// Before:
6+
authorizationResultHandler: { result in
7+
return await withCheckedThrowingContinuation { continuation in
8+
let modifiedResult = // ...modify result (details omitted)
9+
continuation.resume(returning: modifiedResult)
10+
}
11+
}
12+
13+
// After:
14+
authorizationResultHandler: { result, completion in
15+
// ...modify result (details omitted)
16+
completion(result)
17+
}
18+
```
19+
20+
#### STPApplePayContext
21+
ApplePayContextDelegate's `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` has been replaced by an async equivalent. You can use the following example to quickly migrate completion-block based code:
22+
23+
```
24+
// Before:
25+
func applePayContext(
26+
_ context: STPApplePayContext,
27+
didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod,
28+
paymentInformation: PKPayment,
29+
completion: @escaping STPIntentClientSecretCompletionBlock
30+
) {
31+
MyExampleNetworkManager.getPaymentIntent() { paymentIntentClientSecret in
32+
completion("my client secret", nil)
33+
}
34+
}
35+
36+
// After:
37+
func applePayContext(
38+
_ context: STPApplePayContext,
39+
didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod,
40+
paymentInformation: PKPayment
41+
) async throws -> String {
42+
return try await withCheckedThrowingContinuation { continuation in
43+
MyExampleNetworkManager.getPaymentIntent() { paymentIntentClientSecret, error in
44+
if let error {
45+
continuation.resume(throwing: error)
46+
} else {
47+
continuation.resume(returning: paymentIntentClientSecret)
48+
}
49+
}
50+
}
51+
}
52+
```
53+

Stripe/StripeiOSTests/STPApplePayContextFunctionalTest.swift

+125-10
Original file line numberDiff line numberDiff line change
@@ -360,17 +360,16 @@ class STPApplePayContextFunctionalTest: STPNetworkStubbingTestCase {
360360
// When the user taps 'Pay', PKPaymentAuthorizationController calls `didAuthorizePayment:completion:`
361361
// After you call its completion block, it calls `paymentAuthorizationControllerDidFinish:`
362362
let didCallAuthorizePaymentCompletion = expectation(description: "ApplePayContext called completion block of paymentAuthorizationController:didAuthorizePayment:completion:")
363-
if let authorizationController = context?.authorizationController {
364-
context?.paymentAuthorizationController(authorizationController, didAuthorizePayment: STPFixtures.simulatorApplePayPayment(), handler: { [self] result in
365-
XCTAssertEqual(expectedStatus, result.status)
366-
DispatchQueue.main.async(execute: { [self] in
367-
if let authorizationController = context?.authorizationController {
368-
context?.paymentAuthorizationControllerDidFinish(authorizationController)
369-
}
370-
didCallAuthorizePaymentCompletion.fulfill()
371-
})
363+
let authorizationController = context!.authorizationController!
364+
context?.paymentAuthorizationController(authorizationController, didAuthorizePayment: STPFixtures.simulatorApplePayPayment(), handler: { [self] result in
365+
XCTAssertEqual(expectedStatus, result.status)
366+
DispatchQueue.main.async(execute: { [self] in
367+
if let authorizationController = context?.authorizationController {
368+
context?.paymentAuthorizationControllerDidFinish(authorizationController)
369+
}
370+
didCallAuthorizePaymentCompletion.fulfill()
372371
})
373-
}
372+
})
374373
}
375374
}
376375

@@ -380,3 +379,119 @@ class STPTestPKPaymentAuthorizationController: PKPaymentAuthorizationController
380379
completion?()
381380
}
382381
}
382+
383+
// MARK: - Async delegate methods
384+
// Keeps track of which async delegate methods have been called
385+
class TestAsyncApplePayContextDelegate: NSObject, ApplePayContextDelegate {
386+
enum AsyncDelegateMethods {
387+
case didCreatePaymentMethod
388+
case didSelectShippingMethod
389+
case didSelectShippingContact
390+
case didChangeCouponCode
391+
case willCompleteWithResult
392+
case didCompleteWithStatus
393+
}
394+
395+
var delegateMethodsCalled: [AsyncDelegateMethods] = []
396+
397+
func applePayContext(
398+
_ context: StripeApplePay.STPApplePayContext,
399+
didCreatePaymentMethod paymentMethod: StripeCore.StripeAPI.PaymentMethod,
400+
paymentInformation: PKPayment
401+
) async throws -> String {
402+
delegateMethodsCalled.append(.didCreatePaymentMethod)
403+
return "pi_bad_secret_1234"
404+
}
405+
406+
func applePayContext(
407+
_ context: StripeApplePay.STPApplePayContext,
408+
didSelectShippingContact contact: PKContact
409+
) async -> PKPaymentRequestShippingContactUpdate {
410+
delegateMethodsCalled.append(.didSelectShippingContact)
411+
return .init()
412+
}
413+
414+
func applePayContext(
415+
_ context: STPApplePayContext,
416+
didSelect shippingMethod: PKShippingMethod
417+
) async -> PKPaymentRequestShippingMethodUpdate {
418+
delegateMethodsCalled.append(.didSelectShippingMethod)
419+
return .init()
420+
}
421+
422+
@available(iOS 15.0, *)
423+
func applePayContext(
424+
_ context: STPApplePayContext,
425+
didChangeCouponCode couponCode: String
426+
) async -> PKPaymentRequestCouponCodeUpdate {
427+
delegateMethodsCalled.append(.didChangeCouponCode)
428+
return .init()
429+
}
430+
431+
func applePayContext(
432+
_ context: STPApplePayContext,
433+
willCompleteWithResult authorizationResult: PKPaymentAuthorizationResult
434+
) async -> PKPaymentAuthorizationResult {
435+
delegateMethodsCalled.append(.willCompleteWithResult)
436+
return authorizationResult
437+
}
438+
439+
func applePayContext(_ context: StripeApplePay.STPApplePayContext, didCompleteWith status: StripeApplePay.STPApplePayContext.PaymentStatus, error: (any Error)?) {
440+
delegateMethodsCalled.append(.didCompleteWithStatus)
441+
}
442+
}
443+
444+
extension STPApplePayContextFunctionalTest {
445+
func testAsyncDelegateMethodsCalled() {
446+
let delegate = TestAsyncApplePayContextDelegate()
447+
let request = StripeAPI.paymentRequest(
448+
withMerchantIdentifier: "foo",
449+
country: "US",
450+
currency: "USD"
451+
)
452+
request.paymentSummaryItems = [
453+
PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "1.00")),
454+
]
455+
self.context = STPApplePayContext(paymentRequest: request, delegate: delegate)!
456+
context.apiClient = apiClient
457+
_startApplePayForContext(withExpectedStatus: .failure)
458+
waitForExpectations(timeout: 50)
459+
XCTAssertEqual(delegate.delegateMethodsCalled, [.didCreatePaymentMethod, .willCompleteWithResult])
460+
delegate.delegateMethodsCalled = []
461+
462+
// Now test that the 3 optional async methods get called:
463+
let e1 = expectation(description: "didSelectShippingMethod")
464+
XCTAssertTrue(context.responds(to: #selector((PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController(_:didSelectShippingMethod:handler:)))))
465+
context.paymentAuthorizationController(
466+
context.authorizationController!,
467+
didSelectShippingMethod: .init()
468+
) { _ in
469+
e1.fulfill()
470+
}
471+
// didSelectShippingContact should be called
472+
let e2 = expectation(description: "didSelectShippingContact")
473+
XCTAssertTrue(context.responds(to: #selector(PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController(_:didSelectShippingContact:handler:))))
474+
context.paymentAuthorizationController(
475+
context.authorizationController!,
476+
didSelectShippingContact: .init()
477+
) { _ in
478+
e2.fulfill()
479+
}
480+
481+
// didChangeCouponCode should be called
482+
if #available(iOS 15.0, *) {
483+
let e3 = expectation(description: "didChangeCouponCode")
484+
XCTAssertTrue(context.responds(to: #selector(PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController(_:didChangeCouponCode:handler:))))
485+
context.paymentAuthorizationController(
486+
context.authorizationController!,
487+
didChangeCouponCode: .init()
488+
) { _ in
489+
e3.fulfill()
490+
}
491+
} else {
492+
// Fallback on earlier versions
493+
}
494+
waitForExpectations(timeout: 3)
495+
XCTAssertEqual(delegate.delegateMethodsCalled, [.didCompleteWithStatus, .didSelectShippingMethod, .didSelectShippingContact, .didChangeCouponCode])
496+
}
497+
}

0 commit comments

Comments
 (0)