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

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"]
}

Related

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.

How to use ACH payments in North America with stripe payment Intents

attempting to use a payment method of a ACH payment...but I can't figure out how to do it. Theoretically the documentation here says I can.
https://stripe.com/payments/payment-methods-guide#for-professional-services
However when I go here:
https://stripe.com/docs/api/payment_methods/create#create_payment_method-type
I see 'card' listed, but I do not see anything related to 'bank' or 'ach' or 'check' so I am confused.
I have no issues following their instructions to manually create a "bank" object programmatically, and then making then making that bank object a "source" as shown here:
const bank_submit_object = {
"object": 'bank_account',
"country": 'US',
"currency": 'usd',
"routing_number": routing_number,
"account_number": account_number,
"account_holder_name": account_name,
"account_holder_type": account_type
}
const bankAccount = await stripe.customers.createSource(
'customer_id',
{ source: bank_submit_object }
);
My problem is sources is listed as an Older api
https://stripe.com/docs/sources
And previously to today (the warnings are gone today for some reason), the documentation would flash a warning saying sources are the old way of doing things and to stop doing it.
So in order to do things the new way, can somebody help suggest how to do ACH payments using payment methods and NOT sources.
Thanks guys.
This is not supported by the current public API. For payments using ACH today, you should follow this guide using the legacy Charges API.
Note the callout at the top mentioning a beta for ACH payments using Payment Intents. If you're interested in that, you should fill out the linked form.
Edit 2022: The new Payment Methods & Payment Intents API support for ACH direct debit has since been released. See here: https://stripe.com/docs/payments/ach-debit

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.

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 recurring payments for 3DS source

I want to use Stripe to charge cards recurrently every 30 days with amounts that oscilate.
From the docs I got that if there is a possibility that the card requires 3DS we should use Sources so I switched to sources ;)
From the source object stripe.js retrieves I look at three_d_secure param to decide whether to create a source object that requires 3DS or a normal card charging.
The flow:
With JS I get the source object that has three_d_secure set to either optional or required.
When it's set to optional after I retrieve the source with: source = Stripe::Source.retrieve(source_id) it looks like this:
"status": "chargeable",
"type": "card",
"usage": "reusable",
"card":{"exp_month":12,"exp_year":2032,"brand":"Visa",...
I attach it to a customer and charge it. I guess usage: reusable means that I can charge the card again later...
When three_d_secure=='required' I create a new source calling this:
source = Stripe::Source.create({
amount: amount,
currency: currency,
type: 'three_d_secure',
three_d_secure: {
card: source_id, #src_xcvxcvxcvc
},
redirect: {
return_url: return_url
},
})
I redirect the user to the URL Stripe provides, user enters his 3DS PIN and gets back to my return_url. When Stripe redirects the user back to my return_url I retrieve the source again and get something like this:
"status": "chargeable",
"type": "three_d_secure",
"usage": "single_use",
"three_d_secure": {"card":"src_1B1JzQHopXUl9h9Iwk05JV1z","authenticated":true,"customer":null}
I would expect that after passing the 3DS the source becomes reusable and chargeable until the date of expiry or so :|
My questions are:
1 Why is the 3DS source single_use? Is this like this only in sanbox environment or with the card I am using to test?
2 Can a 3DS protected card be charged again at all?
3 What's the correct approach to attach to customer sources (3DS or normal) that can be charged again and again?
Thank you!
Because it is a source payment token, not a source card token. It expires on a due date or when is consumed. You can use reusable token to create single_use tokens. reusable one represents a card source token
Yes if a 3ds is optional or not_supported, no if required. If required then every payement needs to fulfill a 3ds.
Steps:
Create an src_card_token for a card or use saved one (reusable)
Create an customer object with an src from src_card_token
Create an src_payment_token for a customer using one of his saved cards (as token)
fullfil a 3ds redirect process if required.
create a charge

Resources