Roman Briazgalov
Roman Briazgalov

Reputation: 131

Stripe Payment request button with subscriptions

I have really tired looking for the information i need and hope for your help. Also, i have written to Stripe support, but for now the communication with them is very difficult.

Let's start at the very beginning.

I use Stripe subscriptions with Laravel Cashier.

I have already finished the payment with credit/debit cards. It has such a workflow: - user fills the form; - Stripe.js sends the filled data to Stripe server and returns the paymentMethod; - then i send the paymentMethod to my server and make the subscription for user with/without trial days.

I need to add Google pay and Apple pay buttons. According to the Stripe docs about Google pay and Apple pay, i have to create the Payment Request Button. As i understand the docs about Payment Request Button it works by this way: - server-side creates paymentIntent and sends it to the client-side; - the user pushes the Payment Request button; - the browser opens a popup with saved user's cards; - the user chooses a card and stripe.js charges user instantly.

I can't understand on what step stripe knows the plan id for making a subscription for the user.

I don't need to charge user instantly, i need just get the paymentMethod to send it to the server-side. Does anyone have experience with making Stripe subscriptions with Payment Request button?

I would be very appreciated for the help.

Upvotes: 13

Views: 4277

Answers (2)

SeanMC
SeanMC

Reputation: 2346

Here's how I did it. I used JS on the client and the server, but for the most part the steps are the same even if the syntax is different. There's so little documentation on it-I thought I'd see if I can help. Even if it's totally different, most of the logic should be the same. Spefically:

Frontend returns paymentRequest -> Server-takes-paymentRequest -> customer -> subscription -> Done

First, you won't need to do anything with the payment-intent on the frontend. I was hung up on that for a while.

Next, starting everything using stripe.paymentRequest() which takes some payment details, and returns a "PaymentRequest" object..

Here you handle rendering of the button, based on PaymentRequest.canMakePayment()

Next is how I handle when they click the button to Pay.

The paymentRequest has a paymentmethod event listener. The function on takes 2 args, a string 'paymentmethod' indicating what you're listening out for, and a function that is given the event as its argument. so like

    stripePaymentRequest.on('paymentmethod', (event) => {
      const paymentMethod = event.paymentMethod
  
      if (paymentMethod) {
        // call your server and pass it the payment method 
        // for Server side Stripe-API handling of payment
      }

      event.complete('success'); // Completes the transaction and closes Google/Apple pay
    });

When you call the server -you can handle it similar to a card payment, since you have the payment method.

1: you create a "Customer" object from stripe using the payment method. I used something like

        const customer = await stripe.customers.create({
            payment_method: paymentMethod.id,
            email: paymentMethod.billing_details.email,
            invoice_settings: {
                default_payment_method: paymentMethod.id,
            },
        });

2: you create a "Subscription" object from stripe using the Customer, I did it like this

        const subscription = await stripe.subscriptions.create({
            customer: customer.id,
            items: [{ plan: "plan_HereIsYourPlanID" }], // <--
            trial_period_days: 7,
            expand: ["latest_invoice.payment_intent"],
        });

3: The prior step should accomplish everything you need for Stripe. At this point I have some of my own user-management code to keep track of if my user subscribed or not. Then, you return success or not -to that function you called on the frontend, and now it's done you call event.complete('success')

I hope that helps. Sorry it's not PHP, but hopefully it's a start.

Upvotes: 22

jamesjamail
jamesjamail

Reputation: 41

SeanMC, thanks so much for this answer- there is very little documentation on using the PaymentRequestButton with subscriptions. Stripe's guide on the PaymentRequestButton involves paymentIntents, which I mistakenly thought were a requirement for using the RequestPaymentButton.

I did have a little bit of trouble following your code, so wanted to share my implementation for any others that might struggling with this.

I am using Stripe Elements (StripeJS) on the client side for a typical payment page with fields for billing info, as well as a for entering CC info. I want customers to be able to use Apple Pay or Google Pay to expedite this checkout process. Here is my component below which accomplishes this (I've removed some imports/TypeScript definitions for clarity).

import React, { useEffect, useState } from 'react';
import {
  PaymentRequestButtonElement,
  useStripe,
} from '@stripe/react-stripe-js';
import * as stripeJs from '@stripe/stripe-js';

const AlternativePaymentOption: React.FC = ({
  price, //the price for the subscription being purchased
  priceId, //the priceId for the subscription (I found it easiest to create one 
           //product with multiple priceId for the different subscription tiers
  checkout,
}) => {
  const stripe = useStripe();
  const [paymentRequest, setPaymentRequest] = useState<
    stripeJs.PaymentRequest
  >();

  useEffect(() => {
    //return early if missing dependancies
    if (!stripe || !price) return;
    //create the paymentRequest which is passed down to
    //to the <PaymentRequestButtonElement /> as a prop
    const pr = stripe.paymentRequest({
      country: 'US',
      currency: 'usd',
      total: {
        label: 'Website Subscription',
        amount: price * 100,
      },
      requestPayerName: true,
      requestPayerEmail: true,
      requestPayerPhone: true,
    });
    //check if the browser has a saved card that can be used
    pr.canMakePayment()
      .then((result) => {
        //user has a saved card that can be used - display button
        if (result) {
          //the render method below is conditional based on this state
          setPaymentRequest(pr);
          pr.on('paymentmethod', (ev) => {
            //this event is triggered at the completion of the saved payment 
            //interface
            //we don't care about the actual payment request anymore
            //now that we have a paymentMethod, we can check out as normal
            //the checkout function I am not including here, but is very similar 
            //to the 'create a subscription' stripe guide
            //however it is important to note that I am passing in the event as an 
            //argument.  Once checkout receives a response from my server as to 
            //wether the transaction was successful or not, use 
            //ev.complete('success') and ev.complete('fail') accordingly to close
            //the modal or display an error message (browser specific behavior)
            checkout(ev.paymentMethod.id, ev);
          });
        }
      })
      .catch((err) => {
        //log errors, retry, or abort here depending on your use case
      });
     //stripe, priceId, and price are listed as dependancies so that if they 
     //change, useEffect re-runs.
  }, [stripe, priceId, price]);
  return (
    <>
      {paymentRequest && (
        <PaymentRequestButtonElement
          options={{ paymentRequest }}
          //hacky fix to make force this component to re-render
          //a la https://github.com/stripe/react-stripe-elements/issues/284
          key={Math.random()}
        />
      )}
    </>
  );
};

export default AlternativePaymentOption;

Hopefully this helps anyone in a similar situation. I got lost in the weeds working on this for two days before I discovered this approach thanks to SeanMC.

Upvotes: 4

Related Questions