user1184205
user1184205

Reputation: 863

Stripe - PaymentIntent is null when I create or update a subscription

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

Answers (4)

Jim Morrison
Jim Morrison

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

Ateeb Asif
Ateeb Asif

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

Zinedine Benkhider
Zinedine Benkhider

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

vander
vander

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

Related Questions