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

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.

Related

Can I generate a transaction on the server and send it to the client for payment

I have built a smart contract method to which I pass some sensitive data that needs to be stored on the blockchain and alter the state of the contract. I, the creator of the contract don't want to be the one paying for the fees of that transaction. I want the user on the browser to approve and pay for it.
However, I do not want to generate the transaction object on the browser as I want some of the data that will be passed to the contract to be hidden from the client. If I understand the web3 syntax correctly, in the code below, I'm doing just that
web3.eth.sendTransaction({
from: walletAddressOfTheUserThatWillPayForTheTransaction,
data: myContract.methods.changeState(..sensitive data...).encodeABI()
})
However I do not want the above to happen on the browser. In my head, the sequence of events should look like this (pseudocode):
// server
let transactionObject = {
from: walletAddressOfTheUserThatWillPayForTheTransaction,
data: myContract.methods.changeState(..sensitive data...).encodeABI()
}
sendToClient(encrypt(transactionObject))
// client
let encryptedTransactionObject = await fetchEncryptedTransactionObjectFromServer()
// this should open up Metamask for the user so that they may approve and finalise the transaction on the browser
web3.eth.sendTransaction(encryptedTransactionObject)
Is this possible ? Is there some other way of achieving this? Could you provide me with some hints about the actual syntax to be used?
However, I do not want to generate the transaction object on the browser as I want some of the data that will be passed to the contract to be hidden from the client.
Then you should not be using public blockchains in the first place, as all data on public blockchains, by definition, is public. Anyone can read it.

Stripe, webhooks and SCA European law checkout flow

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...

PayPal Checkout client-side: Can users change the order amount in JavaScript?

I'm using PayPal Checkout to make basic payment the client side, I'm asking if it's safe as I don't want the user to change the amount.
This code is the same as given in the docs.
<script>
paypal.Buttons({
createOrder: function(data, actions) {
// This function sets up the details of the transaction, including the amount and line item details.
return actions.order.create({
purchase_units: [{
amount: {
value: '100'
}
}]
});
}
}).render('#paypal-button-container');
</script>
Yes, the simple client-side-only solution can change the order amount. To control the amount and validate successful capture, you should use a server-side integration.
Follow the PayPal Checkout integration guide and make 2 routes (URL paths) on your server, one for 'Create Order' and one for 'Capture Order'. You can use one of the Checkout-lanuagename-SDK (edit: these are now deprecated) for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access_token and then doing the call. Both of these routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should
verify the amount was correct
store its resulting payment details in your database (particularly
purchase_units[0].payments.captures[0].id, which is the PayPal
transaction ID)
perform any necessary business logic (such as
sending confirmation emails or reserving product)
.. all right before forwarding your return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases (restart the checkout if the error is recoverable, or show a failure message if not)
Once you have your 2 server routes available, pair them with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options, add a body parameter to the fetch with a value that is a JSON string or object)

Stripe checkout - Remove or make email field read only

I'm using stripe checkout as described in https://stripe.com/docs/payments/checkout
On the server side, I create a new stripe customer (if there isn't one already), and use that customer id to create a checkout session. and then redirect to checkout using stripe api
stripe.redirectToCheckout({
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
// instead of the {{CHECKOUT_SESSION_ID}} placeholder.
sessionId: '{{CHECKOUT_SESSION_ID}}'
}).then(function (result) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
});
in the checkout page (which is stripe hosted), there is a email field which is pre populated, but is editable. I have had customers changing that email address. Is there a way I can make the email address on stripe checkout readonly field?
A possible work around:
You can use customer_email instead customer parameter when you create a session.
In this case users can not change the value of email field.
Then, you can get the created customer and update it if you need added some data using the API: https://stripe.com/docs/api/customers/update
But, the problem is that stripe will create a new customer for each checkout.
I got an email from stripe today
We are disabling customers’ ability to overwrite their email address
Starting October 26, customers can no longer edit their email address after you pass it into a Checkout Session. We’ve made this change to prevent duplicative subscriptions from being created by accident for existing customers and prevent unexpected updates to the Customer object during Checkout.
And here it is in all its glory
Now, according to the Docs:
If the customer changes their email on the Checkout page, the Customer object will be updated with the new email.
Given that Stripe allows "customers" to change the email id. we pass to the Checkout form, an idea that could work for you is to add your "internal" identifier (be it an email or other internal user id) as Metatadata to the customer you create, like so (below code is in c#, but you get the idea):
Dictionary<string, string> customerMetaData = new Dictionary<string, string>();
customerMetaData.Add("myappuserid", "{useyourinternalidhere}");
var customer = await cs.CreateAsync(new CustomerCreateOptions { Email = "{emailthat youwanttouseforcustomer}", Metadata = customerMetaData });
//Now you can pass this customer when you create the session for checkout
The point being whatever email the user ends up using, you can always get the corresponding internal user id as well from the customer object's metadata.
In the "success" call back, you can then retrieve the customer data and then save the returned customer details - including the stripe customer id / email that end user used in check out form / your own internal id that you can now retrieve from the customer metadata -- that way you can maintain a mapping of stripe's details to your own internal customer details.
//This code is on the "success" call back (when the session id. is passed)
var sessionService = new SessionService();
var session = await sessionService.GetAsync(session_id, new SessionGetOptions { Expand = new List<string> { "customer" , "line_items", "subscription" } });
var sessionCustomer = session.Customer;
//This sessionCustomer now has the metadata that you passed while creating the customer
//Save the Stripe ids / email id along with data retrieved from the Metadata;
//Now you have all the info you required..
Of course this response is delayed - hopefully, it helps others...
Use "customer_email" parameter as shown below and this is the example in Python:
checkout_session = stripe.checkout.Session.create(
customer_email='example#gmail.com', # Here
line_items=[
{
'price_data': {
'currency': 'jpy',
'unit_amount': 1000,
'product_data': {
'name': 'Honey',
'description': 'Good honey',
},
},
'quantity': 1,
},
],
mode='payment',
success_url= "https://example.com/success.html",
cancel_url='https://example.com/cancel',
)
And because you use "customer_email" parameter so the email field is readonly as shown below:

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

Categories

Resources