#shwallie-paymentintent-tax
1 messages · Page 1 of 1 (latest)
shwallie-paymentintent-tax
@steel shore taking a step back for a sec first: Why are you creating a PaymentIntent for a Subscription? Are you doing your own recurring logic/payment yourself?
Hey - good question... Perhaps I'm doing this incorrectly! So, we want a user to setup their default payment method before starting the subscription. So:
- Create PaymentIntent
- Setup payment information
- Update customer to use payment method as a default method
- Use the payment method's Country/Postal Code to determine the 'final' cost of the subscription: sub total + tax(es)
- Provision the subscription (with an optional free trial)
- Use the subscription's appropriate client_secret to the client:
6a: It's a free trial, use the pending_setup_intent.client_secret to call 'confirmSetup'
6b: Non Free Trial, latest_invoice.payment_intent.client_secret (for a non-free trial) to call the 'confirmPayment'
- Listen to web hooks for customer.subscription.(created|updated) events to provision stuff for the user on our site
gotcha so yeah you're doing this wrong 🙂
The easiest path is
1/ Create the Subscription upfront
2/ Collect payment method details client-side to pay the first Invoice
3/ Profit!
That's what the guide here explains https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements
Some developers don't like that integration path where they always create the Customer and Subscription upfront, though it's really better especially if you want to work with tax calculation and such upfront.
An alternative path is
1/ Collect payment method details upfront
2/ Create the Customer/Subscription server-side once ready to really pay
3/ Confirm the underlying PaymentIntent for that Subscription with the payment method details from step 1
That's what's covered at https://stripe.com/docs/payments/accept-a-payment-deferred?platform=web&type=subscription
Okay - yah, we're more along the lines of the alternative path you provided and that's what I've got working. We use the payment method's country/postal code to setup basic address information for tax.
I don't know why, but I just dislike this code on the client: (https://stripe.com/docs/payments/accept-a-payment-deferred?platform=web&type=subscription#web-collect-payment-details)
const options = {
mode: 'subscription',
amount: 1099,
currency: 'usd',
};
I don't particularly understand what 'amount' is doing there. My subscriptions didn't seem to bill correctly when a free trial ended if I set it to 0.... And, 'amount' is also a bit of a lie since I don't have a total with taxes available at that time.
So - yah, that's why I was trying to initialize that with the PaymentIntent's client_secret, so I could provision it with a price_id on the backend.
Well that amount is just a placeholder really. You need some amount to show to the end customer. And many payment method types have strict validation (min/max amount, currency, 3D Secure, etc.)
As the developer, you would calculate the real amount your Subscription is going to Charge upfront. If you have a trial upfront, then you shouldn't use amount at all (but then there's no PaymentIntent either).
So taking a step back: are you taking a payment upfront?
We want to collect/validate payment methods before the subscription starts. If it's a free trial, we'll expect the invoice to be paid using the information that was supplied when the subscription started. If the user is not eligible for a free trial, then they need to pay up front to start the subscription.
either way - the user's default payment method will be used by the subscription.
Yeah but the "either way" part is fundamentally different
There's a different between charging someone the full price upfront or just collecting card details for later
For a trial period the amount would be 0. For non trials it should be the exact amount you are going to charge on the first Invoice.
Exact amount - so, tax inclusive
Shoot - I don't have that amount yet though. I'm using the payment method's country/postal to calculate tax, so I don't have that when I initialize the payment element. And, it doesn't look like I can update that value:
https://stripe.com/docs/js/elements_object/update_payment_element
Okay - huh. Is this a valid flow at all? ie: Using a customer's payment method to determine tax? Feels like I'm 'fighting' with Stripe to make things work the way I want it to work, and I'd rather do things the 'Stripe Way' wherever possible.
@steel shore https://stripe.com/docs/js/elements_object/update
I forgot you update the Elements instance, not PaymentElement
Anything else I can help with?
Well - I guess is this the only way to using payment information to determine tax and also collect payment information before a free trial subscription is started? I just feel like my solution is getting a bit complicated.
I really don't understand what tax have to do with it
I'm sorry, we keep hitting this: If trial -> No payment, no tax. If no trial -> payment tax
Why are you trying to get the upcoming tax of the tax after their trial for example? For Stripe it's not "relevant" at least for the payment method collection
Ha - sorry. But, if it's a trial, we want to tell our customer how much they'll be charged (including tax) when the trial ends. We want them to start being billed when the trial ends with no user interaction (unless their payment method is no longer valid, eg: insufficient funds, expired, etc, etc.
Yeah but that information is not provided to PaymentElement or Stripe and you would never create a PaymentIntent?
(just to make sure)
Okay - I get yah. I'll stop trying to use the payment intent. Here's what I'll do:
- Let a user pick the product/price_id they want
- Use the unit_amount/currency to initialize the payment element, eg:
{
mode: 'subscription',
amount: isFreeTrial ? 0 : unitAmount,
currency: price_currency,
paymentMethodCreation: 'manual',
setupFutureUsage: 'off_session'
} - call 'stripe.createPaymentMethod'
- associate the resulting paymentMethodId with our customer as a default payment method
- Update the customer's address to use the payment method's country/postal code
- Use stripe.Invoice.upcoming with the customer & price_id to get a 'preview' of the price + taxes
- Let the client see the final charge (either now or after free trial elapses)
- Update the Elements instance's 'amount' field to be the tax-inclusive amount (or remain 0 if it's a free trial)
- Now that the user's seen the complete price, they can decide to start their subscription
- create a subcription for the customer.
- Return the appropriate client_secret:
a) Free Trial: new_subscription.pending_setup_intent.client_secret
b) Non-Free Trial: new_subscription.latest_invoice.payment_intent.client_secret - Use the client_secret on the client-side
a) Free Trial: stripe.confirmSetup
b) Non-Free Trial: stripe.confirmPayment
Cool! And, in a situation where they unsubscribe and decide to resubscribe with a valid default payment method:
- Show them the tax-inclusive total (basically skip ahead to #9 from above)
- Create a subscription on the backend
- Use the new_subscription.latest_invoice.payment_intent.client_secret on the client to call 'stripe.confirmPayment'
Done.
yes!
Okay - cool. Thanks for listening and the kick in the right direction.
Of course! I'm sorry this is so complex
people on my team kinda dream of a "Subscription element" where you give it price options and it does everything for you on your website. But that's more what Checkout and such offer instead
Yah exactly - I wish there was a way for a free trial to not be considered started without valid payment information. But, since that first invoice is $0, then it turns to 'trialing' immediately. And, if a user doesn't complete the flow, then we'll have to do a bunch of extra logic if they decide to come back and 'resume' opting into our services.