通过 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 为必填项:带税号的公司,或个人。
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.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. 查询余额
客户再次光临时,用其付款时使用的税号查询未使用余额。余额按整个公司计算:如果一个税号下有多台收银机,收银员无需知道是哪一台收取了预付款。
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`);
}$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 筛选。
const open = await client.listPrepayments({ state: "open" });
const forCustomer = await client.listPrepayments({ customerRef: "01234567" });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 组合。各项金额之和必须等于销售总额:收据不能让部分价款处于未付状态。
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",
},
],
});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 指定它。这样销售只会扣减这笔预付款,不会动其他预付款,其他预付款仍可退款。个人买方同样适用。
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",
},
],
});$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,直到销售完成。
prepaymentId,台账无法判断使用的是谁的预付款,会从公司最早的一笔未使用预付款中扣减,无论是谁支付的。收据是正确的,但 VCR.AM 中的 remaining 数值不正确,而且那笔较早的预付款将无法再退款。请明确指定预付款。prepaymentId 时,SRC 和 VCR.AM 都不会拒绝超过客户未使用余额的 prepayment 金额。销售会成功,其台账记录会在控制台中标记为 needsReview。请在销售前调用 getCustomerPrepaymentBalance。5. 退款
退款退还全部预付款;使用 returnRemainder: true 时退还销售后剩余的全部金额。请求中提供预付款编号和原因,不提供金额。
await client.registerPrepaymentRefund({
cashier: { id: 1 },
prepaymentId: prepayment.prepaymentId,
reason: "customer_request",
});use BlobSolutions\VcrAm\Input\RegisterPrepaymentRefundInput;
use BlobSolutions\VcrAm\RefundReason;
$client->registerPrepaymentRefund(new RegisterPrepaymentRefundInput(
cashier: CashierId::byInternalId(1),
prepaymentId: $prepayment->prepaymentId,
reason: RefundReason::CustomerRequest,
));409 被拒绝,且不保存任何内容。发送 returnRemainder: true 以退还剩余部分;如果销售已全部使用,响应为 400。一笔预付款只有一次退款,因此剩余金额一次性按原付款方式退还。之后预付款收据会显示剩余金额及附件 5 第 15 点要求的标记。销售退款不会恢复已使用的预付款。await client.registerPrepaymentRefund({
cashier: { id: 1 },
prepaymentId: prepayment.prepaymentId,
returnRemainder: true,
reason: "customer_request",
});$client->registerPrepaymentRefund(new RegisterPrepaymentRefundInput(
cashier: CashierId::byInternalId(1),
prepaymentId: $prepayment->prepaymentId,
returnRemainder: true,
reason: RefundReason::CustomerRequest,
));预付款状态
列表和详情响应中的每笔预付款都带有计算得出的 state 字段。
| 状态 | 含义 | 转变为 |
|---|---|---|
| open | remaining 大于零 | 销售用完全部金额后变为 consumed;退款后变为 refunded |
| consumed | remaining 为零,没有退款 | 最终状态 |
| refunded | 已登记退款 | 最终状态 |
使用 VCR.AM 之前收取的预付款
在开始使用 VCR.AM 之前收取的预付款,可以在控制台的公司页面添加到台账中(“导入现有预付款”)。导入不会联系 SRC。导入的金额只在控制台中可见:API 余额查询和销售中的自动扣减都看不到它们。API 中没有导入接口。