Rest API webshop checkout architecture

340 Views Asked by At

I am building a web shop checkout page, and I am stuck with one problem - how can I be sure that the user really paid?

Stack: Quarkus + Angular

I have integrated a Stripe payment method in my checkout page. Each time the checkout page is opened, I get a Stripe Intent from backend. Then, after the user has entered the card info and clicked on "Pay" I get in frontend a response from stripe that "ok, the user has paid!", I call then an PUBLIC endpoint on my server (from frontend) and I say, ok, the user has paid, confirm the order.

How can I be sure that the customer actually paid? He could just call my PUBLIC server endpoint and confirm the order, and I would not know that the stripe payment actually never happened.

My guess was, maybe I can call the stripe server and ask, "hey, was there actually a payment done with this stripe intent"?

What is the best practice here, how I should implement the confirm order part?

2

There are 2 best solutions below

0
alex On

You would want to listen for the relevant webhook events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes.

You can refer to https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements#web-post-payment for what events to listen for.

You can also read up on webhooks in more detail here : https://stripe.com/docs/webhooks

0
Koen Hollander On

When you are using the Stripe API you can start a Checkout Session. When you start it, it will return the object for that session. In that object you can access the Checkout ID

First you start a Checkout Session like this

curl https://api.stripe.com/v1/checkout/sessions \
  -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
  -d success_url="https://example.com/success" \
  -d cancel_url="https://example.com/cancel" \
  -d "payment_method_types[0]"=card \
  -d "line_items[0][price]"=price_H5ggYwtDq4fbrJ \
  -d "line_items[0][quantity]"=2 \
  -d mode=payment

After that you will get a result, similar to

{
  "id": "cs_test_EHNhgisbBCZj4HfTaFiSyoLPLYr1qNPmHeVmZw0BRNDVRYWeAppSFrMt",
  "object": "checkout.session",
  "after_expiration": null,
  "allow_promotion_codes": null,
  "amount_subtotal": null,
  "amount_total": null,
  "automatic_tax": {
    "enabled": false,
    "status": null
  },
  "billing_address_collection": null,
  "cancel_url": "https://example.com/cancel",
  "client_reference_id": null,
  "consent": null,
  "consent_collection": null,
  "currency": null,
  "customer": null,
  "customer_details": null,
  "customer_email": null,
  "expires_at": 1634799676,
  "livemode": false,
  "locale": null,
  "metadata": {},
  "mode": "payment",
  "payment_intent": "pi_1Dpavq2eZvKYlo2Co0uGctWr",
  "payment_method_options": {},
  "payment_method_types": [
    "card"
  ],
  "payment_status": "unpaid",
  "phone_number_collection": {
    "enabled": false
  },
  "recovered_from": null,
  "setup_intent": null,
  "shipping": null,
  "shipping_address_collection": null,
  "submit_type": null,
  "subscription": null,
  "success_url": "https://example.com/success",
  "total_details": null,
  "url": "https://checkout.stripe.com/pay/..."
}

The two most important attributes in this object are

  • id
  • url

The ID is the unique identifier for the session and you should redirect the user to the URL to pay.

After the payment you can lookup the Checkout Session and check the status

curl https://api.stripe.com/v1/checkout/sessions/cs_test_EHNhgisbBCZj4HfTaFiSyoLPLYr1qNPmHeVmZw0BRNDVRYWeAppSFrMt \
  -u sk_test_4eC39HqLyjWDarjtT1zdp7dc:

Resulting in this example response

{
  "id": "cs_test_EHNhgisbBCZj4HfTaFiSyoLPLYr1qNPmHeVmZw0BRNDVRYWeAppSFrMt",
  "object": "checkout.session",
  "after_expiration": null,
  "allow_promotion_codes": null,
  "amount_subtotal": null,
  "amount_total": null,
  "automatic_tax": {
    "enabled": false,
    "status": null
  },
  "billing_address_collection": null,
  "cancel_url": "https://example.com/cancel",
  "client_reference_id": null,
  "consent": null,
  "consent_collection": null,
  "currency": null,
  "customer": null,
  "customer_details": null,
  "customer_email": null,
  "expires_at": 1634799676,
  "livemode": false,
  "locale": null,
  "metadata": {},
  "mode": "payment",
  "payment_intent": "pi_1Dpavq2eZvKYlo2Co0uGctWr",
  "payment_method_options": {},
  "payment_method_types": [
    "card"
  ],
  "payment_status": "unpaid",
  "phone_number_collection": {
    "enabled": false
  },
  "recovered_from": null,
  "setup_intent": null,
  "shipping": null,
  "shipping_address_collection": null,
  "submit_type": null,
  "subscription": null,
  "success_url": "https://example.com/success",
  "total_details": null,
  "url": null
}

As you can see you have the attribute payment_status - with this one you can check if the user really paid.

Alternatively you can also use the webhooks, but I prefer my example

Good luck!