Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stripe v3 - SetupIntents and Subscriptions

I'm trying to use SetupIntents to validate and save a Payment Method and then create a Subscription using it to charge the customer (inmediately and then monthly) for the required amount.

This appears to be working fine:

  • The Card is validated (including SCA if needed)
  • The Payment Method is created, attached to customer as default and enabled for future usage with the status SUCEEDED.
  • The Subscription is created and uses the above payment method

The problem is that Stripe then generates the corresponding Invoice and Payment Intent but the latter with the status "requires_action" whenever the provided Card requires Secure Customer Authorization (SCA), even though the right payment method (enabled for future usage) is being used and the card validation has been already performed.

I thought the whole point of using SetupIntents was precisely to validate the payment method beforehand and be able to charge the customer afterwards.

Is my assumption simply wrong or this is actually possible and I might just be missing something?

Thanks in advance

EDIT: This is the subscription creation code in the backend:

# Set the default payment method on the customer
      Stripe::Customer.update(
        stripe_customer_id,
        invoice_settings: {
          default_payment_method: @requested_source
        }
      )

subscription = Stripe::Subscription.create({
       "customer" => stripe_customer_id,
       "proration_behavior" => 'create_prorations',
       "items" => [
         [
           "plan" => "#{@stripe_plan_api_id}",
         ],
       ],
       'default_tax_rates' => [
         "#{@stripe_tax_rate_id}",
       ],
       "expand" => ["latest_invoice.payment_intent"]
     });
like image 211
Eduardo López Avatar asked Jun 18 '20 16:06

Eduardo López


People also ask

Can you use Stripe for subscriptions?

Subscription objectsStripe Customer objects allow you to perform recurring charges for the same customer, and to track multiple charges. If you create subscriptions, the customer ID is passed to the subscription object. Your customer's payment instruments–how they pay for your service.

What is the difference between a SetupIntent and PaymentIntent?

Use the Setup Intents API if you want to save payment information for a customer but do not want to simultaneously create a charge. Creating a SetupIntent will generate a PaymentMethod to be attached to a customer, which can then be used to create a PaymentIntent when you are ready to charge them.

Does Stripe send invoice for subscription?

Stripe automatically creates an invoice for subscriptions at the end of each billing cycle. To create a one-off invoice for a non-recurring price, read the Invoicing docs. Stripe finalizes and sends the invoice in 1 hour. Before the invoice is finalized, you can edit it.

How do I set up a one time subscription on Stripe?

Add a one-time fee or discount with a new subscription plan : Stripe: Help & Support. To include a one-time charge or discount to the first subscription invoice, you can do so by adding an invoice item to the customer before creating the subscription. This can be done using the API or directly in the Stripe Dashboard.


1 Answers

Thanks for the question, Eduardo.

There are a couple ways to create a Subscription while gaining the Customers SCA authentication and permission to charge the card later. Assuming we already have a Stripe Customer object created and their ID is stored in stripe_customer_id. With the flow you have now there are a couple steps:

  1. Create the SetupIntent. Note that if you create it with usage: 'off_session' and the Customer it'll attach the resulting PaymentMethod when confirmed.

    setup_intent = Stripe::SetupIntent.create({
      customer: stripe_customer_id,
      usage: 'off_session',
    })
    
  2. Collect payment details and confirm the SetupIntent using its client_secret on the client which will attach the PaymentMethod to the customer. Note that it will attach but will not be set as the invoice_settings.default_payment_method by default, so you'll need to make a separate API call later to update the Customer (see step 3).

     stripe.confirmCardSetup(
       '{{setup_intent.client_secret}}', 
       {
         payment_method: {
           card: cardElement,
         },
       }
     ).then(function(result) {
       // Handle result.error or result.setupIntent
     });
    
  3. Update the Customer and set its invoice_settings.default_payment_method equal to the ID of the PaymentMethod on the successfully confirmed SetupIntent.

    Stripe::Customer.update(
      stripe_customer_id, {
      invoice_settings: {
        default_payment_method: 'pm_xxxx', # passed to server from client. On the client this is available on the result of confirmCardSetup 
      }
    })
    
  4. Create the Subscription with off_session: true

    subscription = Stripe::Subscription.create({
      customer: stripe_customer_id,
      proration_behavior: 'create_prorations',
      items: [{
        plan: "#{@stripe_plan_api_id}",
      }],
      default_tax_rates: [
        "#{@stripe_tax_rate_id}",
      ],
      expand: ["latest_invoice.payment_intent"],
      off_session: true,
    })
    

This uses what's called a "Merchant Initiated Transaction" (MIT) for the Subscription's first payment. This is technically okay if the Subscription is created later after the Customer leaves and should technically work.

If the customer is on your site/app when you create the Subscription, there's another flow that is a bit more correct and doesn't require using a MIT exemption for SCA. The flow is the following for a Subscription without a trial:

  1. Collect card details with createPaymentMethod on the client (no SetupIntent)

    stripe.createPaymentMethod({
      type: 'card',
      card: cardElement,
    }).then(function(result) {
      //pass result to your server.
    })
    
  2. Attach those card details to the Customer

    Stripe::PaymentMethod.attach(
      "pm_xxx", {
        customer: stripe_customer_id
      }
    )
    
  3. Update the Customer's invoice_settings.default_payment_method

    Stripe::Customer.update(
      stripe_customer_id,
      invoice_settings: {
        default_payment_method: @requested_source
      }
    )
    
  4. Create the Subscription (without off_session: true)

    subscription = Stripe::Subscription.create(
      customer: data['customerId'],
      items: [
        {
          price: 'price_H1NlVtpo6ubk0m'
        }
      ],
      expand: ['latest_invoice.payment_intent']
    )
    
  5. Use the Subscription's latest_invoice's payment_intent's client_secret to collect payment details and confirm on the client.

    stripe.confirmCardPayment(
      '{{subscription.latest_invoice.payment_intent.client_secret}}', { ......
    

This second payment flow is a bit more correct from an SCA standpoint for getting authorization to charge the card. The second approach is outlined in the guide here: https://stripe.com/docs/billing/subscriptions/fixed-price

We also have a Stripe Sample you can use to experiment here: https://github.com/stripe-samples/subscription-use-cases/tree/master/fixed-price-subscriptions

like image 142
cjav_dev Avatar answered Oct 06 '22 15:10

cjav_dev