In this tutorial, We will create an online shop that will accept Bitcoin payments using Blockonomics Payment API. Let’s get started!
Resources
Source Code | https://github.com/blockonomics/Django-Image-Store |
Youtube Video | https://www.youtube.com/watch?v=xGeD1UUXpMk |
Tech Stack
- Django/Python
- HTML/CSS
- Javascript
Setting Up
You can set up the local version of this tutorial using instructions provided in the README of the GitHub Repository
Creating a New Store
Navigate to your merchant page on the Blockonomics website and click on Add a new store
which will ask you to add the Xpub of your account and optionally you can also provide a tag for your convenience. For example, you can place a tag Django Payments API Demo
. Lastly, you need to provide the HTTP Callback URL.
The HTTP Callback URL
To use the Bitcoin payments API, you need to set up the HTTP Callback URL for your store. Every time a transaction (that was carried out on the address associated with that store) changes its status, Blockonomics will use the HTTP Callback URL provided to notify you about the status update. Also, new transaction notifications are also provided using the same HTTP Callback URL.
The Logic
In order to integrate Blockonomics payment API, you should know how the process works. Firstly, you need the Blockonomics account and your API Key. Next, you should use a unique Bitcoin address every time you do a transaction. You can request a new address using the New Address API. Once the buyer uses that Bitcoin address to transfer the amount, you will get the status update from Blockonomics on the HTTP Callback URL provided by you. Thus, your backend logic must process the requests received at that endpoint to classify the transactions.
Payments App
I have created a core Django application called payments
which is responsible for everything.
Understanding Models
To store the information about the transactions, I created an Invoice
table. The table looks like this
class Invoice(models.Model):
STATUS_CHOICES = ((-1,"Not Started"),(0,'Unconfirmed'),
(1,"Partially Confirmed"), (2,"Confirmed"))
product = models.ForeignKey("Product", on_delete=models.CASCADE)
status = models.IntegerField(choices=STATUS_CHOICES, default=-1)
order_id = models.CharField(max_length=250)
address = models.CharField(max_length=250, blank=True, null=True)
btcvalue = models.IntegerField(blank=True, null=True)
received = models.IntegerField(blank=True, null=True)
txid = models.CharField(max_length=250, blank=True, null=True)
rbf = models.IntegerField(blank=True, null=True)
created_at = models.DateField(auto_now=True)
The Invoice
table stores the product
for which the invoice is created, the address
is the Bitcoin address used for this transaction. The btcvalue
is the amount that you have charged from the buyer and received
is the amount you receive from the buyer. Both these values will be in satoshi. The most important field is status
which describes the current status of this transaction.
Creating Payment Invoice
Once the user clicks on the price button, we process the request inside create_payment
view. The job here is to create a new object in the Invoice
table and then redirect the request to the track_invoice
view.
def exchanged_rate(amount):
url = "https://www.blockonomics.co/api/price?currency=USD"
r = requests.get(url)
response = r.json()
return amount/response['price']
def create_payment(request, pk):
product_id = pk
product = Product.objects.get(id=product_id)
url = 'https://www.blockonomics.co/api/new_address'
headers = {'Authorization': "Bearer " + settings.API_KEY}
r = requests.post(url, headers=headers)
print(r.json())
if r.status_code == 200:
address = r.json()['address']
bits = exchanged_rate(product.price)
order_id = uuid.uuid1()
invoice = Invoice.objects.create(order_id=order_id,
address=address,btcvalue=bits*1e8, product=product)
return HttpResponseRedirect(reverse('payments:track_payment', kwargs={'pk':invoice.id}))
else:
print(r.status_code, r.text)
return HttpResponse("Some Error, Try Again!")
HTTP Callback URL Endpoint
The receive_payment
view is the endpoint for receiving status updates from Blockonomics. It is used to sync our Invoice
table in the database with recent transactions and their status updates.
def receive_payment(request):
if (request.method != 'GET'):
return
txid = request.GET.get('txid')
value = request.GET.get('value')
status = request.GET.get('status')
addr = request.GET.get('addr')
invoice = Invoice.objects.get(address = addr)
invoice.status = int(status)
if (int(status) == 2):
invoice.received = value
invoice.txid = txid
invoice.save()
return HttpResponse(200)
Tracking Payment Invoice
You can track any invoice if you know the invoice ID. The track_invoice
view fetches the latest data of that invoice ID from our database and passes it to the frontend. It also passes whether the user has paid the required amount, if yes then the paid
variable is also passed to the frontend. At this point, you can add your business logic.
def track_invoice(request, pk):
invoice_id = pk
invoice = Invoice.objects.get(id=invoice_id)
data = {
'order_id':invoice.order_id,
'bits':invoice.btcvalue/1e8,
'value':invoice.product.price,
'addr': invoice.address,
'status':Invoice.STATUS_CHOICES[invoice.status+1][1],
'invoice_status': invoice.status,
}
if (invoice.received):
data['paid'] = invoice.received/1e8
if (int(invoice.btcvalue) <= int(invoice.received)):
data['path'] = invoice.product.product_image.url
else:
data['paid'] = 0
return render(request,'invoice.html',context=data)
Invoice Page Front End
In the frontend, we have displayed all the data we get from track_invoice
view. But, when the transaction status gets updated, how will the buyer know about it?
To address this issue, we can either continuously pool our database to get the recent status of this invoice or we can use the WebSocket provided by Blockonomics. It is HIGHLY recommended to use WebSocket as continuous polling may have serious effects on system performance. Blockonomics pushes the status update through websocket as well, so your frontend will instantaneously know the changes, so you know that your page is now outdated. At this point, you can either reload the page to get recent data from track_invoice
view or use AJAX calls to do the same. It is recommended to use a time out of 1 second, because receive_payment
view will take some time to update the database with details and once you wait for a while, you are assured that your database is updated and thus you can perform the action.
var socket = new WebSocket("wss://www.blockonomics.co/payment/"+ address);
socket.onmessage = function(event){
response = JSON.parse(event.data);
//This condition ensures that we reload only when we get a
//new payment status and don't go into a loop
if (parseInt(response.status) > parseInt(status))
setTimeout(function(){window.location.reload() }, 1000);
}