Stripe, webhooks and SCA European law checkout flow - javascript

I'm working on a website with Stripe payments, it's europe based so I'll have to deal with SCA 3D Secure auth, stripe docs recommend me to deal with payment fulfillment asynchronously instead of waiting for confirmCardPayment promise to resolve, to accomplish this you have to use webhooks.
From the docs:
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {
name: 'Jenny Rosen'
}
},
setup_future_usage: 'off_session'
}).then(function(result) {
if (result.error) {
// Show error to your customer
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback execution
// Set up a webhook or plugin to listen for the payment_intent.succeeded event
// to save the card to a Customer
// The PaymentMethod ID can be found on result.paymentIntent.payment_method
}
}
});
Notice the comment that says:
// There's a risk of the customer closing the window before callback execution
// Set up a webhook or plugin to listen for the payment_intent.succeeded event
Because of that warning I'm confused about how to deal with confirmCardPayment promise.
Should I just take the user to another page after I call confirmCardPayment from client and show the order in that new page with status: PENDING or PROCCESING.
Should I do this a few seconds after calling confirmCardPayment with a timer or check there is no error in callback, inside the else brackets of the promise callback and use location.href there to move user to see the order?
After that I would deal with order fulfillment in backend with webhooks (listen to payment_intent.succeded event, remove from stock, send order email and do remaining database operations)
If I do this would the SCA modal still show up? Stripe says Stripe.js deals with SCA and showing modal but if I redirect user to pending orde page with no stripe.js how would SCA modal action complete?
This is pretty confusing.

When we confirm the card payment on the front, we basically close the payment modal, and show the "order" page or whatever page you have with the current status.
We are pushing server updates of status changes from the server to the client using Pusher, which enables us to update the client for user convenience.
If using pusher or another socket based solution is not possible, you could display it in a status "please wait" and poll for server update every X time.
There is no "right or wrong" here; it just depends on your setup and what your exact requirements are...

Related

Stripe - Save customer payment method only if user ask for it

I use stripe payment element in client side. It sends a request to the server to retrieve the payment intent ID and the client secret.
I would like to be able to save the customer's payment method only if the customer has checked the box save this payment method for future purchases
But I would like the method to register only after I pass my complete flow in server side and be successful.
1- For this I set the capture method to "manual" when I create the
payment intent and I capture and update the payment intent when all
the server-side instructions have succeeded like this :
if (isWantSaveMethod) {
await stripe.paymentIntents.update(paymentIntent.id, { setup_future_usage: 'off_session' })
}
await stripe.paymentIntents.capture(paymentIntent.id)
=> The problem is that if I update the payment intent before it is captured, I get the error: You cannot provide a new payment method to a PaymentIntent when it has a status of requires_capture. You should either capture the remaining funds, or cancel this PaymentIntent and try again with a new PaymentIntent.
2- Then I tried to use an intent setup, which apparently allows us to
link a payment method to a customer
if (isWantSaveMethod) {
await stripe.setupIntents.create({
payment_method: paymentIntent.payment_method,
confirm: true,
customer: paymentIntent.customer,
})
}
await stripe.paymentIntents.capture(paymentIntent.id)
=> But : The provided PaymentMethod was previously used with a PaymentIntent without Customer attachment, shared with a connected account without Customer attachment, or was detached from a Customer. It may not be used again. To use a PaymentMethod multiple times, you must attach it to a Customer first.
3- Then I tried to attach the payment intent to a customer like this :
if (isWantSaveMethod) {
await stripe.paymentMethods.attach(paymentIntent.payment_method, {
customer: paymentIntent.customer,
})
await stripe.setupIntents.create({
payment_method: paymentIntent.payment_method,
confirm: true,
customer: paymentIntent.customer,
})
}
await stripe.paymentIntents.capture(paymentIntent.id)
=> But I remember that the documentation say : To attach a new PaymentMethod to a customer for future payments, we recommend you use a SetupIntent or a PaymentIntent with setup_future_usage. These approaches will perform any necessary steps to ensure that the PaymentMethod can be used in a future payment.
So finally I'm here to know what's the good way to do that ? How popular websites do this thing ?
I just want to make sure that all my instructions in the server side executed successfully before save the payment method and I want to be able to use this method later
How can I do that while respecting the recommendations that stripe gave in relation to the method attachment ?
When using Payment Element the intended user flow is that you would pass in setup_future_usage when the Payment Intent is created. Stripe wants to know if you intend to use setup_future_usage ahead of time so they can choose which Payment Method types to in the Payment Element (since not all of them can be used with setup_future_usage. You should either change your checkout flow to ask whether your customer wants to save their payment method before showing the Payment Element, or you can use the individual elements like Card element instead (each of theses elements should support passing in setup_future_usage with the client-side confirmation call - example here).
Alternatively (I really wouldn't recommend this), you can look into doing the following:
Make sure you haven't enabled any Payment Method types that do not support setup_future_usage (you can find the list here).
Change your client-side code to first send a request to your server to update the Payment Intent to set setup_future_usage, and wait for the response to be successful before continuing to call confirmPayment client-side.

How to prevent users from changing html when buying products via PayPal?

Here is the javascript code that PayPal provides for their checkout button:
<script>
paypal.Buttons({
createOrder: function (data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '0.01' //can't this be changed by clients if it's pulled from the html?
}
}]
});
},
onApprove: function (data, actions) {
// This function captures the funds from the transaction.
return actions.order.capture().then(function (details) {
// This function shows a transaction success message to your buyer.
alert('Transaction completed by ' + details.payer.name.given_name);
});
}
}).render('#paypal-button-container');
</script>
When a user purchases a product, how can I make it so they can't simply edit the price in html? I.e. how should this be validated server side? And when they finish the purchase, onApprove gets ran. How am I supposed to record the approval information into my database? I don't want to expose my database credentials with ajax.
Generally you are completely correct that the user could change the amount or any other information passed along. You must never trust the client. But in some situations this might not be that much of on issue. For example for a donation you could have a "Donate 1$" button and a "Donate 5$" button and the user could change the actual amount but it does not really hurt you if he chooses to donate 3$ or 17$ instead of 1 or 5, you get money and the amount does not matter much.
If you actually want to give the customer something in return, e.g. virtual or physical goods, then you must involve your server in the actual processing / fulfilment of the order: https://developer.paypal.com/docs/checkout/reference/server-integration/ . In that case
the displayed button talks to your server with the desired order-data, the server can validate the order
the user is forwarded to paypal with the validated order data and there is no way for the user to change it
the user fills in his credentials, checks the order, etc. and confirms it (or cancels it)
paypal redirects back to the server (via. the so-called redirectUrl)
the server processes the completed transaction, e.g. starts the shipping, sends emails, etc.
the server forwards the user to a page displaying the completed order or something similar

How do I know that 3D Secure 2 authentication works after upgrading to stripe.js version 3

I have updated a site so it uses latest stripe-php (6.39.0) and it now loads stripe.js version 3. I’ve made all the necessary changes to my code so that my credit card fields are now displayed using Stripe Elements. Test transactions work and I have updated the live site and real payments are being excepted.
The reason I made this update was because I was informed by stripe that I needed to upgrade the site so that its stripe integration will work with Strong Customer Authentication (SCA) which is required in the EU by September 2019.
Stripe has different credit card test numbers you can use to test things that arise when processing payments. This numbers can be found here: https://stripe.com/docs/testing#cards
4000000000003220 simulates a transactions where 3D Secure 2 authentication must be completed. But when I use this code stripe turns down payment and returns the message:
"Your card was declined. This transaction requires authentication. Please check your card details and try again."
Does this mean that 3D Secure 2 is working or not?
In the real world it would open a window with an interface from the customer's card issuer. So I not sure wether my integration is working or not. As said before payments are being excepted butI need to ready when Strong Customer Authentication is required in September.
It seems you have an integration problem with the JS part. For a simple charge (the following example does not work for subscription), this is the way you have to implement it:
First you have to create a Payment intent (doc here: https://stripe.com/docs/api/payment_intents/create):
\Stripe\PaymentIntent::create([
"amount" => 2000,
"currency" => "usd",
"payment_method_types" => ["card"],
]);
Once your PaymentIntent response is returned, you will have a client_secret key (doc here : https://stripe.com/docs/api/payment_intents/object). You can see that your payment status is "requires_payment_method"
{
"id": "pi_1Dasb62eZvKYlo2CPsLtD0kn",
"object": "payment_intent",
"amount": 1000,
"amount_capturable": 0,
"amount_received": 0,
...
"client_secret": "pi_1Dasb62eZvKYlo2CPsLtD0kn_secret_6aR6iII8CYaFCrwygLBnJW8js",
...
"status": "requires_payment_method",
...
}
On your server side you have to save this object.
You can now show your payment form with the JS part with the previous client_secret key (doc here : https://stripe.com/docs/payments/payment-intents/verifying-status). The idea is that you have to call the Js function on click on the submit button but do not submit ! Wait for the response to submit.
With some jquery this should like this:
var $mySubmitButton = $('#my-submit-button'),
$myPaymentForm = $('#my-payment-form'),
clientSecret = $cardButton.data('client-secret'); // put your client-secret somewhere
$mySubmitButton.on('click', function(e) {
e.preventDefault();
// Disable button to disallow multiple click
$(this).attr("disabled", true);
stripe.handleCardPayment(
clientSecret,
{
payment_method: clientMethod, // if you have a clientMethod
}
).then(function(result) {
if (result.error) {
// show error message
// then enable button
$mySubmitButton.attr("disabled", false);
} else {
// Submit form
$myPaymentForm.submit();
}
});
});
If everything goes right, when you click your submit button, you will have a test 3D secure popin with the options to "success" or "fail" the security test. If you click on success button, your form is submitted and you have to wait for the webhook "charged.success" to confirm transaction.
Once received, change you server object status and notify user about the transaction.
In my case, once the form submitted, I show a loader and check with ajax call every second to see if my payment intent status have changed (through the webhook). For your test environnement, you can use http://requestbin.net and Postman.
Beware: some cards referenced on this page will not work properly (you can't add them) https://stripe.com/docs/testing#cards (section 3D Secure test card numbers and tokens). Confirmed with their support.
If you work with saved card, you can test only with these cards: https://stripe.com/docs/payments/cards/charging-saved-cards#testing

Meteor technique/pattern for waiting on a database variable change and then doing something after in

I am creating a Meteor web app that a user can order goods but the user must wait for someone on the backend to change the status of an order.
user submits order , message shows on screen order is pending.
I have a specific order object that has a variable called status which is set to pending.
We now have to wait for an admin to change the status to accepted or declined from the backend.
if the order status is changed to accepted i need to redirect to a order processed page based on the id of the order.
my trouble is i dont know how to wait for the order status to change.
here is what i have at the moment
Meteor.call('placeOrder',cartSession,deliveryDetailsId,payment,function(error,result){
if(error){
console.log('ERROR :', error);
}else{
console.log('response:', result);
var pendingOrder = Orders.findOne({_id:result});
console.log("pendingOrder");
console.log(pendingOrder);
//the order status of the pendingOrder is 'pending' at this moment
$('#order-processing').text('order is processing');
//i must now wait for the status to change to accepted or declined
}
});
as Marius Darila comment i have added publications of order
Meteor.publish('userorders', function () {
if(this.userId){
return Orders.find({userId:this.userId});
}
this.ready();
});
user cannot insert , update or remove orders , this only occurs server side
i added this code within my meteor method callback function
if(pendingOrder.orderStatus =="accepted"){
Router.go('/orderProcessed',{orderId:pendingOrder._id})
}
Marius below said that this code would be rerun if the orderStatus changed,
but the code did not run
How Marius Darila helped me answer this question
As i was returning an id from the server of the current user order
i set a Session.set("orderId", orderId)
then within a template helper orderstatus , we find the current order within the orders db, and we complete our logic within this helper.
if the order is pending show order is pending
when the order is accepted route the user to orderConfirmed page.
Db is a reactive source of data so in your client code should have something like this:
var order = Orders.findOne({_id:result});
if(order.accepted == 1){
Router.go("someUrlName", {product: order._id});
}else{
$('#order-processing').text('order is processing');
}
This example uses iron-router for redirect.

Facebook client side authorization flow and get access_token for all scenarios like when scope for an app changes, etc

I am trying to do 3 simple things using JS thru a client side authorization flow :
Check if the user is logged in. If not, display the login dialog with proper scope.
If user is already logged into Facebook on the browser, then ensure the app has been authorized (if not, display the Authorize dialog).
If user has already authorized the app then ensure that the access_token on the authResponse has the right scope (permissions) required by the app. (if not, redisplay the authorize dialog for the new permissions).
I am trying to leverage js sdk for this, without wanting to write any hand-coded dialog's etc, since the sdk handles the browsers/devices nuances automagically.
I could not find this properly described anywhere either on FB documentation or otherwise. (no documentation around the fact that the permissions/scope for the app can change post authorization).
Thanks you in advance.
After an arduous attempt I have this working and here are the steps to do this with the minimum amount of code.
*The Approach *
Use fb-login-button to facilitate login. Login Button is the only thing that is displayed when the user first arrives on the page. All other forms are 'hidden', forcing the user to click on this button before being able to go forward.
setup onlogin="function();" on the fb-login-button - so that you can hide the login-button and display the rest of the forms/etc for the user to proceed on your app. Please note that onlogin is called only once (either when the user logs in or authorizes the app with the new scope) - so it is safe to always hide the login-button and display the remaining of the page/form for the user to proceed.
The most important step : use the same fb-login-button to also do the authorization with the right scope : so even if the user is logged in - force the user to click the login-button. fb-login-button in this case, will check if the scope of existing access_token (for already logged in fb user), is a subset of that requested on the fb-login-button attribute. fb-login-button will display the authorize dialog for you automatically in this case. In any case the onLogin callback is triggered (when the login-button should be hidden() and the remaining page/forms be shown()).
Subscribe to the FB.events to get the authResponses and the corresponding access_token every time it is set, which happens 2 times :
a. When the fetch is first displayed for already logged in user
b. After the user authorizes the app first time or second time with the new scope.
This allows you to keep the authResponse and access_token fresh to be used for all subsequent graph calls/etc..
*Implementation Steps *
fb-login-button must be created with onlogin callback :
<div class="fb-login-button" onLogin="onLogin();" scope="publish_actions"
data-size="large">Use This App</div>
Please note that the text does not say Login, but 'Use this App', since the button is displayed for authorization in addition to just logging in.
Subscribe to the Events.
// authRepsonse Change is needed when the user hits the UseApp button
// and permissions are re-fetched - then a new token needs to be refreshed
FB.Event.subscribe('auth.authResponseChange', handleResponseChange);
// when the user logins and all permissions are all set, clicking on the
// use-app will not do anything and authResponse will always be null.
// so this subscription is necessary
FB.Event.subscribe('auth.statusChange', handleResponseChange);
Login Function to hide Login button and display form.
function onLogin () {
hideLogin();
showMain();
}
Handle Status Changes to save Token.
// global variable that contains the access_token
// for use in graph calls/etc.
var authResponse = null;
function handleResponseChange(response) {
if (response && response.authResponse) {
authResponse = response.authResponse
if (response.status == 'connected') {
updateUserInfo(response);
return;
}
}
else {
authResponse = null;
}
hideMain();
showLogin();
}
Hope this helps and I believe that this is probably the most optimal way of handling client side authentication flow without doing any extra authorization/login dialog and let FB sdk do the trick.

Categories

Resources