Skip to content

Commit

Permalink
Allow specifying amounts in fiat currencies and auto-convert based on…
Browse files Browse the repository at this point in the history
… the current exchange rates. Closes #1.
  • Loading branch information
shesek committed Nov 19, 2017
1 parent ce15f39 commit 4f4b1d5
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 12 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ $ npm start

All endpoints accept and return data in JSON format.

Invoices have the following properties: `id`, `msatoshi`, `rhash`, `peerid`, `payreq`, `created_at`, `completed`, `completed_at` and `metadata`.
Invoices have the following properties: `id`, `msatoshi`, `quoted_currency`, `quoted_amount`, `peerid`, `rhash`, `payreq`, `created_at`, `completed`, `completed_at` and `metadata`.

### `POST /invoice`

Create a new invoice.

*Body parameters*: `msatoshi` and `metadata` (arbitrary order-related metadata, *optional*).
*Body parameters*: `msatoshi`, `currency`, `amount` and `metadata`.

You can either specify the amount as `msatoshi` (1 msatoshi = 0.001 satoshis),
or provide a `currency` and `amount` to be converted according to the current exchange rates.
You should not specify both.
If a currency and amount were provided, they'll be available under `quoted_{currency|amount}`.

You can optionally specify `metadata` with arbitrary order-related meta-data.

Returns `201 Created` and the invoice on success.

Expand Down Expand Up @@ -69,28 +76,31 @@ Returns live invoice payment updates as a [server-sent events](https://developer
## Examples

```bash
# create new invoice
# Create new invoice
$ curl http://localhost:8009/invoice -X POST -d msatoshi=5000 -d metadata[customer_id]=9817 -d metadata[product_id]=7189

{"id":"07W98EUsBtCiyF7BnNcKe","msatoshi":"5000","metadata":{"customer_id":9817,"product_id":7189},"rhash":"3e449cc84d6b2b39df8e375d3cec0d2910e822346f782dc5eb97fea595c175b5","payreq":"lntb500n1pdq55z6pp58ezfejzddv4nnhuwxawnemqd9ygwsg35dauzm30tjll2t9wpwk6sdq0d3hz6um5wf5kkegcqpxpc06kpsp56fjh0jslhatp6kzmp8yxsgdjcfqqckdrrv0n840zqpx496qu5xenrzedlyatesl98dzdt5qcgkjd3l6vhax425jetq2h3gqz2enhk","completed":false,"created_at":1510625370087}

# create invoice with json
# Create EUR-denominated invoice
$ curl http://localhost:8009/invoice -X POST -d currency=EUR -d amount=0.5
{"id":"kGsKjn9jbwgqxQzNgQYhE","msatoshi":"7576148","quoted_currency":"EUR","quoted_amount":"0.5", ...}

# Create invoice with json
$ curl http://localhost:8009/invoice -X POST -H 'Content-Type: application/json' \
-d '{"msatoshi":5000,"metadata":{"customer_id":9817,"products":[593,182]}'

# fetch an invoice
# Fetch an invoice
$ curl http://localhost:8009/invoice/07W98EUsBtCiyF7BnNcKe

# fetch all invoices
# Fetch all invoices
$ curl http://localhost:8009/invoices

# register a web hook
# Register a web hook
$ curl http://localhost:8009/invoice/07W98EUsBtCiyF7BnNcKe/webhook -X POST -d url=https://requestb.in/pfqcmgpf

# long-poll payment notification for a specific invoice
# Long-poll payment notification for a specific invoice
$ curl http://localhost:8009/invoice/07W98EUsBtCiyF7BnNcKe/wait?timeout=120

# stream all incoming payments
# Stream all incoming payments
$ curl http://localhost:8009/payment-stream
```

Expand Down
8 changes: 7 additions & 1 deletion invoicing/model.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import nanoid from 'nanoid'
import { toMsat } from '../lib/exchange-rate'

const debug = require('debug')('lightning-strike')

module.exports = ({ db, ln }) => {
const peerid = ln.getinfo().then(info => info.id)

const newInvoice = async ({ msatoshi, metadata, webhook }) => {
const newInvoice = async ({ msatoshi, currency, amount, metadata, webhook }) => {
// @TODO validation: either msat or currency/amount are required, specifying both should error.

if (!msatoshi) msatoshi = await toMsat(currency, amount)

const id = nanoid()
, { rhash, bolt11: payreq } = await ln.invoice(msatoshi, id, 'ln-strike')
, invoice = {
id, metadata, msatoshi: ''+msatoshi
, quoted_currency: currency, quoted_amount: amount
, rhash, payreq, peerid: await peerid
, completed: false
, created_at: Date.now()
Expand Down
21 changes: 21 additions & 0 deletions lib/exchange-rate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import big from 'big.js'
import request from 'superagent'

const FIXED_RATES = { BTC: 1 }
, BTC_MSAT_RATIO = big('100000000000')

// Fetch current exchange rate from BitcoinAverage
// @TODO cache results?
const getRate = currency =>
request.get(`https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiat=${currency}`)
.then(res => res.body['BTC'+currency].last)
.catch(err => Promise.reject(err.status == 404 ? new Error('Unknown currency: '+currency) : err))

// Convert `amount` units of `currency` to msatoshis
const toMsat = async (currency, amount) =>
big(amount)
.div(FIXED_RATES[currency] || await getRate(currency))
.mul(BTC_MSAT_RATIO)
.toFixed(0)

module.exports = { getRate, toMsat }
4 changes: 3 additions & 1 deletion migrations/0000_01_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
exports.up = db =>
db.schema.createTable('invoice', t => {
t.string ('id').primary()
t.string ('peerid').notNullable()
t.string ('msatoshi').notNullable()
t.string ('quoted_currency').nullable()
t.string ('quoted_amount').nullable()
t.string ('peerid').notNullable()
t.string ('rhash').notNullable()
t.string ('payreq').notNullable()
t.bool ('completed').notNullable()
Expand Down

0 comments on commit 4f4b1d5

Please sign in to comment.