-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
67 lines (58 loc) · 2.71 KB
/
Copy pathindex.js
File metadata and controls
67 lines (58 loc) · 2.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
'use strict';
// U.CASH Pay payment app for Ecwid (scaffold).
// - GET /pay : Ecwid opens this at checkout; we create a U.CASH Pay checkout and redirect.
// - POST /ucashpay/webhook : U.CASH Pay settlement webhook (HMAC-verified) -> mark the Ecwid order PAID.
const crypto = require('crypto');
const express = require('express');
const app = express();
const UCASH_CLOUD = process.env.UCASH_CLOUD_TOKEN;
const UCASH_BASE = (process.env.UCASH_BASE_URL || 'https://pay.u.cash').replace(/\/+$/, '');
const UCASH_SECRET = process.env.UCASH_WEBHOOK_SECRET || '';
if (!UCASH_CLOUD) {
console.error('Set UCASH_CLOUD_TOKEN (and UCASH_WEBHOOK_SECRET).');
process.exit(1);
}
// Redirect the shopper to a hosted U.CASH Pay checkout for the Ecwid order.
app.get('/pay', (req, res) => {
const amount = req.query.total || req.query.amount || '';
const currency = req.query.currency || 'USD';
const ref = req.query.orderNumber || req.query.order || '';
const returnUrl = req.query.returnUrl || '';
const params = new URLSearchParams({
cloud: UCASH_CLOUD,
amount: String(amount),
currency,
external_reference: String(ref),
title: 'Order #' + ref,
redirect: returnUrl,
});
res.redirect(302, `${UCASH_BASE}/embed.php?${params.toString()}`);
});
// U.CASH Pay webhook -> complete the Ecwid order.
app.post('/ucashpay/webhook', (req, res) => {
let raw = '';
req.on('data', (c) => { raw += c; });
req.on('end', () => {
const sig = req.headers['x-webhook-signature'] || '';
const parts = {};
sig.split(',').forEach((c) => { const i = c.indexOf('='); if (i > 0) parts[c.slice(0, i).trim()] = c.slice(i + 1).trim(); });
const fail = (m) => res.status(401).json({ error: m });
if (!parts.t || !parts.v1) return fail('missing t or v1');
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return fail('timestamp expired (>300s)');
const expected = crypto.createHmac('sha256', UCASH_SECRET).update(`${parts.t}.${raw}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return fail('invalid HMAC signature');
let payload = {};
try { payload = JSON.parse(raw); } catch (e) { /* ignore */ }
const tx = payload.transaction || payload;
const ref = tx.external_reference;
// TODO: call the Ecwid API to mark order `ref` as PAID using the merchant's OAuth token.
// PUT https://app.ecwid.com/api/v3/{storeId}/orders/{ref} body: {"paymentStatus":"PAID"}
// Store the access_token per store during the OAuth install flow (see README).
console.log('verified; complete Ecwid order', ref);
res.json({ ok: true });
});
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`ecwid-ucashpay on :${port}`));