简体   繁体   中英

How do you capture a payment in a webhook in Stripe's pre-built checkout flow?

I currently have an Opayo (SagePay) Server integration where I'm apparently spoilt by an ability to simply respond to a payment notification callback with an ERROR status to reject/void a transaction if there is a problem fulfilling an order on my side. I think, there is some sort of automatic authorise then capture happening behind the scenes, where a payment is only captured if a success/OK status is returned by my notification handler.

I'm looking at how I can achieve the same with a Stripe checkout integration (Pre-built Checkout, PHP with webhooks) and it seems that I need to to use authorisation and manual capturing:

$session = \Stripe\Checkout\Session::create([
  ...
  'payment_intent_data' => [
    'capture_method' => 'manual',
  ],
]);

In the sample code and documentation (which is generally excellent, but I feel falls short here), it shows how to create a webhook to handle the completion of the checkout session:

// Handle the checkout.session.completed event
if ($event->type == 'checkout.session.completed') {
  $session = $event->data->object;

  // Fulfill the purchase...
  fulfill_order($session);
}

Curiously, the payment_status of the transaction is not checked and the example suggests you would simply fulfil your order here. I can only assume, if you are not using a delayed payment method then you can assume the payment is successful at this point, although it seems a dangerous assumption to make.

Further along in the documentation, under "Handle delayed notification payment methods Server-side", we have a fuller example, where the payment status is actually checked before fulfilling the order:

switch ($event->type) {
  case 'checkout.session.completed':
    $session = $event->data->object;

    // Save an order in your database, marked as 'awaiting payment'
    create_order($session);

    // Check if the order is paid (e.g., from a card payment)
    //
    // A delayed notification payment will have an `unpaid` status, as
    // you're still waiting for funds to be transferred from the customer's
    // account.
    if ($session->payment_status == 'paid') {
      // Fulfill the purchase
      fulfill_order($session);
    }
...

If I am using the manual capture method, how exactly should the code above be used/adapted? Presumably the payment_status will not be paid so what do I check for and how do I access the PaymentIntent to be able to capture it? Should this all happen in the checkout.session.completed event?

A code example would be great; unfortunately there is only this footnote in the documentation:

To capture an uncaptured payment, you can use either the Dashboard or the capture endpoint. Programmatically capturing payments requires access to the PaymentIntent created during the Checkout Session, which you can get from the Session object.

I have checked the online code samples but this exact scenario isn't covered.

EDIT: Adding below my understanding of the flow I need with questions:

switch ($event->type) {
  case 'checkout.session.completed':

    $session = $event->data->object;

    if ($session->payment_status == 'unpaid') {

      // We need to capture the payment if we have arrived here?
      // Is this the only scenario where we would end up here?
      // How do we get the PaymentIntent, what status if any should we check on it?

      // Attempt to fulfil the purchase here, if all good then
      // capture the payment

    } else if ($session->payment_status == 'paid') {
      // Assume this was not an auth/capture type payment?
      // There is no other scenario where we would end up here?
    }
...

If you receive the checkout.session.completed event, then the synchronous payment method completed successfully.

For async payment methods, there are other events:

  • checkout.session.async_payment_succeeded
  • checkout.session.async_payment_failed

I'm not sure why you'd use auth and capture here, since the payment is successful either upon receipt of checkout.session.completed or checkout.session.async_payment_succeeded , but if you need to, you'd set the option to do so here and then you'd need to capture the Payment Intent associated with the Checkout Session .

If you need to determine if the Charge was captured or not, you'd need to retrieve the Payment Intent and look a the Charge's captured value: https://stripe.com/docs/api/charges/object#charge_object-captured

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM