Reputation: 131
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
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
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