Miguel Piedrafita

Miguel Piedrafita

Billing access to your Laravel apps with Gumroad

I recently worked on a project where users made a one-time purchase for access to the application. Following the lead of other sites with this exact system (like Tailwind UI), we decided to go with Gumroad. Here's how we made it work with Laravel.

First, you'll need to create a Gumroad account. Then, create a new product and fill out all the required fields. On the Content section, you'll need to make sure the checkbox that says Generate a unique license key per sale is checked. You will also need to set the content to a URL instead of a file. In my case, this URL will be the registration page.

Gumroad Options

Let's move over to the Laravel side now. The first thing we need is a way to connect with Gumroad. Instead of downloading any packages, we can use the new Http client on Laravel 7.x to interact with the Gumroad API. Here's a simple class that will make our lives easier.

<?php

namespace App\Utils;

use Illuminate\Http\Client\PendingRequest;

class Gumroad
{
    protected PendingRequest $client;

    public function __construct(PendingRequest $client)
    {
        $this->client = $client->baseUrl('https://api.gumroad.com/v2/')->withToken(config('services.gumroad.token'));
    }

    public function getSale(string $id) : array
    {
        return $this->client->get("/sales/{$id}")->throw()->json();
    }

    public function verifyLicense(string $license) : array
    {
        return $this->client->post('/licenses/verify', [
            'product_permalink' => config('services.microlink.product'),
            'license_key' => $license,
        ])->throw()->json();
    }
}

With this out of the way, we can now start working on validating the license key on registration. For this purpose, I've created a custom validation rule you can just add into the validation array on your RegisterController (or CreateNewUser if you're using Fortify). This rule will not only make sure that the license key exists, but also that it hasn't been used before and hasn't been refunded or canceled in any way.

<?php

namespace App\Rules;

use Facades\App\Utils\Gumroad;
use Illuminate\Contracts\Validation\Rule;

class GumroadLicense implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $license = Gumroad::verifyLicense($value);

        return
                $license['success'] &&
                $license['uses'] == 1 &&
                !$license['purchase']['refunded'] &&
                !$license['purchase']['disputed'] &&
                !$license['purchase']['chargebacked'];
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The provided license is invalid or has expired.';
    }
}

We could stop here, but we can still improve the experience of our users a little more. See, when someone completes the purchase, they get redirected to the URL we specified earlier with a special query parameter called sale_id, which we can use to retrieve the license key and pre-fill the input. This will save the user searching for the email Gumroad sends with their key and copying, resulting in a better experience. To implement this, just run this code before returning the registration view.

if (request()->query('sale_id')) {
    $sale = Gumroad::getSale($request->query('sale_id'))['sale'];

    request()->session()->flashInput(['email' => $sale['email'], 'license_key' => $sale['license_key']]);
}

And that's everything you need to start billing for access to your Laravel apps with Gumroad!