#mangle_invoice-proration-credits

1 messages ยท Page 1 of 1 (latest)

stark solarBOT
#

๐Ÿ‘‹ 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.

cinder axle
#

`` ` 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```

vernal juniper
cinder axle
#

So it doesn't come from webhook invoice.paid anymore? Need to make another invoice call from invoice.paid?

vernal juniper
#

Correct. The motivation behind the change and workaround both are explained in the doc I shared above ๐Ÿ™‚

cinder axle
#

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

vernal juniper
#

NP! The change was unavoidable in order to support partial/multiple payments for a single invoice which has been a frequent asks from users ๐Ÿ™‚

cinder axle
#

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

vernal juniper
#

has the invoice been finalized?

#

Can you share the invoice ID?

cinder axle
#

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

vernal juniper
#

Ah this invoice was paid using Customer Balance automatically. So there's no PaymentIntent (associated paymnets) in that case

cinder axle
#

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

vernal juniper
#

Oh interesting. If you look at the customer invoice balance history - there are a bunch of these "Invoice too small" credits

cinder axle
#

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

stark solarBOT
cinder axle
#

You still here? All good?

vernal juniper
#

Yup, I need to step away soon so catching @split bronze up on your thread

cinder axle
#

thank you!

split bronze
cinder axle
#

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?

split bronze
#

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?

cinder axle
#

So..

  1. I create a new stripe customer/user
  2. User creates a stripe checkout session then he pays
  3. 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
  4. payments comes empty array
#
  1. 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

split bronze
#

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

cinder axle
#

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?

split bronze
#

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

cinder axle
#

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

split bronze
#

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.

cinder axle
#

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

split bronze
#

It's totally fine to do, I was just trying to explain what is happening here

cinder axle
#

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

split bronze
cinder axle
#

I can't do it directly from dashboard right?

cinder axle
#

Thanks. All good!

#

Everything is fixed now

stark solarBOT