a11rew / medusa-payment-paystack

Paystack payment provider for Medusa
https://storefront-production-ae6c.up.railway.app/
MIT License
58 stars 12 forks source link

Order reconciliation issue #94

Closed OWUREE closed 6 months ago

OWUREE commented 7 months ago

Some transactions are not getting updated correctly on the Medusa dashboard. This happened randomly.

Manually had to fulfil some orders. Successful orders (2 of them) were recorded as pending on Medusa

iaremarkus commented 7 months ago

Hello @a11rew 👋

Just some context here... @OWUREE is a Paystack employee. I had a long call with them this week where I detailed an issue that I was having, and their investigation led back to this plugin as being the potential cause.

I've had now 2 orders that have been placed where Paystack completes the transaction and the money is cleared, but the MedusaJS store does not allocate the payment to the order and so the order is left in limbo.

It seems that this MedusaJS Paystack plugin sometimes misallocates a payment to an order and then nothing shows up in my MedusaJS backend, even though the transaction was completed with them.

Most recently, in the last 30 minutes, a colleague placed an order. They were kind enough to screenshot the message they saw (attached here)

It'd be great if you had the time to debug this a bit together and hopefully we can resolve?

Many thanks

PS: This image was taken after the payment was completed - IE the payment ref could not be attached to the order..?

pic-004428

iaremarkus commented 7 months ago

PS: I also had a long chat with a MedusaJS dev on their Discord, and they also verified that its likely the payment plugin that is the issue here :/

iaremarkus commented 7 months ago

Extra info: I can see a record in payment table in MedusaJS, and this transaction that was successful, is missing the payment field that contains the order in question

pic-004431

iaremarkus commented 7 months ago

And here's the rest of the record showing the Paystack TX Ref, except missing the order details

pic-004433

kevinrobert3 commented 7 months ago

Something tells me this is similar to this issue https://github.com/a11rew/medusa-payment-paystack/issues/95#issue-2175774603.

I am curious are all amounts exactly how you want them. You mention only this one transaction had errors. But am curious of the amounts of the rest, is there that difference i.e in db cart amount being 21500 and customer paid 215. is this expected correct behavior on your side and how were the rest 3 transaction amounts (in db) in relation to amount paid by customer.

And also when the previous last successful transactions were made. So we can know what changed in between. If it was an update to the plugin that caused this issue

kevinrobert3 commented 7 months ago

Image 08-03-2024 at 16 Same issue

iaremarkus commented 7 months ago

@kevinrobert3 its standard that the prices are the lowest common price as @a11rew mentions. So R90, is represented as 9000. All storefronts have a formatAmount type util that will present the price to the user in the right way, and Paystack etc are setup to handle the transaction value in this manner.

It's only the storefront that needs to format the price to the user.

Also, I don't believe this has anything to do with the issue that this thread is discussing.

a11rew commented 7 months ago

Thank you for all the information!

I see two issues to be addressed here (correct me if wrong):

  1. Orders are paid for in Paystack but the plugin fails to mark them as paid for in Medusa I appreciate the wealth of information you've shared here but they're missing probably the most important of them all: the backend logs at the time of these happening. Errors in the plugin should be getting logged out to give you clues on what's going wrong, whether they're Paystack API issues or internal Medusa issues. Especially with debug mode turned on. Could you share these? This is most likely occuring because of an issue authorizing those specific payments. Without any more information, I've seen this happen most often because of stale transaction references: The transaction reference in the cart data is updated by the plugin when the cart is updated with new information (think emails, amount, line item count etc). If for any reason your storefront is not using the latest txRef in the cart data at the time of transaction initialization, users will pay for one transaction while the plugin tries to verify another. You should confirm this is not happening in your storefront

  2. Duplicate transaction reference error after making payment. This I am guessing happened because the person made payment in the Paystack popup successfully, waited for order confirmation but was sent back to the checkout page. They then clicked the payment button again (because what is going on?!) and got greeted with the duplicate transaction error. This is happening because of issue number 1: Medusa tries to confirm if the order was paid for, the plugin errors out. The default starter-kit does not have very good checkout error handling so users just get sent back to the checkout page. Concluding therefore this can be collapsed into issue number 1 above.

Again, thank you for the detailed bug report. Hoping we can resolve this here as opposed to a meeting so the fix is public, for posterity.

a11rew commented 7 months ago

Saw you shared server logs in the discord, followed up there. A 504 from Paystack when trying to confirm the transaction will do this. You could handle the error and retry the cart completion to resolve this.

iaremarkus commented 7 months ago

Thanks for the time to reply @a11rew

@OWUREE any thoughts on the above? Why would Paystack reply with a 504?

kevinrobert3 commented 7 months ago

They say it is unusual and should be reported, see this on 5xx errors. So the go-to should be what @a11rew suggests. Handle the error so if it happens in future, you can retry the cart completion. I'd also suggest doing something custom like send yourself a text or email, or using something like Sentry to catch the error, and know when it happens hopefully, slim chances of it happening again.

iaremarkus commented 7 months ago

thanks for this @a11rew - I have chatted about this with Paytstack. Question: Is the Paystack callback URL meant to be my storefront URL, or the MedusaJS URL? I have it set to storefront, but just wanting to confirm this?

pic-004458

a11rew commented 7 months ago

@iaremarkus this plugin does not provide first class support for the Redirect payment flow which setting the callback URL implies you're looking to use.

Setting it to either the storefront or backend without doing additional work to support the flow (unless of course you've done this) will not do much.

"Additional work" here means setting up a route which when users are redirected to, calls the cart completion method which in turn gets Medusa to ask this plugin to verify the payment transaction.

I say we do not have 1st class support because the plugin exposes the authorization_url you need for the Redirect flow in the paystackTxAuthData object but does not set up the route for you. This is part of the payment session's data:

"paystackTxRef": "3eg6u4benc",
"paystackTxAuthData": {
  "reference": "3eg6u4benc",
  "access_code": "itxwli1w3yo3epx",
  "authorization_url": "https://checkout.paystack.com/itxwli1w3yo3epx"
}

You could redirect customers to the authorization_url in the storefront, handle the aforementioned calling of the complete cart method and show users the order success page (since you know the order was definitely paid for). However, this only hides the underlying problem instead: orders will not be marked as paid for in Medusa, services like order notifications and automated fulfillments will not be triggered, and the order will not show up in the user's account.

Regardless of the flow used (Redirect as opposed to the Popup flow here), a 5xx error from Paystack when verifying the transaction without great error handling in place will break the payment flow and leave the user in limbo. To fix this issue, you should be handling errors from calling the cart completion method: retrying in case of Paystack 5xx errors as a temporary fix for this particular issue (more on this below) or showing users an error page when anything else goes wrong so you don't have users clicking the payment button again after errors. This is what I meant by you should be handling the errors and retrying where appropriate.

Handling and retrying on Paystack 5xx errors should not be something you should have to do however (sorry 😅), this plugin should be handling the verification of payments better. 5xx errors from Paystack are rare and I never ran into them during dev hence the lack of support and test cases for it.

I have created issues #97 and #98 to improve this experience: both by supporting 5xx retries internally and verifying payments definitively with webhook notifications from Paystack. I hope to get to these this week, anyone is welcome to submit PRs for them too. In the meantime, handling retries on cart completion errors should fix this for you

iaremarkus commented 7 months ago

Thanks @a11rew. TBH I didn't think your plugin needed this, but @OWUREE had asked me via email about this setting so I wanted to confirm/make sure.

Appreciate your work on this plugin. Certainly made my job easier 😊

a11rew commented 7 months ago

v1.2.3 is out with support for internally retrying these errors from Paystack, it makes the verification process more resilient against one-off network/5xx errors hence significantly mitigating this issue.

Webhook support when added will close this issue definitively. Try the latest release out.

iaremarkus commented 7 months ago

You're awesome, thanks @a11rew

Considering the context of this thread, would you suggest that disable_retries be true in my case?

iaremarkus commented 7 months ago

*bump 😊

a11rew commented 7 months ago

No @iaremarkus the context of this thread is why retries were added. You should leave it set to false (the default). The disable_retries option was added for configuration but you should not have to use it, retries only happen on safe to retry requests.

iaremarkus commented 7 months ago

Thank you very much 🙏

OWUREE commented 7 months ago

Hi @a11rew. This is Haneefat from Paystack's Developer Relations team.

Based on our investigation of the reference dhpg5ky5o2 @iaremarkus shared, there was no request to verify the transaction on our server. Kindly share the endpoint you made this request to, and the request payload.

Please share the necessary logs.

Thank you

a11rew commented 6 months ago

v1.3.0 is out with support for order confirmations via webhook. This section in the readme describes setting webhooks up.

When enabled, the issue that occurred here (order confirmation failing because of a 504 from Paystack that wasn't retried when verifying the transaction) should not occur anymore because in addition to the manual verification that happens when you call completeCart, the plugin listens for webhook events from Paystack and confirms the order automatically (also with retries) on the backend.

For optimal UX in cases where completeCart fails for reasons like this, you can setup a notification system either with websockets, server-sent events or just long polling on the frontend checkout page to listen for the order being confirmed through the webhook then redirect users to the confirmation page for the cart's associated order.

Closing this issue. Feel free to re-open if this happens again with webhooks enabled.