Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Bitcoin jQuery Payment Widget

5.00/5 (12 votes)
16 Oct 2017CPOL5 min read 24.5K   391  
Coding jQuery widget to accept Bitcoin payments

Image 1

Introduction

This article describes a jQuery widget for making payments using Bitcoin.

Paying with Bitcoin

So what exactly is needed to be able to pay with Bitcoin?

Unlike the other traditional payment systems, Bitcoin requires each transaction to be presigned by a secret key which is stored in payer's wallet.

This secret key can't be exposed to the public and that's why the only secure way to pay with Bitcoin is to initiate a payment from a payer's wallet using the address and amount provided by merchant.

Payer scans this information from widget in format of QR code. After information was scanned and payment initiated, the payer should notify the widget to start monitoring for transactions.

After widget receives the transaction from Bitcoin network, payment is considered successful.

Auto Update

Bitcoin exchange rate changes very frequently and is currently very unpredictable.

That's why the address and amount of the payment (in format of QR code) should constantly auto update and reflect the most recent price for the service.

Note: address usually changes along with amount because it's a more secure approach.

Widget Requirements

Generalizing all of the requirements, widget should be able to do the following:

  1. Show a payer information about the most recent merchant address and amount in a format of QR code
  2. Autoupdate the QR code every 5 minutes. Notify the payer with a timer when the QR code will be updated at least 1 minute before expiration.
  3. Provide a button for the payer to notify that the payment has been submitted. Start monitoring for a new transaction after button was clicked.
  4. When the transaction has been received, execute necessary callbacks and notify the payer that the payment was successful.

Using this widget

Widget requires jQuery and jquery-qrcode libraries.

Here is the example of usage:

HTML:

HTML
<div class="bitcoin-payment-widget" id="widget">
    <div class="border-cover">
        <div class="header-logo"><img src="~/Content/bitcoin.png" /></div>
        <div class="header-label">Bitcoin Payment</div>
        <div class="qr-image-container">
            <div class="qr-image" data-role="qr-image"></div>
        </div>
        <div class="update-label" data-role="update-label"></div>
        <div class="check-payment-button-container">
            <button class="check-payment-button"
            data-role="check-payment-button"></button>
        </div>
    </div>
</div>

JavaScript:

JavaScript
$(document).ready(function () {
    $("#widget").bitcoinpayment({
        getNewLinkUrl: "/Payment/GetNewLink",
        getPaymentStatusUrl: "/Payment/GetPaymentStatus",
        postPaymentUrl: "/Payment/PostPayment",
        postPaymentCallback: function (result) {
            alert("Payment received at " + result.paidAt + ",
                   transaction id: " + result.transactionId);
        }
    });
})

Pre-requirement for this widget is a REST service which should communicate to Bitcoin (or upperlayer) network and be able to generate new address/amount pairs and check their corresponding transaction status.

Here is the description of the param object:

  • getNewLinkUrl - URL of REST service method returning Bitcoin link and unique paymentRequestId corresponding to it. Bitcoin link should be in URI format (see Bitcoin URI) and should contain address and amount.
  • getPaymentStatusUrl - URL of REST service method accepting paymentRequestId string as a parameter and returning paymentSuccessful boolean as response. This service method should make requests to Bitcoin (or upperlayer) network and check if there are any transactions related to specific paymentRequestId. If true, the method should return paymentSuccessful as true. Optionally, can also return paidAt and transactionId.
  • postPaymentUrl - URL of REST service to postback on successful payment. Should take paymentRequestId as a parameter.
  • postPaymentCallback - Callback to trigger on successful payment.

Below is a possible server side implementation (in C# and ASP.NET):

C#
[HttpPost]
 public ActionResult GetNewLink()
 {
     var paymentInfo = new PaymentInfo()
     {
         Amount = 0.25M,
         Currency = "USD"
     };

     var paymentRequest = BitcoinNetworkService.GenerateNewPaymentRequest(paymentInfo);

     return Content("{ \"link\": \"" +
     paymentRequest.BitcoinUri + "\", \"paymentRequestId\": \"" +
     paymentRequest.PaymentRequestId + "\" }");
 }

 [HttpPost]
 public ActionResult GetPaymentStatus(string paymentRequestId)
 {
     var paymentResult = BitcoinNetworkService.GetPaymentStatus(paymentRequestId);

     return Content("{ \"paymentSuccessful\": " +
     paymentResult.IsSuccessful.ToString().ToLowerInvariant() +
                    ", \"paidAt\": \"" +
                    paymentResult.PaidAt + "\", \"transactionId\": \"" +
                    paymentResult.TransactionId + "\" }");
 }

 [HttpPost]
 public ActionResult PostPayment(string paymentRequestId)
 {
     // Posting payments
     return new EmptyResult();
 }

Demo Project

Demo project implements server logic using Coinbase. To run demo, create a Merchant account in Coinbase and specify credentials (CoinbaseApiKey and CoinBaseApiSecret) in Web.Config file.

Widget Implementation Details

Let's start observing the code with initialization.

JavaScript
function init($container) {
    $qrImage = $container.find("[data-role='qr-image']");
    $checkPaymentButton = $container.find("[data-role='check-payment-button']");
    $updateLabel = $container.find("[data-role='update-label']");

    $checkPaymentButton.text("Check Payment");
    $updateLabel.text("Pay QR in your wallet");

    $checkPaymentButton.click(function () {
        if (isMonitoringPayments) {
            $updateLabel.text("Pay QR in your wallet");
            $checkPaymentButton.text("Check Payment");
            stopMonitoringPayments();
            startAutoUpdater();
        }
        else {
            startMonitoringPayments();
            stopAutoUpdater();
            $checkPaymentButton.text("Cancel");
            $updateLabel.text("Checking payment...");
        }
    })

    startAutoUpdater();
}

In initialization, we should do a few things: initialize variables, start auto updater and hook logic of the "Check Payment" button depending on its current state (which can be either "Check Payment" or "Cancel Checking Payment").

First, the parameters $qrImage, $checkPaymentButton and $updateLabel are initialized with HTML elements having specific data-role attributes. Using these attributes allows to make HTML robust and use different markup for the elements if necessary

Then $checkPaymentButton is assigned a click event which has two conditions. If this button was clicked when widget was not monitoring payments, it should mean that the user clicked Check Payment button and widget should start monitoring payments and stop auto updating of the QR code. Otherwise, it means that user clicked Cancel button and in this case as opposed to the previous behavior, widget should interrupt monitoring payments and continue auto update of the QR code. $updateLabel and $checkPaymentButton are updated accordingly.

In the end of the method, startAutoUpdater is executed to start auto updater and perform the initial update of the QR code.

Now let's look at the main functions in more detail:

JavaScript
function checkForUpdate() {
    var minimumNotificationTime = 60000;
    var currentTime = Date.now();
    if (!timeToNextUpdate || timeToNextUpdate.getTime() < currentTime) {
        timeToNextUpdate = new Date(currentTime + updateIntervalInMinutes * 60000)
        $qrImage.empty();
        $.post(options.getNewLinkUrl)
        .done(function (r) {
            var result = JSON.parse(r);
            console.log("Received link: " + result.link);
            paymentRequestId = result.paymentRequestId;
            $qrImage.empty();
            $qrImage.qrcode({
                text: result.link,
                width: $qrImage.width(),
                height: $qrImage.height()
            });
            $updateLabel.text("Pay QR in your wallet");
        })
        .fail(function () {
            console.error("Service " + options.getNewLinkUrl + " is not accessible.");
        });
    }
    else if (timeToNextUpdate.getTime() - currentTime <= minimumNotificationTime) {
        $updateLabel.text("Time before update: " +
                           fmtMMSS(timeToNextUpdate.getTime() - currentTime));
    }
}

This function is executed in scope of the auto updater loop. Its main responsibility is to update the QR code with most recent amount and payment address.

timeToNextUpdate is a time generated each time when QR is generated. It is assigned in this method using a sum of currentTime and updateIntervalInMinutes multiplied by 60000 since all these values are in milliseconds. When timeToNextUpdate is not set (initial state) or less than currentTime, it means that the time has come and QR code needs to be updated. On this update, widget does the POST request to getNewLinkUrl provided in options which must return a JSON string with paymentRequestId and link in the Bitcoin URI format containing information about payment and merchant address. QR code is updated with this link using the qrcode function which is a part of the jquery.qrcode library (see GitHub). minimumNotificationTime variable stores the time when user needs to be notified about future expiring of the QR code. When that time has come, the user will see a notification in $updateLabel that the QR code will expire soon.

JavaScript
function checkPayment() {
    $.post(options.getPaymentStatusUrl,
        {
            paymentRequestId: paymentRequestId
        })
    .done(function (r) {
        var result = JSON.parse(r);
        if (result.paymentSuccessful) {
            console.log("Payment received")
            stopMonitoringPayments();
            stopAutoUpdater();

            $updateLabel.text("Payment Succeeded!");
            $checkPaymentButton.attr("disabled", "disabled")

            $.post(options.postPaymentUrl, {
                paymentRequestId: paymentRequestId
            })
            .done(function () {
                if (options.postPaymentCallback) {
                    options.postPaymentCallback(result);
                }
            });
        }
    }).fail(function () {
        console.error("Service " + options.getPaymentStatusUrl + " is not accessible.");
    });
}

The main purpose of this function is to check if payment has been received by the server.

System does request with paymentRequestId to getPaymentStatusUrl and receives JSON response with the payment status. When payment is successful, the system stops the processes and invokes callbacks. First system stops monitoring and auto updating. $updateLabel is updated to notify the payer about the successful transaction. Then $checkPaymentButton is disabled to prevent any further actions by the user since all operations on widgets were stopped. In the end, postback is done on postPaymentUrl and postPaymentCallback to notify the server and client correspondingly about the successful result.

Points of Interest

Edge cases when user has paid more or less than the required amount are not covered in this widget. However, they can be added easily assuming the appropriate functionality is implemented on server side.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)