Stripe Automatic Confirmation and order fulfillment without Webhooks? - stripe-payments

The stripe documentation says when using payment intent using automatic confirmation you should Asynchronously fulfill the customer’s order using webhooks.
If i'm offering a service e.g. ability to download a file once purchased then why do I need to monitor for the payment_intent.succeeded webhook?
If payment succeeded in the following handleCardPayment function then is there still a possibility for the charge to fail? Why shouldn't I allow the user to download the file straight away if the payment has succeeded?
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
var clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', function(ev) {
stripe.handleCardPayment(
clientSecret, cardElement, {
payment_method_data: {
billing_details: {name: cardholderName.value}
}
}
).then(function(result) {
if (result.error) {
// Display error.message in your UI.
} else {
// The payment has succeeded. Display a success message.
}
});
});
Maybe I've not understood how the handleCardPayment works. Any help appreciated.

When using handleCardPayment, the issue is not so much that the payment might fail, it's that it might succeed but your code doesn't know about it.
handleCardPayment kicks off several asynchronous steps — showing the user a dialog to authenticate a payment with their bank, processing the actual charge against their card, and closing the dialog. It's only after all that completes does the Promise resolve and your function that receives result executes.
Consider the case where:
the customer clicks Pay
handleCardPayment is called
the customer sees a dialog from their bank to authenticate the
payment
they do that, and they consider that their payment is now complete,
and they close their browser immediately.
In this case, your code will never execute, but Stripe still processed the charge. So you don't know that the payment happened and you can't fulfil the order.
That's why it's important to use webhooks, so you get notified asynchronously when the payment completed, even if the user closed their browser mid-process and your code after handleCardPayment never got a chance to run. Alternatively you can use the manual confirmation flow, where the steps are split up and the actual charge isn't processed until your server makes a separate API call.

Related

Shopify Webhook Real Time changing

is there an api on shopify where I can see real time when data changes ? Maybe I have a node server and I use sockets to see when anyone has bought anything from my shop that I get a notification via nodejs on my backend. is it possible ? a few websites has this, they offers you to sell on their site and you can see real time changes data when anything was bought
Yes, you can subscribe to multiple Webhooks to get notified when a change occurs on your shop. Using the REST Admin API, available webhook event topics include:
orders/create: occurs whenever an order is created / someone buys from your shop.
orders/paid: occurs whenever an order is paid.
orders/fulfilled: occurs whenever an order is fulfilled.
orders/cancelled: occurs whenever an order is cancelled.
Use the /admin/api/2023-01/webhooks.json endpoint to subscribe to a webhook:
// Node.js - Session is built by the OAuth process
const webhook = new shopify.rest.Webhook({session: session});
webhook.topic = "orders/create";
webhook.address = "https://example.hostname.com/";
// format you want to receive the event data in
webhook.format = "json"; // or XML
// fields you want to receive
webhook.fields = [
"id",
"note"
];
await webhook.save({
update: true,
});
You can also use the GraphQL Admin API for the same purpose.

How To Confirm Stripe PaymentIntent In Server Side Integration Test

I have an integration test connected to Stripe (test mode) and I would like to test the complete flow, i.e. creating payment-intent and handling the webhook of succeeded event. The first part is pretty straightward. Now for the second part, Obviously I can't do actual flow on the Element widget. So I tried to manually confirm the payment intent (as part of the test code) with the following call:
fun confirmPaymentIntent(pi: PaymentIntent): PaymentIntent {
val params = PaymentIntentConfirmParams.builder()
.addExpand("latest_charge")
.setPaymentMethod("pm_card_visa") // Don't know what's the correct value!
.setReturnUrl("https://example.com/return")
.build()
return pi.confirm(params)
}
And it works fine, I receive the "payment intent succeeded" event in the webhook. Now the problem is that the real payment flow would contain billing details. So I tried the following:
.setPaymentMethodData(
PaymentIntentConfirmParams.PaymentMethodData.builder()
.setBillingDetails(
BillingDetails.builder()
.setAddress(BillingDetails.Address.builder().setCountry("US").build())
.setName("My Customer")
.setEmail("customer#example.com")
.build()
)
.build()
)
But when I run the code, it's complaining about missing type in the payment method data. Type enum doesn't have card and I have no idea what is the correct value.
In my case, the application supports only cards and it doesn't care about the card details in any way (so anything works, like fake card, token, etc, as long as I get the payment intent event with the name and email info in the webhook.)
First, is there any better way to do this? Second, if not, then how can I manually confirm a payment intent in the test to be able to verify webhook call? Thanks.
You can trigger the payment_intent.succeeded event directly via Stripe CLI. If that still doesn't give you the information to test, you would want to build an end-to-end frontend confirmation flow using PaymentElement. It's not that complicated.

Amazon Pay: Complete Checkout Session is successful but not confirming transaction

For my Amazon Pay integration with the NodeJs SDK, everything seems to be working great until I get to completeCheckoutSession. When I call completeCheckoutSession, the call is successful and appears to work, but I never receive a confirmation email stating the order has been processed and taking a look in seller central "Payment Status" is still marked as "Open". Moreover, I receive an email 24 hours later noting that the order was canceled.
Here is my code calling completeCheckoutSession with the NodeJs SDK:
const testPayClient = new Client.WebStoreClient(config)
const checkoutSessionId = requestBody.checkoutSessionId
const purchasePrice = requestBody.productInfo.price
const payload = {
chargeAmount: {
amount: `${purchasePrice}`,
currencyCode: 'USD'
}
}
let apiResponse = await testPayClient.completeCheckoutSession(checkoutSessionId, payload)
The price and checkoutSessionId are both coming from the front-end, and I have verified that the checkoutSessionId is the same as that being passed to getCheckoutSession and updateCheckoutSession.
Here is the data object that is returned if I console log apiResponse.data. I've removed null values for the sake of brevity. Additionally, the status code I receive is 200 if I were to log the entire "apiResponse" object.
{
checkoutSessionId: "not sure I should expose this, but it's here.",
statusDetails: {
state: 'Completed',
lastUpdatedTimestamp: '20211124T141939Z'
},
chargePermissionId: 'S01-9642704-9639513',
creationTimestamp: '20211124T141931Z',
}
Regarding that "state: 'Completed'", the docs note the following:
The Checkout Session moves to the Completed state after you call Complete Checkout Session if transaction processing was successful.
Is there something that I am missing about this call or doing incorrectly that my implementation is not moving the PaymentStatus from "Open" to something along the lines of "Succeeded"? I'd also like to point out that I am doing this all in Sandbox mode.
Thank you in advance for any guidance you can provide.
This issue is about the paymentIntent of the checkout session. According to the docs it has to be set to one of these values:
Confirm: Create a Charge Permission to authorize and capture funds at a later time
Authorize: Authorize funds immediately and capture at a later time
AuthorizeWithCapture: Authorize and capture funds immediately
When set to Confirm, you have to manually create a Charge to prevent the expiry, which you currently encounter. There are only very few use cases for using Confirm. In fact I have not seen any in real life until now.
Authorize is suitable, if you would like to have maximum control over the point of time to capture the amount. It also allows for an asynchronous flow, which increases the chances for a successful authorization by giving Amazon Pay up to 24h to decide.
AuthorizeWithCapture is the easiest and safest. It takes care of everything including the capture. If the checkout session is completed successfully, you will always have a complete payment.

How to manually complete the stripe checkout session from backend for testing?

I am writing tests for how the billing system reacts to various events in Stripe. In our backend tests there is a mode to run billing tests so that it talks to Stripe API instead of using fixtures. And it saves responses from Stripe automatically as fixtures. This means you only need to run tests in fixture generation mode when there are changes made to the Stripe API or billing system.
The tricky part ofcourse is to see how the system reacts to webhook events from Stripe. The workaround I found is to use the stripe events API endpoint to fetch the recent events and post it to the webhook endpoint. You can make this work for pretty much all the important events. For example attaching a valid a PaymentMethod to a PaymentIntent will result in a payment_intent.succeeded event. And you can get that event using stripe.Event.list and send the event to the webhook the billing system to emulate the event.
The event I am having a toruble generating is the checkout.session.completed event. Since it looks like you can generate the event only after going through Stripe checkout which is not possible in backend. Ofocurse, you can create a fake event like this
[checkout_setup_intent] = stripe.SetupIntent.list(limit=1)
stripe_setup_intent = stripe.SetupIntent.create(
payment_method=payment_method.id,
confirm=True,
payment_method_types=checkout_setup_intent.payment_method_types,
customer=checkout_setup_intent.customer,
metadata=checkout_setup_intent.metadata,
usage=checkout_setup_intent.usage,
)
[stripe_session] = stripe.checkout.Session.list(limit=1)
stripe_session_dict = stripe_session.to_dict_recursive()
stripe_session_dict["setup_intent"] = stripe_setup_intent.id
event_payload = {
"object": "event",
"data": {"object": stripe_session_dict},
"type": "checkout.session.completed",
}
But that is less than ideal. Is there a way to trigger the checkout.session.completed event with real data and without the need of a web ui?
My solution was to use e2e testing (with cypress) to complete payment, and call it from my integration testing code.
This takes around 10 seconds to complete.
NUnit test:
var paymentUrl = "https://checkout.stripe.com/pay/cs_test_a1nqdPnsxtq.......";
Process process = new Process();
process.StartInfo.WorkingDirectory = #"C:\PathToCypress";
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = $"/c npx cypress run --env url={paymentUrl}";
process.StartInfo.UseShellExecute = true;
process.Start();
process.WaitForExit();
Cypress script:
describe('Stripe Billing', () => {
it('Completes a checkout session', () => {
cy.visit(Cypress.env('url'));
// Enter billing details
cy.get('#cardNumber').type('4242424242424242');
cy.get('#cardExpiry').type('3333');
cy.get('#cardCvc').type('3333');
cy.get('#billingName').type('TestName');
cy.get('.SubmitButton').click();
// Wait for payment to succeed
cy.server().route('GET', 'https://api.stripe.com/v1/checkout/sessions/completed_webhook_delivered/*').as('completed'); // define request
cy.wait('#completed', { timeout: 60000 });
})
})
You can with the Stripe CLI:
stripe trigger checkout.sessions.completed

Stripe - create / retrieve customer in one call

Is there a stripe API call that we can use to create a user if they don't exist, and retrieve the new user?
say we do this:
export const createCustomer = function (email: string) {
return stripe.customers.create({email});
};
even if the user with that email already exists, it will always create a new customer id. Is there a method that will create a user only if the user email does not exist in stripe?
I just want to avoid a race condition where more than one stripe.customers.create({email}) calls might happen in the same timeframe. For example, we check to see if customer.id exists, and does not, two different server requests could attempt to create a new customer.
Here is the race condition:
const email = 'foo#example.com';
Promise.all([
stripe.customers.retrieve(email).then(function(user){
if(!user){
return stripe.customers.create(email);
}
},
stripe.customers.retrieve(email).then(function(user){
if(!user){
return stripe.customers.create(email);
}
}
])
obviously the race condition is more likely to happen in two different processes or two different server requests, than the same server request, but you get the idea.
No, there is no inbuilt way to do this in Stripe. Stripe does not require that a customer's email address be unique, so you would have to validate it on your side. You can either track your users in your own database and avoid duplicates that way, or you can check with the Stripe API if customers already exist for the given email:
let email = "test#example.com";
let existingCustomers = await stripe.customers.list({email : email});
if(existingCustomers.data.length){
// don't create customer
}else{
let customer = await stripe.customers.create({
email : email
});
}
Indeed it can be solved by validating stripe's customer data retrieval result against stored db.
And then call another API to create afterward.
However for simplicity sake, i agree with #user7898461 & would vouch for retrieveOrCreate customer api :)
As karllekko's comment mentions, Idempotent Keys won't work here because they only last 24 hours.
email isn't a unique field in Stripe; if you want to implement this in your application, you'll need to handle that within your application - i.e., you'll need to store [ email -> Customer ID ]s and do a lookup there to decide if you should create or not.
Assuming you have a user object in your application, then this logic would be better located there anyways, as you'd also want to do this as part of that - and in that case, every user would only have one Stripe Customer, so this would be solved elsewhere.
If your use case is like you don't want to create a customer with the same email twice.
You can use the concept of stripe idempotent request. I used it to avoid duplicate charges for the same order.
You can use customer email as an idempotent key. Stripe handles this at their end. the two request with same idempotent key won't get processed twice.
Also if you want to restrict it for a timeframe the create an idempotent key using customer email and that time frame. It will work.
The API supports idempotency for safely retrying requests without
accidentally performing the same operation twice. For example, if a
request to create a charge fails due to a network connection error,
you can retry the request with the same idempotency key to guarantee
that only a single charge is created.
You can read more about this here. I hope this helps

Resources