ilp-plugin-virtual
Version:
ILP virtual ledger plugin for directly transacting connectors
408 lines (358 loc) • 13.3 kB
JavaScript
'use strict'
module.exports = SettlementsControllerFactory
// const co = require('co')
const uuid = require('uuid4')
const Auth = require('../lib/auth')
const Config = require('../lib/config')
const Connector = require('../lib/connector')
const Paypal = require('../lib/paypal')
const Activity = require('../lib/activity')
const SettlementFactory = require('../models/settlement')
const SettlementMethodFactory = require('../models/settlement_method')
const UserFactory = require('../models/user')
const PeerFactory = require('../models/peer')
const NotFoundError = require('../errors/not-found-error')
const InvalidBodyError = require('../errors/invalid-body-error')
function SettlementsControllerFactory (deps) {
const auth = deps(Auth)
const config = deps(Config)
const Settlement = deps(SettlementFactory)
const SettlementMethod = deps(SettlementMethodFactory)
const User = deps(UserFactory)
const Peer = deps(PeerFactory)
const connector = deps(Connector)
const paypal = deps(Paypal)
const activity = deps(Activity)
const getDestination = async function (destination) {
return (await User.findOne({ where: { destination } })) || Peer.findOne({ where: { destination } })
}
return class SettlementsController {
static init (router) {
// Public
router.get('/destinations/:destination', this.getDestination)
router.post('/settlements/:destination/paypal', this.paypal)
router.get('/settlements/:destination/paypal/execute', this.paypalExecute)
router.get('/settlements/:destination/paypal/cancel', this.paypalCancel)
// User
router.get('/settlements/:id', auth.checkAuth, this.getResource)
// Admin
router.get('/settlements', auth.checkAuth, this.checkAdmin, this.getResources)
router.post('/settlements/:destination', auth.checkAuth, this.checkAdmin, this.custom)
}
// TODO move to auth
static async checkAdmin (ctx, next) {
if (ctx.req.user.username === config.data.getIn(['ledger', 'admin', 'user'])) {
return next()
}
throw new NotFoundError()
}
/**
* @api {GET} /destinations/:destination Get destination
* @apiName GetSettlementDestination
* @apiGroup Settlement
* @apiVersion 1.0.0
*
* @apiDescription Get all settlement methods
*
* @apiParam {int} destination destination
*
* @apiExample {shell} Get destination
* curl -X GET
* https://wallet.example/destinations/813133
*
* @apiSuccessExample {json} 200 Response:
* HTTP/1.1 200 OK
* {
* "type": "user"
* }
*/
static async getDestination (ctx) {
const destination = await getDestination(ctx.params.destination)
if (!destination) {
throw new NotFoundError('Invalid destination')
}
ctx.body = {
type: destination.hostname ? 'peer' : 'user',
hostname: destination.hostname
}
}
static async settle (params) {
const destination = params.destination
const amount = params.amount
const currency = params.currency
const prefix = `settlement.${uuid()}.`
let ilpAddress
if (destination.hostname) {
ilpAddress = connector.peers[destination.destination].ledgerName + connector.peers[destination.destination].publicKey
} else {
ilpAddress = config.data.getIn(['ledger', 'prefix']) + destination.username
}
// Add the plugin to the connector
await connector.instance.addPlugin(prefix, {
plugin: 'ilp-plugin-settlement-adapter',
currency,
options: {
prefix,
currency,
amount,
destination: ilpAddress,
connectors: [`${prefix}connector`]
}
})
// Emit a payment that gets routed to destination
await connector.instance.getPlugin(prefix).receive()
// Remove the plugin
await connector.instance.removePlugin(prefix)
// Store the settlement in the database
let settlement = new Settlement()
settlement.settlement_method_id = params.settlementMethod.id
if (destination.hostname) {
settlement.peer_id = destination.id
} else {
settlement.user_id = destination.id
}
settlement.amount = amount
settlement.currency = currency
settlement = await settlement.save()
await activity.processSettlement(settlement)
return settlement
}
/**
* @api {POST} /settlements/:destination Settle
* @apiName PostSettle
* @apiGroup Settlement
* @apiVersion 1.0.0
* @apiPermission admin
*
* @apiDescription Settle
*
* @apiParam {int} destination destination
*
* @apiExample {shell} Settle
* curl -X POST -H "Authorization: Basic YWxpY2U6YWxpY2U=" -d
* '{
* "amount": "10",
* "currency": "USD",
* "settlement_method": "7b4a73b0-19c5-46ed-8905-febeae2b0a05"
* }'
* https://wallet.example/settlements/813133
*
* @apiSuccessExample {json} 204 Response:
* HTTP/1.1 204 OK
*/
static async custom (ctx) {
const destination = await getDestination(ctx.params.destination)
if (!destination) throw new NotFoundError('Invalid destination')
const settlementMethod = await SettlementMethod.findOne({
where: { id: ctx.body.settlement_method }
})
if (!settlementMethod) {
throw new InvalidBodyError('Invalid settlement method')
}
this.body = null
return SettlementsController.settle({
destination,
settlementMethod,
amount: ctx.body.amount,
currency: ctx.body.currency
})
}
/**
* @api {POST} /settlements/:destination/paypal Get Paypal link
* @apiName PostPaypalLink
* @apiGroup Settlement
* @apiVersion 1.0.0
*
* @apiDescription Get Paypal link
*
* @apiParam {int} destination destination
*
* @apiExample {shell} Get destination
* curl -X POST
* https://wallet.example/settlements/813133/paypal
*
* @apiSuccessExample {json} 200 Response:
* HTTP/1.1 200 OK
* {
* "approvalLink": "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-7R410477WT7455126"
* }
*/
static async paypal (ctx) {
const destination = await getDestination(ctx.params.destination)
if (!destination) throw new NotFoundError('Invalid destination')
ctx.body = {
approvalLink: await paypal.createPayment(destination.destination, ctx.body.amount)
}
}
static async paypalExecute (ctx) {
const destination = await getDestination(ctx.params.destination)
if (!destination) throw new NotFoundError('Invalid destination')
try {
const payment = await paypal.executePayment(ctx.query)
// Weird
if (destination.destination !== payment.destination) {
// TODO this is caught locally
throw new InvalidBodyError("Payment destination doesn't match the expected destination")
}
const settlementMethod = await SettlementMethod.findOne({ where: { type: 'paypal' } })
const settlement = await SettlementsController.settle({
destination,
settlementMethod,
amount: payment.amount,
// TODO support other currencies
currency: 'USD'
})
// TODO:BEFORE_DEPLOY handle destination.hostname, destination.currency for user
ctx.redirect(`${config.data.get('client_host')}/settlement/${settlement.id}`)
} catch (e) {
console.log('settlements:174', e)
ctx.redirect(`${config.data.get('client_host')}/settlement/cancel`)
}
}
static async paypalCancel (ctx) {
const destination = await getDestination(ctx.params.destination)
if (!destination) throw new NotFoundError('Invalid destination')
ctx.redirect(`${config.data.get('client_host')}/settle/paypal/${destination.destination}/cancel`)
}
/**
* @api {GET} /settlements/:id Get Settlement
* @apiName GetSettlement
* @apiGroup Settlement
* @apiVersion 1.0.0
*
* @apiDescription Get Settlement
*
* @apiParam {UUID} id settlement id
*
* @apiExample {shell} Get destination
* curl -X GET
* https://wallet.example/settlements/da978aa3-93c1-4899-8507-6888cb4ce8ca
*
* @apiSuccessExample {json} 200 Response:
* HTTP/1.1 200 OK
* {
* "amount": 110,
* "currency": "USD",
* "method": "paypal",
* "date": "2017-03-16T17:03:02.767Z",
* "peer": null,
* "user": "alice"
* }
*/
static async getResource (ctx) {
const settlement = await Settlement.findOne({
where: { id: ctx.params.id },
include: [{ all: true }]
})
if (!settlement) throw new NotFoundError()
ctx.body = {
amount: settlement.amount,
currency: settlement.currency,
method: settlement.SettlementMethod.type,
date: settlement.created_at,
peer: settlement.Peer && settlement.Peer.hostname,
user: settlement.User && settlement.User.username
}
}
/**
* @api {GET} /settlements Get Settlements
* @apiName GetSettlement
* @apiGroup Settlement
* @apiVersion 1.0.0
* @apiPermission admin
*
* @apiDescription Get Settlement
*
* @apiParam {string} type settlement type. 'peer' or 'user'
*
* @apiExample {shell} Get destination
* curl -X GET -H "Authorization: Basic YWxpY2U6YWxpY2U="
* https://wallet.example/settlements
*
* @apiSuccessExample {json} 200 Response:
* HTTP/1.1 200 OK
* [
* {
* "id": "da978aa3-93c1-4899-8507-6888cb4ce8ca",
* "amount": 110,
* "currency": "USD",
* "created_at": "2017-03-16T17:03:02.767Z",
* "updated_at": "2017-03-16T17:03:02.767Z",
* "peer_id": null,
* "user_id": 2,
* "settlement_method_id": "7b4a73b0-19c5-46ed-8905-febeae2b0a05",
* "Peer": null,
* "User": {
* "id": 2,
* "username": "alice",
* "email": "alice@example.com",
* "email_verified": true,
* "github_id": null,
* "destination": "451744",
* "profile_picture": "upload_3a252b77b8f4c76f3037d7df30892441_square.jpeg",
* "name": "Alice",
* "phone": null,
* "address1": null,
* "address2": null,
* "city": null,
* "region": null,
* "country": null,
* "zip_code": null,
* "created_at": "2016-12-02T22:27:49.360Z",
* "updated_at": "2017-06-02T20:20:29.214Z",
* "invite_code": null
* },
* "SettlementMethod": {
* "id": "7b4a73b0-19c5-46ed-8905-febeae2b0a05",
* "type": "paypal",
* "name": "Paypal",
* "logo": null,
* "description": null,
* "uri": null,
* "enabled": true,
* "options": {
* "clientId": "...",
* "secret": "...",
* "sandbox": true
* },
* "created_at": "2017-02-02T19:20:04.190Z",
* "updated_at": "2017-03-07T01:48:29.769Z"
* },
* "ActivityLogs": [
* {
* "id": "67407a5e-08df-46e2-b7fe-41b46b067626",
* "stream_id": null,
* "created_at": "2017-03-16T17:03:02.813Z",
* "updated_at": "2017-03-16T17:03:02.813Z",
* "user_id": 2,
* "ActivityLogsItem": {
* "id": 4482,
* "activity_log_id": "67407a5e-08df-46e2-b7fe-41b46b067626",
* "item_type": "settlement",
* "item_id": "da978aa3-93c1-4899-8507-6888cb4ce8ca",
* "created_at": "2017-03-16T17:03:02.847Z",
* "updated_at": "2017-03-16T17:03:02.847Z"
* }
* }
* ]
* }
* ]
*/
// TODO don't return this much stuff
static async getResources (ctx) {
const type = ctx.query.type
let where
if (type === 'user') {
where = { user_id: { $not: null } }
} else if (type === 'peer') {
where = { peer_id: { $not: null } }
} else {
throw new InvalidBodyError('Settlement type is not specified')
}
ctx.body = await Settlement.findAll({
where,
include: [{ all: true }]
})
}
}
}