Accept Payments#

To accept a payment in DCC or a token, a shop gives the customer an amount and a payment reference, the customer sends a Transfer transaction with that reference in its attachment, and the shop watches its own address for a matching transfer. No smart contract is involved.

Note

The sample is typechecked against @decentralchain/transactions@5.0.1 and @decentralchain/node-api-js@2.0.1. It has not been broadcast as part of this documentation pass.

Before You Start#

You need Node.js 24 or later, a Testnet address for the shop and a funded Testnet account for the customer (see the faucet).

npm install @decentralchain/transactions@5.0.1 @decentralchain/node-api-js@2.0.1

Pay an Invoice#

In a real shop the customer signs in their own wallet, for example with Cubensis Connect, and your page only fills in the recipient, the amount and the attachment. The sample signs with a seed phrase so that both sides fit in one script:

import { transfer, broadcast, waitForTx, libs } from '@decentralchain/transactions';
import { create } from '@decentralchain/node-api-js';

const nodeUrl = 'https://testnet-node.decentralchain.io';
const merchant = '31LAqgqVTLySNCPuYqgLAYhcgS76wvBaLr9';
const invoice = { id: 'INV-1042', amount: 250_000_000 }; // 2.5 DCC, in Decentralites

// Customer: pay the invoice and put its ID in the attachment
const customerSeed = 'customer secret seed phrase here';
const payment = transfer(
  {
    recipient: merchant,
    amount: invoice.amount,
    attachment: libs.crypto.base58Encode(libs.crypto.stringToBytes(invoice.id)),
    chainId: '!',
  },
  customerSeed,
);
await broadcast(payment, nodeUrl);
await waitForTx(payment.id, { apiBase: nodeUrl, timeout: 120_000 });

The attachment carries the invoice ID as base58-encoded UTF-8 bytes, up to 140 bytes. It is public, like the rest of the transaction, so put a reference in it, never personal data.

Check for Payment#

The shop reads its latest transactions and looks for a transfer that matches the invoice:

// Merchant: look for a confirmed DCC transfer that pays this invoice in full
const api = create(nodeUrl);
const recent = await api.transactions.fetchTransactions(merchant, 100);
const paidBy = recent.find(
  (tx) =>
    tx.type === 4 &&
    tx.recipient === merchant &&
    tx.assetId === null &&
    Number(tx.amount) >= invoice.amount &&
    libs.crypto.bytesToString(libs.crypto.base58Decode(tx.attachment ?? '')) === invoice.id,
);
console.log(paidBy ? `Invoice ${invoice.id} paid by ${paidBy.id}` : `Invoice ${invoice.id} not paid yet`);

What to check:

  • The type, recipient and token. type === 4 is a Transfer transaction. assetId === null means DCC; for a token payment, compare it with the token’s ID.

  • The amount. Amounts are integers in the smallest unit: 1 DCC is 100,000,000 Decentralites.

  • The attachment. fetchTransactions returns it base58-encoded, so decode it before you compare.

fetchTransactions(address, limit) returns only transactions that are already in a block, newest first. A shop with more traffic pages back with its after argument (the ID of the last transaction already read), or keeps its own record of the transactions it has seen. To know a payment is in a block before you ship, rather than just accepted into a node’s pool, wait for it with waitForTx, or with waitForTxWithNConfirmations to also wait for a number of blocks on top.