Handling semi-recurring payments with PHP

El Niño
4 min readSep 4, 2017

--

For a recent project we had the need to integrate a payment system in the platform. It is a platform where users have the option to buy one or more services, and these services can be purchased on separate tiers. We had a few requirements about what the payment system needed to do.

  • It should support recurring payments
  • It should support a way to issue payments without user interaction
  • It should be simple to integrate

While there are many payment service providers offering such services, we opted for a Dutch company called Mollie. They check all the requirements. They offer a subscriptions api, they have a way to issue a mandate and then do all communication without user interaction and they have a really well documented api.

Implementation

The flow of the application should be as follows:

  • A customer wanted to use one of the offered services
  • The customer selected the service and selected the tier they need.
  • We created a first payment and redirected customers to the payment service
  • They gave a mandate and pay for the first period of the service
  • Whenever the period ends, mollie automatically issues a new payment

This is all fairly straight forward and implementing this should not be too difficult. However there is one thing the flow mentioned above did not consider, customers changing their plans. Because we support a wide amount of different plans with a couple of different tiers, customers probably will be changing their plan often.

With most of the payment data outside our systems, and the frequent amount of changes happening in this plans, it is not easy to lean on this recurring api. It would be a hassle to calculate how many the new plan should cost, and is not currently an option to let Mollie handle this.

Improvement

When not using the subscriptions api, the flow becomes a little different:

  • Whenever the period ends, mollie automatically issues a new payment.
  • Whenever the period ends, we issue a new payment.

With this a little extra handling needs to take place in our system, however we have far more control over the payments issued by our system. To save all this information in our database we have a table with:

  • account_id a relation to our accounts table
  • service_id a relation to one of the customers services
  • product_id a relation to the product which was purchased at this time, including a tier
  • mollie_id a unique token identifying the payment (returned by Mollie)
  • The rest of the columns should speak for itself

The only thing left to do now is to create a payment with Mollie each time a payment is due to expire. We solve this with a cron job that triggers once a day to loop over all active subscriptions, and check if the subscription is due to expire. We send the customer an email saying we have just send a payment request and then request the payment with mollie. All payment updates trigger through a webhook which will keep our database up to date.

Calculate when a customer changes his/her plan

Now we can focus on the stuff that needs to happen when a customer changes his/her plan. See the code below:

// Fetch the objects from the database
$objOldProduct = $mollie_product->get($old_product_id);
$objNewProduct = $mollie_product->get($new_product_id);
// Check if we have a previous payment,
// otherwise we can treat this as a new payment
if(!$service->hasBeenPaidFor($service_id))
{
return $this->createPayment([
'amount' => $objNewProduct->price,
'customerId' => $this->createOrFindMollieAccount(),
'description' => $objNewProduct->name,
], $service_id, $new_product_id);
}
// Calculate the difference so we the customer does not pay twice for the same period
$newPrice = $this->calculateDifference($service_id, $old_product_id, $new_product_id);
$oldBillingDate = $service->getPaidSince($service_id);return $this->createPayment([
'amount' => $newPrice,
'customerId' => $this->createOrFindMollieAccount(),
'description' => 'From '. $objOldProduct->name .' to '. $objNewProduct->name,
], $sync_rule_id, $objNewProduct->id, $oldBillingDate);

The code above is commented, so you should follow what is going on. You can find the method calculateDifferencebelow

$objOldProduct = $mollie_product->get($old_product_id);
$objNewProduct = $mollie_product->get($new_product_id);
// Calculate the price per month for the old and the new product,
// this way we can compare them safely
$oldPricePerMonth = $objOldProduct->price / $objOldProduct->interval;
$newPricePerMonth = $objNewProduct->price / $objNewProduct->interval;
// Calculate the difference
$diffPricePerMonth = $newPricePerMonth - $oldPricePerMonth;
if($diffPricePerMonth < 0) {
// We do not want to give refunds if a customer previously had
// a more expensive product
$diffPricePerMonth = 0;
}
// Check how many months there are left in the current subscription
//
// Note: We calculate the month as if it had 31 days, this way
// the customer never should pay more than the price of a subscription
$remainingMonths = (new Datetime('now'))->diff(new DateTime($service->getPaidUntil($service_id)))->days / 31;
// Double check the customer actually had paid for a subscription
if($remainingMonths <= 0) {
$diffPricePerMonth = $objNewProduct->price;
$remainingMonths = 1;
}
// Return the price the customer should pay to get this new service
// untill the end of his/her payment date
return round($diffPricePerMonth * $remainingMonths, 2);

You can see that by having all this data available, checking what the new price should be has become very simple.

By Victor Lap. Victor is a back-end developer at El Niño who loves to build native apps.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

El Niño
El Niño

Written by El Niño

http://www.elnino.tech. Digital Development Agency building tailor made solutions, ensuring success by making it measurable.

No responses yet

Write a response