I am trying to create a word-press plugin with stripe that support strong customer authentication. charges with stripe is working fine for me. I have referred this link https://www.codexworld.com/stripe-payment-gateway-integration-php/ for creating payments. Any such reference link for sca implementation in stripe? Thanks in advance
I just moved my company over from the old Charges API to SCA. It wasn't pretty. I'm a back-end engineer so I'm only going to tackle the back-end components needed to make this work. If you want a fully customised front-end too, you're going to need to read the Stripe docs and do a lot more bounces around form submit -> enhanced validation -> collect more data -> submit more data. This workflow was too complicated and thankfully my company went with the Stripe checkout solution for this part.
I work for a SaaS company and we take money for room bookings, so we have non-fungible time dependent 'stock' items. Since we sell booking slots on behalf of our customers we also have Stripe connected accounts in play. Basically, all the nightmare corner cases you could wish for. Since the checkout session is live for 24 hours we have to allocate then collect - if you start the process and then go for lunch there is no guarantee that when the payment is processed the room you want will still be free for your timeslot.
My process looks like:
Back-end calls Stripe and starts a checkout session with Session::create(). Set your payment_method_types and line_items in this as well as your return URLs. Also send up a payment_intent_data. Mine looks like this:
'payment_intent_data' => [
'transfer_data' => [
'destination' => 'acct_ number of linked account',
],
'capture_method' => 'manual',
'description' => 'description of item',
'statement_descriptor' => 'description of item, max 22 chars',
],
Obviously if you don't have connected accounts then omit the transfer_data but definitely include both descriptors. If you are sure of your stock levels (eg: digital goods) you can change your capture_method to automatic.
On a return from Stripe I send the session key (sk_) to the front-end who then hand this over to Stripe's payment form. This is the point you'll need to do a lot more work if you want a totally custom front-end. Good luck.
When the checkout session is successful I have a webhook event on checkout.session.completed to call my API, as well as handling the URL returns from the front-end. I keep extra data in my database about the payment state so I only handle each return once.
Either return leads me back to the session - the ID is in the front-end links and the session can be retrieved from $session = $event->data->object in the webhook callback. You can then get the intent out of the session with $intent = PaymentIntent::retrieve($session->payment_intent);
At this point, I handle the various status codes in the PaymentIntent;
requires_payment_method
requires_confirmation
requires_action
canceled
requires_capture
succeeded
The one I'm really interested in is requires_capture where I then check to see if the resource is still free. If it is, I $intent->capture() to finalise the payment and create the booking for the user. You can handle each of them as needed by your business process.
If you are dealing with connected accounts you will also need to load the transfer item associated with the PaymentIntent Charge object and then load the Charge referenced in the destination_payment field (you can load charges with the py_ key). Then set the description and statement_descriptor fields and save the charge back so your connected customers know what the payment is for.
Related
Our user can enter a card to pay our services.
He/she can choose to save or to NOT save card for reusing.
When user is NOT saving card, we are creating a PaymentIntent passing
[
'amount' => floatval($this->cart->total_gross) * 100,
'currency' => 'EUR',
'payment_method' => $this->pm,
'off_session' => true,
'capture_method' => 'manual',
'confirm' => true,
];
This because it's a preauthorization of a payment that will be captured in 2-3 days.
Using this config we got the following error
Stripe\Exception\MissingParameterException - 400 - The provided PaymentMethod is already attached to another object. You cannot reuse PaymentMethods without attaching them to a Customer object first.
To be clear:
user enters a NEW CARD
choose to NOT save for future usage
stripe.js automatically handle the 3d secure card auth
the returned pm_... is sent to server with the amount and the instruction to NOT save card (so we do not create a stripe customer)
using the above config we call the \Stripe\PaymentIntent::create method
we got the error above
What is the meaning of this error? Why does it says that the PM is already attached to another object? Which? Of which kind?
I triple-checked my code and it's the ONLY api call we are making after receiving the pm from frontend.
And the frontend, previously, simply use a setupIntent to authorize the card using official stripe.js calls. So the pm, returned from stripe.js is sent to our server without doing nothing with it. And our server simply call the create method, and got this error every time.
Asking your help to diagnose and understand.
We're using latest official stripe-php versions
Thanks in advance
I finally received an official response on this problem.
Simply: we cannot save a card for of_session payments and then use the payment method in a later moment to capture the pre-authorized amount
So, we modified our flow
User choose or save a card and we inform the user that we must save the card to be able to reuse to complete the payment flow.
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.
I'm hoping someone here could throw their eye over my flow, to see if I'm doing things correctly and, if not, to advise me as to where to improve.
My scenario is a simple one:
Step 1
Customer logs in with an email address and password, note that this
could be their first visit, or a return visit
Customer is presented
with a list of products, makes their selection, clicks button to pay
Customer is presented with a CC form, as per Stripe guidelines and
utilising Stripe.js.
Upon submission, the details are sent to Stripe
and a response is received which I pass to my PHP script.
At this point I have, I believe, a response id and a source object containing the abstracted card details as well as a card id.
Step 2
I connect to the Stripe API:
Stripe::setApiKey($stripe_secret_key); // note this is a Connect account
Stripe::setApiVersion('2018-05-21');
I check to see if I already have a Stripe customer id stored for this email address. If so, I use that. Otherwise, I create a Stripe customer:
$customer = Stripe_Customer::create(
[
'description' => $u->user_email,
'email'=>$u->user_email
], $stripe_access_token
);
$customer_id = $customer->id;
I then fetch that customer's Stripe object:
$customer = Stripe_Customer::retrieve($customer_id);
and then I fetch the source object, using the response id
$source = Stripe_Token::retrieve($form_response_id);
Step 3
I want to make sure that the fee is taken from the card the Customer is using right now, irrespective of whether or not they have visited before or not, or used the same card before or not. So I save this source to the customer:
$customer->sources->create(['source' => $source->id]);
$customer->save();
... and, although the latest source is supposed to be the default source (i.e. the one that's used), I make sure of it:
$customer->default_source = $source->card->id;
$customer->save();
Step 4
I do some local work to get fee descriptions, calculate application fees, etc. I put this altogether as a charge array, using the inititial card id I received from Stripe.js as the 'source' parameter
$charge = [
'customer' => $customer->id,
'source' => $transaction_card_id,
'amount' => $total_gross_received_cents,
'currency' => 'EUR',
'description' => $transaction_desc,
'application_fee' => $application_fee
];
$charge_obj = Stripe_Charge::create($charge, $stripe_access_token);
$charge_id = $charge_obj->id;
I then save the $charge_id in my transaction records.
Questions
My biggest concerns revolve around Step 3 - making sure that the right card is accepted and used. Note that:
I don't store any card information (nor do I want to), so every transaction requires card details to be inputted by the customer and abstracted by Stripe.js.
I've run into trouble before with cards that are renewed - i.e. the number is the same, but the expiry is different. I want to make sure this doesn't happen.
Can I save the card as a new source for the customer every time, even if it already exists and is assigned to them? It seems to be working, and overwriting duplicate source records, but I want to be sure.
Obviously, I'm nervous about rolling out something I feel I don't fully grok yet, so if someone could give me a thumbs up or thumbs down, I be very grateful.
Here are my notes:
The card information needs to be stored safely on the side of trusted payments gateways (e.g: Stripe, PayPal). Not storing the sensitive credit card information is one of the PCI compliance requirements.
In your case, I think you can use the token. Or creating a customer and saving your customer ID for later use.
https://stripe.com/docs/saving-cards
https://stripe.com/docs/recipes/updating-customer-cards
https://stackoverflow.com/a/18377973/5179786
I've got a site that offer users 3 different subscription plans (with 3 different prices). So there's a sign up page with 3 different subscribe buttons, each of which opens Stripe Checkout for the appropriate amount for each plan (I'm using the standard Stripe simple Checkout integration https://stripe.com/docs/checkout#integration-simple ) .
On my server I know which plan a user has subscribed to because I've got a hidden field in each Stripe Checkout button form with the plan name. The problem is how to stop someone editing the form (e.g. using developer tools in their browser) and changing the hidden plan field so they get e.g. the Gold plan at the Basic plan price.
The obvious solution seemed to be to check the amount associated with the stripeToken that Stripe returns (i.e. the same as the data-amount field in the Stripe Checkout form), before calling the Stripe API to actually subscribe the user. However, I'm not sure how to get the amount from Stripe using just the token and can't find an obvious API call for this.
Or is there some better way of handling multiple subscription plan options while ensuring a user can't somehow get away with paying a lesser amount?
Thanks.
I have set up subscription based website that allows people to have multiple subscriptions. I decided to go with Stripe for payment and card processing. It took very little time to get it integrated into my Symfony2 project. I was able to create subscriptions, customers, and add cards within a couple of hours. Then I ran into an issue. If a customer has multiple cards, I wanted to be able to allow them to choose which card they wanted to use when they create a new subscription. It sounded easy. After 2 days and about 30 hours of combing through their documentation I have to say that I cannot figure out how to get this to work.
The way I have this set up is that when the customer creates a card I store the "card id" in my database along with the brand. This just makes it easy to load details on the server side when the page is being requested. The customer creating the new subscription sees their cards and choose which one they want to use for the new subscription. This is passed to my php script via AJAX to create the new subscription. However, when I try to use a specific card, I am getting a 400 error indicating that the "card id" is not a token. I know that it is not a token since the token was used to add the card to the customer account but how in the world do I specify the exact card that the customer wants to use?
NOTE: Using an a new token creates another instance of the card.Not an option.
PHP:
require_once('../stripe-php/init.php');
//Set Secret API Key
\Stripe\Stripe::setApiKey("sk_test_XXXXXXXXXXXXXXXXXXXXX");
//Retrieve Customer
$cu = \Stripe\Customer::retrieve($_POST['customer_id']);
//Create Subscription using saved customer "card id"
$createSubscription = $cu->subscriptions->create(array("plan" => $_POST['sub_option'], "source" => $card));
POSTED TO STRIPE:
plan: "500-2016"
source: "card_xxxxxxxxxxxxxxxxxxxxx"
STRIPE ERROR: TYPE 400
error:
type: "invalid_request_error"
message: "No such token: card_xxxxxxxxxxxxxxxxxxxxxxx"
param: "source"
I got a reply from Stripe support on this: it is not possible for one Customer to have subscriptions with different payment sources. All invoices for subscriptions are always billed to the current default_source for the Customer. So if you change the default as Giles Bennett suggested, you'll be changing it for all subscriptions, regardless of what the default was at time of creation.
If you need one user to have subscriptions with more than one source, you need to create multiple stripe Customer objects for that user, with a different default_source for each.
Since I have not received any input from SO or Stripe, I have somewhat came to the conclusion that this cannot be done. I found a similar question on a different forum that ended with the results being - No Response From Stripe - and that this cannot be done. Though the Stripe documentation does not hit on this subject it does appear that a Subscription can only be charged to the default card. There is no "Card" object for subscriptions as there is for a "Charge".
I realise this thread is quite old, but having come across it whilst trying to answer the same question myself, this solution may be of use to those who come along afterwards.
The Stripe API says that the "source" parameter for the API call to create a new subscription is optional - if ommitted, then it will default to the customer's default card. If included, then it can only be a token (ie. a new card) or a dictionary entry (again, for a new card, just not tokenised).
The solutionĀ could be to update the customer's default source first. Using Cartalyst through Laravel, as we are :
$customer = Stripe::customers()->update( "customer_id", [
'default_source' => "card_id"
]);
You can then proceed to add your subscription as normal - the newly-defaulted card will be assigned to it. If needs be (depending on your application) then you may also wish to save the previous default card ID to a variable first, to then allow you to set it back to being the default card after your new subscription.
I hope that helps.