New payment state - Pending
See original GitHub issueIs your feature request related to a problem? Please describe. Looking at vendure’s payment plugins I see that current payment workflow is:
- Client call
createStripePaymentIntent
through shop-ai or some method, which creates an entity in payment gateway. - Client make a payment through payment form.
- An incoming webhook informs vendure about successfull payment.
- Vendure calls
addPaymentToOrder
, which callscreatePayment
method on appropriate payment handler.
Basically, this scheme is okay, but problem arises when client want to specify more than one payments in the order. Say an order has total - 100$.
- Client adds a gift card or bonus payment to an order - 20$. It does not cover order’s total.
- Client creates a stripe payment intent through
createStripePaymentIntent
. But stripe payment plugin forceorder.totalWithTax
field as amount, so amount result is 100$. - Client see 100$ amount in payment form, which is wrong.
What I would like to see:
- Client adds a gift card or bonus payment to an order - 20$. It does not cover order’s total.
- Client adds a stripe payment intent through classical
addPaymentToOrder
, with amount 80$, because it calculates by formula: order.totalWithTax - all authorized/settled payments. createPayment
handler returns an object, like { state: ‘pending or created’, metadata: { url: ‘stripe payment url’ } }- Client uses an url from metadata to go to payment page, where amount to pay is 80$, which is right.
- An incoming webhook informs vendure about successfull payment.
- Vendure calls
transitionPaymentToState
which automatically transit a payment as well as an order to appropriate state.
I understand that I am completely able to implement this, thanks to vendure’s extensible mechanisms, but it’s cool to have a solid pattern here.
Describe the solution you’d like
- Do not add new mutations, like
createStripePaymentIntent
. It generates an additional logic on storefront side. Imagine you have 3-4 connected payment plugins - your storefront or multiple storefronts should support every mutation for every provider. - Use traditional
addPaymentToOrder
to add new payments. It worth to place payment url into predefined field for that, not to metadata directly. Any of your storefront will be able to pay with any payment provider without an additional logic in unified way. - Consider adding new payment state ‘Pending’. Right after payment creation, payment handler may transit to state ‘Pending’ (Created -> Pending), so it means payment plugin waits user to be paid with payment url/form.
Describe alternatives you’ve considered @vrosa doesn’t that depend on the implementation of this additional payment, e.g. gift card? I would expect the order.totalWithTax to be $80 by the time createStripePaymentIntent is called because that’s what the customer is going to be charged. Other platforms, e.g. Shopify, also consider subTotal or total to the be amount after the gift card has been applied. Not sure how Vendure’s paid gift card plugin will behave but I would expect it to subtract the amount from the order’s total.
Additional context @michaelbromley The gift card plugin I’m working on actually treats gift cards/store credit as a payment method, in the way described by Alexander. One reason for this is to do with the issue of how to deal with something like a return - if I buy 2 t-shirts @ $20 each, and then use a Promotion-style discount to make the order total $0, then Vendure is actually treating each OrderItem as having a value of $0 - i.e. the value of OrderItem.proratedUnitPrice will be 0. So for the purposes of returns & refunds, this indicates that the amount to refund would be $0, which is not correct. Treating the gift card as a payment method allows us to model the actual transaction values more accurately - i.e. if I use a gift card to “pay” for the above order, then a return of 1 t-shirt implies a refund/re-credit of $20. So I think it does make sense to take into account any existing payments on this line when creating the Stripe payment intent.
Slack thread: https://vendure-ecommerce.slack.com/archives/CKYMF0ZTJ/p1656064741229719
Issue Analytics
- State:
- Created a year ago
- Comments:8 (6 by maintainers)
Top GitHub Comments
To me,
Created
state already used in order state machine, and in fact, normally you will not be able to get an order somehow in this state, because on creation order transit toAddingItems
state immediately. I guess this is done to get proper eventOrderStateChangedEvent
after order creation. So, gettingCreated
state means that something went wrong, and I think the same rule should be applied to payment state machine also. Consider adding new statePending
instead of using existingCreated
to have similar behavior with other state machines.I’ve been working on the StripePlugin over the past couple of days, improving on some edge cases and security holes that were descovered. I now tend to agree that it would be best to unify the payment flows with calls to
addPaymentToOrder
.At this point, we can create a Payment in the
Created
state and attach the payment intentclient_secret
to the Payment’s public metadata, so that it can be used in the frontend part. I’d prefer not to change the return type ofaddPaymentToOrder
(which on success returnsOrder
), but in any case the Payment metadata is easily retrievable from the Order type.Doing it this way also has the following advantage: when we create the Payment, we can add the Stripe payment intent ID to the Payment’s metadata. Then we can implement a CustomOrderProcess
onTransitionEnd
hook and catch transitions fromArrangingPayment -> AddingItems
. When this occurs, we can look up the Payment and use the payment intent ID to cancel the payment intent with Stripe.This would give use a more robust and auditable sequence of events for each order.