How to use Payment Intent and Setup Intents with Stripe in iOS

Issue #356

StripeHandler.swift

From Stripe 16.0.0 https://github.com/stripe/stripe-ios/blob/master/CHANGELOG.md#1600-2019-07-18

Migrates STPPaymentCardTextField.cardParams property type from STPCardParams to STPPaymentMethodCardParams

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
final class StripeHandler {
func createPaymentMethod(
textField: STPPaymentCardTextField,
completion: @escaping (Result<STPPaymentMethod, Error>) -> Void) {

let paymentMethodParams = STPPaymentMethodParams(
card: textField.cardParams,
billingDetails: nil,
metadata: nil
)

STPAPIClient.shared().createPaymentMethod(
with: paymentMethodParams,
completion: { paymentMethod, error in
DispatchQueue.main.async {
if let paymentMethod = paymentMethod {
completion(.success(paymentMethod))
} else {
completion(.failure(error ?? AppError.request))
}
}
})

STPAPIClient.shared().createPaymentMethod(
with: paymentMethodParams,
completion: { paymentMethod, error in
DispatchQueue.main.async {
if let paymentMethod = paymentMethod {
completion(.success(paymentMethod))
} else {
completion(.failure(error ?? AppError.request))
}
}
})
}

func confirmSetupIntents(
clientSecret: String,
paymentMethodId: String,
context: STPAuthenticationContext?,
completion: @escaping (Result<STPSetupIntent, Error>) -> Void) {

guard let context = context else {
completion(.failure(AppError.invalid))
return
}

let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: clientSecret)
setupIntentParams.paymentMethodID = paymentMethodId

let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmSetupIntent(
setupIntentParams,
with: context,
completion: { status, intent, error in
DispatchQueue.main.async {
if case .succeeded = status, let intent = intent {
completion(.success(intent))
} else {
completion(.failure(error ?? AppError.invalid))
}
}
})
}

func confirmPaymentIntents(
clientSecret: String,
context: STPAuthenticationContext?,
completion: @escaping (Result<STPPaymentIntent, Error>) -> Void) {

guard let context = context else {
completion(.failure(AppError.invalid))
return
}

STPPaymentHandler.shared().handleNextAction(
forPayment: clientSecret,
with: context,
returnURL: nil,
completion: { status, paymentIntent, error in
if case .succeeded = status, let paymentIntent = paymentIntent {
completion(.success(paymentIntent))
} else {
completion(.failure(error ?? AppError.invalid))
}
})
}

func toCard(paymentMethod: STPPaymentMethod) -> MyCard? {
guard
let card = paymentMethod.card,
let last4 = card.last4
else {
return nil
}

return withValue(MyCard()) {
$0.expiryYear = UInt32(card.expYear)
$0.expiryMonth = UInt32(card.expMonth)
$0.lastFourDigits = last4
$0.brand = STPCard.string(from: card.brand)
}
}
}

Payment intents

https://stripe.com/docs/payments/payment-intents/creating-payment-intents

When using automatic confirmation, create the PaymentIntent at the beginning of the checkout process. When using manual confirmation, create the PaymentIntent after collecting payment information from the customer using Elements or our iOS and Android SDKs. For a detailed comparison on the automatic and manual confirmation flows, see accepting one-time payments.

Step 3: Authenticate the payment if necessary

Pass the confirmed Payment Intent client secret from the previous step to STPPaymentHandler handleNextActionForPayment. If the customer must perform 3D Secure authentication to complete the payment, STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MyAPIClient.createAndConfirmPaymentIntent(paymentMethodId: paymentMethodId) { result in
guard case .success(let paymentIntentClientSecret) = result else {
// Handle error
return
}
STPPaymentHandler.shared().handleNextAction(forPayment: paymentIntentClientSecret, with: self, returnURL: nil) { (status, paymentIntent, error) in
switch (status) {
case .succeeded:
// ...Continued in Step 4
case .canceled:
// Handle cancel
case .failed:
// Handle error
}
}
}

Setup intents

There is Setup intents https://stripe.com/docs/payments/cards/reusing-cards#saving-cards-without-payment for saving cards

Use the Setup Intents API to authenticate a customer’s card without making an initial payment. This flow works best for businesses that want to onboard customers without charging them right away:

Step 4: Submit the card details to Stripe from the client

Pass the STPSetupIntentParams object to the confirmSetupIntent method on a STPPaymentHandler sharedManager. If the customer must perform additional steps to complete the payment, such as authentication, STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

1
2
3
4
5
6
7
8
9
10
11
12
13
let setupIntentParams = STPSetupIntentParams(clientSecret: clientSecret)
setupIntentParams.paymentMethodId = paymentMethodId
let paymentManager = STPPaymentHandler.shared()
paymentManager.confirmSetupIntent(setupIntentParams, authenticationContext: self, completion { (status, setupIntent, error) in
switch (status) {
case .succeeded:
// Setup succeeded
case .canceled:
// Handle cancel
case .failed:
// Handle error
}
})

Authentication context

In STPPaymentHandler.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)_canPresentWithAuthenticationContext:(id<STPAuthenticationContext>)authenticationContext {
UIViewController *presentingViewController = authenticationContext.authenticationPresentingViewController;
// Is presentingViewController non-nil and in the window?
if (presentingViewController == nil || presentingViewController.view.window == nil) {
return NO;
}

// Is it the Apple Pay VC?
if ([presentingViewController isKindOfClass:[PKPaymentAuthorizationViewController class]]) {
// We can't present over Apple Pay, user must implement prepareAuthenticationContextForPresentation: to dismiss it.
return [authenticationContext respondsToSelector:@selector(prepareAuthenticationContextForPresentation:)];
}

// Is it already presenting something?
if (presentingViewController.presentedViewController == nil) {
return YES;
} else {
// Hopefully the user implemented prepareAuthenticationContextForPresentation: to dismiss it.
return [authenticationContext respondsToSelector:@selector(prepareAuthenticationContextForPresentation:)];
}
}

Use stripe SDK

STPSetupIntentConfirmParams.useStripeSDK

A boolean number to indicate whether you intend to use the Stripe SDK’s functionality to handle any SetupIntent next actions.
If set to false, STPSetupIntent.nextAction will only ever contain a redirect url that can be opened in a webview or mobile browser.
When set to true, the nextAction may contain information that the Stripe SDK can use to perform native authentication within your app.

1
2
let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: clientSecret)
setupIntentParams.useStripeSDK = NSNumber(booleanLiteral: true)

Read more

Comments