Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 946
Roman Briazgalov Avatar asked Mar 26 '20 10:03

Roman Briazgalov


People also ask

Can you do subscriptions on Stripe?

Stripe 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.


2 Answers

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.

like image 144
SeanMC Avatar answered Oct 02 '22 16:10

SeanMC


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.

like image 29
jamesjamail Avatar answered Oct 02 '22 16:10

jamesjamail