@delopay/sdk

中文文档
0.385.0发布于 2026/11/12MITTypeScriptESM + CJS零依赖近 30 天下载 1.5万

Official Delopay TypeScript SDK

原始使用文档
文档目录19 节
  1. Installation
  2. Quick Start
  3. Configuration
  4. Usage Examples
  5. Create a payment
  6. Create a refund
  7. Manage customers
  8. Sell from the catalog
  9. Issue invoices
  10. Handle disputes
  11. Inspect why a payment failed
  12. Manage shops and gateways
  13. Subscriptions
  14. Platform fee rules
  15. Webhook verification
  16. Error Handling
  17. TypeScript
  18. Environments
  19. License

@delopay/sdk

TypeScript SDK for the Delopay payments API. Zero dependencies, works in Node 18+ and browsers.

Installation

pnpm add @delopay/sdk
npm install @delopay/sdk
yarn add @delopay/sdk

Quick Start

import { Delopay } from '@delopay/sdk';

const delopay = new Delopay(process.env.DELOPAY_API_KEY!);

const payment = await delopay.payments.create({
  amount: 1000, // in minor units (€10.00)
  currency: 'EUR',
  description: 'Order 1234',
  customer_id: 'cus_abc123',
});

console.log(payment.payment_id, payment.status);

Configuration

const delopay = new Delopay(apiKey, {
  sandbox: true, // Use https://sandbox.delopay.net (default: false → production)
  baseUrl: 'https://…', // Override base URL entirely
  timeout: 30_000, // Request timeout in ms (default: 30 000)
});

API keys:

  • prd_… / snd_… — server-side secret key. Full API access. Keep this private.
  • pk_prd_… / pk_snd_… — client-side publishable key. Restricted to browser-safe operations.

Usage Examples

Create a payment

Never send raw card numbers through the SDK. Delopay's API is not a raw-PAN endpoint: cards are collected on the Delopay hosted checkout (or via a payment link), so card data never touches your server and stays out of your PCI scope. Server-side you create the payment and hand the buyer off; you confirm server-side only with a saved payment_token, a mandate_id, or a redirect method (e.g. a PayPal wallet) — never card data.

// Recommended: hosted checkout via a payment link.
const payment = await delopay.payments.create({
  amount: 2500,
  currency: 'EUR',
  payment_link: true,
  customer_id: 'cus_abc123',
  description: 'Order 1234',
  return_url: 'https://example.com/checkout/complete',
});

// Send the buyer here — they pick a method and enter card details on the
// hosted page. Delopay handles 3-D Secure and redirects.
console.log(payment.payment_link?.link);

// Fulfil on the payment_succeeded webhook, or re-check server-side:
const final = await delopay.payments.retrieve(payment.payment_id);

Confirm server-side without card data — with a saved token (off-session):

const confirmed = await delopay.payments.confirm(pending.payment_id, {
  payment_token: savedPaymentToken, // from paymentMethods.listForCustomer()
  off_session: true,
  return_url: 'https://example.com/checkout/complete',
});

console.log(confirmed.status); // 'succeeded' | 'requires_customer_action' | …

Or with a redirect payment method that involves no card data at all:

const paypal = await delopay.payments.create({
  amount: 2500,
  currency: 'EUR',
  confirm: true,
  payment_method: 'wallet',
  payment_method_type: 'paypal',
  payment_method_data: { wallet: { paypal_redirect: {} } },
  return_url: 'https://example.com/checkout/complete',
});
// paypal.next_action?.redirect_to_url → send the buyer there to approve

Create a refund

const refund = await delopay.refunds.create({
  payment_id: 'pay_abc123',
  amount: 1000, // partial refund; omit for full refund
  reason: 'Customer request',
});

console.log(refund.refund_id, refund.status);

Manage customers

const customer = await delopay.customers.create({
  name: 'Jane Doe',
  email: '[email protected]',
  metadata: { plan: 'pro' },
});

// List saved payment methods
const { customer_payment_methods } = await delopay.paymentMethods.listForCustomer(
  customer.customer_id,
);

// Use a saved method on a new payment
const payment = await delopay.payments.create({
  amount: 1000,
  currency: 'EUR',
  customer_id: customer.customer_id,
  payment_token: customer_payment_methods[0]?.payment_token,
  confirm: true,
});

Sell from the catalog

// A product belongs to a shop; every call names the shop last.
const product = await delopay.products.create(
  {
    name: 'Annual report',
    kind: 'one_time',
    prices: [{ amount: 2999, currency: 'EUR', is_default: true }],
  },
  'pro_abc123',
);

// Price a payment from the catalog: no amount, no currency — the server
// freezes the product's active default price (or a named `price_id`) onto it.
const payment = await delopay.payments.create({
  product_id: product.id,
  profile_id: 'pro_abc123',
  payment_link: true,
});

// What the payment sold, as it was when it was created — unchanged by later
// catalog edits or deletion.
const sold = await delopay.payments.productLines(payment.payment_id);

Issue invoices

// Invoices and credit notes in your name, from a paid payment or subscription
// cycle. Amounts always come from the source.
const invoice = await delopay.invoices.create(
  { source: { payment_id: 'pay_abc123' }, finalize: true },
  { headers: { 'Idempotency-Key': 'invoice-order-1001' } },
);
const pdf = await delopay.invoices.downloadPdf(invoice.id);

// How they look and are taxed: the merchant default, or one shop's override.
const current = await delopay.invoiceSettings.retrieveForShop('pro_abc123');
await delopay.invoiceSettings.updateForShop('pro_abc123', { ...current, locale: 'de' });

Handle disputes

// Disputes for one payment come from the payment object:
const payment = await delopay.payments.retrieve('pay_abc123');

for (const dispute of payment.disputes ?? []) {
  console.log(dispute.dispute_id, dispute.dispute_stage, dispute.dispute_status);
}

// Or list disputes across the account, filtered by status:
const open = await delopay.disputes.list({ dispute_status: 'dispute_opened' });

Inspect why a payment failed

// Every attempt on a payment — including retries across connectors — with its
// full failure detail (raw code + Delopay-unified, human-readable reason).
const { size, data } = await delopay.payments.listAttempts('pay_abc123');

for (const attempt of data) {
  // e.g. "stripe failure — Insufficient funds (51)"
  console.log(
    `${attempt.connector ?? 'unknown'} ${attempt.status} — ` +
      `${attempt.unified_message ?? attempt.error_message ?? 'no error'}` +
      `${attempt.error_code ? ` (${attempt.error_code})` : ''}`,
  );
}

Manage shops and gateways

// Create a shop (business profile)
const shop = await delopay.shops.create(merchantId, {
  shop_name: 'My Online Store',
  webhook_url: 'https://example.com/webhooks/delopay',
  return_url: 'https://example.com/checkout/complete',
});

// Connect Stripe as a payment gateway
const gateway = await delopay.shops.gateways.connect(merchantId, shop.shop_id, {
  connector_type: 'payment_processor',
  connector_name: 'stripe',
  connector_account_details: {
    auth_type: 'HeaderKey',
    api_key: process.env.STRIPE_SECRET_KEY,
  },
  test_mode: true,
});

// List connected gateways
const gateways = await delopay.shops.gateways.list(merchantId, shop.shop_id);

Subscriptions

Recurring billing runs through a billing processor connected to the shop (Stripe Billing or PayPal). Every subscription call is profile-scoped — pass the shop's X-Profile-Id so the backend can resolve the billing processor (you get IR_04 otherwise):

const opts = { headers: { 'X-Profile-Id': profileId } };

Browse plans and estimate cost before creating anything:

// List plans (or addons) with their prices. An item the processor lists but
// cannot sell right now comes back with `available: false` and no prices;
// skip it rather than offering it.
const plans = await delopay.subscriptions.getItems({ item_type: 'plan' }, opts);
const priceId = plans.find((item) => item.available)?.price_id[0]?.price_id;
if (!priceId) {
  throw new Error('No purchasable plan on this shop');
}

// Preview what the customer will be charged
const estimate = await delopay.subscriptions.getEstimate({ item_price_id: priceId }, opts);
console.log(estimate.amount, estimate.currency, estimate.interval); // 1500 'EUR' 'Month'

Never send raw card numbers. The subscription API rejects payment_method_data.card with a raw PAN. Cards are collected client-side by the connector's hosted fields (Stripe Elements) so the card never touches your server, keeping raw card data out of your PCI scope. Confirm with a hosted checkout session or a previously-saved token, as shown below.

Recommended: hosted checkout. Create the subscription server-side, then send the buyer to the Delopay hosted checkout with the returned client_secret. The buyer enters their card in the connector iframe; you never handle the PAN:

const pending = await delopay.subscriptions.create(
  {
    item_price_id: priceId,
    customer_id: 'cus_abc123',
    payment_details: { return_url: 'https://example.com/subscription/complete' },
  },
  opts,
);

// Redirect the buyer to the hosted checkout to enter their card.
const checkoutUrl =
  `https://checkout.delopay.net/pay/${merchantId}/${pending.id}` +
  `?cs=${encodeURIComponent(pending.client_secret ?? '')}`;
// → res.redirect(checkoutUrl)

// Activation arrives via the subscription/invoice webhooks; never trust the
// client. Reconcile with subscriptions.retrieve(pending.id, opts).

Saved payment method (off-session). If the customer already has a saved, tokenized payment method, confirm server-side with the token — still no PAN:

const sub = await delopay.subscriptions.createAndConfirm(
  {
    item_price_id: priceId,
    customer_id: 'cus_abc123',
    payment_details: {
      payment_method: 'card',
      payment_method_id: savedPaymentMethodId, // token, not a card number
      setup_future_usage: 'off_session',
      return_url: 'https://example.com/subscription/complete',
    },
  },
  opts,
);

if (sub.redirect_url) {
  // Some processors (e.g. PayPal) still need buyer approval — redirect there.
} else {
  console.log(sub.status); // 'active'
}

You can also split create and confirm — call subscriptions.confirm(id, …) with the client_secret and a payment_token once the buyer has a token. Same rule: a payment_token / payment_method_id, never a raw card.

Manage the lifecycle. Pause, resume, and cancel take optional timing and proration controls; called with no body they act immediately:

await delopay.subscriptions.pause(sub.id, { pause_option: 'end_of_term' }, opts);
await delopay.subscriptions.resume(sub.id, undefined, opts);
await delopay.subscriptions.cancel(
  sub.id,
  { cancel_option: 'immediately', credit_option_for_current_term_charges: 'prorate' },
  opts,
);

// Retrieve one, or list for the profile
const current = await delopay.subscriptions.retrieve(sub.id, opts);
const all = await delopay.subscriptions.list({ limit: 20 }, opts);

Each billing cycle raises an invoice (sub.invoice) with its own payment leg (sub.payment); track cycle outcomes via the subscription/invoice webhooks.

Bill on a catalog product. Instead of the processor's item_price_id, name a recurring product of the shop (and optionally one of its prices). The server resolves it to the reference the billing account holds and refuses the request before anything is charged when the processor's price no longer matches. Check first which prices can start a subscription:

const { prices } = await delopay.products.billingReadiness(['prd_pro'], profileId);
// state: 'ready' | 'unverified' | 'other_environment' | 'needs_reference'

await delopay.subscriptions.createAndConfirm(
  { product_id: 'prd_pro', customer_id: 'cus_abc123', payment_details: { payment_method_id: pm } },
  opts,
);
await delopay.subscriptions.update(sub.id, { product_id: 'prd_pro', price_id: 'prc_yearly' }, opts);

Mirror the processor. subscriptions.sync({ subscription_ids }) (1 to 50) and subscriptions.syncOne(id) read subscriptions back from the processor and mirror what it reports; nothing is written there.

Take existing processor subscriptions over. subscriptions.importPreview lists what a billing account holds and classifies each row; importApply adopts up to 25 of them. Word capabilities.reason_code and each row's note_code yourself; the English reason / note beside them are for API callers.

const preview = await delopay.subscriptions.importPreview({ merchant_connector_id: mca }, opts);

if (!preview.capabilities.listing || !preview.capabilities.adoption) {
  // PayPal cannot be listed (`reason_code: 'listing_unsupported'`); an
  // account may list without allowing adoption. Say why, and stop.
  showUnavailable(preview.capabilities.reason_code);
} else {
  // Rows whose buyer is already a DeloPay customer can be taken over as they
  // are; `needs_customer` rows need a customer named first.
  const picks = preview.items.flatMap((row) =>
    row.classification === 'new' && row.customer_id
      ? [{ external_subscription_id: row.external_subscription_id, customer_id: row.customer_id }]
      : [],
  );
  // The route takes 1 to 25 selections: send nothing when there is nothing
  // to take over, and batch anything larger.
  for (let i = 0; i < picks.length; i += 25) {
    const { results } = await delopay.subscriptions.importApply(
      { merchant_connector_id: mca, items: picks.slice(i, i + 25) },
      opts,
    );
    // One result per selection: `adopted`, `already_held`, or `failed` with a `reason_code`.
  }
}

A buyer returning from the processor's checkout can read the subscription's status with the return token (rt) the return URL carried, from the browser:

const session = new CheckoutSession({ merchantId, paymentId: subscriptionId });
const { status, awaiting_processor } = await session.subscriptionReturnStatus(rt);

Platform fee rules

Price the platform fee by payment method, connector, amount, currency or card network. Build the rule program with feeProgram() — rules are tried in order, first match wins, otherwise the default applies:

import { Delopay, feeProgram } from '@delopay/sdk';

const delopay = new Delopay(process.env.DELOPAY_API_KEY ?? '');

const algorithm = feeProgram()
  .rule({ name: 'crypto', when: { paymentMethod: 'crypto' }, fee: { percentage: 1.0 } })
  .rule({
    name: 'card_on_cryptomus',
    when: { paymentMethod: 'card', connector: 'cryptomus' },
    fee: { percentage: 2.0 },
  })
  .otherwise({ percentage: 3.0 })
  .build();

await delopay.fees.rules.upsert({ algorithm }, 'merchant_abc123');

const program = await delopay.fees.rules.retrieve('merchant_abc123'); // or null
await delopay.fees.rules.delete('merchant_abc123'); // revert to flat schedules

Merchants without a rule program keep their existing flat fee schedules / volume tier unchanged.

Webhook verification

Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body and delivers the hex-encoded digest in the X-Webhook-Signature-512 header. Use express.raw() (not express.json()) so the bytes reach the verifier unchanged.

The verified event matches the wire body: { merchant_id, event_id, event_type, content: { type, object }, timestamp }. event_type says what happened (e.g. 'payment_succeeded'); content.type tags the payload kind (e.g. 'payment_details') — narrow on it to get a typed content.object (the payment/refund/dispute, with payment_id etc.).

import express from 'express';
import { Delopay } from '@delopay/sdk';

app.post('/webhooks/delopay', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.header('x-webhook-signature-512') ?? '';
  const secret = process.env.DELOPAY_WEBHOOK_SECRET!;

  let event;
  try {
    event = await Delopay.webhooks.verify(req.body, signature, secret);
  } catch {
    return res.status(400).send('Invalid signature');
  }

  if (event.content.type === 'payment_details') {
    const payment = event.content.object; // typed: PaymentResponse
    switch (event.event_type) {
      case 'payment_succeeded':
        // fulfil order — payment.payment_id, payment.amount, payment.currency
        break;
      case 'payment_failed':
        // notify customer — payment.error_message
        break;
    }
  }

  res.json({ received: true });
});

Error Handling

All errors are instances of DelopayError:

import { Delopay, DelopayError, DelopayAuthenticationError } from '@delopay/sdk';

try {
  const payment = await delopay.payments.retrieve('pay_does_not_exist');
} catch (err) {
  if (err instanceof DelopayAuthenticationError) {
    // status: 401 — invalid or missing API key
    console.error('Check your API key');
  } else if (err instanceof DelopayError) {
    console.error(err.message); // human-readable message
    console.error(err.status); // HTTP status code
    console.error(err.code); // machine-readable error code
    console.error(err.type); // error category (e.g. 'not_found')
    console.error(err.data); // structured context for select codes (see below)
  }
}

Error classes:

ClassWhen
DelopayErrorBase class for all API errors
DelopayAuthenticationError401 — invalid or missing API key

Network timeouts throw DelopayError with code: 'TIMEOUT'. Network failures throw with code: 'NETWORK'.

An export (delopay.export.*, disputes.workspaceExport) is held to the record count the server announces in X-Delopay-Record-Count: a file that arrives with a different number of records — or ends part-way through a line or a JSON document — throws with code: 'EXPORT_INCOMPLETE' (err.data is { expected_records, received_records }) instead of resolving to a file that is missing rows. Pass onProgress in an export's options to follow the download, and signal to cancel it.

A live stream (delopay.live.stream(), dashboard JWT) throws with code: 'NOT_EVENT_STREAM' when the server answers 2xx with something other than text/event-stream, and with code: 'TIMEOUT' when it stays silent past idleTimeoutMs (default 45 s). It is never reconnected by the SDK: see the integration guide's Live updates for dashboards.

Structured error context (err.data): populated for a small set of codes that benefit from a machine-readable hint. Currently:

err.codeerr.data shapeMeaning
UR_48{ retry_after_secs: number }TOTP attempt counter locked out — wait this many seconds before retrying.
UR_63{ retry_after_secs: number }Auth-endpoint rate limit tripped — wait this many seconds before retrying.

TypeScript

The SDK is written in strict TypeScript. All request and response shapes are fully typed. Import types directly when needed:

import type { PaymentResponse, PaymentCreateRequest, Currency } from '@delopay/sdk';

Environments

EnvironmentBase URLAPI key prefix
Productionhttps://api.delopay.netprd_… / pk_prd_…
Sandboxhttps://sandbox.delopay.netsnd_… / pk_snd_…
// Sandbox
const delopay = new Delopay(process.env.DELOPAY_API_KEY!, { sandbox: true });

License

MIT

版本与发布记录

@delopay/sdk 共发布 314 个版本,首次发布于 2026/04/15;过去 12 个月发布了 314 个版本。

版本发布日期距今
0.385.0latest2026/11/12今天
0.384.02026/11/12今天
0.383.02026/11/11今天
0.380.02026/11/101 天前
0.379.02026/11/092 天前
0.378.02026/11/092 天前

其他发布通道:backfill → 0.169.0。

关于 @delopay/sdk 的常见问题

@delopay/sdk 是什么?

Official Delopay TypeScript SDK

如何安装 @delopay/sdk?

在项目目录运行 npm install @delopay/sdk,或使用 pnpm add / yarn add / bun add。

@delopay/sdk 支持 TypeScript 吗?

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

@delopay/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@delopay/sdk 需要什么版本的 Node.js?

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

@delopay/sdk 可以商用吗?

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

@delopay/sdk 的下载量有多少?

根据 npm 官方下载统计,@delopay/sdk 在 2026/10/12 – 2026/11/10 这 30 天内被下载 15,497 次,日均约 517 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@delopay/sdk 还在维护吗?

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