DEV Community

Gunstein Vatnar
Gunstein Vatnar

Posted on • Edited on

Send and receive payments with the stellar cryptocurrency

Background

I made a simple demo to convince my self that cryptocurrency for e-commerce might not be so hard.
Hopefully I can convince others too.

A bit about the demo

The demo is a gallery web app showing some images. Customers can order an image and will receive payment information. Customers send payments to the gallery via the Stellar network. When the payment is received, by the gallery, an image with high resolution will be available to the customer.

I chose Stellar, stellar.org, as cryptocurrency. Stellar provides fast and cheap transactions and good api's. I use their test network. That basically means trading with monopoly money.

Disclaimer: Please be very skeptic about security in my system. It's really just an example of how to send and receive payments with stellar.

ScreenShot

Links

Concept

Server starts up and begin to listen for payments received by the gallery's stellar account. An incoming payment marks an order as paid.

A typical client-server interaction could be:

  1. Client collects thumbnail art from server.
  2. Client makes an order to server and get a memo and downloadkey in response.
  3. Client starts a SSE, Server Sent Event, stream/channel to the server. This stream is used for listening on payments received by the server.
  4. Client makes payment to the gallery account with the received memo.
  5. Client is noticed that server has received payment.
  6. Client queries server for "big image url" and provides both memo and downloadkey. If order is marked as paid, at the server, the client will receive the url.
  7. Client download high resolution image from image server.

The client also has functionality for making a stellar account and fund it with "monopoly money". (The funding part is off course only possible on the stellar test network.)

Below is a drawing of what I tried to describe above.
Concept drawing

The basics:

Client

A react app. Started out with https://create-react-app.dev/.
If you know React it shouldn't be hard. I avoided redux, react-router and axios to keep things simple. Instead I use context for state and simple routing. Fetch is used for communication with backends.

The payment page needs some explanation

Screenshot paymentpage
It's not relevant for an e-commerce site to make an account and automagically fill it with money.
I guess, in most cases customers would pay with a wallet on their phone. So the payment page is a little weird.
If you notice the account balance changes with more than the price of the piece of art, that is caused by the transaction fee.
I've copied most of the "interact with stellar" code from here

This is how easy it is to create and fund an account:

export async function createAccount() {
  const pair = StellarSdk.Keypair.random();
  try {
    const response = await fetch(
      `https://friendbot.stellar.org?addr=${encodeURIComponent(
        pair.publicKey()
      )}`
    );
    const responseJSON = await response.json();
    console.log("SUCCESS! You have a new account :)\n", responseJSON);
    return pair;
  } catch (e) {
    console.error("ERROR!", e);
  }
}
Enter fullscreen mode Exit fullscreen mode

Server

Made with Go.

Main imports and components

Some details

A goroutine handling incoming payments is fired up at startup. It's listening on an SSE stream from the stellar network and receives all payments for the gallery account. I'm using the horizonclient for the payment streaming. If the payment is ok the order will be marked as paid and the memo, which is the orderid, will be broadcasted to all clients. The clients have to decide for them selves if the memo is meant for them. The security is in the downloadkey. Clients will not get the "big file url" without the right combination of downloadkey and memo.

The goroutine listening for payments:

go func(){
  for{
    //Start listening for payments on the shops account
    client := horizonclient.DefaultTestNetClient
    opRequest := horizonclient.OperationRequest{ForAccount: *account_publickey}
    err := client.StreamPayments(context.Background(), opRequest, controllers.CreatePaymentHandler(broadcaster, *account_publickey, client))
    if err != nil {
      fmt.Println(err)
    }
  }
}()
Enter fullscreen mode Exit fullscreen mode

The clients are notified by SSE. Alle clients receive all memos.
Streams to clients are handled by gin:

r.GET("/stream", func(c *gin.Context) {
  ch := make(chan interface{})
  broadcaster.Register(ch)
  defer broadcaster.Unregister(ch)

  c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
  c.Writer.Header().Set("Content-Type", "text/event-stream")
  c.Writer.Header().Set("Cache-Control", "no-cache")
  c.Writer.Header().Set("Connection", "keep-alive")
  c.Writer.Header().Set("Transfer-Encoding", "chunked")
  c.Writer.Flush()
  c.Stream(func(w io.Writer) bool {
    // Stream message to client from message channel
    if msg, ok := <-ch; ok {
      c.SSEvent("message", msg)
      return true
    }
    return false
  })
})
Enter fullscreen mode Exit fullscreen mode

Setting up the demo for "production"

I've used the cheapest Linode-server with ubuntu 20.04, docker and docker-compose.
Traefik, reverse proxy, is handling https and Letsencrypt certificates.
The Dockerfiles are in their respective github repositories. (Links are in the beginning of this article.)
I've already mentioned that the database is PostgreSQL.
Maybe I'll write some more about this setup later.

From here

Maybe I should add an admin page for adding new art? I don't want every visitor to be able to perform this action.
This article describes an interesting authentication concept I consider using.

Top comments (0)