Building a simple store in less than 300LoC with CashPayServer

42 17

One of the key difficulties in building a service on top of BCH is receiving payments and identifying what that particular payment was actually for.

The problem with the traditional method of displaying a standard BCH address is that there's no additional information attached to that address. When a user sends a transaction to that address, how are we meant to know what the user has actually purchased? We don't have a Reference Number or an Invoice ID - just an Address.

The conventional solution to this is to generate a new address each time an Invoice is requested and, in our backend, "attach" the order information to that new address. Although this can work (if done correctly), it does add overhead and makes it difficult to implement services where you would like the funds to land directly at a particular address (without acting as a temporary custodian of the funds and forwarding to the target address).

This is where protocols like BIP70 become extremely useful. One of the key advantages of protocols like BIP70 is that we end up with the ability to attach arbitrary data to a payment (or technically, to the 'payment request'). This means we can attach that Reference Number or Invoice ID - and a lot of other data too if we wanted.

However, BIP70 has problems too in that it is difficult to implement. Adding to this, there's JSONPaymentProtocol which uses the same bitcoincash:?r=pathToSomeInvoiceID URL scheme, so some wallets may request a BIP70 invoice, while others may request a JSONPaymentProtocol invoice. This means that, if we don't support both, some wallets will fail as the Payment Request URL does not contain any information stating which protocol the service supports and which the wallet should use. Depending on the wallet, it will do one or the other.

Supporting both protocols is one of my goals with CashPayServer. I'm also trying simplify some of the other more difficult aspects of accepting payments. For example, giving the developer event hooks (broadcasted, confirmed, etc) and making it convenient to give the user quick feedback once the transaction has been broadcast to the network by providing a default UI that can be integrated on-site.

The default CashPay container will give notification once a payment has been made.

In this respect, my intention with CashPayServer is to allow developers to provide a pleasant User Experience with very little code - but also keep it extensible (and secure) enough to permit more advanced use-cases.

Integration

I'd like to demonstrate what a simple CashPayServer integration might look like, from a coding perspective. The following was built in under 300 lines of code, in total (frontend, backend - and comments inclusive). It is not intended to look pretty, but to demonstrate how an actual CashPayServer integration might be done (from a coding perspective).

Live Demo: https://store.example.developers.cash/

I'm not going to walk-through all of the frontend code. This isn't as relevant as most would integrate with an existing service (or a new service being built) and this is just intended as a simple example for reference. For any that are interested, it's been built with Vue's Qusar framework (and is only about 180LoC in total).

To give a brief, we have the following endpoints:

  • Backend (Server-Side) (Using Node/ExpressJS)

    • /request-invoice [POST]
      This handles the creation of the invoice. The CashPay JS library supports doing generating invoices from the browser also, but to follow best-practices, I'm going to generate this server-side as, for most implementations, this is how it should be done.

    • /webhook [POST]
      This handles the broadcasted and confirmed webhooks. These will be triggered when an Invoice has been paid (broadcasted) and when that transaction has been included in a block (confirmed).

      The full backend code can be viewed here (88 lines of code)
      https://pastebin.com/kw5CiuDy

  • Frontend (Browser)

Frontend (Browser)

The first thing we do is include the CashPay JS library from a CDN. This could also be included with NPM as a module - but to keep this simple, let's just use this for now.

<script src="https://cdn.jsdelivr.net/npm/@developers.cash/cash-pay-server-js/dist/cashpay.min.js"></script>

A few lines above this, we're also including animate.css. The reason for this is that the default container will use animate.css classes to make things look a bit more visually appealing. However, this is entirely optional.

Now let's jump to the piece of the frontend code that triggers once the user is ready to make a payment.

async onPay() {
  // ... UI Code here

  // Request invoice from server endpoint
  let invoice = await CashPay.Invoice.fromServerEndpoint('./request-invoice', {
    cart: this.cart,
    customer: this.customer
  })
  
  // Hook into the broadcasted event
  invoice.on('broadcasted', e => {
    this.$q.notify(`Payment has been received!`)
    
    // Wait five seconds and then clear cart and show the catalog page
    setTimeout(() => {
      this.reset()
    }, 5000)
  })
  
  // And then load it into our container on page
  await invoice.create(document.getElementById('invoice-container'))
},

What is happening here is that an invoice is being read back from our server-side /request-invoice endpoint. We are then setting an event listener for the broadcasted event so that we can make additional changes to the front-end state (empty the cart, change page, etc). We then call create(...) and pass it the container we want the QR code, etc, to appear in. Passing a container is optional - if this is left blank, we can still hook into the Events (created, requested, broadcasting, broadcasted and failed) and render everything manually - or use reactive properties if we are using something like a SPA framework. But we want this example to be simple and convenient, so we're going with the container.

Server-side

We need to do a little bit of setting up when we first initialize the server-side code.

const CashPay = require('@developers.cash/cash-pay-server-js')
const Catalog = require('./public/catalog.json')

// ...

// We need to tell our Webhook to trust any requests signed with pay.infra.cash keys
let webhook = new CashPay.Webhook()
await webhook.addTrusted('https://v1.pay.infra.cash')

// Let's store a list of Orders as InvoiceID:Status pairs. Usually these would be stored in a database.
let orders = {}

We include CashPayJS and our catalog of items (stored in a JSON). We then setup CashPay's Webhook and add the https://pay.infra.cash Server as "Trusted". This is necessary so that we can reject any spoofed Webhook requests (i.e. someone manually sending a POST request to our endpoint with fake data).

We also create an orders InvoiceIDs:Status keypair so that we can make sure that the invoices received at our Webhook endpoint were actually created by us. In a typical implementation, these InvoiceID's would probably be stored against an orders table in the Database backend with a status (e.g. PENDING, BROADCASTING, BROADCASTED, CONFIRMED) and other details (items, total, etc). However, we're trying to keep this example simple. In the database case, the same general principles would apply, but as the order and the totals would already exist, you would probably just store the relevant orderId in the privateData and then pull the other details from your database.

We then define a route for the /request-invoice endpoint that we leverage in our frontend code.

app.post('/request-invoice', async (req, res) => {
  // Don't trust the browser to send correct prices and item details. Use the item id and read from the catalog directly.
  let items = req.body.cart.map(cartItem =>
    catalog.find((catalogItem) => cartItem.id === catalogItem.id)
  )
  
  // Calculate the total amount due
  let total = items.reduce((total, item) => total += item.price, 0)
  
  // Set the parameters of the invoice
  let invoice = new CashPay.Invoice()
  invoice.addAddress('bitcoincash:qplf0j8krjrsv95v0t3zj9dc4rcutw5khyy8dc80fu', `${total}USD`)
    .setWebhook(['broadcasting', 'broadcasted', 'confirmed'], 'https://store.example.developers.cash/webhook')
    .setPrivateData({
      customer: req.body.customer, // Customer details
      items: items
    })
  
  // Actually create the invoice
  await invoice.create()
  
  // Create an entry for this InvoiceID in our list of orders
  orders[invoice.id] = 'pending'
  
  // Return the invoice (this method will omit sensitive data (unless 'true' is passed as a param)
  return res.send(invoice.payload())
})

One cool feature here is that we can pass Fiat amounts which will be automatically converted to BCH equivalents by CashPayServer (based on Coinbase exchange rates). You could also add multiple addresses/amounts which is useful for services that wish to take commission (e.g. add 10% onto the amount due and pay it to a different address).

If we run this as is, we'd now have a QR Code rendered on our page. However, if we tried to pay, it would actually fail as there is no route to the Webhook.

Let's setup the Webhook endpoint:

app.post('/webhook', async (req, res) => {
  try {
    // An exception will be thrown if signature does not match
    await webhook.verifySignature(req.body, req.headers)
    
    // The type of event (e.g. broadcasting, broadcasted or confirmed)
    let eventType = req.body.event
    let invoice = req.body.invoice
    
    // Make sure this invoice was created by us
    if (typeof orders[invoice.id] === 'undefined') {
      throw new Error(`${eventType}: Invoice ${invoice.id} does not exist in our orders.`)
    }
    
    // Mark the invoice as broadcasting/broadcasted/confirmed
    orders[invoice.id] = eventType
    
    // Get the data associated with the invoice
    let data = JSON.parse(invoice.options.privateData)
    
    // Output event type and list of items to console
    console.log(`${data.customer.email} ${eventType}:`)
    console.log(`  ${data.customer.comments}`)
    data.items.forEach(item => { console.log(`  ${item.id}: ${item.title}`) })
    
    // You must send a 200 response, otherwise CashPay will throw an error
    res.status(200).send('Success')
  } catch(err) {
    console.log(err)
    res.status(500).send(err.message)
  }
})

This isn't doing much. It's simply verifying that the Webhook payload actually came from the Trusted Server we added earlier (https://pay.infra.cash), marking the invoice as broadcasting, broadcasted or confirmed and then outputting the items that the user purchased to the console. A better example might do something like send an email to both the user and the merchant when payment is broadcasted and/or confirmed, but I do not have an SMTP handy.

But, that's pretty much what would be required to integrate, in a nutshell. There are some other edge-cases that still need to be fleshed out (what if your server goes down?), but that's something I'll look at covering in future as there are ways around that for now.

Summary

I'm probably still a month away from having a version of this that's safe for Public Use. Most of the schema I'm happy with, but would gladly take some developer feedback, particularly from those who could make use of a service such as this (e.g. what features would you need, what is currently not supported, improvements to the JS interface, etc).

Feel free to contact me if any suggestions:

Telegram: https://t.me/jimtendo

I will add quickly (in case someone has not seen my other posts) that this platform will be self-hostable (once stable, I'll create a Docker Container for easy deployment). There is no need to trust my https://pay.infra.cash instance - this is simply being offered as a convenience for those who wish to get up and running quickly.

Additionally, I will have some more formal documentation on the API's supported, capabilities for customization, etc, within a few weeks.

EDIT: Updated API in examples (2020-06-03)

2
$ 0.00

Comments

Tested it out and it worked smoothly!

$ 25.00
4 years ago

Thanks for trying it out (and thanks for the $0.25 - always exciting when it rolls in). I want this instance (https://pay.infra.cash) to always be freely accessible. However, depending on how I go with DoS attacks, etc, I may offer an instance at a different address that provides some kind of SLA to larger merchants for a fee. In terms of a "BCH Shop Backend", I am considering building something like this, but I have some other projects/infra I'd like to prioritize first.

$ 1.05
4 years ago

https://pay.infra.cash

what is this?

I have some other projects/infra I'd like to prioritize first

whatchudoin these days? do tell

$ 0.00
4 years ago

Next project is to work with Jonathan Silverblood's CashID concept:

https://www.cashid.info/

I want to create a similar self-hostable, easy-to-integrate, service for this. Hopefully wallet-support of it will then follow.

In short though, most projects I have at the moment are with the intention of providing useful BitcoinCash infrastructure so that dev's can create things quickly (but still mitigate the trust issue by self-hosting).

$ 0.10
4 years ago

useful BitcoinCash infrastructure so that dev's can create things quickly (but still mitigate the trust issue by self-hosting)

that's what I'm all about at the moment myself

I have my own projects (eg. https://nitojs.org), but more importantly is getting devs that tools they need to BU!LD BCH!

when I joined the community just a few months ago, it was super tough to get answers to even the most basic questions .. I'm NOT on Telegram (which is where everyone keeps pointing me), but I don't think that's a good knowledge base anyway; this info needs to be indexable via search engines like Google and DDG ..

I made an attempt to get BCH onto Stack Exchange, but then they canceled my initiative (without "good" reason); so now I'm working on https://devops.cash to try and offer a portal for developers as a kind of one-stop-shop .. Ethereum devs are the ones that I'm targeting, so I'm doing things like offering auth via Metamask and will soon offer micro-exchange from ETH to BCH..

everyone's been all about the IFP, but I'm trying to get devs to build on TOP of the infra, after all, what good is infra without useful services that offer real value???

$ 0.00
4 years ago

I'm trying to get devs to build on TOP of the infra, after all, what good is infra without useful services that offer real value??? Agree with you completely there.

DevOps.cash look really really nice!

I'm working on a simple index of resources at https://developers.cash to put libraries, self-hosted infra code and things of that sort. This is still very buggy and not ready yet though.

$ 0.00
4 years ago

i ❤️ what ur doing with developers.cash .. i plan on "slowly" building out devops.cash, but it's just not a priority atm .. if you have any thoughts, feedback, etc I'm all open to contributions, and I will do the same for your efforts 😉

$ 0.00
4 years ago

clicked the wrong button. Would you forward my tip to the author please.

$ 1.00
4 years ago

Done.

$ 0.00
4 years ago

clicked the wrong button. Would you forward my tip to the author please.

thanks for supporting us devs working to add value sans IFP 🙏

$ 0.00
4 years ago

Of course. I was not enthusiastic about the IFP and I don't think it would have been terrible either. Amaury is doing a great job and we need to better fund devs.

Could you take this code and make it a plugin for Thunderbird to support pay for email not being spam? Doesn't require any non-bitcoin casher person to download. If user receives an email with no BCH paid, then an email is automatically sent to the sender. If their client doesn't have it, then they get a BIP-70 QR code to pay with the hash of the email then put in OP_RETURN.

$ 0.10
4 years ago

Could you take this code and make it a plugin for Thunderbird to support pay for email not being spam?

fyi, I'm not a contributor of this codebase, just wanted to show my appreciation for your contributions..

otherwise, absolutely!! @jimtendo payment server is an ideal UI/UX for handling this process manually (as I don't see this being an automated process, at least not anytime soon)..

there is/was a service that did this, but it was web-based .. however, I really like the idea you present of a plugin, as Thunderbird happens to be my default Macbook mail client..

Sooo, if you receive an email that's NOT in your contact list / addr book, then it would auto-reply with a payment request for $X.xx in BCH to reach your inbox .. ❤️ it!! but I would definitely need this for K-9 Mail as well, for my Android devices.

a bounty would probably be the best way to facilitate this development

$ 0.00
4 years ago

a bounty would probably be the best way to facilitate this development

I started something like this last year... https://bounty.cash.

It's actually what provoked me to build CashPayServer (I just didn't have time at that point). I'll be rebuilding this site and making it more 'proper'. If you have any suggestions on how a Bounty site should work, let me know... My 'get running quick' plan is to:

  1. Integrate CashPayServer on it
  2. Allow CashID to login
$ 0.25
4 years ago

I started something like this last year... https://bounty.cash.

oh wow! I'm surprised I've never seen this before, looks very well put together..

If you have any suggestions on how a Bounty site should work, let me know

absolutely! my approach is usually to just DO IT, and let things kinda work themselves out "organically"; so now if I have a bounty, I know exactly where to go.. 👍

$ 0.00
4 years ago

I think this would require quite a few dependencies (and I don't know much about Thunderbird). I think I kind of follow your idea though:

  1. User sends you email that may or may not be spam.
  2. Email Client assumes that it is spam by default.
  3. Email Client sends an auto-reply with QR code telling sender that if they want to NOT mark it as spam, they must send X amount to this QR code.
  4. Email Client determines whether paid (and is not spam) based on Email Contents Hash (or Invoice ID - or some other thing).

I hope we see services like this. I do have an idea for something similar, but this is based more-so on the idea of a Social Network on-top of BCH - and is not even close to the PoC stage yet.

$ 0.10
4 years ago

I hope we see services like this.

eventually, this could go beyond email to any messaging service that support bots .. eg. I know this could be done in Slack, probably Telegram too .. the only issue is that people have to acquire BCH to facilitate the micro-payment, and that is still easier said than done (smh)

$ 0.00
4 years ago

Nice article man. It quite long. But i mange to reead it. Lol. Need to statt again coz it is quite technical for me

$ 0.00
4 years ago

It's a dull read from that perspective. Might be a bit easier to understand the flow quickly with a Sequence Diagram.

https://gateway.pinata.cloud/ipfs/QmcL2fj2qbQa8NX8fcpjo7AwLijh1heEGmUXT4XUMVeUxT

$ 0.00
4 years ago

Ok thnks man . I relay apreciste your understanding will sure read it. In no time . Thak you

$ 0.00
4 years ago

CashPayServer is a laudable project and will boost the popularity and mass adoption of BCH. The most important thing is to support the two protocols. That will boost the project acceptability.

$ 0.00
4 years ago

Nice article man. It quite long. But i mange to reead it. Lol. Need to statt again coz it is quite technical for me

$ 0.00
4 years ago

Incidentally, this is exactly the type of movie streaming platform that would make everyone happy. A single platform hosting every movie ever made that allows studios to set their own prices that also has no requirement for user accounts/signups. Instead they want us to set up a dozen accounts on a dozen different streaming services and charge our credit cards every month even if we don't use their service. It's shit like this that makes me wonder how on earth crypto hasn't taken off yet.

$ 0.00
4 years ago

Nice article man. It quite long. But i mange to reead it. Lol. Need to statt again coz it is quite technical for me

$ 0.00
4 years ago

I just got "The Godfather" gekauft😉. It worked great. The user experience is really good. Keep going. I hope your CashPay gets wide use.

$ 0.00
4 years ago

great work

$ 0.00
4 years ago

this is really good shit man 👏👍 this makes merchant services on par with BitPay, BUT self-hosted, which if freakin' sweeett

I can't think of anyway that I would use this "personally", but I'm all for supporting the cause; just let me know how I can help with any testing..

$ 0.00
4 years ago

Not quite on par - BitPay can potentially convert to actual fiat, this will just convert to the BCH equivalent if a fiat value is given.

I have no intention on supporting conversion to fiat as this would mean I have to act as a custodian of funds to send from BCH address to Merchants Bank Account (which places me in a problematic situation legally). As it is setup currently, the BCH is sent direct to the target address(es).

That said, I could see conversion to fiat occurring if:

  1. Exchanges started adding support for an "InstantConvert" address. I.e. I set the output address to the "InstantConvert" address of my account in an exchange, which (as soon as possible) sells the BCH received for its Fiat equivalent (I'd really like to see Exchanges support a feature like this).
  2. We use stable-coins. This is something I'm actually investigating as I might be able to support USDC (USD Stable Coin) without acting as a custodian using SideShift.ai. For anyone interested, we could define the "final" outputs before the PaymentReq response by retrieving a Conversion Order from SideShift. No plans on doing this until after V1 though.
$ 0.10
4 years ago

Not quite on par - BitPay can potentially convert to actual fiat, this will just convert to the BCH equivalent if a fiat value is given.

oh yeah ur right, automatic fiat conversion is HUUGGGEEEE for merchants..

We use stable-coins. This is something I'm actually investigating

imo, BCH desperately needs a "transparent" stablecoin (and USDH just doesn't cut it), so i'm "investigating" as well .. at the moment, I've got a cross between https://makerdao.com/en/ and https://reserve.org/

$ 0.00
4 years ago

Wow, really, thank you for sharing this.

$ 0.00
4 years ago

Good article... Though it is a large one but i read and took some point as lessons.. Thanks for this article

$ 0.00
4 years ago

great information bro, if you can visit my last post you are welcome, im new here ... take a good day bro!.

$ 0.00
4 years ago

Nice writing. I will try to follow these. It will help me in future. Thanks for sharing. Good luck.

$ 0.00
4 years ago

Well. A great article. Thank you for sharing the things. It will help me to go furthermore. I will try to follow those.

$ 0.00
4 years ago

Just tested it. It's slick, easy and fast. Great job!

$ 0.00
4 years ago

I love ur article dude

$ 0.00
4 years ago

Hello there! Thanks for the detailed article! I am trying this out and running into some issues. Let me know if we can connect sometime!

$ 0.00
4 years ago

Actually nevermind, I got it to work. It's very straightforward. Can I run this through some sort of a test network without using actual BCH?

$ 0.00
4 years ago

Hi @potta, very sorry, there will actually be some changes incoming to this (project was unfinished and I took a break from it for a few weeks). Are you able to message me on Telegram? Will work on getting this 'finally' done/stable within the next few days. https://t.me/jimtendo

$ 0.00
4 years ago

Thanks for trying it out (and thanks for the $0.25 - always exciting when it rolls in). I want this instance (https://pay.infra.cash) to always be freely accessible. However, depending on how I go with DoS attacks, etc, I may offer an instance at a different address that provides some kind of SLA to larger merchants for a fee.

$ 0.00
4 years ago

CashPayServer is an exceptional tool for e-commerce creation. It is a great tool for promoting BCH for mass adoption. Thanks.

$ 0.00
4 years ago

The invention of CashPayServer is a blessing to the BCH ecosystem. Integrating CashPayServer to e-commerce websites will boost the mass adoption of BCH worldwide. Thanks.

$ 0.00
4 years ago