Payments API Integration in Django/Python

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 Videohttps://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);
}