@x-money/core

中文文档
0.2.0发布于 2026/10/30MITTypeScriptES Module零依赖

X Money API client for Node.js

原始使用文档
文档目录12 节
  1. Quick start
  2. Resources
  3. Pagination
  4. Idempotency
  5. Test mode
  6. Webhooks
  7. Express checkout callbacks
  8. Errors
  9. A note on int64
  10. Documentation
  11. Support
  12. License

@x-money/core

The TypeScript server SDK for the X Money API. Create checkout sessions, charge linked accounts, issue refunds and payouts, and verify webhooks.

Zero runtime dependencies, ESM only, Node 24 or newer.

npm install @x-money/core

Quick start

import { XMoney } from "@x-money/core";

// The key is always explicit. Read it from wherever you keep secrets; the SDK
// will not pick one up from the environment on its own.
const apiKey = process.env.XMONEY_API_KEY;
if (!apiKey) throw new Error("Missing XMONEY_API_KEY");
const xmoney = new XMoney(apiKey);

const session = await xmoney.checkouts.create({
  line_items: [
    { name: "PlayStation 5", amount: 49999, currency: "USD", quantity: 1 },
  ],
  success_url: "https://example.com/success",
  failure_url: "https://example.com/failure",
  reference: "order-2481",
});

// Send the customer to session.url, or hand it to @x-money/checkout in the
// browser to open it in a popup instead of redirecting.

Amounts are integers in the currency's smallest unit, so 49999 is $499.99.

Resources

checkouts, exchanges, accounts, customers, payments, refunds, payouts, settlements, and webhooks.

Every endpoint the API exposes has a method here. The SDK's own test suite checks the two against each other in both directions, so an endpoint cannot be added to the API without a matching method shipping in the next release.

Pagination

Listable resources have a list method. Await it for one page, or for await to walk every page. Checkout sessions and account exchanges have no list in the API.

const firstPage = await xmoney.payouts.list({ limit: 100 });

for await (const payout of xmoney.payouts.list({ limit: 100 })) {
  console.log(payout.id);
}

Idempotency

Every POST carries a generated idempotency key. Pass your own to make a retry safe across process restarts, which is what you want for anything driven by an order id.

await xmoney.refunds.create(
  { payment: "xmpy_...", reference: "refund-order-8471" },
  { idempotencyKey: "refund-order-8471" },
);

Test mode

With a test key, testOutcome sends the Xm-Test-Outcome header to force a decline, an expiry, or an authorization error on that request. The amount never picks the outcome.

await xmoney.payments.create(
  { customer_id: "xmcu_...", account_id: "xmac_...", amount: 2437, currency: "USD" },
  { testOutcome: "INSUFFICIENT_FUNDS" },
);

Webhooks

constructEvent verifies the xm-signature header and returns a typed event. It throws XMoneySignatureVerificationError on a bad signature, a malformed header, or a timestamp outside the 300 second replay window.

import { XMoney, XMoneyUnknownEventError, XMoneySignatureVerificationError } from "@x-money/core";

app.post("/webhooks", express.raw({ type: "*/*" }), async (req, res) => {
  let event;
  try {
    event = xmoney.webhooks.constructEvent(
      req.body,
      req.headers["xm-signature"],
      process.env.XMONEY_WEBHOOK_SECRET,
    );
  } catch (err) {
    if (err instanceof XMoneyUnknownEventError) return res.status(200).end();
    if (err instanceof XMoneySignatureVerificationError) return res.status(400).end();
    throw err;
  }

  if (event.type === "checkout_session_completed") {
    await fulfillOrder(event.event.merchant_reference);
  }
  res.status(200).end();
});

req.body must be the unparsed request body. A body that has been JSON-parsed and re-serialized will not match the signature. Express needs express.raw() on this route rather than express.json().

An unrecognized event type is a genuine delivery this package is older than. Catch XMoneyUnknownEventError and return 200, or the endpoint is retried until it ages out.

To sign a payload in tests, generateTestHeader returns an xm-signature value constructEvent will accept:

const header = xmoney.webhooks.generateTestHeader(rawBody, secret);
const event = xmoney.webhooks.constructEvent(rawBody, header, secret);

constructEvent uses node:crypto and is sync. constructEventAsync HMAC-signs with Web Crypto (crypto.subtle) instead of createHmac. Same arguments, same errors, and it accepts headers generateTestHeader produced. A Fetch Request still needs await request.text(). The rest of this package still imports node:crypto for the HTTP client, so run the webhook route on Node until a webhooks-only export exists.

const event = await xmoney.webhooks.constructEventAsync(
  await request.text(),
  request.headers.get("xm-signature"),
  process.env.XMONEY_WEBHOOK_SECRET,
);

Express checkout callbacks

A checkout session can ask for customer details and let you answer while the customer is still in checkout:

const session = await xmoney.checkouts.create({
  line_items: [{ name: "Starter kit", amount: 1986, currency: "USD", quantity: 1 }],
  reference: "order-2481",
  requested_customer_details: ["EMAIL", "SHIPPING_ADDRESS"],
  allowed_shipping_countries: ["US"],
  callback_events: ["CUSTOMER_DETAILS_UPDATED", "CONFIRMATION_REQUESTED"],
});

X Money then posts to your callback endpoint, which is registered separately from your webhook endpoint and has its own xmcbsk_ signing secret. checkout_session_customer_details_updated arrives when checkout opens and whenever the customer changes their shipping address or email; answer with tax for that address, or reject it. checkout_session_confirmation_requested arrives when they pay, before any money moves; approve or reject the order. The customer waits on your answer, so reply within 5 seconds. A timeout, a non-200 status, or a body that is not a decision fails the step, and the customer can retry.

app.post("/xmoney/callbacks", express.raw({ type: "*/*" }), (req, res) => {
  let callback;
  try {
    callback = xmoney.callbacks.constructEvent(
      req.body,
      req.headers["xm-signature"],
      process.env.XMONEY_CALLBACK_SECRET,
    );
  } catch {
    return res.status(400).end();
  }

  const address = callback.event.customer_details?.shipping_address;
  if (callback.type === "checkout_session_customer_details_updated") {
    if (address && /^p\.?\s*o\.?\s*box/i.test(address.street)) {
      return res.json(xmoney.callbacks.reject({ field: "SHIPPING_ADDRESS", message: "We can't ship to PO boxes" }));
    }
    return res.json(xmoney.callbacks.approve({ tax_line_items: [{ name: "Sales tax", amount: taxFor(address), currency: "USD" }] }));
  }
  res.json(xmoney.callbacks.approve());
});

approve() without tax leaves the session's tax unchanged, and only checkout_session_customer_details_updated accepts tax. reject() without a field rejects the whole order. A rejected_message is shown to the customer and is limited to 100 characters. constructEventAsync and generateTestHeader work as they do for webhooks.

Errors

ErrorRaised when
XMoneyErrorBase class. Every SDK error is one.
XMoneyMissingApiKeyErrorNo API key was passed to the constructor.
XMoneyInvalidApiKeyErrorThe key is not an xmsk_test_ / xmsk_prod_ secret key.
XMoneyInvalidBaseUrlErrorbaseUrl was passed and is not an absolute URL.
XMoneyAPIErrorThe API returned a non-2xx. Carries a normalized code, the HTTP status, and requestId.
XMoneyConnectionErrorThe request never got a response. code is timeout, aborted, or network.
XMoneySignatureVerificationErrorA webhook or callback signature did not verify, or the signing secret is the wrong kind (xmwhsk_ for webhooks, xmcbsk_ for callbacks).
XMoneyUnknownEventErrorThe signature verified, but this package has no type for the event. Catch it and return 200.

Requests time out after 30 seconds and retry twice on 429s, 5xxs, and network failures, with jittered exponential backoff that honors Retry-After. Both are configurable per client, and the timeout per request.

const xmoney = new XMoney({
  apiKey,
  timeoutMs: 10_000,
  maxRetries: 5,
  onResponse: ({ requestId, status, method, path }) => {
    console.log(method, path, status, requestId);
  },
});

The package ships a source map with the TypeScript sources embedded. Node applies it to stack traces only when started with --enable-source-maps, or after process.setSourceMapsEnabled(true). Without one of those, a trace from inside the SDK points into the minified bundle.

A note on int64

The API serializes 64-bit integers as JSON strings, since large values do not survive JavaScript numbers. The SDK converts the known int64 fields back to numbers on the way out, so amounts and timestamps arrive as you would expect.

metadata is skipped, so your own data is never rewritten.

Documentation

Guides, the API reference, and the webhook catalog are at xmoney.mintlify.site/docs.

To have an AI coding agent collect a payment with this package:

npx skills add https://xmoney.mintlify.site/docs

The browser half of the SDK, @x-money/checkout, renders the Pay with X button and opens the sessions this package creates.

Support

Report bugs and ask questions through the X Help Center. Security issues go through the same form; please do not post them publicly.

License

MIT. The full text ships in the package as LICENSE.

版本与发布记录

@x-money/core 共发布 2 个版本,首次发布于 2026/10/03;过去 12 个月发布了 2 个版本。

版本发布日期距今
0.2.0latest2026/10/304 天前
0.1.02026/10/031 个月前

关于 @x-money/core 的常见问题

@x-money/core 是什么?

用于Node.js的X Money API客户端

如何安装 @x-money/core?

在项目目录运行 npm install @x-money/core,或使用 pnpm add / yarn add / bun add。

@x-money/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@x-money/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@x-money/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@x-money/core 可以商用吗?

@x-money/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@x-money/core 还在维护吗?

最新版本 0.2.0 发布于 2026/10/30(4 天前);至今共发布 2 个版本,其中过去 12 个月发布了 2 个;首次发布于 2026/10/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。