I have a paid membership site which takes payments via Stripe Checkout (server integration). When a user registers, they are redirected to the Stripe checkout (with their ID passed as the 'client_reference_id') where they enter their card details. For the checkout fulfillment, I have a webhook setup that is linked to the event 'checkout.session.complete'.
Everything is working fine, the webhook triggers and the endpoint processes the data and marks the corresponding account as activated, sends an email to the account holder as well as the site admin to let them both know the account has been created and a payment has been successful.
However, if a payment fails, I don't know what event to attach to the webhook to detect failed payments via Stripe Checkout. The only checkout event documented is 'checkout.session.complete', there isn't one for something like 'checkout.session.failed'. There's 'invoice.payment_failed' but this is used in subscriptions not one-off checkout payments.
We're wanting to basically notify the site admin when a payment fails so they are aware an account has been created but it still awaiting payment.
Is there a checkout event I'm missing or another way to go about finding failed checkout transactions?
To charge a credit or a debit card, you create a Charge object. You can retrieve and refund individual charges as well as list all charges. Charges are identified by a unique, random ID.
-- From the Stripe API Documentation.
You can then detect a webhook response of charge.failed to show that an attempted use of a charge object has failed.
Stripe used to be very cleanly documented, but they've lost their way a little, recently.
Stripe card payments ALWAYS use Charge objects, sometimes these are set by you (invoices, subscriptions, etc.) and sometimes these are set behind the scenes by Stripe ("Checkout Process", etc) -- but they are always set, so your webhook can always detect a charge.failed event.
Addendum
This does work, however, since the client_reference_id is not in the charge.failed data, I can't link the failed payment to a specific account anyway. But in regards to my actual question, this event does work and is the correct answer, just unfortunate that it doesn't solve my specific situation.
To solve this; either using the Stipe interface or by coding:
1) Create a Customer object and keep some sort of record of this customer Id.
2) When the Stripe Charge runs, an associated $charge->customer is set. This is available to your webhook.
3) When the webhook result appears, use this (typically) $event->data->object->customer value to cross reference the charge with the correct customer on your server.
4) As mentioned in comments, Stripe Webhook Testing does not populate the webhooks with example data such as ->customer... :-(
I know this is a bit late for a reply - but I have a working solution!!
When I create a "Session" in Stripe, I make sure to fill out the SessionCreateOptions so that the PaymentIntentData.Metadata field is setup to have my customer's ID and stuff in it. When I do that, the metadata not only shows up in payment_intent.created and payment_intent.succeeded webhooks, but it ALSO shows up in charge.succeeded/failed!
This means that when a payment fails I can look up the attempted payment that I keep track of and email them back letting them know that it failed and why, and I can also log it for myself for reference when I go look up that customer in my customer service tools.
It's not clear from the documentation and I spent a lot of time grokking how it works, so I hope this will save you some time.
The checkout session CANNOT fail (!)
A Checkout Session can have one of 3 states: "open" is an active session which can be paid, "completed" is paid ("happy path"), and "expired" if within the expiration time the session was not completed.
A session can only expire with two options:
After 24 hours if not paid (or sooner, if you set a shorter expiration time https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-expires_at)
Manually by calling https://stripe.com/docs/api/checkout/sessions/expire
A session's URL can be accessed unlimited number of times for payment attempts until it is either paid or expires. This is by design, so a user can have a couple of failed attempts to pay until she actually pays. With a session, you do not care about those attempts, and in most cases you do not need to track them.
So one option for your workflow can be the following, for example:
For creating a new order, create a new session, save it's ID in the order's record in the DB, and redirect the user to the session's URL
On the back-end, listen for "session completed" event, which is the happy path - everything is paid (watch out for the async payments, if you accept any! See herehttps://stripe.com/docs/payments/checkout/fulfill-orders#delayed-notification)
Also listen for "session expired" event. This will tell you that the user bailed and hasn't paid the order. You can send them a "failed payment, try again" email in this case (see below for Recovery workflow)
If the user returns to your website before paying for the order, you can display "NOT PAID, TRY AGAIN" message next to the unpaid order.
Attention: it is perfectly safe for the user to open a dozen of the session's URL and attempt to pay as Stripe won't allow the double payment after card information is submitted.
So you are thinking about creating expiring the old session and creating a new one for this unpaid order? Wrong! You can retrieve it by ID (you saved it in the DB, right?) and send the same URL to the user.
However, if the retrieved session is "expired", there will be no URL to send, so you will be forced to create a new session, save it in DB, and the whole cycle repeats.
Another possibility is for the repayment workflow to happen in rare state between the payment and "session is completed" webhook event (the user already paid in another tab, but you haven't received the "paid" event yet), you will get that the retrieved session state is "completed", in this case you will need the capability for the front-end not only to redirect the customer to a session's URL, but instead sometimes show a message like "nope, it's already paid".
As you can see, there is no "failed" state for the checkout session.
Now, another thing for recovering the unpaid and expired sessions is the Recovery workflow (https://stripe.com/docs/payments/checkout/abandoned-carts). The gist of it is the following:
When creating a session, you set "recovery" to true, so if the session expires, you will have access to a special URL that will "revive the same session with the same parameters" that will be active for 30 days after the original session expiration. You then send this URL to users with possibly some discount to recover their abandoned cart. Also, you can track whether the session is "original" or "revived". However, I did not use this way, and instead opted to simply creating a new session manually if the old one is expired.
Related
So, I have a Stripe webhook for a subscription site for one of my clients and I am using Stripe for the payment process. Because it was faster in my case to use the Stripe PHP SDK directly, what I did is the following preparation steps.
I have created the subscription plans manually in the Stripe dashboard.
I have a database table with specific subscription options and I've copied the corresponding Stripe Subscription Plan IDs to the table.
This way I know which plan has been selected and can make the correct correspondence/relation between customer and subscription type.
Then, the process I have is:
Customer selects the subscription type desired.
I prepare a checkout session using the PHP SDK and send the customer to Stripe to complete the subscription process.
Customer completes the subscription process and comes back to the site to a 5 second "Wait for the subscription to be created" section. This is literally a page with a setTimeout that redirects the customer after 5 seconds to the subscription section and gives the webhook time to do its job.
During those 5 seconds if everything goes well, the webhook is notified of the subscription, creates the transaction and then the subscription. The thing is that sometimes it works and sometimes it doesn't work. If I re-send it, it works, but the idea is not to do this manually.
My question is the following, I am using 3 events for the webhook:
customer.subscription.created
charge.succeeded
customer.subscription.updated
In the end I only end up using the charge.succeeded and the customer.subscription.updated events, because they are the ones I've found useful.
According to the webhook dashboard, the three events were sent and correctly received, but it would seem something's not working on the other end.
Now I am also not sure how this works, because of what my webhook does. The original code the Webhook generator gives me has the events like this:
switch ($event->type) {
case 'charge.succeeded':
$charge = $event->data->object;
case 'customer.subscription.created':
$subscription = $event->data->object;
case 'customer.subscription.updated':
$subscription = $event->data->object;
// ... handle other event types
default:
echo 'Received unknown event type ' . $event->type;
}
But I had to add breaks after each case because in some cases, variables I needed didn't exist in the context of another event.
My question is the following, does the switch with no breaks execute each case in order or only the events it receives, and what would be a good practice in this case to ensure I always receive the information required.
I thought of creating the subscription before the payment, but that just adds extra steps, because if the person decides to go back before paying, I'd have to delete it.
If you use Checkout, you should only be listening to the checkout.session.completed Event instead which indicates the Checkout Session was completed successfully. This is also what Stripe covers in their documentation about fulfillment.
Once you get that Event, you can look at the Session, find the id of the Customer (cus_123) and Subscription (sub_123) associated with that Session and save all the information in your database.
Additionally, event delivery to your webhook endpoint can fail or be slow. Those can happen for various reasons and Stripe has a retry logic documented here.
Since the customer is redirected to your success url, you should already be able to write code to check who they are. This is usually done from a cookie and then you look up their Session id cs_live_123. Alternatively, you can configure Checkout to put the Checkout Session id in the URL as a query parameter which is documented here. This would allow you to synchronously look up the Session, check its completion state, store all the information you need (by re-using the code used in your webhook handler) and synchronously redirect them to the right page. Or you can also poll your database until you got the Event.
It's important to always have a webhook endpoint to catch those events. The customer could close the tab or lost the connection and never be redirected. But if the redirect succeeds, you can already look them up more directly.
I have website where I use PayPal NVP sandbox. Sometimes after successful payment of user, PayPal get back to my return URL very slow. Unless PayPal returns to my website with details I am unable to check whether user has completed the order (For all attempt website adds order id to the database) or not and that destroys website's work principle.
What I need is that I want to retrieve the data of transaction without transaction id if possible. Maybe with custom field or something else. If I would be able to check the transaction details without transaction id , then I would be able to handle the requests by user accordingly. Or if there is a way to get a transaction id somehow with the help of another variable that also fine as I will make checking after finding out the transaction id.
Current PayPal Checkout integrations give an immediate response of success/failure. There is no need for any redirect away from your site, and there is no need to wait for asynchronous IPN or webhook messages. Don't use such things.
Follow the PayPal Checkout integration guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order' (see the optional step 5 in 'Add and modify the code'; you can use the Checkout-PHP-SDK). Both of these routes on your server should return/output only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller.
Pair those 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
Well, I was wondering if it is possible to automatically logins the user in paypal everytime they checks out?
Given the scenario a new user registers in my site.
New User->Logins to paypal->get permissions(etc..)->approve->gets back to my site
So, everytime the user checks-out it automatically logins in paypal.
I would like to link his paypal account to his profile in my site thus easier check out
I saw the documents but it is just too much for me to comprehend.
It sounds like you are referring to reference transactions. With PayPal you can create a billing agreement, and you would save the billing agreement ID to the local user record in your database.
In the future when they are logged in and checking out on your site you can pull the billing agreement ID and pass that into a reference transaction request to PayPal. This will immediately process the new payment without any approval necessary, so you can simply display the order complete page to the user.
Their experience would be choosing their saved payment method, clicking to buy, and the complete page would show up immediately. No checkout forms or login (other than logging in to your site) necessary.
Depending on whether you're working with the Classic API or REST the specific calls would be different, but "reference transactions" and "billing agreements" are the terms you're looking for.
I am implementing PayPal payment to my application.
I am using Laravel Framework and merchant-sdk-php package to handle NVP/SOAP API. I would accually prefer REST API, but i need customers to make Reference Transactions with various amount, in non regular time periods and as far as i know it's possible only with NVP/SOAP API.
The payment flow in shortcut:
1. Payer clicks "connect" button, which is to create billing agreement using "SetExpressCheckout" method. Amount is set to 0 and adding a Billing Agreement field to request. Customer is redirected to PayPal, log in to his account, agreeing to direct debit and finally redirected to my return url.
2. After response is come, return action is fired (the one, which is passed in returnurl field). Next using token from paypal resposne i use CreateBillingAgreement method to get "BillingAgreementID" which i store in database.
3. Using "BillingAgreementID" i make "DoReferenceTransaction" request. No prompt to login is occurring, everything is happening behind the scene. Finally i get response after transaction.
The thinks i want to know are:
1. Is there a way, to get an email address, which consumer used to log in when creating billing agreemenet? I want to show in application which PayPal account (related to mentioned email) is direct debit set to?
2. I want to make some action in databse both after "BillingAgreementID" and "DoReferenceTransaction". Is the response status "Success" and additionally in "DoReferenceTransaction" field "PaymentStatus" set to "Completed" enought to conclude, that transaction is fully completed, and i cant i.e. share some digital goods or should i wait for IPN from this request?
Thanks for all contributions!
Found solution to question 1.
After betting billing agreement id i had to make "GetBillingAgreementCustomerDetails" action. In response i got customer details including email.
Still watching for hint to second question
I have a recurring payment solution set up via PayPal's IPN service, basically the user fills in a form, pays the money and via IPN my system gets a ping to grand the user access to the system.
Everything technically works fine, but occasionally on the last day of a user's cycle they forget that it is a recurring payment and complete the form again. PayPal doesn't seem to mind this and creates a second recurring payment profile.
Currently I go in, refund the money and cancel this new payment profile, but obviously this isn't ideal - is there any way I can configure PayPal to not accept new profiles from people with currently active profiles? Or will I need to catch this at my website's end and do some form of lookup before allowing the payment?
Paypal did not recognize the a transaction is a duplicate one, It tackles the transaction as a new transaction. You will have to tackle it from your system.
When implementing payment gateways, It is good approach to save the billing with a status pending or something in your system before sending to payment gateway.
Also in the form user is filling you can implement a field to uniquely identify the user like his email address and you can also save the expiry or second recurring date against the user unique field in your system. Now when the user enter the same unique field and want to submit a form again you can validate that the same user is paying again but its expiry of second billing date is not yet reached, so prompt and restrict him for paying again.
Hope it will help you.