Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

JamesMills's avatar

Top-up payments with Stripe/Cashier

I have a service which will basically operate as an API using credits. Simple enough, an API call is made, a credit is used.

I already have Cashier set up in the application for another subscription service but I'm looking at adding a credit top-up payment system too.

My idea is to have a button which allows the user to do an initial setup/top-up. I have a "product/price" in stripe for $200 which allows the user to purchase 200 credits. My idea is to have the user do the checkout flow for the initial purchase and setup using the Stripe managed payment platform. This will allow me to capture the users credit card details and address etc.

I'd then have a system running on an event/schedule setup which monitors the credit usage and when their balance drops below a certain threshold it will take an automatic payment for another set of credits.

Has anyone done anything similar or have any advice on how to setup this flow?

From my initial research, it looks like I can do something like this

\Stripe\Stripe::setApiKey(config('cashier.secret'));

\Stripe\InvoiceItem::create([
    'customer' => $request->user()->stripeId(),
    'price' => 'price_123',
]);

$invoice = \Stripe\Invoice::create([
    'customer' => $request->user()->stripeId(),
    'auto_advance' => true, // Automatically finalize and pay the invoice
]);

$invoice_result = $invoice->pay(); // Attempt to pay the invoice immediately

When testing this I'm finding the invoice does get created and paid but the InvoiceItem is not added to the Invoice.

Thanks in advance for any insights or suggestions.

James

0 likes
2 replies
LaryAI's avatar
Level 58

Hello James,

It seems like you're on the right track with using Stripe and Cashier to handle top-up payments for credits. However, the issue you're encountering with the InvoiceItem not being added to the Invoice might be due to the asynchronous nature of Stripe's invoicing system.

When you create an invoice item and then immediately create an invoice, the invoice item might not have been fully processed by Stripe's system before the invoice is created and paid. To ensure that the invoice item is included, you can create the invoice item and then create the invoice in a separate request after a short delay, or you can use webhooks to listen for the invoice item's creation and then create the invoice.

Here's a revised approach that should work:

  1. Create an invoice item and save the response.
  2. Use the invoice parameter in the invoice item creation to link it to an upcoming invoice.
  3. Create the invoice manually or let Stripe create it during its regular billing cycle.

Here's how you might adjust your code:

\Stripe\Stripe::setApiKey(config('cashier.secret'));

// Create an invoice item and associate it with the customer's upcoming invoice
$invoiceItem = \Stripe\InvoiceItem::create([
    'customer' => $request->user()->stripeId(),
    'price' => 'price_123',
]);

// You can either wait for Stripe to automatically create the invoice at the end of the billing cycle,
// or you can manually create the invoice if you want to bill the customer immediately.
$invoice = \Stripe\Invoice::create([
    'customer' => $request->user()->stripeId(),
    'auto_advance' => true, // Automatically finalize and pay the invoice
]);

// If you're manually creating the invoice, you can attempt to pay it immediately
$invoice_result = $invoice->pay();

Remember that if you're manually creating the invoice, you should ensure that the invoice item has been fully processed by Stripe before creating the invoice. You can do this by setting up a webhook to listen for the invoiceitem.created event and then creating the invoice in response to that event.

For the automatic top-up system, you can use Laravel's scheduled tasks (cron jobs) to check the user's credit balance regularly and trigger the top-up process when their balance falls below a certain threshold.

Here's a basic example of how you might set up a scheduled task in Laravel:

// app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        // Check each user's credit balance and top up if necessary
        $users = User::where('credit_balance', '<', $threshold)->get();
        foreach ($users as $user) {
            // Trigger the top-up process for $user
        }
    })->daily(); // Adjust the frequency as needed
}

Make sure to test this thoroughly in a staging environment with Stripe's test mode before going live to ensure that the flow works as expected.

I hope this helps you set up your credit top-up payment system! If you have any further questions, feel free to ask.

JamesMills's avatar

Looking at this a little more it looks like the best way might be to use the PaymentIntent

$paymentMethods = $request->user()->paymentMethods();

$customerId = $request->user()->stripeId();

\Stripe\Stripe::setApiKey(config('cashier.secret'));
        
$paymentIntent = \Stripe\PaymentIntent::create([
    'amount' => 2000, // Specify the amount.
    'currency' => 'usd',
    'customer' => $customerId,
    'description' => 'Immediate one-time payment',
    'payment_method' => collect($paymentMethods)->first()->id,
    'off_session' => true, // Important: Indicates that the customer is not present
    'confirm' => true, // Automatically confirm the payment
]);

I guess I just need to monitor the status to check if this is requires_action.

Subsequent off-session payments might also trigger SCA requirements, in which case Stripe will set the PaymentIntent's status to requires_action, and you'll need to handle this scenario, possibly by contacting the customer to complete the authentication process.

Will continue to test...

Please or to participate in this conversation.