Skip to content

Instantly share code, notes, and snippets.

@caub
Last active October 22, 2024 11:32
Show Gist options
  • Save caub/0999d3a139d44efd6609fb110eff23b1 to your computer and use it in GitHub Desktop.
Save caub/0999d3a139d44efd6609fb110eff23b1 to your computer and use it in GitHub Desktop.
Storeganise custom billing / custom payment gateway addons
import crypto from 'crypto';
export default async (req, res) => {
try {
if (req.method !== 'POST') {
return res.status(404).send();
}
if (req.path.endsWith('/webhook')) {
console.log('webhook', req.body);
// Here you could potentially handle async webhooks used by the remote payment gateway API
return res.send();
}
// Verify request's signature, to ensure it comes from Storeganise
if (req.headers['Sg-Signature'] !== crypto.createHmac('sha256', process.env.SG_API_SECRET).update(JSON.stringify(req.body)).digest('base64')) {
res.status(401).send();
}
const { data, type, businessCode, apiUrl, addonId } = req.body;
const api = storeganiseApi({ apiUrl, addonId }); // See example of storeganiseApi helper: https://gist.github.com/caub/14439d39ef9adf14bdd5dc25a402ce18
const addon = await api.get(`addons/${addonId}`); // If necessary fetch addon, as that's a good place to store credentials for the remote payment API
const user = await api.get(`users/${data.userId}`, { include: 'customFields' }); // load target user
const payApi = customPaymentApi(addon); // Initialize your own custom payment API, using addon.customFields credentials if needed
// Handles the 3 main events:
switch (type) {
case 'billing.list': {
if (!user.customFields.pay_token) return res.json([]); // for example store your remote payment customerId as pay_token user custom field
try {
const data = await payApi.retrievePaymentMethods({ token: user.customFields.bpoint_token });
return res.json(data);
// the format of the response should be an array of object with either a `card` or `bank` property
// example:
/*
[{
"card": {
"number": "411111...111",
"expiry": {
"month": "99",
"year": "00"
},
"name": "Cyril Testing",
"scheme": "Visa",
"localisation": "International",
"type": "Debit"
}
}]
*/
} catch (err) {
return res.status(400).json({ message: err.message });
}
}
case 'billing.charge': {
// data.amount can be negative for refunds, usually you'll need to handle these separately: if (data.amount < 0) { .. }
if (!user.customFields.bpoint_token) {
return res.status(204).send();
}
// Process charge using payApi
const txn = await payApi.createPayment({
token: user.customFields.bpoint_token,
amount: Math.round(data.amount * 100), // in cents (often needed), as data.amount is in main currency unit, not cents
currency: addon.customFields.pay_currency, // example use of addon custom fields
});
// Send response in this format:
return res.send({
id: txn.txnNumber,
amount: txn.amount,
status: txn.responseCode === '0' ? 'succeeded' : txn.responseCode === '1' ? 'processing' : 'failed',
currency: txn.currency, // optional
paymentMethod: txn.paymentMethod, // optional
isTest: txn.isTestTxn, // optional
});
}
case 'billing.checkout': {
// Here you need to render or redirect to a custom checkout form, where customer will enter their card or bank account details
return res.send(`<!doctype html>
<html>
<head>
<title>BPOINT Payment</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<h1>Pay with {{customBilling}}</h1>
<form>
...
</form>
</body>
</html>`);
}
default:
return res.status(404).send(`Unknown event type ${type}`);
}
} catch (err) {
console.log(err);
res.status(400).send(err.message);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment