Webhooks for Cancelled Terminal Payment

We have an issue with a merchant where the end consumer has invoked a payment, but later cancelled the payment, but we have received APPROVED status webhooks after the cancellation as well.

Order ID: QB9nQiFijAOkmcDHOdM9BJpWs9JZY
Location ID: LVTX8NQD7J2DW

Sequence of events:

“Wed Nov 06 21:07:53 GMT+5:30 2024” : “PAYMENT_CREATED” → We add this. Not from WH.
“Wed Nov 06 21:08:15 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:17 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:28 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:28 GMT+5:30 2024” : “PAYMENT_FAILED” → We add this. Not from WH.
“Wed Nov 06 21:08:39 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:40 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:41 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:52 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:52 GMT+5:30 2024” : “PAYMENT_FAILED” → We add this. Not from WH.

Checkout Request Log:

2024-11-06 15:37:07 - SQUARE-TERMINAL API >>>>>>>>>> 
URI         : https://connect.squareup.com/v2/terminals/checkouts
Method      : POST
Headers     : {Accept=[application/json], Content-Type=[application/json], Authorization=[Bearer REDACTED], Square-Version=[2024-01-18], Content-Length=[315]}
Request body: {"idempotency_key":"SQT-a508359f33bca1-1730907427787","checkout":{"amount_money":{"amount":587,"currency":"USD"},"tip_money":{"amount":0,"currency":"USD"},"order_id":"QB9nQiFijAOkmcDHOdM9BJpWs9JZY","device_options":{"device_id":"846CS108A2000949","tip_settings":{"allow_tipping":false},"skip_receipt_screen":true}}}
2024-11-06 15:37:08 - SQUARE-TERMINAL API API  <<<<<<<<<< Status = [200] path = [https://connect.squareup.com/v2/terminals/checkouts]
Status code  : 200
Status text  : OK
Headers      : {Date=[Wed, 06 Nov 2024 15:37:08 GMT], Content-Type=[application/json], Connection=[keep-alive], CF-Ray=[8de629bfac9929bc-IAD], CF-Cache-Status=[DYNAMIC], Strict-Transport-Security=[max-age=631152000; includeSubDomains; preload], Vary=[Accept-Encoding], frame-options=[DENY], square-version=[2024-01-18], squareup--connect--v2--common--versionmetadata-bin=[CgoyMDI0LTAxLTE4], x-content-type-options=[nosniff], x-envoy-decorator-operation=[/v2/terminals/**], x-frame-options=[DENY], x-sq-dc=[aws], x-sq-region=[us-east-1], x-xss-protection=[1; mode=block], Server=[cloudflare]}
Response body: {"checkout": {"id": "qigAOSg61qhqO","amount_money": {"amount": 587,"currency": "USD"},"device_options": {"device_id": "846CS108A2000949","collect_signature": false,"tip_settings": {"allow_tipping": false},"skip_receipt_screen": true,"show_itemized_cart": true},"status": "PENDING","created_at": "2024-11-06T15:37:08.220Z","updated_at": "2024-11-06T15:37:08.220Z","app_id": "sq0idp-TpHen_F96miOUr07zsUKTw","order_id": "QB9nQiFijAOkmcDHOdM9BJpWs9JZY","location_id": "LVTX8NQD7J2DW","payment_type": "CARD_PRESENT","payment_options": {"autocomplete": true},"tip_money": {"amount": 0,"currency": "USD"}}}

Our concern is, can a cancellation happen even after a payment is approved? Also, for a cancelled payment, can any previous approved webhooks be received after?

Yes, this can happen if the customer pays and the checkout is canceled during the payment. The payment will succeed but the request to cancel will fail. Also the cancel webhook is a notification of a request to cancel the checkout. It’s not a notification that the checkout has been canceled. :slightly_smiling_face:

We’re constantly working to improve our features based on feedback like this, so I’ll be sure to share your request to the API product team. :slightly_smiling_face:

I’m assuming the payment would have actually been cancelled though?

Comes back to the need for some sort of “final” status via a bit set in the “final” hit rather than a crazy mish-mash of various statuses in 8-10 hits as shown above. This is made far worse because webhook hits can be delayed or even rarely dropped, and they can arrive in random order. So some status types have to take precedence over others, etc.

I know we could always check the final status via API, thought that could be a lot of extra work if the webhook receiver doesn’t have API access. The aim here is to make it easy to program, not to have customers jump through hoops because the webhook design isn’t so good for transactions.

The desire would be to have a bit set or something in the webhook hit when all is done.

1 Like