Home > database >  Stripe Checkout and webhook: return failure if webhook fails
Stripe Checkout and webhook: return failure if webhook fails

Time:02-17

I have the current payment flow: my app calls a Cloud Function to generate a Stripe checkout session. The function calls Stripe like this

session = stripe.checkout.Session.create(
    success_url=_build_redirect_to_app_link(match_id, "success"),
    cancel_url=_build_redirect_to_app_link(match_id, "cancel"),

    payment_method_types=["card", "ideal"],
    line_items=[
        {
            'price_data': {
                "unit_amount": price,
                "product": product_id,
                "currency": "eur"
            },
            'quantity': 1,
        }
    ],
    mode="payment",
    customer=customer_id,
    metadata={"user_id": user_id, "match_id": match_id}
)

and returns the session URL.

The app then launches the session URL in the browser and user can pay.

In Stripe I set up a webhook that listens to events with the following logic

def listener(request, secret):
    event = None
    payload = request.data
 
    print(event)
    event_data = event["data"]["object"]

    # Handle the event
    if event["type"] == "checkout.session.completed":
        print("checkout successful")
        save_to_db()
    ...

This webhook runs after a payment and it works fine. However it is asynchronous, meaning that Stripe payment flow doesn't wait for this webhook to succeed to consider the payment flow successful (or not).

This means that if save_to_db() fails still the payment is considered successfull (and Stripe checkout redirects me to the success_url)

Is there a way to tell Stripe to consider the payment successful if

  • payment went well
  • AND webhook post payment went well

?

I think if I redirect first to a custom built backend I own I could implement this but I would avoid so if Stripe has already something (or some webhook setting I could use)

CodePudding user response:

You should call to step:

  1. you must get the "client secret key" from your backend
  2. after that you must call the stripe payment sheet:
func pay() {
        guard let paymentIntentClientSecret = viewModel.intentPaymentResponse?.paymentMethodMetadata.clientSecret else {
            return
        }

        var configuration = PaymentSheet.Configuration()
        configuration.merchantDisplayName = "Edgecom, Inc."
        configuration.primaryButtonColor = ColorPalette.buttonColor

        let paymentSheet = PaymentSheet(
            paymentIntentClientSecret: paymentIntentClientSecret,
            configuration: configuration)

        paymentSheet.present(from: self) { [weak self] (paymentResult) in
            switch paymentResult {
            case .completed:
                self?.tableView.reloadData()
                print("Payment complete!")
                if let transactionUuid = self?.viewModel.intentPaymentResponse?.uuid {
                    self?.viewModel.confirmPayment(request: ConfirmPaymentRequest(transactionUuid: transactionUuid))
                }
            case .canceled:
                print("Payment canceled!")
                if let transactionUuid = self?.viewModel.intentPaymentResponse?.uuid {
                    self?.viewModel.cancelPayment(request: CancelPaymentRequest(transactionUuid: transactionUuid))
                }
            case .failed(let error):
                print("Payment failed!--\(error.localizedDescription)")
                if let transactionUuid = self?.viewModel.intentPaymentResponse?.uuid {
                    self?.viewModel.cancelPayment(request: CancelPaymentRequest(transactionUuid: transactionUuid))
                }
                self?.showPaymentError()
            }
        }
    }

after that, you can wait for the response status, you can check the below link: https://stripe.com/docs/payments/quickstart

CodePudding user response:

However it is asynchronous, meaning that Stripe payment flow doesn't wait for this webhook to succeed to consider the payment flow successful

This is a misunderstanding of the sequence of events here. When you get that event, the payment is already successfully completed. The webhook event is merely informing you of that for fulfillment.

You should respond quickly with a 200 success message to indicate successful receipt of the event before performing any substantial processing. Whether your processing is successful or not does not and cannot impact the payment.

For Checkout specifically, Stripe will wait up to ten seconds (see callout_ for your response to that event before redirecting your customer:

Your webhook endpoint redirects your customer to the success_url when you acknowledge you received the event. In scenarios where your endpoint is down or the event isn’t acknowledged properly, your handler redirects the customer to the success_url 10 seconds after a successful payment.

By adding a processing step, you are potentially delaying that redirect.

  • Related