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

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.

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.

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

- (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.

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

Read more