NEMp3 - Using the NEM Blockchain to Buy and Sell Items Without Storing Customer Data

Written by Chris Leary (Telegram: @csleary) In this guide I'll show you how to use the NEM API to interact with the NEM blockchain, allowing you to sell downloads without saving any customer data locally. All sales will instead be logged to the blockchain using NEM's transaction messages, allowing us the ability to retrieve customer transactions at a later date. In this instance, I'll be using the Ruby web app framework, Sinatra, but so long as you can make GET http requests, you can use any language. That's the beauty of using a RESTful API -- it's entirely language agnostic. ## Example App: *NEMp3* ![NEMp3 Title](https://s3.us-east-2.amazonaws.com/nemp3/blog/title.png) Mainnet: https://nemp3.herokuapp.com/ Testnet: https://nemp3-testnet.herokuapp.com/ Source: https://github.com/csleary/nemp3 A barebones music download app. I recommend following along with the testnet NEMp3 or source on GitHub open in another tab. ## The Setup When starting new projects, it's often useful to jot the basics down to create an overview of what we're looking to achieve. For us, it boils down to these core needs and stages: 1. Securely link a customer ID to purchases, written to the blockchain. 2. Check the blockchain for these purchases. 3. Ensure the customer has paid enough. 4. If so, give the customer their download. For the app, I decided to split these up into a three-page user flow: 1. Introduction: Create a unique ID for the customer, display product details, helpful information, etc. 2. Payment: Give the customer all the necessary information to easily and securely make a payment. 3. Download: Check the blockchain for customer transaction(s), and either serve a download or ask the customer to make/top-up payments. Let's take a look at each of these stages in greater detail. ## 1. Introduction To begin, we have our product information, including price, format details (i.e. what the customer is actually buying), as well as details outlining the payment methods. I've also included a FAQ at the end of the page, as a way of making a brief primer on how the site works, how to obtain some XEM, and how much XEM is currently worth (by checking live currency prices). As you'll have noticed, there are no login forms anywhere -- only a single field prompting for the user's email address. As I've hinted at earlier, we don't actually need to store any user information at all locally within the app, so no databases or admin control panels are necessary. All our storage needs for this simple payment portal are met by the NEM blockchain itself. But we do need to be able to identify our customers in order to check for their payments, so we ask for their email address as it's usually unique to each customer. ![Email prompt](https://s3.us-east-2.amazonaws.com/nemp3/blog/email.png) Now we have an email address, but we don't want to simply publish this on the blockchain alongside customer payments, as it's not very private. Instead, we use a process fundamental to blockchains -- we create a *digest* or *hash* of this email address using a cryptographic algorithm (in this case a SHA-256 function). This creates a unique and uniform string of hexadecimal characters that we can use as the customer ID (just as each block in the blockchain has a unique hash ID). The ID hash is linked to each customer's individual email address without compromising their privacy, as each hash cannot be 'decoded', reversed or otherwise resolved to reveal the input string. The only way for anyone to discover someone's email address would be to repeatedly guess, checking for a matching output hash. Possible, but highly unlikely. At least unless someone knew the customer. To make it more difficult still, we can *salt* the email address before it's hashed, by adding (i.e. concatenating) a random string to it. We will use a random string that only we know, stored inside the app as an environment variable, which we fittingly call the *secret*. This way, not even someone who knew that a particular user made a purchase with a specific email address can tell what the output hash would be, as they don't have the 'secret salt'. If you've used hashes at all in the past, you'll know that the slightest change to the input string will vastly alter the output hash. Note: In my app, I've truncated the ID hash down to 32 characters to minimize transaction costs, as of writing messages cost 1 XEM per 32 characters (so the whole fee will be 2 XEM). Transaction fees might change in the near future, rendering these actions unnecessary, in which case I wouldn't bother with the truncation, even if the likelihood of hash collisions (generating the same hash for different input strings) is still vanishingly small. ## 2. Payment ![Payment info](https://s3.us-east-2.amazonaws.com/nemp3/blog/payment.png) So, at this point, we have our customer, who has submitted their email address via the form on the front page. We've taken this address, salted it with our *secret*, and have hashed this combination to create our unique customer ID. The next step will be for our customer to make their payment. We've already put our own NEM payment address into our app, set our price, and have our customer ID hash, so we can present all this to the customer on a separate page/route, so they can easily send their payment to us. How can we do this without burdening our customer with tedious form fields? By using a QR code! The NEM wallet app has a QR code scanner that can decipher all the necessary information needed to make a payment, encoded into the now-familiar series of monochromatic cubes. But we need to ensure we can present a code for each customer, containing their unique ID hash in the transaction 'message' field. As you can see in the app code, this object is a series of key-value pairs, to be used to create the QR code. In my app, the raw code for this object (which I've assigned the variable name `payment_data`) looks like this: ```ruby payment_data = { v: 2, type: 2, data: { addr: settings.payment_address.delete('-'), amount: settings.price * 10**6, msg: @id_hash } } ``` We're most interest in the `data` key here, as it contains our own payment address, the amount to pay, and our unique customer ID hash. I've used a Ruby-specific method to remove the dashes from the payment address, plus and I've converted the price into a form the NEM network understands (my price might be 40 XEM, but given that each XEM is divisible down to a millionth, the network sees this as 40 million units, so I simply multiply my price variable by a million, or ten to the power of six). Incidentally, the `v:` denotes the network version: ‘1’ for the testnet and ‘2’ for mainnet. Our customer can give this a quick scan, double-checking the price, before sending it on its way to us. All without having to type in a credit card number, fill in any more forms, or generally endure a tedious copy-paste finger disco. But what if they're not able to use a phone for scanning and need to make payments? Below the QR code, I've included the same information to allow the customer make payments manually, by copying and pasting my payment address and their ID hash (taking care to remind the user that they need to include this!) into a wallet's payment area (e.g. the NEM Nano wallet). To make things slightly easier, I've used some CSS and text area attributes to make the values easy to select and copy, though you could go further and create a button for each field to copy the text to the clipboard automatically (I'll probably do this in the future). So payment has now been made using all the information we've generated for the customer, and this will all be available on the blockchain, just as soon as the transaction has been confirmed (or *harvested* to use NEM parlance) by one of the nodes on the network. Which leads us to the final and most interesting part of the customer flow: the download! ## 3. Download After our customer has made their payment and had it confirmed by the network, we will be able to search for the transaction on the blockchain. This is where the NEM API gets to show off. As soon as the customer hits the 'Next: Download' button to navigate to the next page/route, our app makes a series of API requests. ### 3.1 Finding a Node In order to be able to search for our payment, we first need to connect to one of NEM's nodes. Any up-to-date node will do, as they all store the same blockchain history, being in consensus with one another. For my app, I selected a handful of node IP addresses for both the mainnet and the testnet, and put these in an array (i.e. a list). In the unlikely event that the app tries to connect to a node that is unresponsive or offline, it will move to the next node in the list and try that one instead. Here is an example of a specific address and API 'endpoint' to which my app makes its first http request: ``` http://108.61.182.27:7890/node/info ``` Assuming we get a successful response (i.e. a '200' status code), we'll be able to parse a message body JSON object containing useful info about the node we're connected to. In my app, I use this to display the node name by following the `identity` and `name` path of the response object, below: ```json { "metaData": { "features": 1, "application": null, "networkId": 104, "version": "0.6.87-BETA", "platform": "Oracle Corporation (1.8.0_25) on Linux" }, "endpoint": { "protocol": "http", "port": 7890, "host": "108.61.182.27" }, "identity": { "name": "Hi, I am Alice5", "public-key": "6ecd181da287c9ccb0075336de36427f25cbc216dc6b1f0e87e35e41a39f63fe" } } ``` Here we can see our node's name is `Hi, I am Alice5`. I find it useful to display the node name when searching for results, though it's not essential by any means. It's mainly just to confirm we have a node ready to chat with us. ### 3.2 Querying the Node for Matching Transactions Now we have our node picked, and we know its name, let's ask it about the customer's payment. For this, we'll need to use a different endpoint, and this time we'll also include our own payment address (i.e. the 'to' address), so that we may see a list of purchases made to us. Here's the endpoint we want: ``` http://108.61.182.27:7890/account/transfers/incoming?address= ``` We add our NEM address at the end of that request (without dashes), and will, in turn, receive a list of transactions made to it. Note, there's a maximum of 25 transactions in each request, so to receive more, we need to repeat the request, adding transaction `hash` and `id` parameters to the end of it, after our address (the node will return the 25 most recent transactions preceding the transaction specified). We can grab these parameters from the last transaction returned from the first request we make, repeatedly passing these from each group of 25 transactions, to get our full transaction history. After a moment, we'll have a list of transactions we can look through for our customer's ID hash. This time we need to parse the list of data we get back, checking each transaction message against our customer's ID hash. To do this, we look up the nested JSON object following the `transaction`, `message`, `data` path to find the transaction message, and we compare this with our customer's ID hash (the message is encoded as an ASCII hex string for url safety, so be sure to decode this *before* you compare it!). If we have a match, we save this transaction to a new list, and continue looking for more of them, as there may be more than one transaction. Note: Multisignature transactions are structured slightly differently to standard payments, so we need to check if the JSON object contains an `otherTrans` key. If we find it, then we know we’re looking at a multisig payment, and parse accordingly. Finally, we present the customer with a list of these matching transactions, totaling up the amounts paid using the `transaction` and `amount` path in our list, subtracting the sum from the price we set. At this point, we take one of three actions (perhaps with each corresponding to a different app page/template). 1. If there's nothing else to pay, then we confirm our transactions with the customer (in my case I add links so the customer can see all the transactions they've made with that email address on the Nembex block explorer), and show them a download button. ![Payment success!](https://s3.us-east-2.amazonaws.com/nemp3/blog/download-paid.png) 2. If, however, the customer's payments don't quite meet the price we've set (perhaps they paid manually, piecemeal), we can direct the customer to another page that displays the transactions we've found, plus a message displaying exactly how much they still need to pay before they'll be able to access the download. ![Partial payment](https://s3.us-east-2.amazonaws.com/nemp3/blog/download-partial-payments.png) 3. Lastly, we might not be able to find any matching transactions at all, in which case we might ask the customer to double-check that their payments have been confirmed by the network, before re-checking the transactions from the node. ![No payments found](https://s3.us-east-2.amazonaws.com/nemp3/blog/download-no-payments.png) ### 3.3 Present a Download We'll assume that our customer has made a few payments, the sum of which more than cover the price we've set. We now need to present them with their download. But how can we do this securely? In my app, I decided to use an Amazon S3 bucket, as we can redirect the user to a secure download using our Amazon AWS credentials stored on our server, so that the customer doesn't see a plain URL (which could very easily be shared). If we like, we can also set the download link to expire after a certain time. To make the download a bit more secure, we can use a hash of the current time as a download link, calculated on the previous 'payment' page, to ensure our customer follows the flow of our app to the download page in that session. The important thing to note is the customer will never lose access to their own downloads, as their purchases have been logged on the blockchain, and can be retrieved at any point in the future -- all they need to remember is the email address they used initially. No accounts, passwords or logins necessary, which is pretty amazing, really. ### Good luck! I hope this guided tour has given you some ideas on how you might be able to use the NEM API to store and retrieve unique data to power your own apps, whether for download stores, payment portals, time-sensitive event registers, etc. I personally found being able to use a blockchain instead of a database quite exciting, and from a customer's perspective, being able to retain complete control of their spending without sharing any sensitive information (and being able to publicly confirm these payments) is confidence-inspiring. No need to worry about data breaches, insecure password/payment info or data mining. If you have any questions or notice any issues with the app, please don't hesitate to let me know.
5 Likes

Really great tutorial! Thank you.

One of the best, I’ve read so far. Thanks for sharing.

Thanks folks. :slight_smile:

I’ve uploaded the images to Imgur as I didn’t anticipate the blog post hotlinking them from my S3 account (which is just for temporary use really). If anyone’s able to swap the image URLs for these, I’d certainly appreciate it, thanks.