Replace Stripe Subscriptions with a Smart Contract

In traditional finance, you trust the payment processors to store your credit card information, they are highly regulated companies, and if they mess up it’s possible to get your money back by reversing transactions.

In the beginning, when the only crypto payments were with native currencies like BTC or ETH you could not let someone else perform automatic payments from your wallet, without sharing your private keys, and that’s the stupidest idea ever.

All this changed with the invention of ERC20 tokens. What’s great about tokens is that they are smart contracts, they are programmable and they have an allowance mechanism. With tokens, you can allow someone else to spend a portion of your tokens by providing them with an allowance without sharing your keys.

There are already quite a few promising crypto projects for recurring payments already like Superfluid and Loop.

In this article, we’re building a proof of concept for a Subscription manager Smart Contract.

Main features of Subscription Manager

  1. Only the end user can create and cancel their own subscriptions.
  2. Perform payments with an allowance.
  3. Prevent spending more money than defined in the subscription.
  4. Prevent charging more recently than defined in the subscription.
  5. Anyone can execute the payment function but it will always send money to the correct recipient.

If this were to be built into a production system, we could offer even more benefits to the end users like:

  • A single place to manage all subscriptions, to easily cancel.
  • Manage only one allowance to our Subscription manager instead of separate allowances for every service.
  • Easier integration for service providers that need to accept recurring payments.
  • Timely reminders if running out of funds based on upcoming subscriptions.

The Smart Contract

Storing subscription data

We start by defining what data to store for each subscription. The first 4 fields are self explanatory: we must store who is paying, who is receiving the payment, the recurring amount and the token used for payment.

Then we have fields for keeping track of the payment. The payout period is there to help us enforce how often the subscription can be paid out, it’s the time window between payments, usually monthly or yearly, but presented in seconds here. We also must store the time of the last payment so we know when the next payment is due. The active flag will be used for canceling subscriptions, or maybe for pausing as an exercise to the reader.

Finally, we have a mapping called subscriptions that will store our subscriptions for later retrieval and the subscription counter that will help index them in the mapping.

Create a subscription

At this point we also define our events for subscription creation, cancel and payout. Now let’s focus on the creation logic:

The user executing the createSubscription function will automatically become the payer, which doesn’t have to be the case but I made that choice. At this point, we specify the essential data for the subscription: receiver, amount, token, and recurrence period.

Then we must store the subscription in our subscriptions mapping with the next subscription index. If we don’t store it somewhere we can’t retrieve it later or use it. We use mapping instead of an array because we will always perform actions one subscription at a time. Plus arrays can become really expensive if they get big, stay away from them if possible. In the end, we emit the appropriate event with all the fields I felt would be interesting to inspect later.

Cancel subscription

Canceling subscription is the easiest, we just set our active flag to false and emit an event. This is called a soft delete because the data is still there and can be accessed, but that’s ok because we don’t have a method that re-activates the subscription so it’s not possible for other actors to activate our subscription behind our backs.

This method can also be called pause subscription and it’s trivial to add another one for resuming subscription, I’ll leave that as an exercise to the reader.

 I also added a comment that we can run:

delete subscription[subscriptionId]

and this will set our subscription values to null values, it doesn’t actually delete but rather assigns null values over the existing ones.

Pay subscription

In the payout function, we must perform some checks. First, we make sure that the subscription is active. Then we make sure that there is enough time passed between the last payout and the current date.

This time must be greater or equal to our defined payout period. This is how we enforce our subscription to only be paid once per month or once per year etc. After the check I update the lastPayout field with the current timestamp, because the payment will be performed in this transaction. I do this before the actual token transfer because it’s best practice to follow the checks-effects-interactions pattern whenever possible.

Batch payouts

In order for this to be useful, we need to have two parties. On one side the payer and on another side the payee or the company that is providing the service to the end user. This company would have many active subscriptions using our system and it’s usually their responsibility to pull the money every month, that’s why we made the payout function public, so both the payer and payee can call it.

Because the company has many subscriptions they can execute the payout function in batch for 100 subscriptions at a time for example, all the subscriptions that need to be paid that day. With this, they will save a good amount on gas fees.

    function batchPayout(uint256[] memory subscriptionIds) public {
        for(uint256 i; i < subscriptionIds.length; i++) {
            payoutSubscription(subscriptionIds[i]);
        }
    }

All we need is this batchPayout function that accepts a list of subscription ids and just iterates them and calls payoutSubscription on each one.

Here's a link to the complete code on GitHub, I will be adding more smart contracts to this same repo as we go along: https://github.com/KolevDarko/smart-contracts/blob/master/contracts/SubscriberManager.sol

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *