I am attempting to create a subscription to a product in Stripe in the same step as the user registration. I’m ultimately trying to avoid having them first create an account, and then entering their payment details once an account has been created.
However, this process is proving to be tricky. It seems that in order to create a subscription, it needs me to set up a User Intent, which provides a client_secret, which I need to pass along to Stripe in order to create a subscription on the Stripe side of things. That’s where I am getting hung up.
Right now, the payment information form looks like this:
<form id="payment-form" action="{{ route('subscription.create') }}" method="POST">
@csrf
<input type="hidden" name="plan" id="plan" value="{{ $plan->id }}">
<div class="row">
<div class="col-xl-4 col-lg-4">
<div class="form-group">
<label for="">Name</label>
<input type="text" name="name" id="card-holder-name" class="form-control" value="" placeholder="Name on the card">
</div>
</div>
</div>
<div class="row">
<div class="col-xl-4 col-lg-4">
<div class="form-group">
<label for="">Card details</label>
<div id="card-element"></div>
</div>
</div>
<div class="col-xl-12 col-lg-12">
<hr>
<button type="submit" class="btn btn-primary" id="card-button" data-secret="{{ $intent->client_secret }}">Purchase</button>
</div>
</div>
</form>
In that markup, if you look at the data-secret attribute of the submit button – that seems to be the hangup point for me. I need to basically take the client_secret in the intent that is created, and pass it along in my registration method – but I’m not sure how to do that.
Basically, I have the default Laravel Auth form, and am operating inside of the App/Http/Controllers/Auth/RegisteredUserController.php file, which is where the store method is that creates the user inside of the database.
Here is my code for that method:
public function store(Request $request) {
$userID = uniqid('u-');
$plan = Plan::find($request->plan);
$request->validate([
'first_name' => ['required', 'string', 'max:255'],
'last_name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
]);
$user = User::create([
'unique_id' => $userID,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'name' => $request->first_name." ".$request->last_name,
'email' => $request->email,
'password' => Hash::make("abcd1234"),
'user_role' => 'customer',
'status' => 'pending',
'show_in_directory' => 'on',
]);
event(new Registered($user));
Auth::login($user);
/* The following lines are where I am passing data to Stripe to create the subscription */
$intent = $user->createSetupIntent();
$clientSecret = $intent->client_secret;
/* How do I pass $clientSecret to the subscription request from here??? */
$subscription = $user->newSubscription($request->plan, $plan->stripe_plan)->create($request->token);
return view("subscription_success");
As I am looking at the sample code, it seems to want to create the user first so that it can create an Intent, and then pass a client_secret string into a hidden field inside of the payment forms submit button.
So, I guess what I am ultimately asking is, how can I create that client_secret, pull it out of the Intent that was created, and pass that along to Stripe, so that it knows it’s kosher?
2
Answers
You can get the
payment_intent
from the subscription’s latest_invoice, as explained in this integration docThere’re multiple ways to go about it
Option 1A
This is the preferred way, and Stripe has an example / guide for step 2 : https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements
client_secret
to render the Card / Payment Element. You would probably also want to set payment_settings.save_default_payment_method=trueOption 1B
Unless you have a compelling reason to use option 1B, you should ideally use option 1A as it has less requests.
customer
from step 1client_secret
in step 2Option 2
This option also uses more requests than 1A.
client_secret
Option 3
I don’t recommend this option because it’s a pain to implement.
However, because the PaymentMethod was not created with a SetupIntent, when creating the Subscription, the issuer could require authentication and you would subsequently need to display the 3DS modal for the user to authenticate by calling stripe.confirmCardPayment with the Invoice’s PaymentIntent
client_secret
, and pass in the same Payment Method id