Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customers are able to pay twice for an order in Mollie #2620

Closed
nicoes opened this issue Jan 11, 2024 · 15 comments · Fixed by #2691
Closed

Customers are able to pay twice for an order in Mollie #2620

nicoes opened this issue Jan 11, 2024 · 15 comments · Fixed by #2691
Assignees
Labels

Comments

@nicoes
Copy link

nicoes commented Jan 11, 2024

Describe the bug
We get reports from customer service that every now and then a customer pays twice for an order in Mollie.
In Mollie I do indeed see two payments created for that order. Whereas we would expect that this is prevented somehow. As in prevent the customers from starting a 2nd check-out.

Screenshot 2024-01-11 at 10 47 53

To Reproduce
Steps to reproduce the behavior:

  1. Add items to an order
  2. Use createPaymentIntent mutation
  3. Go to the returned check-out url
  4. Use createPaymentIntent for the order again
  5. A new url is returned
  6. Go to the returned check-out url
  7. Finish payments in both urls
  8. You have done two payments which should be revealed in Mollie's dashboard as well. (See schreenshot)

Expected behavior
At step 4 an error is returned / user is warned that another payment has already been started.

Environment (please complete the following information):

  • @vendure/core version: 2.1
  • Nodejs version: 18
  • Database (mysql/postgres etc): MySQL
@nicoes nicoes added the type: bug 🐛 Something isn't working label Jan 11, 2024
@martijnvdbrug
Copy link
Collaborator

👀 Joining this issue :-)

@seminarian
Copy link
Contributor

seminarian commented Jan 11, 2024

I remember encountering this.

There are multiple scenarios possibly:

  1. Customer goes back and starts a new payment for same order content.
  2. Customer goes back and starts a new payment for a changed order content.

So depending on the scenario, different limitions / checks should probably be applied.

Perhaps a solution could be the Mollie Plugin:

  • Enforcing an order to be in ArrangingPayment for being able to use createPaymentIntent
  • Store the paymentIntent on the Order.
  • If other requests are being received it can just return the same paymentIntent
  • If the Order transitions back to AddingItems it can remove the paymentIntent stored on the Order. (I don't think it's possible to delete it at Mollie's side though)

This will probably tackle the issue reported. It won't be perfect but I will be an improvement.

I think I remember the "Payment API" of Mollie to be a limitation in removing payment intents/updating them/..
Would a more refined payment handling be possible with the "Order API" of Mollie?
This last paragraph should probably be checked for correctness, as mentioned it's been a while.

@martijnvdbrug
Copy link
Collaborator

Thanks for yout input @seminarian. The Mollie Plugin now uses the order API, so indeed with a reference to the Mollie order, we are able to cancel the order and create a new one.

How would you cancel the Mollie order when transitioning to AddingItems? I guess we can listen for OrderTransitionEvent, but that will make it async. I guess this is fine, but if you are REALLY quick you could still pay for your order twice

@seminarian
Copy link
Contributor

seminarian commented Jan 11, 2024

@martijnvdbrug

The Mollie Plugin now uses the order API, so indeed with a reference to the Mollie order, we are able to cancel the order and create a new one.

Just make sure this statement correct. I remember something about payments not always being cancelable: https://docs.mollie.com/reference/v1/payments-api/cancel-payment#:~:text=Some%20payment%20methods%20are%20cancellable,the%20payment%20can%20be%20canceled.

How would you cancel the Mollie order when transitioning to AddingItems? I guess we can listen for OrderTransitionEvent, but that will make it async. I guess this is fine, but if you are REALLY quick you could still pay for your order twice

Not sure about that, the backend could probably track if it got succesfully cancelled or if it's a different amount to be paid.
Perhaps this decision can happen in Vendure Backend when a new paymentIntent is requested?

@martijnvdbrug
Copy link
Collaborator

Perhaps this decision can happen in Vendure Backend when a new paymentIntent is requested?
Even better indeed! I guess to keep things simple, we always cancel the current mollie-order with payments when a new pament intent is created.

I guess when the order or one of the payments couldn't be cancelled, we just log an error and create a new order with payments, just as we do now.

@nicoes
Copy link
Author

nicoes commented Jan 12, 2024

Thanks for all the input. Great that this has been moved to the backlog

@michaelbromley
Copy link
Member

How would you cancel the Mollie order when transitioning to AddingItems? I guess we can listen for OrderTransitionEvent, but that will make it async. I guess this is fine, but if you are REALLY quick you could still pay for your order twice

If we use a custom OrderProcess we could put this check in the inTransitionEnd hook which would execute non-async (i.e. serially with the state transition) which should prevent the edge case you mention.

@martijnvdbrug do you have any capacity to work on this in the near future?

@martijnvdbrug
Copy link
Collaborator

How would you cancel the Mollie order when transitioning to AddingItems? I guess we can listen for OrderTransitionEvent, but that will make it async. I guess this is fine, but if you are REALLY quick you could still pay for your order twice

If we use a custom OrderProcess we could put this check in the inTransitionEnd hook which would execute non-async (i.e. serially with the state transition) which should prevent the edge case you mention.

Thats an option, but it feels pretty intrusive if a payment plugin installs a custom order process. Plus a consumer could define their own order process, breaking the payment plugin. @seminarian s suggestion for doing it on new payment creation also works ai think.

@martijnvdbrug do you have any capacity to work on this in the near future?

Yep, still working together with Nico, and its causing issues, so I will be picking this up.

@michaelbromley
Copy link
Member

it feels pretty intrusive if a payment plugin installs a custom order process

Remember that the orderOptions.process is an array, so all the configured processes compose together as a pipeline. So you would just create a bare-bones OrderProcess which only adds the specific logic for this check. All other processes that are already configured will still run as usual.

And great to hear you can work on this 👍

@martijnvdbrug
Copy link
Collaborator

Remember that the orderOptions.process is an array, so all the configured processes compose together as a pipeline. So you would just create a bare-bones OrderProcess which only adds the specific logic for this check. All other processes that are already configured will still run as usual.

And great to hear you can work on this 👍

Ahh I see it now. Thanks. mergeStrategy: 'replace' is only for actual state transitions, so an onTransitionEnd would always be called and can not be overridden?

@michaelbromley
Copy link
Member

Yes correct. The only way to prevent it from executing would be to actively remove that OrderProcess from the array. Source reference:

await awaitPromiseOrObservable(process.onTransitionEnd(fromState, toState, data));

@martijnvdbrug
Copy link
Collaborator

Seeking input

Ok, so the proposed solution is:

  1. We will transition to ArrangingPayment before redirecting the customer to Mollie
  2. When an order is transitioned from ArrangingPayment back into AddingItems we cancel the Mollie order (if possible)

The question still remains, who will transition the order back into AddingItems? Is it the storefront? The customer could still have another tab open with the storefront, so on what event would you transition an order back to AddingItems?

@michaelbromley
Copy link
Member

So do you mean that the createMolliePaymentIntent mutation will do the state transition automatically to ArrangingPayment (if not already in that state)?

The question still remains, who will transition the order back into AddingItems?

I would leave this as a concern of the storefront implementation.

@martijnvdbrug
Copy link
Collaborator

So, we are struggling a bit on when the storefront should transition an order back to AddingItems. When does the storefront know that the order is in ArrangingPayment. There are some possibilities, but none seem very clean:

  • Refetch active order when a Browser tab gets focus again. If in ArrangingPayment, transition back to AddingItems
  • Check for Order State Errors on every mutation, so that we know when to first transition back to adding items.
  • Time based checks: Refetch order in browser every X seconds

Any suggestions are welcome :-)

@martijnvdbrug
Copy link
Collaborator

After discusssing with @nicoes we opted for the following solution:

Transition to ArrangingPayment after payment

  • When createMolliePaymentIntent is called, we leave the order in AddingItems, but we do check if it's transitionable to ArrangingPayment (This is how we do that)
  • We store the Mollie order ID on the Vendure order.
  • When the Mollie webhook comes in to Vendure with status paid, we transition to ArrangingPayment and add the payment to the order.
  • This in turn transitions to PaymentSettled if the total amount has been paid.
  • If a customer calls createMolliePaymentIntent again, the plugin will check if a mollie order ID is already present on the order. If so, it cancels the previous Mollie order (if possible) and starts a new Mollie order.
  • If it's not possible to cancel the Mollie order for whatever reason, that's unfortunate, but we leave it. We will still prevent the bulk of the duplicate payments with this, and we don't want to block new payment intents from customers.

What does it not solve

A customer can still open a new tab with the storefront after a Mollie Checkout has been started and add items to the active order. If the customer then finalizes payment with the old Mollie order, the Vendure order will go to ArrangingAdditionalPayment. This is something the storefront could handle, but doesn't have to.

Why not transition to ArrangingPayment before going to Mollie checkout?

It's quite a big change for the storefront compared to the current way of working. All mutations need to check if the order is in AddingItems or catch the OrderModificationError.

The proposed solution doesn't require any changes on frontend. The issue where an order goes to ArrangingAdditionalPayment instead of PaymentSettled can optionally be handled on the storefront's confirmation page, but is not required.

@michaelbromley michaelbromley moved this from 📋 Backlog to 🔖 Ready in Vendure OS Roadmap Mar 4, 2024
@michaelbromley michaelbromley moved this from 🔖 Ready to ✅ Done in Vendure OS Roadmap Apr 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: 🚀 Shipped
Development

Successfully merging a pull request may close this issue.

4 participants