Prepayment workflow
Taking an advance through the VCR.AM API: the prepayment receipt, balance lookup, using the advance in a sale, and refunds. Examples in TypeScript and PHP.
Two receipts
Government Decision 1976-Ն defines two kinds of e-HDM receipt: a prepayment receipt for money taken before the goods or service are delivered, and a regular sale receipt (Annex 5, points 12–14). The sale that uses the advance records it as the payment type «use of prepayment» (Annex 5, point 18). A prepayment receipt carries no items, totals or VAT (Annex 7, point 2).
So the flow is two calls: POST /prepayments when the customer pays the advance, and later POST /sales with the advance in amount.prepayment. SRC receives only that amount; nothing on the wire links the sale to a particular prepayment receipt.
VCR.AM keeps the link for you in a ledger per company: every deposit, use and refund is a row, keyed by the buyer's TIN. Each prepayment comes back with derived remaining and state fields.
1. Register the advance
Register the prepayment as soon as the customer pays. buyer is required: a company with its TIN, or an individual.
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. Look up the balance
When the customer comes back, look up their open balance by the TIN they paid under. The result covers the whole company: with several registers under one TIN, the cashier doesn't need to know which one took the advance.
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. List and reconcile
For reconciliation or your own ledger view, list the prepayments registered on the calling register. Filter by customerRef (the buyer's TIN) or by 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');The endpoint returns at most the 500 most recent prepayments, and state is applied within those 500. On a busy register, filter by customerRef to reach older ones.
4. Use the advance in a sale
Spending the advance is a regular sale with a prepayment entry in amount, combined with cash, nonCash or compensation as needed. The amounts must add up to the sale total: a receipt cannot leave part of the price unpaid.
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'),
));For a company buyer, the ledger takes the amount from that TIN's open prepayments, oldest first.
When a prepayment belongs to a specific order, name it with prepaymentId. The sale then spends exactly that prepayment and leaves every other one alone, so the others stay refundable. This also works for individual buyers.
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,
));A prepayment that cannot cover amount.prepayment refuses the sale with 400 before anything reaches SRC: it is refunded, too little of it is available, or it belongs to another TIN. Nothing is saved, so send the corrected sale under a new Idempotency-Key. An unknown id answers 404. While the sale waits on SRC its amount is held: another sale cannot take it, and refunding that prepayment answers 409 until the sale settles.
prepaymentId, the ledger cannot tell whose advance is being spent and takes it from the company's oldest open prepayment, whoever paid it. The receipt is correct; the remaining figures in VCR.AM are not, and that older prepayment can no longer be refunded. Name the prepayment instead.prepaymentId, neither SRC nor VCR.AM rejects a prepayment amount larger than the customer's open balance. The sale goes through and its ledger row is flagged needsReview in the dashboard. Call getCustomerPrepaymentBalance before the sale.5. Refund
A refund returns the whole prepayment, or with returnRemainder: true everything sales left of it. The request names the prepayment and a reason, never an amount.
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 and nothing is saved. Send returnRemainder: true to return what is left; if sales used everything the answer is 400. A prepayment has one refund, so the remainder goes back in one go, split the way it was paid. The prepayment receipt then shows the remainder and the mark required by Annex 5, point 15. Refunding a sale does not restore the prepayment it used.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,
));Prepayment states
Every prepayment in the list and detail responses carries a derived state.
| State | Meaning | Becomes |
|---|---|---|
| open | remaining is above zero | consumed once sales use all of it; refunded after a refund |
| consumed | remaining is zero, no refund | Final |
| refunded | A refund was registered | Final |
Advances taken before VCR.AM
Advances taken before you started with VCR.AM can be added to the ledger from the company page in the dashboard («Import an existing prepayment»). The import does not contact SRC. Imported balances are visible only in the dashboard: the API balance lookup and the automatic attribution of sales do not see them. There is no API endpoint for the import.