BoomPay/docs

Ecwid by Lightspeed

A Payment API app: Ecwid posts an encrypted order payload to a URL you register, you decrypt it, create the intent, and confirm payment back through Ecwid's own Orders API.

Node.jsPayment API appLightspeed

Ecwid’s extension point for custom payment methods is its Payment API: register a payment method and an "action URL," and Ecwid posts order details to that URL whenever a customer chooses it at checkout. The payload arrives AES-128 encrypted rather than as plain JSON or a signed token, which makes this integration look a little different from the others on this site even though the BoomPay half of it is identical.

Architecture

01

Customer selects Boomcoin

At checkout, the customer picks the custom payment method you registered.

02

Encrypted order POST

Ecwid sends an AES-128-ECB encrypted order payload to your registered action URL.

03

Decrypt and create intent

Your server decrypts it with a key derived from your app's client secret, then calls createIntent().

04

Customer redirected, signed return

The customer pays on BoomPay's hosted page, then is sent back with paymentIntentId and X-Boom-Signature.

05

Order marked paid

Your server verifies the signature and updates the order's paymentStatus to PAID via Ecwid's REST API.

Files

server.js
const express = require('express');
const BoomPay = require('boom-pay-sdk');

const app = express();
app.use(express.json());

const boomPay = new BoomPay({
  apiKey: process.env.BOOMPAY_API_KEY,
  sandbox: process.env.NODE_ENV !== 'production',
});

app.use('/ecwid', require('./routes/ecwid-payment')(boomPay));
app.use('/boompay', require('./routes/boompay-return')(boomPay));

app.listen(process.env.PORT || 3000);
routes/ecwid-payment.js
const express = require('express');
const crypto = require('crypto');

module.exports = function (boomPay) {
  const router = express.Router();

  // Registered as this payment method's "action URL" in your Ecwid app
  // config. Ecwid POSTs an AES-128-ECB encrypted order payload here, keyed
  // by the first 16 characters of your app's client secret.
  router.post('/payment', express.text({ type: '*/*' }), async (req, res) => {
    const order = decryptEcwidPayload(req.body, process.env.ECWID_CLIENT_SECRET);

    const intent = await boomPay.payments.createIntent({
      amount: Number(order.total),
      successUrl: `${process.env.APP_URL}/boompay/return?status=success&order=${order.orderNumber}&store=${order.storeId}`,
      failureUrl: `${process.env.APP_URL}/boompay/return?status=failure&order=${order.orderNumber}&store=${order.storeId}`,
      label: `Ecwid order ${order.orderNumber}`,
      metadata: { ecwidOrderNumber: order.orderNumber, ecwidStoreId: order.storeId },
    });

    // The response shape Ecwid expects here -- a redirect instruction back
    // to the storefront -- is the one piece of this flow worth confirming
    // against Ecwid's current Payment App docs rather than trusting verbatim.
    res.status(200).json({ type: 'redirect', value: intent.link });
  });

  return router;
};

function decryptEcwidPayload(encryptedBase64, clientSecret) {
  const key = Buffer.from(clientSecret.slice(0, 16), 'utf8');
  const encrypted = Buffer.from(encryptedBase64, 'base64');
  const decipher = crypto.createDecipheriv('aes-128-ecb', key, Buffer.alloc(0));
  const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
  return JSON.parse(decrypted.toString('utf8'));
}
routes/boompay-return.js
const express = require('express');

const ecwidApi = (storeId) => `https://app.ecwid.com/api/v3/${storeId}`;

module.exports = function (boomPay) {
  const router = express.Router();

  // boomPay.webhooks() validates paymentIntentId + X-Boom-Signature before
  // this handler runs.
  router.get('/return', boomPay.webhooks(), async (req, res) => {
    const { order, store, status } = req.query;
    const payment = await boomPay.payments.getPayment(req.query.paymentIntentId);

    if (status === 'success' && payment.paidAt) {
      await fetch(`${ecwidApi(store)}/orders/${order}`, {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${process.env.ECWID_ACCESS_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ paymentStatus: 'PAID' }),
      });
      return res.redirect(`${process.env.STORE_URL}/order-confirmation`);
    }

    res.redirect(`${process.env.STORE_URL}/cart`);
  });

  return router;
};
i

The decryption key is the first 16 characters of your app’s client secret, used as a raw AES-128 key in ECB mode — not a passphrase you derive a key from. Get this wrong and decryption fails silently into garbage bytes rather than throwing somewhere obvious.

Setup

Register an Ecwid application to get a client id and secret, then register the Boomcoin payment method with your deployed /ecwid/payment URL as its action URL. Install the app on your store to get a store-level OAuth access token for ECWID_ACCESS_TOKEN, used to update order status after payment.

Testing in sandbox

With sandbox mode on, place a test order choosing Boomcoin and confirm the redirect lands on a sapi.boom.market page. Approve the payment and check the order’s payment status flips to Paid in your Ecwid control panel. Cancel a second test payment and confirm the order is left in a state that makes it obvious to a store owner that it still needs attention, rather than silently looking the same as a successful one.

Go-live checklist

  • Switch off sandbox mode and use a live API key once a full test order has gone through.
  • Store your Ecwid client secret and OAuth token as securely as your BoomPay API key — both are equally capable of letting someone else move money or fake order data here.
  • BoomPay settles in BMC only; convert the order total before calling createIntent if your store prices in another currency.