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

AbehoM's avatar

Charge an user every 30 days. (job/queue)

Unfortunatelly I can't use Stripe cause it's not supported in my country and most of the services doesn't have a nice implemention of subscription system and for that I created my own using Laravel Cashier as inspiration and a payment API, the different is that instead of using the subscription service provided by the payment gateway, I have to every 30 days charge the user and for that I need to create jobs (I guess). The question here is: What is the best way to create a job and schedule it so every 30 days it wil charge the user?

0 likes
21 replies
AbehoM's avatar

I just realized that running it every 30 days might not be accurate, imagine if I have 1000 subscription from different users, each one with different days, how would that work? And why command over jobs btw?

Snapey's avatar
Snapey
Best Answer
Level 122

Well if I were a user, I might want to look into my account and see both billing history and the date of the next bill.

So, if each account has a nextInvoice date, at some point in every day you can run a job that fetches all accounts that are overdue (next invoice is today or in the past) and not on hold.

Then, create a billing job for each account that is due.

Run the billing and as each account is confirmed as payed, move the billing date out to the next 30 days.

If you have a billing failure, record the failure reason and mark the account as on hold and then have an admin task that you perform at your convenience to check all accounts that are on hold and see if they need new card details or whatever.

Be very careful how you handle failed jobs. Users will not take kindly to being billed multiple times for the same period so if there is an issue, back out and investigate. Don't just keep trying.

1 like
aurawindsurfing's avatar

Ok that changes the perspective a bit however I would still go for a scheduled command.

Queue and jobs are not meant for storing this kind of information. Imagine you generate a job and wait for it to be executed 30 days + 30 days + 30 days and so on. Once you reset your queues you might lose data and money.

I would put all the subscription renewal dates for a client on a table. Run scheduled command every day to check what subscriptions need renewal and then run those directly or maybe put them only then on a queue if they might be taking longer to execute or be too many of them

Hope it helps!

AbehoM's avatar

I don't actually have a nextInvoice date but I do have starts_at which specifies when the subscription started, every month when the new payment is made it will be rewriten with the current date now which specifies thet new cycle (period). So I actually need to check the different between starts_at and now if it passed 30 days already so I can charge the user again. The billing history (invoices) will be generated every month when the user is charged (with the date, amount, etc).

So, if each account has a nextInvoice date, at some point in every day you can run a job that fetches all accounts that are overdue (next invoice is today or in the past) and not on hold.

Then, create a billing job for each account that is due.

With that you mean two different jobs right? One for checking and another one for billing, is that corrent?

Run the billing and as each account is confirmed as payed, move the billing date out to the next 30 days.

I have a webhook configured to receive when a payment is made so I can just make those changes, Not sure tho if I need to create a job for this one too.

If you have a billing failure, record the failure reason and mark the account as on hold and then have an admin task that you perform at your convenience to check all accounts that are on hold and see if they need new card details or whatever.

That is something interesting because the payment gateway has a payment checking to see if the credit card is valid and it can take up to seconds or 48 hours*. I created a field called waiting_since that specifies if the subscription is waiting on the gateway to process the payment, it will wait until the payment gateway returns failed or paid for 48 hours, it's enough room for the payment gateway to send me a notification if the payment has been made or not. I think I will have to create a job to cancel expired waiting payment subscriptions just to be sure (in case - God knows how - the payment gateway doesn't send a notification about the payment) - not sure tho.

Be very careful how you handle failed jobs. Users will not take kindly to being billed multiple times for the same period so if there is an issue, back out and investigate. Don't just keep trying.

That is true, how would you recommend me doing that? As the @aurawindsurfing, is it better to use commands instead?

@aurawindsurfing

Ok that changes the perspective a bit however I would still go for a scheduled command.

Queue and jobs are not meant for storing this kind of information. Imagine you generate a job and wait for it to be executed 30 days + 30 days + 30 days and so on. Once you reset your queues you might lose data and money.

I would put all the subscription renewal dates for a client on a table. Run scheduled command every day to check what subscriptions need renewal and then run those directly or maybe put them only then on a queue if they might be taking longer to execute or be too many of them

Valid points. The site is estimated to have more than 5000 subscriptions (active).

aurawindsurfing's avatar

@zfdeveloper we are both saying basically the same thing with @snapey

  1. Put dates/subscriptions on DB tables.
  2. Iterate daily over that table to find new charges to be made.
  3. Create jobs from charges and queue them.
AbehoM's avatar

Just to be 100% sure, the daily checking is made using a scheduled command and the billing using jobs/queue, is that right?

Snapey's avatar

cron job will allow you to check for billing due, but then you will have several hundred renewals to process.

If the payment provider API is quick, you could just rattle through them in the cron job. If it takes a while, eg more than a few minutes, I would queue a job for each payment.

Jobs are safer because if the job crashes then only that payment is affected, whereas if you are processing 200 and after 10 the service goes down you might end up with some payments where you are not sure if you have sent them or not.

AbehoM's avatar

I'm kinda confused now, you mean that I don't need to use command not even to check every day for due subscription? With jobs you mean creating a job for each one for 30 days instead of checking everyday? Is that it? The payment gateway will only return for me the status of the payment as notification through the webhook. The jobs would be to check for due subscription and also for creating a new charge to renew the subscription. Can you give me an example of how you would structure if like using class names so I understand what kind of jobs you would create? I need to check for subscription due, charge and renew subscription.

Snapey's avatar

'cron job' is the scheduler. Create a schedule task ( lets call it task so as not to confuse it with jobs) that runs once per day, gets a collection of accounts that need billing

Iterate over the collection and create a billing job (queued) for each account

Service the queue. in each queue entry you will get the account that needs billing. Raise the charge on the customer through the api.

Separately you have a callback endpoint. Your payment provider calls this to say the customer has been charged. Update their record.

1 like
Robstar's avatar

@zfdeveloper With the greatest respect, have a read about the basics of commands, jobs and the difference between them. I belive there videos on this site covering the topic too.

AbehoM's avatar

@snapey Thank you, I was confused with the naming conventions, when you said cron job I didn't know if you meant scheduler. I will implement the way you said, thank you very much

AbehoM's avatar

@aurawindsurfing @snapey Regarding the server, do you guys think that I will need a dedicated server to handle this or a regular VPS will do the trick? I will need to handle a lot of visitors and also around 5k subscriptions.

aurawindsurfing's avatar

It all depends on your setup. Use the newest PHP. Maintain your code. You should be ok I think. If not then move to a bigger instance.

AbehoM's avatar

I was thinking about using Forge with the DigitalOcean $80 plan (16 GB 6 vCPUs 320 GB 6 TB) ¯_(ツ)_/¯ (I hope it handles haha). I'm already using PHP 7.3 with the nice features in place.

AbehoM's avatar

With 10e you mean $10? (2 GB 1 vCPU 50 GB 2 TB)

Snapey's avatar

Go for the smallest possible. Extra resources can be added as you need in the future.

AbehoM's avatar

I'm afraid of getting the $5 dollar one for example and get into the problem of jobs getting stuck and not billing the users and stuff like that. That is why I thought about getting a medium plan. Another thing I have to figure out is how to avoid a subscription that was queued already for billing to be scheduled again for some reason). I put the command schedule to run every five minutes to look for overdue subscriptions and queue them, need to know if 5 minutes is good enough.

Snapey's avatar

You probably only need to check once per day, but however frequently you do it, you need to write something to the account model so that you know its 'in progress' and to exclude it next time you check for people that need billing.

As for DO, upgrading the server is a very quick job. You don't have to rebuild it on a bigger box.

Please or to participate in this conversation.