I have been fascinated with the Ethereum ecosystem since the beginning of DeFi. I continue to see cryptocurrency as a solution to several failure points of the internet. One of those is online payments.
Every online startup I’ve ever run has always had a single serious problem, payment gateways are extremely complex to configure. PayPal was the big one when I was first getting started. Their API used to be somewhat simple, but has grown so complex that I now struggle to integrate it. Stripe, on the other hand, requires a ton of tax information to open a store.
So I decided I would just build my own payment processor. I first built it using the Bitcoin network, but opted to migrate to Ethereum thanks to the myriad of tools available.
How do we approach a payment processor? Lets start with the high level view, how should funds flow from customer to vendor.
CUSTOMER
1. Start a transaction
2. Transfer funds to intermediary wallet
INTERMEDIARY
3. Receive funds
4. Forward funds to vendor
VENDOR
5. Receive funds
6. Execute post-transaction code
An intermediary wallet is used to accept customer funds. This makes identifying the completion of a deposit extremely easy; we can simply check the balance of the intermediary.
A transaction has 5 states:
- Pending – Waiting for deposit from user
- Confirming – Waiting for X confirmations on chain
- Forwarding – Transfer from intermediary to vendor
- Notifying – Sending vendor a notification
- Complete
This makes it obvious we should treat each transaction as a Finite-state Machine.
I want this processor to be easy to integrate. For that, we’ll notify vendors of a completed payment via Webhooks. So step #4 will send a POST request to a URL hosted by the vendor with information on the transaction.
To enable vendors to pass extra information, much like PayPal and Stripe APIs allow them to, we’ll add an arbitrary userdata attribute to transactions which pass through to the webhook.
Mapping out our processors endpoints, we’ll have:
http://localhost/buy/<ProductID>?data=<ArbitaryUserData>
http://localhost/checkout/<TransactionId>
The vendors webhook will look something like this:
POST http://localhost/onpurchase
{“product”: “<ProductID>“, “data”: <ArbitraryUserData>}
The buy page will create a new transaction and forward us to the checkout page. The checkout page will track the state of the transaction. Checkout lets the user know where to send funds, how much to send, and what is the state of that transfer.
All of this will work for any cryptocurrency, but by using Ethereum we get the benefit of working with GETH.
The source is quite large, so I only want to point out some key functions. First lets look at the buy API:
This:
- Generates a unique intermediary
- Converts the USD price of the product to WEI
- Generates a new transaction expecting WEI to be deposited into the intermediary
- Redirects the user to the checkout page
I also want to take a look at snippets from the state transitions:
The first few are checking the state of our intermediary wallet’s balance. Once funds have reached the intermediary, and the blocks are confirmed, we fire off a notification to the vendor. Once the vendor confirms they’ve received the notification, we transfer all funds to the vendor’s wallet (minus a transaction fee).
So all we need to do is iterate all open transactions periodically and transition them between states.
This doesn’t handle the complexities of all eCommerce, but it does provide a simple interface vendors could integrate their system with.
There are some trade-offs made in the design:
- Fees are passed on to the vendor, rather than an additional charge to customers.
- The processor does not take any cut, so the system is best self hosted by the vendor.
- Products are stored in a Database, dynamic checkouts are not supported (though could be!)
- Not simple to add ERC20 token support in checkout
- Not easy to add support for L2 chains.
As a thought exercise: if we wanted to add ERC20 support, we would need to avoid the Ethereum transaction costs associated with paying out the vendor. We could use a Web3 wallet like Metamask & automate a Uniswap transaction to convert user funds to ETH before transferring them to our vendor. With this approach, the gateway could accept any Ethereum asset!
Anyway I thought this project was quite fun. I plan several projects around it, so you’ll be seeing more of this in the future.