跳转到内容
开发者文档
指南v1

通过 API 处理预付款

如何通过 VCR.AM API 收取预付款:预付款收据、余额查询、在销售中使用预付款以及退款。附 TypeScript 和 PHP 示例。

两张收据

第 1976-Ն 号政府决议规定了电子收银机(e-HDM)的两种收据:预付款收据,用于在交付商品或提供服务之前收取的款项;以及普通销售收据(附件 5 第 12–14 点)。使用预付款的销售以“使用预付款”付款方式记录该金额(附件 5 第 18 点)。预付款收据不包含商品、合计和增值税(附件 7 第 2 点)。

因此需要两次调用:客户支付预付款时调用 POST /prepayments,之后调用 POST /sales,并在 amount.prepayment 中填写预付款金额。SRC 只接收这个金额;请求中没有任何内容把销售与某张预付款收据关联起来。

VCR.AM 在每家公司的台账中替您保存这种关联:每笔预付款、使用和退款都是一行记录,以买方的税号(TIN)为键。每笔预付款都会返回计算得出的 remaining 和 state 字段。

1. 登记预付款

客户付款后立即登记预付款。buyer 为必填项:带税号的公司,或个人。

TypeScript
import { VCRClient } from "@blob-solutions/vcr-am-sdk";

const apiKey = process.env.VCR_API_KEY;
if (apiKey === undefined) throw new Error("VCR_API_KEY is not set");
const client = new VCRClient(apiKey);

const prepayment = await client.registerPrepayment({
  cashier: { id: 1 },
  amount: { nonCash: "5000" },
  // A company buyer: the ledger tracks its balance by TIN.
  // For a private person: buyer: { type: "individual" }
  buyer: { type: "business_entity", tin: "01234567" },
});

// Keep prepayment.prepaymentId: a refund needs it.
PHP
use BlobSolutions\VcrAm\Input\Buyer;
use BlobSolutions\VcrAm\Input\CashierId;
use BlobSolutions\VcrAm\Input\PrepaymentAmount;
use BlobSolutions\VcrAm\Input\RegisterPrepaymentInput;
use BlobSolutions\VcrAm\VcrClient;

$client = new VcrClient(apiKey: $apiKey);

$prepayment = $client->registerPrepayment(new RegisterPrepaymentInput(
    cashier: CashierId::byInternalId(1),
    amount: new PrepaymentAmount(nonCash: '5000'),
    // For a private person: Buyer::individual()
    buyer: Buyer::businessEntity(tin: '01234567'),
));

// Keep $prepayment->prepaymentId: a refund needs it.

2. 查询余额

客户再次光临时,用其付款时使用的税号查询未使用余额。余额按整个公司计算:如果一个税号下有多台收银机,收银员无需知道是哪一台收取了预付款。

TypeScript
const balance = await client.getCustomerPrepaymentBalance({
  customerRef: "01234567", // the buyer's TIN
});

console.log(balance.balance); // 5000
for (const open of balance.openPrepayments) {
  console.log(`Prepayment #${open.prepaymentId}: ${open.remaining} left`);
}
PHP
$balance = $client->getCustomerPrepaymentBalance('01234567');

echo $balance->balance; // 5000.0
foreach ($balance->openPrepayments as $open) {
    printf("Prepayment #%d: %s left\n", $open->prepaymentId, $open->remaining);
}

3. 列表与对账

用于对账或您自己的台账视图时,可以列出在当前收银机上登记的预付款。可按 customerRef(买方税号)或 state 筛选。

TypeScript
const open = await client.listPrepayments({ state: "open" });
const forCustomer = await client.listPrepayments({ customerRef: "01234567" });
PHP
use BlobSolutions\VcrAm\PrepaymentState;

$open = $client->listPrepayments(state: PrepaymentState::Open);
$forCustomer = $client->listPrepayments(customerRef: '01234567');

该接口最多返回最近的 500 笔预付款,state 筛选只在这 500 笔中进行。在业务繁忙的收银机上,请按 customerRef 筛选以查到更早的记录。

4. 在销售中使用预付款

使用预付款就是一笔普通销售,在 amount 中填写 prepayment,根据需要与 cash、nonCash 或 compensation 组合。各项金额之和必须等于销售总额:收据不能让部分价款处于未付状态。

TypeScript
const sale = await client.registerSale({
  cashier: { id: 1 },
  buyer: { type: "business_entity", tin: "01234567" },
  // 5,000 from the advance, the remaining 10,000 paid now by card.
  amount: { prepayment: "5000", nonCash: "10000" },
  items: [
    {
      offer: { externalId: "legal-service" },
      quantity: "1",
      price: "15000",
      unit: "pc",
    },
  ],
});
PHP
use BlobSolutions\VcrAm\Input\Offer;
use BlobSolutions\VcrAm\Input\RegisterSaleInput;
use BlobSolutions\VcrAm\Input\SaleAmount;
use BlobSolutions\VcrAm\Input\SaleItem;
use BlobSolutions\VcrAm\Unit;

$sale = $client->registerSale(RegisterSaleInput::withAmount(
    cashier: CashierId::byInternalId(1),
    items: [
        new SaleItem(
            offer: Offer::existing('legal-service'),
            quantity: '1',
            price: '15000',
            unit: Unit::Piece,
        ),
    ],
    // 5,000 from the advance, the remaining 10,000 paid now by card.
    amount: new SaleAmount(prepayment: '5000', nonCash: '10000'),
    buyer: Buyer::businessEntity(tin: '01234567'),
));

如果买方是公司,台账会从该税号的未使用预付款中扣减金额,从最早的一笔开始。

如果某笔预付款属于特定订单,请用 prepaymentId 指定它。这样销售只会扣减这笔预付款,不会动其他预付款,其他预付款仍可退款。个人买方同样适用。

TypeScript
const sale = await client.registerSale({
  cashier: { id: 1 },
  buyer: { type: "individual" },
  amount: { prepayment: "15000" },
  // The prepayment taken for this order.
  prepaymentId: prepayment.prepaymentId,
  items: [
    {
      offer: { externalId: "legal-service" },
      quantity: "1",
      price: "15000",
      unit: "pc",
    },
  ],
});
PHP
$sale = $client->registerSale(RegisterSaleInput::withAmount(
    cashier: CashierId::byInternalId(1),
    items: [
        new SaleItem(
            offer: Offer::existing('legal-service'),
            quantity: '1',
            price: '15000',
            unit: Unit::Piece,
        ),
    ],
    amount: new SaleAmount(prepayment: '15000'),
    buyer: Buyer::individual(),
    // The prepayment taken for this order.
    prepaymentId: $prepayment->prepaymentId,
));

如果预付款不足以支付 amount.prepayment,销售会在发送到 SRC 之前以 400 被拒绝:预付款已退款、可用金额不足,或属于其他 TIN。不会保存任何内容,请使用新的 Idempotency-Key 发送更正后的销售。未知编号返回 404。在销售等待 SRC 答复期间,其金额被保留:其他销售无法使用,对该预付款的退款会返回 409,直到销售完成。

5. 退款

退款退还全部预付款;使用 returnRemainder: true 时退还销售后剩余的全部金额。请求中提供预付款编号和原因,不提供金额。

TypeScript
await client.registerPrepaymentRefund({
  cashier: { id: 1 },
  prepaymentId: prepayment.prepaymentId,
  reason: "customer_request",
});
PHP
use BlobSolutions\VcrAm\Input\RegisterPrepaymentRefundInput;
use BlobSolutions\VcrAm\RefundReason;

$client->registerPrepaymentRefund(new RegisterPrepaymentRefundInput(
    cashier: CashierId::byInternalId(1),
    prepaymentId: $prepayment->prepaymentId,
    reason: RefundReason::CustomerRequest,
));
TypeScript
await client.registerPrepaymentRefund({
  cashier: { id: 1 },
  prepaymentId: prepayment.prepaymentId,
  returnRemainder: true,
  reason: "customer_request",
});
PHP
$client->registerPrepaymentRefund(new RegisterPrepaymentRefundInput(
    cashier: CashierId::byInternalId(1),
    prepaymentId: $prepayment->prepaymentId,
    returnRemainder: true,
    reason: RefundReason::CustomerRequest,
));

预付款状态

列表和详情响应中的每笔预付款都带有计算得出的 state 字段。

状态含义转变为
openremaining 大于零销售用完全部金额后变为 consumed;退款后变为 refunded
consumedremaining 为零,没有退款最终状态
refunded已登记退款最终状态

使用 VCR.AM 之前收取的预付款

在开始使用 VCR.AM 之前收取的预付款,可以在控制台的公司页面添加到台账中(“导入现有预付款”)。导入不会联系 SRC。导入的金额只在控制台中可见:API 余额查询和销售中的自动扣减都看不到它们。API 中没有导入接口。