#mangle_invoice-proration-credits
1 messages ยท Page 1 of 1 (latest)
๐ Welcome to your new thread!
โฒ๏ธ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
๐ This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1399468024205148160
๐ Have more to share? Add more details, code, screenshots, videos, etc. below.
`` ` case 'invoice.paid':
try {
console.log('dataObject invoice.paid', dataObject);
subscriptionId = dataObject?.parent?.subscription_details?.subscription;
const payment_intent_id = dataObject?.payment_intent;
const invoiceObject = event?.data?.object;
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
if (subscription?.status === 'canceled') {
return res.status(200).end();
}
// Retrieve the payment intent used to pay the subscription
let payment_intent;
if (payment_intent_id) {
payment_intent = await stripe.paymentIntents.retrieve(payment_intent_id);
}
const subscriptionsSettings = {
cancel_at_period_end: false
};
if (payment_intent?.payment_method) {
subscriptionsSettings.default_payment_method = payment_intent?.payment_method;
}``
I had this before API upgrade, trying to add the payment_method to update the subscription with the default payment method```
Hello
PaymentIntent association with Invoice has changed in the latest API version. I'd recommend reading through - https://docs.stripe.com/changelog/basil/2025-03-31/add-support-for-multiple-partial-payments-on-invoices as it also explains how you can handle this moving forward
So it doesn't come from webhook invoice.paid anymore? Need to make another invoice call from invoice.paid?
Correct. The motivation behind the change and workaround both are explained in the doc I shared above ๐
These changes on API are a bit weird.. they force me to make extra unnecessary calls to Stripe. But thank you for the info. I will make the extra call and fix it
NP! The change was unavoidable in order to support partial/multiple payments for a single invoice which has been a frequent asks from users ๐
aaa okay, got it
@vernal juniper
const invoiceWithPayments = await stripe.invoices.retrieve(
dataObject?.id,
{
expand: ['payments'],
}
);
doesn't send payment intent here
in payments object i think right?
invoiceWithPayments {
object: 'list',
data: [],
has_more: false,
url: '/v1/invoices/payments'
}
it comes empty
sure
in_1RpwmaDmVlmqORBIlqW69COc
this id comes in invoice.paid
and i'm trying to retrieve with payments expand so i can get payment_intent_id
Ah this invoice was paid using Customer Balance automatically. So there's no PaymentIntent (associated paymnets) in that case
hmm... customer balance from where, that's weird..
nobody should have customer balance in the app
the weird thing that next invoice preview says 5.99$ to refund.. but i don't have payment intent
Oh interesting. If you look at the customer invoice balance history - there are a bunch of these "Invoice too small" credits
why this happens?
payments are completed still..
const session = await stripe.checkout.sessions.create({
line_items: [
{
price: priceId,
quantity: 1,
},
],
customer: customerId,
mode: 'subscription',
customer_update: {
name: 'auto',
address: 'auto'
},
allow_promotion_codes: true,
tax_id_collection: {
enabled: true,
},
automatic_tax: { enabled: process.env.VAT_ENABLED === 'true' },
...(trialEnd && {
subscription_data: {
trial_end: trialEnd,
},
}),
}, { idempotencyKey: uuidv4() });
I'm creating a stripe checkout session like this, and i just pay it.. i do nothing with the invoices
You still here? All good?
Yup, I need to step away soon so catching @split bronze up on your thread
thank you!
It looks like the credit on that invoice came from cancelling a different subscription on the same customer with prorations https://dashboard.stripe.com/test/logs/req_cbd5a6QUFWaAqf
Yeah, we cancel with prorations
static async cancelSubscriptionWithProratedRefund(subscriptionId, subscriptionToDeleteAndRefund, nextInvoice) {
try {
if (subscriptionToDeleteAndRefund) {
await database.Subscriptions.destroy({
where: { subscription_id: subscriptionId }
});
const refund = await stripe.refunds.create({
payment_intent: subscriptionToDeleteAndRefund?.payment_intent,
amount: nextInvoice.ending_balance ? Math.abs(nextInvoice.ending_balance) : 0,
});
await stripe.customers.createBalanceTransaction(
subscriptionToDeleteAndRefund?.dataValues?.customer_id,
{
amount: -nextInvoice.ending_balance,
currency: 'usd',
}
);
return { refund: { status: refund?.status } };
}
return 'No Subscription Found';
} catch (error) {
throw error;
}
}
This is what i use
Last year a collegue of yours helped me out with this
Something changed in the meantime?
the thing is that i think i refunded this without payment intent, because i didn't had it initially and it remained like that?
That should have always created a credit as far as I am aware. I may have been confused on the question. What about this balance are we trying to understand at the moment?
So..
- I create a new stripe customer/user
- User creates a stripe checkout session then he pays
- On my server the stripe webhook invoice.paid doesn't send payment_intent anymore, so i need to fetch it through invoice.retrieve with expand payments
- payments comes empty array
- i cannot get payment_intent anymore
cus_SlUFoVeYlhTUqJ
this is the custumer_id of a new account without refunds
still the same issue
sub_1RpxIGDmVlmqORBIL2X9dPHQ
This is the change my colleague mentioned before with the latest API version. Unfortunately basil requires an extra API call to get the payment intent ID(s) related to an invoice. I have heard of users who have stuck to the API version just before basil (acacia) because they want to keep that flow simpler
I'm doing that extra API call for payment intent ID, but it doesn't come
invoiceWithPayments {
object: 'list',
data: [
{
id: 'inpay_1RpxNCDmVlmqORBIltI3PUaQ',
object: 'invoice_payment',
amount_paid: 599,
amount_requested: 599,
created: 1753733411,
currency: 'usd',
invoice: 'in_1RpxN9DmVlmqORBIY0P1pSTn',
is_default: true,
livemode: false,
payment: [Object],
status: 'paid',
status_transitions: [Object]
}
],
has_more: false,
url: '/v1/invoices/payments'
}
Actually it does come... id is payment_intent_id?
maybe something is weird with that customer
Remaining on accacia for a few more years is not an issue right?
Oh sorry I see the confusion now, because the credit fully paid for the invoice no payment was needed so no payment intent was created.
We will support acacia for years, the one issue I can think of is that new features will require upgrading to newer API versions going forward. So that would be a reason you may still want to upgrade at some point
cus_S403yGUBFm2tt0
this customer has some issues like with invoice too small, even if i pay 5.99$ payment intent still doesn't come
but a new customer/user doesn't have issues as i see
I think I know what is happening here. So when an invoice is smaller than $.50 or roughly the equivalent of that in another currency, we close the invoice as being too small because charging it will often incur more network costs than the transaction is worth. When you cancel a subscription with proration, we create an invoice to credit the user for the time they already paid for. That is done by creating an invoice with a negative amount, which is a smaller number than the minimum charge amount, so the credit invoices are closed as "too small" which is when their credit is applied.
Normally the way this would work is that if you have an invoice for $.49 we would close it as too small and put a debit for $.49 on the customer's balance. When the next invoice is created for them, we add that debit to the new invoice so they pay for that balance there.
This means it's not okay that i do refund with prorations for that small amount?
If user has the subscription active for 1 hour and then he decides to refund
It's totally fine to do, I was just trying to explain what is happening here
got it, i managed to get the payment intent id and it seems it's working fine on my end, even with refunding with prorations and creating new subscriptions
cus_S403yGUBFm2tt0
can you reset the credit on this user? does it have credit?
Other users work really fine, this is the only user that has issues. Maybe you can reset it so i can enter into the normal flow
You can modify their credit via the API. Looking in to how you can check a specific customer's credit
https://docs.stripe.com/api/customer_balance_transactions/create
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
I can't do it directly from dashboard right?