Laravel auth user on webhook - php

I am having trouble with authenticated user in my Laravel/Vue app. Once you log in, you can choose to make a purchase via Stripe which leads you off the page, and returns back upon payment.
Just to make sure, I've made an endpoint:
Route::get('test', function(){
return Auth::user();
});
And before and after Stripe, when I hit it, I do get back the user. So authentication is in order.
What happens though is that Stripe upon payment event makes a webhook callback to my route:
Route::post('api/stripe/checkout-session-completed', 'StripeController#checkoutSessionCompleted');
Inside a hook, event is fired which should propagate number of credits purchased to the user who made the purchase, however I am always getting that Auth::user() is not defined.
use Illuminate\Support\Facades\Auth;
...
public function checkoutSessionCompleted()
{
...
$this->handleCheckout($session); // this is Stripe session object
...
}
private function handleCheckout($session)
{
...
event(new PaymentSuccessful($payment, Auth::user()));
...
}
Was this supposed to happen? How can I get the currently auth user if not like this?

Looks like sessions aren't shared when external source makes a POST request to your route. I made a workaround to include user ID within Stripe session metadata, so I can find user by that same ID when request returns via webhook.
$stripeSession = Session::create([
'success_url' => route('stripe.success') . '/?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('stripe.cancel'),
'payment_method_types' => ['card'],
'mode' => 'payment',
'line_items' => [
[
'price_data' => [
'currency' => 'eur',
'product' => env('STRIPE_PRODUCT_ID'),
'unit_amount' => $price->stripe_price * 100,
],
'description' => "Credits to receive: $price->quantity",
'quantity' => 1,
],
],
'metadata' => [
'quantity' => $price->quantity,
'user_id' => Auth::user()->id,
],
'customer_email' => optional(Auth::user())->email ?? null,
'client_reference_id' => optional(Auth::user())->id ?? null,
]);

Related

Trying to charge before or after Subscription Schedule is created Stripe API

I am developing a wordpress website for a client.
He needs different types of packages. For most of these packages I developed a simple Stripe checkout webpage, using its documentation.
The problem is that I need this workflow:
first month x dollars
second month x dollars
after one year subscription y dollars
I've already done this using Subscription Schedule. But it needs a customer ofc. How can I charge before and after charging create this Subscription Schedule? I don't know how to deal with this, how to charge using Stripe checkout simple already built page or do I need to create one by myself, where user needs to add his card, pay, and get the customer_id?
function checkout3() {
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
\Stripe\Stripe::setApiKey('sk_test_51e7DRPLRnISGb5vSFxnvvuDx1GzhlBIFeazcmpEevsUFf29jHXJ1YgE2xaJ1lGfzjtKzE8uoN0eR9Klaq00CnMFWvfB');
// The price ID passed from the front end.
// $priceId = $_POST['priceId'];
$priceId = 'price_1LPahmIne7DRPLRnFXV6Uz34';
$futureDate= strtotime(date('Y-m-d', strtotime('+1 year')));
$customer = \Stripe\Customer::create(
[
'description' => 'My First Test Customer (created for API docs at https://www.stripe.com/docs/api)',
]
);
$session = \Stripe\SubscriptionSchedule::create([
'customer' => $customer["id"],
'start_date' => 'now',
'end_behavior' => 'release',
'phases' => [
[
'items' => [
[
'price' => 'price_1LRF5CIne7DRPLRnwuLVE2pu',
'quantity' => 1,
],
],
//'end_date' => $futureDate,
'iterations' => 1,
],
[
'items' => [
[
'price' => 'price_1LRF5cIne7DRPLRngevvIZiw',
'quantity' => 1,
],
],
'iterations' => 1,
],
[
'items' => [
[
'price' => 'price_1LPujQIne7DRPLRnj3EOweJN',
'quantity' => 1,
],
],
],
],
]);
// Redirect to the URL returned on the Checkout Session.
// With vanilla PHP, you can redirect with:
//header("HTTP/1.1 303 See Other");
//header("Location: " . '$session->url');
}
So right now, the subscription schedule is added to the Stripe dashboard, the page it's keep loading infinitely, but without charging... How to deal with this?
public static function firebase_checkout3_func() {
$html = "";
$html .= "<form id='firebase-checkout' action='/wp-json/api/checkout2' method='POST'>
<button type='submit' id='checkout-button'>Începe acum</button>
</form>";
return $html;
}
Use Stripe hosted Checkout Page with setup mode to collect
a Customer's SetupIntent
Retrieve the Payment Method inside the SetupIntent
Set it as the customer's invoice_settings.default_payment_method
Create a Subscription Schedule with that Customer Id as normal

Symfony Stripe Disconnect after Payment

in my application Symfony 5.4 and PHP 8.0, i'm using Stripe Payment Checkout https://stripe.com/docs/payments/checkout, after payment success or error my user need to be redirect.
'success_url' => $YOUR_DOMAIN.'/domain/projet/{CHECKOUT_SESSION_ID}/domain/succes',
'cancel_url' => $YOUR_DOMAIN.'/domain/projet/{CHECKOUT_SESSION_ID}/error',
But user is disconnect and i don't want user disconnect, sometime if we are lucky user is not disconnect after payment.
#[Route('/espace-client/payer/project/{idBilling}/{idProject}', name: 'final')]
public function stripePayment(int $idBilling, int $idProject)
{
$project = $this->projectRepository->findOneById($idProject);
$billing = $this->billingRepository->findOneById($idBilling);
$YOUR_DOMAIN = 'https://127.0.0.1:8000';
$productStripe[] = [
'price_data' => [
'currency' => 'eur',
'unit_amount' => $billing->getPrice(),
'product_data' => [
'name' => $billing->getName(),
'images' => null,
],
],
'quantity' => 1,
];
Stripe::setApiKey($this->publicKey);
$checkout_session = Session::create(
[
'line_items' => [[
$productStripe,
]],
'payment_method_types' => [
'card',
],
'mode' => 'payment',
'success_url' => $YOUR_DOMAIN.'/espace-client/projet/{CHECKOUT_SESSION_ID}/paiement/succes',
'cancel_url' => $YOUR_DOMAIN.'/espace-client/projet/{CHECKOUT_SESSION_ID}/erreur',
]
);
$billing->setStripeSessionId($checkout_session->id);
$this->entityManager->flush();
return $this->redirect($checkout_session->url);
}
I tryied everyting but user is disconnected always and i don't know why.
Payment, checkout all is ok
If your users get disconnected after the Stripe redirect, it usually means that one or more of the user’s cookies (such as their login or session cookie) are lost during the redirect process. And there are two common causes for this:
A mismatch between HTTPS and HTTP protocols after the redirect. For example if the user is browsing your website on HTTPS, but then gets redirected to a HTTP URL. To fix this, make sure all URLs involved use the same protocol.
The SameSite attribute is set to strict on the cookies used for login or session. If that’s the case, those cookies will not be included when the user is redirected back to your website because that redirect is considered a cross-site request. To fix this, change the SameSite attribute from strict to either lax or none. More info here.

PHP Stripe: You can not pass `payment_intent_data` in `subscription` mode

I'm trying to create a subscription for the merchant's users but facing "You can not pass payment_intent_data in subscription mode" error. With regular payments, it works well, but subscriptions aren't working.
Here is an example of what I want to do: John has an e-commerce shop based on recurring billing. Matthew is John's customer and wants to purchase a subscription from John. How can I easily take fees and transfer money to John's connect account while using "Stripe Checkout"?
$session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price' => $priceIntent->id,
'quantity' => 1,
]],
'customer' => Auth::User() -> stripe_code,
'mode' => 'subscription',
'payment_intent_data' => [
'application_fee_amount' => $total_fees,
'transfer_data' => [
'destination' => $merchantId,
],
],
'success_url' => env('APP_URL') . '/order/success/{CHECKOUT_SESSION_ID}',
'cancel_url' => env('APP_URL') . '/order/cancel/',
]);
Thanks!
Basically, you can't use payment_intent_data on a Checkout Session in subscription mode, since the subscription creates invoices instead of PaymentIntents.
To do this, you need to use the subscription_data hash: https://stripe.com/docs/api/checkout/sessions/create?lang=php#create_checkout_session-subscription_data-application_fee_percent and specify the merchant account.
Example of the API call with merchant details:
$session = \Stripe\Checkout\Session::create([
'subscription_data' => [
'application_fee_percent' => $fees_percent,
],
],array("stripe_account" => "acct_xxxxxxxxx"));
Also, don't forget to pass all the other required variables in the call.
Cheers :)

Stripe: handle multiple Webhooks

I'm working on a project which has two types of products: subscriptions and event registration.
I'm using Stripe Checkout Session for both. As their process is different, I'm using two webhooks; one for each.
e.g:
http://example.com/payment/subscription_webhooks.php
http://example.com/payment/event_webhooks.php
The problem is that once a checkout session is completed, whatever if it's for subscriptions or event registration, both webhooks are triggered.
I'm looking for a solution to define which webhook should be triggered.
Have 1 end point for both session.complete triggers, but send metadata to the endpoint and throw an IF statement into your listener so you can see which session you're listening for.
Here is some code I wrote up.
Checkout Session A:
$checkout_session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'gbp',
'unit_amount' => '1000',
'product_data' => [
'name' => 'Example Product',
],
],
'quantity' => 1,
]],
'metadata' => ["session" => "session_a"],
'mode' => 'payment',
'success_url' => "https://www.example.co.uk/success",
'cancel_url' => "https://www.example.co.uk/cancel",
]);
Checkout Session B:
$checkout_session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'gbp',
'unit_amount' => '1000',
'product_data' => [
'name' => 'Example Product',
],
],
'quantity' => 1,
]],
'metadata' => ["session" => "session_b"],
'mode' => 'payment',
'success_url' => "https://www.example.co.uk/success",
'cancel_url' => "https://www.example.co.uk/cancel",
]);
Take note that the metadata is different in the 2 above examples.
Now, use SWITCH and CASE to find which one you're listening for.
Webhook
$payload = #file_get_contents('php://input');
$event = null;
try {
$event = \Stripe\Event::constructFrom(json_decode($payload, true));
} catch(\UnexpectedValueException $e) {
http_response_code(400);
exit();
}
switch($event->data->object->metadata['package']) {
case 'session_a':
// Do your Session A stuff, SQL etc
break;
case 'session_b':
// Do your Session B stuff, SQL etc
}
Some useful tips:
You can pass the session ID from your checkout.session start through to your success page or hook, then grab data...
Checkout session page
'success_url' => "https://www.example.co.uk/success?session_id={CHECKOUT_SESSION_ID}",
session_id={CHECKOUT_SESSION_ID} is exactly as I've written it, you don't actually need to enter the checkout session ID, just literally copy it exactly like its written.
Hook page or Success page
if($_GET['session_id']){
$session_id = $_GET['session_id'];
}
$stripe = new \Stripe\StripeClient('API key');
$session_data = $stripe->checkout->sessions->retrieve($session_id,[]);
$payment_status = $session_data->payment_status;
$payment_address0 = $session_data->shipping->name;
$payment_address1 = $session_data->shipping->address->line1;
$payment_address2 = $session_data->shipping->address->line2;
$payment_address3 = $session_data->shipping->address->city;
$payment_address4 = $session_data->shipping->address->country;
$payment_address5 = $session_data->shipping->address->postal_code;
$payment_buyer_email = $session_data->customer_details->email;
$payment_metadata = $session_data->metadata->user_id;
You can grab data, just checkout the Stripe docs for the JSON response.
Anyone learning Stripe, all seems real confusing, but keep trying, testing, var_dump your results etc and you'll soon see what's happening.
You only need 1 webhook endpoint, then you handle switch case (or if else) for each event of these subscription (subscriptions and event registration in your case).
And i as you, i don't know how to distinguish what event for subscriptions, and what for event registration.
You can create a webhook endpoint on Stripe through the dashboard or through the API [1] and when doing so specify which types of events you want to be notified about.
You'll likely want to remove one of those webhook endpoints and use only one for all checkout.session.completed events.
[1] https://stripe.com/docs/api/webhook_endpoints/create

How to authorize with Stripe 3d Secure while Subscription type is Trailing

I want the 3d-secure modal authorization check with subscription type = trialing.
I am following this link to setup stripe subscription. When I create subscription without 'trial_period_days' the 3d-secure authorization modal pops-up as subscription status becomes 'incomplete'.
But when i pass >trial_period_days and 'payment_behavior' => 'allow_incomplete', modal don't work as subscription status becomes 'active'.
How can i show authorization modal when subscription is trialing?
I have seen this link https://stripe.com/docs/payments/3d-secure#manual-three-ds too, but no progress.
Suggest me a way to implement this.
Here is my code:
public function createCustomer($token) {
\Stripe\Stripe::setApiKey(secretKey);
$customer = \Stripe\Customer::create([
'email' => 'any_email#domain.com',
'source' => $token,
]);
return $this->createSubscription($customer, $token);
}
public function createSubscription($customer, $token) {
$plan_id = $this->getPlanId();
$payment_intent = $this->createSetupIntent($customer->id, $token);
$subscription = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => $plan->id,
],
],
'trial_period_days' => 14,
'expand' => ['latest_invoice.payment_intent'],
'payment_behavior' => 'allow_incomplete',
]);
return [
'subscription' => $subscription,
'payment_intent' => $payment_intent
];
}
public function createSetupIntent($customer_id, $token) {
$client = new Client();
$url = "https://api.stripe.com/v1/setup_intents";
$response = $client->request('POST', $url, [
'auth' => ['sk_test_key', ''],
'form_params' => [
'customer' => $customer_id,
'payment_method_types' => ["card"],
'payment_method_options' => [
"card" => [
"request_three_d_secure" => "any"
]
]
],
'timeout' => 10.0
]);
$setup_intent = $response->getBody()->getContents();
return json_decode($setup_intent, true);
}
I expect 3d-secure authorization check modal too when i set subscription to trialing.
What you are describing is a scenario in Stripe doc
Basically when you create a subscription with trial period, since there is no immediate payment occurs, there will be no 3DS authentication needed.
The authentication is delayed until the trial period end.
To ask the user for authentication so that when the trial end there will be no need for 3DS authentication, when a subscription is created with trial period, The subscription will have a pending_setup_intent attribute
You could use that pending_setup_intent to ask the user to complete the authentication. You don't have to create a setup Intent explicitly. What you can do is to check the status within the subscription.
If subscription is in trialing, check if there is a pending_setup_intent, if so, pass to the pending_setup_intent.client_secret to the frontend where your customer is subscribing to your product, and call Stripe.js handleCardSetup
stripe.handleCardSetup(psi.client_secret)
.then(siResult => {
log({siResult});
}).catch(err => {
log({siErr: err});
});
When the card is setup and the trial ends, there will be less likely that the charge will need to go through 3DS authentication again.
You could use Stripe Test Card, 4000002500003155 is good for this testing.
You could simulate the trial end by updating the subscription with
trial_end: "now"
off_session: true // This is needed because by default, subscription update is considered on_session
Hope the above helps

Categories