Advertisement

How to Setup Recurring Payments

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

It's likely that, at some point, you'll want to implement recurring or subscription payments for a SaaS or other service. Thankfully, PayPal offers the tools we need to implement a fully integrated subscription payment solution.


What We'll Build

Note: I'm using Laravel in this tutorial, but you can apply these concepts to other languages and frameworks.

We'll setup PayPal to accept subscription payments and configure our instant payment notification (IPN) listener. Once we complete the sign-up component, we'll look at the cancellation process via PayPal's API. I'm starting with a barebones Laravel app with simple registration and login, and we'll add in the components we need to introduce subscription payments as we proceed through the tutorial.


Step 1: Setting Up Your Subscription Buttons

PayPal provides a custom field that we can use to pass the user's ID.

Before we begin, ensure you have a PayPal business account. It's free, but you must have a business account in order to implement subscription payments on your site.

PayPal offers several types of buttons that we can use, including: Buy Now, Subscribe, and Automatic Billing. Today, we'll look at the subscribe button. Subscription buttons allow you to set the billing amount, as well as the recurring payment time period. You can also setup a trial period and let PayPal create the user account when they ping your server.

First, login to your PayPal account and navigate to Profile -> My Selling Preferences. Then look for the PayPal Buttons section and select Subscriptions from the drop down list. Be sure to fill out the rest of the information.

I recommend leaving the "Have PayPal create user names and passwords for customers" box unchecked and select the "Use my secure merchant account ID" option.

We're going to allow users to sign up for a free account and then upgrade later on.

Create your button and copy the generated code; you'll use it in your application.

Passing Additional Information

As we're linking subscriptions to accounts, we'll need to know who makes a payment. The easiest way to do this is to match the email address of the PayPal transaction to the email account used to sign in to our application. This isn't foolproof, however, because many people use different email addresses. So we need to pass the user's ID from our application to PayPal.

Thankfully, PayPal provides a custom field that we can use to pass the user's ID. It has a limit of 256 characters. So I'm going to use a JSON structure, allowing us to pass additional information should we need to.

<input type="hidden" name="custom" value="{{json_encode(array('user_id' => Auth::user()->id))}}">

Be sure to check out the full list of accepted HTML variables.


Step 2: Readying Your Application

We first need to set up our payments table, where we'll store our transactions. Let's create it with the following columns:

  • id: An auto-increment integer and our primary key.
  • txn_id: Our transaction id that PayPal will give us. Set this as a varchar.
  • user_id: We'll use this to setup our relationship to the user table, an integer will do.
  • paypal_id: The PayPal Profile ID which we'll need to handle cancellations later on. Set this as a varchar.
  • created_at: We'll use this to see when the transaction is completed and start the subscription.

Also, add a subscription column to your user table. I'm going to set this as an integer to allow for multiple levels: one, two, and three to unlock additional features. I've also gone ahead and created the model that will allow us to interact with the payments table.


Step 3: Writing Your Listener

PayPal provides a simple solution for notifying us when a payment has been processed; they call it Instant Payment Notifications (IPN). In order to take advantage of IPN, we need to create an IPN listener for our application. The listener validates the data, inserts it into our payments table, and sets the subscription level of the user.

Luckily we don't have to reinvent the wheel thanks to this handy class. We'll use the IpnListener class to quickly verify the data, and we can then process this to insert it into our database. With Laravel, we can place the class into our libraries folder, making it autoload for us.

Create a new controller or route and add the following code:

$listener = new IpnListener();

try {
    $verified = $listener->processIpn();
} catch (Exception $e) {
    exit(0);
}

if ($verified) {
    // IPN response was "VERIFIED"
} else {
    // IPN response was "INVALID"
}

I called my file ipn.php, and I'll map it to /ipn. Don't forget that PayPal will POST to this URL; so if you're using HTTP/REST verbs, set it up accordingly. From here we can process our data:

$listener = new IpnListener();

try {
    $verified = $listener->processIpn();
} catch (Exception $e) {
    return Log::error($e->getMessage());
}

if ($verified) {

    $data = $_POST;
    $user_id = json_decode($data['custom'])->user_id;

    $subscription = ($data['mc_gross_1'] == '10') ? 2 : 1;

    $txn = array(
        'txn_id'       => $data['txn_id'],
        'user_id'      => $user_id,
        'paypal_id'    => $data['subscr_id'],
        'subscription' => $subscription,
        'expires'      => date('Y-m-d H:i:s', strtotime('+1 Month')),
    );

    Payment::create($txn);

} else {
    Log::error('Transaction not verified');
}

In this code, we first decode the JSON passed to our custom field, giving us easy access to the user ID. We then set the subscription level based on the transaction amount and store it in the database. We also log any errors using Laravel's Log class. It might also be nice to send a receipt to the user, but I'll leave that up to you.


Step 4: Enabling IPN

Next, we need to enable IPN and set up PayPal to ping our listener. Because PayPal's admin panel can be tricky to navigate, I've included the following screenshots to help guide you. Go to Profile -> Selling Preferences:

Then look for Instant Payment Notifications:

Next, click Choose IPN Settings:

And then enter the URL to your listener:


Step 5: Getting Your API Username, Password, and Signature

While we're in the dashboard, it's worth getting our API credentials before we sort out our cancellation process. Go to Profile -> Selling Preferences:

Look for API Access:

Select option 2:

Request an API signature:

And take note of your credentials:


Step 6: Handling Cancellations

You must have a business account in order to implement subscription payments.

Note: You're going to need an SSL certificate to handle cancellations, or PayPal with throw an error.

To provide the best user experience, it's likely you're going to want to handle cancellations inside your application or service. PayPal provides a few APIs that allow us to do this: NVP or SOAP. I'd recommend going with NVP.

PayPal's documentation seems to over-complicate things, but NVP (Name-Value-Pair) is essentially just a URL encoded string that we can cURL.

The method we're interested in is ManageRecurringPaymentsProfileStatus. We can change the state of the subscription by choosing one of the following three actions:

  • Cancel: Only active or suspended subscriptions can be cancelled. Choosing this action prevents the user from reactivating at a later time.
  • Suspend: Only active subscriptions can be suspended. This action allows users to reactivate their subscription later.
  • Reactivate: Only suspended subscriptions can be reactivated, re-instating the subscription.

We're going to use the cancel action, as our subscribe buttons create an entirely new subscription should the user want to re-activate at a later time. So our request would look something like this:

$input = Input::all();

$req = array(
    'USER'      => 'YOUR_API_USER',
    'PASSWORD'  => 'YOUR_API_PASSWORD',
    'SIGNATURE' => 'YOUR_API_SIGNATURE',
    'VERSION'   => '76.0',
    'METHOD'    => 'ManageRecurringPaymentsProfileStatus',
    'PROFILEID' => urlencode($input['paypal_id']),
    'ACTION'    => 'Cancel',
    'NOTE'      => 'User cancelled on website',
);

$ch = curl_init();

// Swap these if you're testing with the sandbox
// curl_setopt($ch, CURLOPT_URL, 'https://api-3t.sandbox.paypal.com/nvp');
curl_setopt($ch, CURLOPT_URL, 'https://api-3t.paypal.com/nvp');
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($req));
curl_exec($ch);
curl_close($ch);

return Redirect::to('settings')->with('cancelled', true);

We can now easily cancel the subscription, but for some reason PayPal opted to not send an IPN request. This isn't a problem though, as we setup our app to include an expires column. So we can check that field to determine if the user is subscribed.


Step 7: Playing in the Sandbox

PayPal provides a sandbox so that we can test our implementation. If you haven't used this before, you'll need to create a free account. From there, you'll be able to test with multiple users and transactions to ensure that your code works as expected.

We're going to test our listener, making sure it works correctly without having to perform real transactions. Before we do anything in the sandbox, we need to configure our IPN listener to ping the sandbox instead of the live server.

$listener->use_sandbox = true;

Now head over to the PayPal Sandbox, login, click Test Tools on the left and open up the IPN simulator.

Input the URL of your listener and select Express Checkout from the drop down. You can use most of the default details, but don't forget to put your JSON into the custom field toward the bottom of the page.


Step 8: Checking for the Subscription

So we've set up our subscription and enabled users to cancel the service, now how do we check for this? Laravel makes it easy, and its method translates across many languages and frameworks. I'm going to add a method to my User model that gives us access to the subscription using Laravel's Auth class:

public function subscription(){
    return Payment::where('user_id', '=', Auth::user()->id)
                ->where('expires', '>', date('Y-m-d H:i:s', time()))
                ->first(array('subscription', 'expires', 'paypal_id', 'txn_id'));
}

We can now access this and check within our controllers and views very easily. For example:

<?php
$sub = Auth::user()->subscription();
if($sub && $sub->subscription == 1){
    echo 'You\'re on the standard plan';
} elseif($sub && $sub->subscription == 2){
    echo 'You\'re on the premium plan';
} else {
    echo 'You\'re not subscribed';
}
?>

In Conclusion

Hopefully this tutorial has given you an idea of just how easy it is to set up recurring or subscription payments in your applications. PayPal is by no means the only payment processor, but it is one of the most recognized and widely used.

From here, you could add receipts, payment reminders, and cancellation notifications to build a fully integrated solution.

Advertisement