How to Accept Cryptocurrency on your Website with Coinbase Commerce
This is a hands on technical tutorial on how to accept Cryptocurrency payments for your website, SaaS, or product using Coinbase Commerce.
Coinbase Commerce was just launched to the world yesterday (at time of writing) and I was blown away by it’s utility. We got ours set up in just a few hours.
Unfortunately they released no documentation, code samples, or tutorials. So I figured it all out so you wouldn’t have to!
Coinbase Commerce works a lot like PayPal Checkout in that a user can purchase a product from your site using a 3rd party service without having to ever leave it.
With a single line of code you can accept Bitcoin, Ether, Litecoin, and Bitcoin Cash.
To see how easy it is just click here!
Setting Up Your Coinbase Commerce Account
We are going to do more than just have a clickable link. We want our database to be able to know when someone has purchased via Coinbase. But in any case we need to first sign up.
- Go to https://commerce.coinbase.com/ and signup for a free acount
- You’ll see a dashboard similar to this:
It’s pretty self explanatory, but Coinbase Commerce created a new wallet address for Bitcoin, Bitcoin Cash, Ethereum, and Litecoin on behalf of your company.
Balances show how much money you have made. Payments are the actual transactions that have occurred.
3. Click the Accept Payments button to create a new product that you want to sell. You can Sell a Product or Accept Donations
4. Pick Sell a Product
and then enter a Product Name, Description, Price, Currency, and then upload an image for your new product.
5. Choose what meta data you want to capture from a user.
I personally think that most people will not want to give you their name or email address, and the reality is you may already have that information. For Devslopes we decided to not collect any information.
Note: Do NOT use Full name or Email Address meta data to look up customer information in your database. You cannot validate user input on that form. We will show you how to use Webhooks to sync accounts down below.
6. You can now choose whether to just use a link or embed the link.
We just used the link to open a new browser tab. We tried getting the modal to work like they have on their demo link but after about 15 minutes of fiddling we gave up and just went with the direct link.
That’s it! You can start sharing that link anywhere to start accepting payments in Bitcoin, Litecoin, Ether, and Bitcoin Cash. If you want to truly integrate Coinbase Commerce into your website, continue on to the next section.
Syncing Crypto Purchases With Your Database
Sending out links is super easy, but not overly useful (unless you want to manually send every customer your product — that is if you even have their contact info).
Let’s build out a production-ready solution. We will cover the architecture and some code — but this is not a “how to build your very first API tutorial”.
What we need to do is have a customer purchase our product with Cryptocurrency, then we need to somehow associate that purchase with a product in our database so our app can know that they now own the product.
Since blockchain transactions need to first be validated by consensus nodes on various blockchains (which can take between a few seconds and an hour), there is no asynchronous callback that we can tap into to process the transaction immediately.
We need to design a good technical solution!
Purchase Flow
This is the flow that I designed, which is likely the flow most people will need to implement:
Here is the flow:
- A user chooses a product on our website
- They checkout with Coinbase Commerce
- Coinbase processes the transaction
- When the transaction is completed, our server/API listens for a Coinbase Commerce webhook
- In the webhook we store the transaction. At this point we have no idea who the user is who just spent money
- The user gets sent an email from Coinbase when the transaction completes
- The user enters their payment order number into our website which talks to our server to associate a product to a user.
- All done.
Building It All Out
This section assumes that:
- You have a website or system where a user can select a product and purchase it with Coinbase Commerce
- You have an API running that can persist to a database
- Or that you are using a platform where you can add webhook listeners
In our case at Devslopes:
- Our front end website is build with Angular
- Our backend is a Node server running Express with a MongoDB database
Angular will talk to Coinbase Commerce, which will talk to our API. Then later Angular will make an http request against our API to try and redeem a receipt id.
First you need to add your domain to the Coinbase Commerce whitelist. Then you will need to add one or more webhook urls. You can do this from the Settings panel.
A Webhook is like a listener or observer — it is a public API that waits to be called from another service, in our case — Coinbase.
If these webhooks don’t make sense to you, it means you probably aren’t ready to implement this into your API and server. You will need to write code to make this all work.
Webhooks can be annoying to work with since most of the time you will develop on your computer’s localhost (ie http://localhost:3000/myapi) but services emitting posts to webhooks need a public url like https://devslopes.com/api/my-webhook
So what we did was use a cool free service called webhook.site that could be a temporary webhook handler so we could test it all out.
- Create a webhook on webhook.site
- Add that URL to Coinbase Commerce webhook subscriptions
- Set the events you want to listen for — we just wanted to know when it was a successful transaction
4. Purchase your own product so you can see the result
Currently there is no test mode on Coinbase Commerce — so you have to spend real cryptocurrency to test!!
(Or maybe we are just idiots and can’t find it!)
So I recommend making your product price as low as possible for testing, which is about $1 for Ether.
4. After you make the purchase go to your webhook url.
If it all worked out you would see something like this:
Here is the raw JSON of one of our test purchases:
{
"attempt_number": 1,
"event": {
"created_at": "2018-02-15T08:54:36Z",
"data": {
"id": "9f4d5080-a243-43ac-9a79-7bbe8f57a915",
"name": "Test ETH",
"status": "CONFIRMED",
"pricing": {
"BCH": {
"price": {
"amount": "0.00072595",
"currency": "BCH"
},
"exchange_rate": "1377.505"
},
"BTC": {
"price": {
"amount": "0.00010152",
"currency": "BTC"
},
"exchange_rate": "9850.005"
},
"ETH": {
"price": {
"amount": "0.001058000",
"currency": "ETH"
},
"exchange_rate": "944.805"
},
"LTC": {
"price": {
"amount": "0.00432348",
"currency": "LTC"
},
"exchange_rate": "231.295"
}
},
"product": {
"id": "21cdf642-4971-4b93-b53c-f782527e6440",
"resource": "product",
"resource_path": "/products/21cdf642-4971-4b93-b53c-f782527e6440"
},
"donation": false,
"logo_url": "https://res.cloudinary.com/commerce/image/upload/v1518681078/hokmuzy0zz4fgsrhkscu.png",
"metadata": {
"name": "Jack Black",
"email": "joetest@dev.com"
},
"resource": "charge",
"created_at": "2018-02-15T08:50:05Z",
"expires_at": "2018-02-15T09:05:05Z",
"order_code": "CKZPJMYK",
"description": "Test for ETH",
"local_price": {
"amount": "1.00",
"currency": "USD"
},
"confirmed_at": "2018-02-15T08:54:36Z",
"failure_body": null,
"failure_title": null,
"resource_path": "/charges/9f4d5080-a243-43ac-9a79-7bbe8f57a915",
"failure_reason": null,
"payment_receivers": [
{
"id": "38525552-d189-43ab-b309-f3e0fe9efad1",
"address": "0x44220292a496027e1c627d30e77c97b4c4643bd0",
"currency": "ETH",
"payments": [
{
"id": "624dbda7-a076-4370-bb5e-720416aa2010",
"value": {
"amount": "0.001058000",
"currency": "ETH"
},
"status": "CONFIRMED",
"tx_hash": "0xf34c497ace598117f17b61e200e13cbff799b9f5a723774c64a14f8e9f445210",
"block_height": 5093770,
"confirmations_required": 8,
"confirmations_accumulated": 7
}
]
},
{
"id": "dd2ad0dc-1372-4109-8f27-a0c421c786a4",
"address": "17nennXodpRARswwnCvx4n4ztz5yjeB2TZ",
"currency": "BTC",
"payments": []
},
{
"id": "722e6e31-8f00-4b48-aa5c-ce532f421dfc",
"address": "qznw3wy09u52phgmzhhur4vysw5a9jjkkuqstlvw5g",
"currency": "BCH",
"payments": []
},
{
"id": "e72dfd5a-a362-4053-859e-8e0e64699d51",
"address": "LQ89tHQHEuxxe2vuAyrxNbsacNsAYJrzHa",
"currency": "LTC",
"payments": []
}
],
"shopify_redirect_url": null,
"third_party_provider": null,
"primary_payment_value": {
"amount": "0.001058000",
"currency": "ETH"
},
"local_primary_payment_value": {
"amount": "1.00",
"currency": "USD"
}
},
"id": "75bcb3b5-5509-4853-accc-4784676238b8",
"resource": "event",
"resource_path": "/events/75bcb3b5-5509-4853-accc-4784676238b8",
"type": "charge:confirmed"
},
"id": "b837d104-e40f-46df-9d80-246e2e6d519f",
"resource": "webhook-delivery",
"resource_path": "/webhook-deliveries/b837d104-e40f-46df-9d80-246e2e6d519f",
"scheduled_for": "2018-02-15T08:54:36Z",
"webhook_subscriber": {
"id": "5b8d4f16-1efb-44d8-8b7e-04b1b31fecf4",
"resource": "webhook-subscriber",
"resource_path": "/webhook-subscribers/5b8d4f16-1efb-44d8-8b7e-04b1b31fecf4"
}
}
So when I first saw this, I simply identified which fields were important to us, and wrote them down.
Coding the Webhook
So what we need to do is save every transaction that comes into our webhook into a database. So, later when the customer tries to redeem the product, we can look up the transaction by a key.
I never code any Javascript unless it’s TypeScript. So I made some types for the fields I wanted to capture out of the JSON:
export enum CoinbaseStatus {
confirmed = 'CONFIRMED',
pending = 'NEW'
}
export enum CryptoType {
ETH = 'ETH',
BTC = 'BTC',
BCH = 'BCH',
LTC = 'LTC',
USD = 'USD'
}
export interface CoinbaseProduct {
id: string;
}
export interface CoinbasePurchaseValue {
amount: string;
currency: CryptoType;
}
export interface CoinbasePurchaseEvent {
status: CoinbaseStatus;
product: CoinbaseProduct;
order_code: string;
primary_payment_value: CoinbasePurchaseValue;
local_primary_payment_value: CoinbasePurchaseValue;
}
Next I needed to create the actual webhook. This would be the function that receives the JSON from Coinbase Commerce:
api.post('/coinbase-webhook', (req: Request, res: Response) => {
try {
const data: CoinbasePurchaseEvent = req.body.event.data;
global.db.collection('coinbaseTransactions').save(data);
} catch (err) {
console.log(err);
console.trace();
}
res.status(200).send({status: 200});
});
Some APIs require a get
request and others a post
— since there was no documentation for Coinbase Commerce, I went and looked at the Coinbase Developer API to see if they had docs on webhooks. My hope was it was built by the same developers and in the same fashion.
They said use post
as well as you must always send back a 200
status no matter what — otherwise they will keep attempting to hit your webhook until it succeeds.
I tried it, and it all magically worked out of the box!
So the webhook is done.
Redeeming the Product
The last and most difficult piece is having a customer redeem the product. This is what needed to happen:
- Customer gets an email with the Order Number (
order_code
in the JSON) - Customer then enters that Order Number in a Text Field on our Website
- Website sends the purchase
order_number
andaccount_id
of the customer.account_id
is the id we use at Devslopes to manage customers. Purchases can only be made by logged in users, so we have that available to send to the API - API checks to make sure this code hasn’t already been redeemed
- If not redeemed yet, then the API redeems it, and adds the product to the users account
In the image above the user clicks the buy button which loads Coinbase Commerce in a new tab. When Coinbase is done processing, the user is emailed a receipt from Coinbase Commerce with an Order Number
Users then enter that Order Number in the box, then it’s on to the API.
First we create the endpoint, then grab the data from the website and also we search for the transaction by order_code
api.post('/process-purchase', async (req: Request, res: Response) => {
const receipt: CoinbaseReceiptCheck = req.body; // { order_code, account_id }
const trans: CoinbasePurchaseEvent = await global.db.collection('coinbaseTransactions')
.findOne({order_code: receipt.order_code});
});
The next thing I do is find a product in our products catalog that matches a Coinbase Product Id.
Note: We manually entered Coinbase Product ID’s into our database and associated them with a Devslopes product_id
const cbDevProduct: DevslopesCoinbaseProduct = await global.db.collection('devslopesCoinbaseProductSets')
.findOne({coinbaseProductId: trans.product.id});
if (!trans) { return reject(stError.TRANS_CB_RECEIPT_INVALID); }
if (!cbDevProduct) { return reject(stError.TRANS_CB_PRODUCT_NOT_FOUND); }
If there is no product or if no transaction can be found that matches the order_code
we reject this redemption.
Finally we grab the user’s account and the Devslopes product and add it to the database, then send the newly owned product back to the website so the user can start using it:
const product: Product = await getProduct(cbDevProduct.devslopesProductId);
const account: Account = await getAccount(receipt.accountId);
res.send(onChargeSuccessCoinbase(
account,
product,
receipt.order_code,
PaymentType.Coinbase,
trans.primary_payment_value.amount,
trans.primary_payment_value.currency,
trans.local_primary_payment_value.amount));
That last function onChargeSuccessCoinbase
is a function that takes the purchase data, then adds products to user accounts, sends, an email etc.
The full function looks like this:
api.post('/process-purchase', async (req: Request, res: Response) => {
const receipt: CoinbaseReceiptCheck = req.body; // { order_code, account_id }
const trans: CoinbasePurchaseEvent = await global.db.collection('coinbaseTransactions')
.findOne({order_code: receipt.order_code});
const cbDevProduct: DevslopesCoinbaseProduct = await global.db.collection(DB_COINBASE_PRODUCTS)
.findOne({coinbaseProductId: trans.product.id});
if (!trans) { return reject(stError.TRANS_CB_RECEIPT_INVALID); }
if (!cbDevProduct) { return reject(stError.TRANS_CB_PRODUCT_NOT_FOUND); }
const product: Product = await getProduct(cbDevProduct.devslopesProductId);
const account: Account = await getAccount(receipt.accountId);
res.send(onChargeSuccessCoinbase(
account,
product,
receipt.order_code,
PaymentType.Coinbase,
trans.primary_payment_value.amount,
trans.primary_payment_value.currency,
trans.local_primary_payment_value.amount));
});
Conclusion
The purpose of this article was to point you in the right direction in setting up Cryptocurrency payments on your website — especially since, at time of writing, Coinbase Commerce has no tutorials or documentation on the subject.
It took us about 6 hours to develop this feature (so a relatively small feature). The thing that really sucks is you have to test with real Crypto payments, so launching on production wasn’t as well tested as I wanted. But if there are any issues, I’m sure our customers will reach out 😂
You can see the final result of the purchase flow on our website.
If you want to learn more about Blockchain, Front and Back End web development, iOS, or Android development, head over to www.devslopes.com and enroll in our courses where you’ll learn everything you need to know to develop and release your own product. ❤️