Reputation: 863
I'm new to Stripe, so if I'm missing something, please advise.
Tech Stack: React (front-end) Node (back-end)
I am trying to create or update a Stripe subscription, and in either case, I get a null paymentIntent. I thought I read that I should check the paymentIntent status to make sure the payment for the subscription has gone through.
My subscription workflow, when a user signs up, I create a Stripe customer and add them to a subscription. This subscription is a free tier so no payment method is needed. The payment_intent
is null.
//create a Stripe customer code here
...
//create the subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ priceID }],
expand: ['latest_invoice.payment_intent'],
});
const invoice = subscription.latest_invoice as Stripe.Invoice;
const payment_intent = invoice.payment_intent as Stripe.PaymentIntent;
Later after they want to upgrade to a paid plan, I request a credit card and upgrade their current subscription to a paid plan. On the front-end, I use Stripe Element and create a payment method by doing this.
if (!elements || !stripe) {
return
}
const cardElement = elements.getElement(CardElement);
if (!cardElement) {
return
}
// Confirm Card Setup
const {
error,
paymentMethod
} = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details:{
name,
email: currentUser.email,
phone,
address: {
city,
line1: address,
state,
},
}
});
if (error) {
console.log("createPaymentMethod: ", error.message)
} else {
const paymentMethodId = paymentMethod!.id
// Switch to new subscription
await switchSubscription(priceID, paymentMethodId)
}
And on the back-end, I get the stripe customer, add a payment method, and upgrade the subscription to the new plan. The payment_intent
is null.
function switchSubscription(priceID, user, paymentMethod) {
//get Stripe customer code here ...
...
await stripe.paymentMethods.attach(paymentMethod, { customer: customer.id });
await stripe.customers.update(customer.id, {
invoice_settings: { default_payment_method: paymentMethod },
});
const currentSubscription = await getSubscriptions(user)
const updatedSubscription = await stripe.subscriptions.update(
currentSubscription.id,
{
cancel_at_period_end: false,
proration_behavior: 'always_invoice',
items: [
{
id: currentSubscription.items.data[0].id,
price: priceID,
},
],
expand: ['latest_invoice.payment_intent'],
})
const invoice = updatedSubscription.latest_invoice as Stripe.Invoice;
const payment_intent = invoice.payment_intent as Stripe.PaymentIntent;
}
Upvotes: 6
Views: 5033
Reputation: 2187
Quick fix if you're creating a subscription with a free trial ...
... client_secret
will be in pending_setup_intent
because of the trial .. not where you'd expect to find it in latest_invoice.payment_intent
So:
// you probably have this already
'payment_behavior' => 'default_incomplete',
// and you're expanding some stuff already (from the docs)
'expand' => [
// and this, expecting the payment intent
'latest_invoice.payment_intent',
// but for a trial, expand *this* element `client_secret` etc..
'pending_setup_intent',
],
Upvotes: 2
Reputation: 184
For Subscriptions that are created with free trial or amount $0 to $0.5, for that stripe won't generate a payment intent. In order to solve this issue you need to set "payment_behavior: default_incomplete" while creating the subscription with free trial or the first Invoice is $0 then you will be getting back a Setup Intent at pending_setup_intent. check the stripe docs. Then you get the setupIntent_clientSecret and sent it to your client to complete the payment process by using stripe.confirmCardSetup().stripe reference
Here is the code example for all this process. starting from client
first you create a payment method by
const createPaymentMethodResult = await stripe.createPaymentMethod({
type: "card",
card: elements.getElement(
CardNumberElement,
CardExpiryElement,
CardCvcElement
),
billing_details: billing_details,
metadata,
});
then get the paymentMethodId from
//! getting paymentMethod ID
const paymentMethodID = createPaymentMethodResult?.paymentMethod?.id;
now make a request to your backend to send the paymentMethod Id because we will use payment Method Id while creating a customer and attach it to that customer and set it their default payment method
//! Now make request to backend for creating subscription
const url = "http://localhost:3001/subscription/create";
const res = await axios.post(url, {
payment_method_id: paymentMethodID,
email: userData?.email,
selectedProduct: {
productName: selectedProduct?.productName,
productPriceId: selectedProduct?.productPriceId,
},
billing_details,
metadata,
});
// getting the client_secret in response from server
const { pending_setup_intent: client_secret, success } = res?.data;
//! Now confirm CardPayment if using trial period
const confirmedCardPaymentResult = await stripe.confirmCardSetup(
client_secret,
{
payment_method: {
card: elements.getElement(
CardNumberElement,
CardExpiryElement,
CardCvcElement
),
billing_details,
},
}
);
Now for the backend server code
// getting the data from the request
const { email, payment_method_id, selectedProduct, billing_details,
metadata } = req.body;
const { productName, productPriceId } = selectedProduct;
Now creating the customer with the paymentMethodId
const customer = await stripe.customers.create({
payment_method: payment_method_id,
email: email,
invoice_settings: {
default_payment_method: payment_method_id,
},
metadata,
});
creating trial period for this example it's 7 days
const date = moment().valueOf();
const trial = moment(date).add(7, "days").valueOf();
const unixTrial = moment(trial).unix();
now creating the actual subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: productPriceId }],
expand: ["latest_invoice.payment_intent"],
description: productName,
trial_end: unixTrial,
payment_behavior:"default_incomplete" //! add this if using trial
metadata,
});
now getting setup intent
//! getting setup intent from sub to extract the client secret, if using
trial with subscription
const setupIntent = await stripe.setupIntents.retrieve(
subscription?.pending_setup_intent
);
after this getting setupIntent client secret
//! getting client secret from setup-intet
const pending_setup_intent = setupIntent?.client_secret,
now sending the response back to client so the client can use this setupIntent client secret to confirm card setup or payment
res.json({
customerId: customer.id,
subscriptionId: subscription.id,
success: true,
pending_setup_intent: pending_setup_intent,
});
Upvotes: 3
Reputation: 386
I solved this probleme by adding the quantity to items. I think the invoice is not created (null) when the total amount is 0.
Upvotes: 2
Reputation: 133
Your customer might have a balance in their account so that the amount might be taken out from the customer's available balance. I think for cases like that, Stripe doesn't create a payment intent, and therefore returns null
.
Upvotes: 1