Amazon Pay: Complete Checkout Session is successful but not confirming transaction - node.js

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.

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.

Stripe subscriptions.create - can you force use of payment intent?

Background:
Having obtained a paymentMethodId from the client with Stripe Elements, I am doing this on the server side:
stripe.paymentMethods.attach(..),
stripe.customers.update(...default_payment_method: ...).
stripe.subscriptions.create(..).
When additional action is required by 3DSecure, the subscription object (returned from create or fetched at a later date) includes a pending_setup_intent, which gives the info needed to resolve the intent (and i've got that working ok).
However if we run the subscriptions.create call when there aren't any payment methods on the Stripe Customer, the subscription object doesn't include a pending_setup_intent or anything else to indicate that the subscription doesn't have a way to pay.
Question:
Short of waiting for webhooks is there some way to have reasonably high confidence that the first payment on the subscription is likely to succeed, ie. there is a card, it's going to accept the payment (and yes, the 3DSecure stuff is sorted)?
Notes:
Although I'm not using trials on the subscription, the billing_cycle_anchor is in the future to provide a free period (basically a trial, but not explicitly making use of the stripe trial concept).
I'm using Stripe API version: 2015-03-24 (upgrading is a lower priority than resolving this)
Further clarification:
At the point we make the call to stripe.subscriptions.create(), the code is not aware of whether the paymentMethod was successfully attached and set as the default (I suppose we could check this, but my hope was that Stripe can handle that for us). For example when using the test card 4000000000000002 the payment method is not attached.
The call to stripe.subscriptions.create is of the form:
{
customer: stripeCustomerId,
billing_cycle_anchor: 1606946400,
cancel_at_period_end: undefined,
items: [{plan: "pro_annual"}],
prorate: false,
expand: ["pending_setup_intent", "pending_setup_intent.latest_attempt"]
}

Stripe Automatic Confirmation and order fulfillment without Webhooks?

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.

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