I'm trying to auto charge customers on stripe in NodeJS
I have an issue where some customers have a:
default_source
and some have
invoice_settings.default_payment_method
My current workflow (C# WPF):
Create customer + Add subscription to customer
Get URL for portal
Customer adds card details on stripe portal
This works fine and payment are running well.
I also charge for SMS fees using a nodejs api:
async function chargeSMS(stripeCusID,chargeValue,chemistName,chemistID,countSMS){
//console.log(stripeCusID);
//console.log(chargeValue);
let customerObject;
let payMethodID;
customerObject = await stripe.customers.retrieve(
);
console.log("PaymentID received:" + customerObject.invoice_settings.default_payment_method);
payMethodID = customerObject.invoice_settings.default_payment_method;
await stripe.paymentIntents.create({
amount: chargeValue,
currency: 'aud',
customer: stripeCusID,
description: 'SMS Charges: ' + countSMS + " sent",
payment_method: payMethodID,
confirm: true
},
function(err, response) {
if (err) {
console.log("Charge", err.message,stripeCusID,chargeValue,chemistName);
return;
}
console.log("Successful SMS Charge:", response.id,chemistName);
chemCharged(chemistID);
})
}
This has also been working fine as a Cron on 1st of each month.
My issue is that recently one of my customer's cards expired.
They used my WPF to open the portal and add a new card
This worked.
HOWEVER this new card on the portal is only listed as
customer.default_source
Their
invoice_settings.default_payment_method
Is now null!
So my code above fails.
I've checked and:
All new customers using portal have their cards saved as invoice_settings.default_payment_method
any time a customer adds a new card it is only added as defult_source
I ahve no idea why this is the situation but I'm using the stripe customer portal so i would have thought they would add in the same way!
Any ideas how I can fix this or figure out what I've done wrong?
On the stripe web portal (my business) I can see that this customer DOES have a default card, and it looks the same as any other customer, but the api side has it registered under the different section!
You can add the Card as the Customer's invoice_settings.default_payment_method yourself by calling the Update Customer API.
FYI in a subscription the precedence chain is:
Subscription's default_payment_method
Subscription's default_source
Customer's invoice_settings.default_payment_method
Customer's default_source
Related
I am building an app in which i am using stripe to handle all payments. And in our app we have a common feature where user can update the default card for their subscriptions but it not working as expected. I Have followed the instruction to update the customer payment method as described in the stripe documentation but it's not working as expected i still see the old credit card details as default payment method in stripe dashboard.
Updating the customer:
const customer = await stripe.customers.update(body.customerId, {
invoice_settings: {default_payment_method: body.pId},
});
Attaching payment method to customer and updating subscription:
const paymentMethod = await stripe.paymentMethods.attach(body.pId, {
customer: body.customerId,
});
await stripe.subscriptions.update(subscriptionDetails?.planId as string, {
default_payment_method: paymentMethod.id,
});
But still nothing, i am not sure what i am missing here but i couldn't update the default payment method.
The screenshot you shared refers to the payment methods saved on the Customer. If you wish to set the default payment method on the Customer, it will be under invoice_settings.default_payment_method on Customer API.
Subscription's default payment method is different from Customer's default payment method. With default_payment_method on Subscription API, it will only update the default on the Subscription object, not Customer object like the page you see. You should be able to find Subscription's default payment method at the Subscription page.
Subscription page link will be: https://dashboard.stripe.com/test/subscriptions/sub_xxx where sub_xxx is the Subscription ID.
I am using the React components for Stripe.js to try and process a payment for a connected account, and collect a fee off of each payment. I am not sure if the flow between my client and server is properly picking up my cloned customer and payment method. When my test customer tries to pay, I get "No such payment_intent: 'pi_abc...zyx'". I have ensured I am using the correct private test keys on both client and server. My Connected accounts are express accounts. When I go to my Stripe dashboard to look at the 'Customers' tab under 'Connect', this is what I see:
It looks like a blank entry is being created each time I make a payment attempt.
Here are the steps I am currently taking to let the platform customer pay a Connected account:
When a customer first signs up on my website, the Stripe-firebase extension I have installed automatically generates a customer ID. These are now considered my platform customers
I allow a platform customer to create an Express Connected account, this works perfectly fine. I now have their Stripe Account Id ex: 'acct_abc...xyz`.
Now here is the flow for when a platform customer tries to make a payment to a Connected Account:
React/Client Side - loadStripe with only the test key, not with a Stripe Connected account Id
const stripePromise = loadStripe('pk_test_abc...');. I provide this to <Elements stripe={stripePromise}>
React/Client Side - User fills in payment form and presses submit. Create a paymentMethodReq from client/platform:
const paymentMethodReq = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: billingDetails
});
Node/Server Side - Client then makes a request to my server to try and clone the platform payment method to a Connected account:
const serverPaymentMethod = await stripe.paymentMethods.create({
customer: data.customerStripeId, // Customer ID of the customer paying (platform customer)
payment_method: data.paymentMethodId, // Using the payment method Id generated from 'paymentMethodReq' on the client side
}, {
stripeAccount: connectedAccountStripeAccountId, // Using the Connected Account
});
Node/Server Side - Client then makes a request to my server to create/"clone" a new customer from the platform and link it to the Connected account
const customer = await stripe.customers.create({
payment_method: data.paymentMethodId, //Payment method id generated from 'serverPaymentMethod' above
}, {
stripeAccount: connectedAccountStripeAccountId, // Using the same Connected Account
});
Node/Server Side - Client then makes a request to my server to create a payment intent with the customer id that was linked to the Connected account along with the payment method that was generated from the server
const intent = await stripe.paymentIntents.create({
amount: data.price * 100,
currency: 'usd',
payment_method_types: ['card'],
payment_method: data.paymentMethodId, // Payment method id generated from 'serverPaymentMethod' above
customer: data.customerStripeId, // Customer Id generated from 'customer' above
description: data.desc,
capture_method: 'manual',
application_fee_amount: (data.price * 100) * 0.15,
}
React/Client Side - I try to confirm the card payment which results in the "No such payment_intent: 'pi_abc...zyx'" error
const confirmedCardPayment = await stripe.confirmCardPayment(paymentIntentResult?.data?.client_secret, {
payment_method: serverPaymentMethod?.data.id //Payment method id generated from 'serverPaymentMethod' above
});
I tried replacing stripe.confirmCardPayment on the client with a call to my server that instead confirms the payment intent like so:
const confirmPaymentIntent = await stripe.paymentIntents.confirm(
data.paymentIntentId, // Payment intent id generated from 'intent' above
{
payment_method: data.paymentMethodId // Payment method id generated from 'serverPaymentMethod' above
});
and this also results in "No such payment_intent 'pi_3K...9Rx'"
If anyone could help me figure out where I am going wrong in this process, that would be greatly appreciated. Thank you
The error indicates that the PaymentIntent belongs to a different account with whose your current call is using a key of. It could be:
You created the PaymentIntent on Platform account, then confirm it
from your Connected Account
You created the PaymentIntent on Connected Account, then confirm it
from your Platform Account
While I am seeing both PaymentIntent creation and confirmation calls doesn't use the stripeAccount parameter, I suspect that you missed the code somewhere and the PaymentIntent was actually created in your Connected Account, lead to #2 possibility above.
To debug this you can check which account the PaymentIntent belongs to, by searching its ID on Dashboard's search box, or simply write to Stripe Support with the ID and they can check it for you.
I'm working on stripe integration with react node.js
While creating a subscription without trial, I have no issues, and all use-cases are much simple.
But, I'm trying to create free trial without collecting credit card details, later when trial expired, I would like to collect credit card details
Server side: Creating Trial Subscription without payment method:
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: priceId }],
trial_end: expiredAt.unix(),
expand: ['latest_invoice.payment_intent'],
});
ClientSide: Later when trial expired, I would like to collect credit card details(paymentMethod), check if user action is required due to failures or 3ds and update the subscription by updating the customer's default payment_method:
const updateCustomerDefaultPaymentMethod = await stripe.customers.update(
customerId,
{
invoice_settings: {
default_payment_method: req.body.paymentMethodId,
},
}
);
How can I update the subscription to perform paymentIntent or charge the user, and return to client same subscription object with status 'in_complete' same as when creating subscription without trial_period?
Currently when running my code, I keep getting status='active' because the first invoice is in status='paid' with price of '0$'.
In the case of a free trial, creating a subscription won't result in an initial payment, and Stripe will instead create a SetupIntent under the hood for you automatically. This SetupIntent is meant to be used when collecting the customer's payment method. So at any point during the trial, the customer would need to return to your app to enter their credit card details, which you would then save using the SetupIntent:
subscription.pending_setup_intent
Confirm the SetupIntent with the collected card details
Once the payment method is setup and saved to the customer (as the default payment method for invoices) it will be used to pay for the subscription's first invoice when the trial ends. The first payment after a trial is considered an off-session payment (last FAQ), so if that payment fails Stripe will attempt to recollect the payment using the usual smart retries and dunning functionality.
In a nutshell, you need to use the SetupIntent that's created (and attached to the subscription) to save the user's payment information.
I am encoutering a problem with stripe. let me explain my working
scenario.my requirement is do not charge user for 14 days with card up front
1)user enter card details
2)sca popup appear
3)regardless of user complete the authentication or not a subscription is created in stripe because i set trial_end_date=>now()+14 days
4)user payment fails in some reason and attempt again, another subscription created
i am worried about the duplicate subscription as the stripe will attempt to pay after the 14 days for both of these subscription as it send a Stripe-hosted link for cardholders for both of these subscription
let me give a snapshot of what i have so far
$data['customer']='customerId';
$data['items']=[];
$data['items'][0]['plan']='stripe_plan_id'
$data['default_payment_method']='pm_xxxx'
$data['trial_end']= strtotime('+14 days');
$data['prorate']=true;
$data['tax_percent']=env('VAT_PERCENTAGE');
$data['expand']= ['latest_invoice.payment_intent', 'pending_setup_intent'];
try {
$subscription=\Stripe\Subscription::create($data);
}
catch(Exception $e) {
return response()->json(['success' => false,'message'=>$e->getMessage()]);
}
what i am missing? how to prevent the duplicate subscription scenario.please expain with the correct example which is i am missing.thanks in advance
I think the problem is in your payment flow, there is not a stripe api that explicitly detects duplicate subscription. You are after all allowed to assign more then 1 subscription per customer id. You can create idempotent keys, but that isn't for the same thing you're talking about, idempotent keys are for accidently hitting the submit button twice within the same timeframe.
The solution would be to attach a payment method to your stripe customer id before your subscription trial is over. For example if you are using stripe elements you would call
Node/JS Example below :
const result = await stripe.createPaymentMethod({
type: 'card',
card: card
})
then pass that result to your backend
const result = await stripe.paymentMethods.attach(paymentMethodId, {customerId})
You do not need to create a new subscription, as one has already been created for that user. Credit cards are assigned to customer Ids, and not to subscriptions. Stripe will do the rest.
You do also need to update the customer with the default payment method as follows :
const customer_update = await stripe.customers.update(stripeCustomerId,{invoice_settings: {default_payment_method:paymentMethodId}});
Now when you visit your dashboard you will see a default card assigned to your customer. Once the subscriptions falls out of the trail period, the default card will be charged.
So in this case there wont be a duplicate subscription created, as you are not calling stripe.subscriptions.create again.
New Customer created by Checkout, then create a new Subscription on the same Customer by Node SDK results in Error: This customer has no attached payment source.
However if I look at the Customer at the dashboard, there is a Card, but not set as Default. Once it is "Set as Default" by clicking the ... it works.
Here is the code I used to create a new Subscription on a Customer:
const customer = 'cus_xxxxxx'
const plan = 'plan_xxxxxx'
stripe.subscriptions.create({
customer,
items: [
{
plan
}
]
})
I'm not sure if this is a limitation of Checkout since https://stripe.com/docs/payments/checkout says
Better support for saving customer details and reusing saved payment methods
Right now my workaround is to use webhook to update Customer's invoice_settings.default_payment_method on payment_method.attached.
This works but it feels strange. Did I miss something? Why does Checkout not set the only Card as invoice_settings.default_payment_method?
This behavior seems intentional on Stripe's part, the card from Checkout is attached to the Customer as a Payment Method, and is not set as default.
The same thing happens if you create a Customer directly with a PM,
let cust = await stripe.customers.create({ payment_method: "pm_card_visa" });
Also, fwiw, one can create their subscription directly from Checkout, passing a plan instead of sku https://stripe.com/docs/stripe-js/reference#stripe-redirect-to-checkout
From Stripe support:
Checkout does not currently support the ability to reuse saved payment
methods. We are aware that this is a feature request for a lot of our
users, and we are working on implementing this in the future.
If you'd like, you can see a roadmap of the updates we'll be making to
Checkout in the document below.
https://stripe.com/docs/payments/checkout#checkout-roadmap
That said, the work around you're doing for the moment is the same
work around that we're suggesting to users for the time being.
After a lot of digging I realized there is one step that is easy to miss in the docs: take the attached payment method and set it up as a default.
Here is my full server node.js code for creating a subscription:
const customerResponse = await stripe.customers.create({ email, name })
const customer = customerResponse.id
await stripe.paymentMethods.attach(paymentMethodId, { customer })
await stripe.customers.update(customer, {
invoice_settings: { default_payment_method: paymentMethodId }
})
await stripe.subscriptions.create({
customer,
items: [{ price: 'price_XXXXXX' }]
})
paymentMethodId name and email come from the client.