Skip to content
Docs for developers
Guidev1

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.

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. 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.

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. 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.

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');

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.

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'),
));

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.

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,
));

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.

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.

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,
));

Prepayment states

Every prepayment in the list and detail responses carries a derived state.

StateMeaningBecomes
openremaining is above zeroconsumed once sales use all of it; refunded after a refund
consumedremaining is zero, no refundFinal
refundedA refund was registeredFinal

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.