PSPG eHDM API

Get started with docs

How it works

  • 1. Get WooCommerce order
  • 2. Sync WooCommerce order items into eHDM
  • 3. Get prepared items from eHDM storage
  • 4. Call $eHDM->print() with the required currency argument and amounts in that currency
  • 5. Receive the fiscal receipt response with amounts in AMD

Currency and conversion to AMD

The currency argument is required for every print() call, including AMD receipts. Supported currencies are AMD, USD, EUR and RUB. Numeric currency codes are also accepted as strings: '051', '840', '978' and '643', respectively.

Foreign-currency receipt printing is disabled by default, including after updating an existing site. To enable it, open Փոխարժեքներ (Exchange rates), select Արտարժույթով պատվերների կտրոնները տպել ՀՀ դրամով and click the Save button beside that checkbox. While this setting is off, new AMD receipts continue to work, but new USD, EUR and RUB receipts are not printed. Viewing, emailing, copying and returning existing receipts remain available. Retrieving rates or enabling their automatic updates does not enable foreign-currency receipt printing.

  • For WooCommerce orders, use $currency = (string) $order->get_currency();. Do not use the current store currency for an existing order.
  • Pass all payment amounts, item prices and monetary discounts in the same source currency specified by currency. Do not convert them to AMD before calling print().
  • AMD requests keep their monetary values unchanged. For USD, EUR and RUB, conversion happens once inside print(), immediately before the fiscal print request.
  • The conversion uses the currently selected saved rate at print time, not the rate at order creation or payment time. Printing does not fetch a new rate from the bank.
  • Item quantities and percentage discounts are not converted. Monetary discounts are converted: discountType = 2 or 4, and additionalDiscountType = 16. Percentage types 1 and 8 retain their values.
  • For foreign-currency goods receipts (mode = 2), the AMD total is calculated from the converted item prices, quantities and discounts. Monetary conversion uses two decimal places, so the result can differ slightly from directly converting the order total. Mixed payment amounts are allocated proportionally to this AMD total.
  • For foreign-currency receipts without items (mode = 3), the payment amount is converted directly to AMD. The original payment or WooCommerce order is not changed.

Configure rates in the eHDM dashboard under Փոխարժեքներ (Exchange rates). Choose cba.am or a manual rate for each foreign currency. A rate is expressed as AMD per one unit of the source currency. For example, at an illustrative rate of 360.93, a payment of 10 USD becomes 3,609.30 AMD.

Automatic CBA updates are disabled by default. Enable Ինքնաշխատ թարմացում and save the schedule to turn them on. The default scheduled time is 16:15, Asia/Yerevan, independent of the WordPress site timezone. The manual CBA refresh button remains available when automatic updates are off. Without automatic updates, printing uses the selected saved rate; if no valid rate is available, the fiscal print request is not sent. WP-Cron execution depends on site traffic or a server cron configuration.

After successful printing, the original currency, original amount and applied rate are saved with the receipt. Later rate changes do not recalculate that receipt. The Tax Service response and its QR payment amounts are in AMD; the conversion snapshot is stored in eHDM history, not added as new fields to the Tax Service response.

Local validation errors return code = -3 with an error_code and errorMessage. Examples include currency_required, foreign_currency_print_disabled, unsupported_currency and rate_unavailable. Check the result before treating the receipt as printed.

Supported scenarios

  • Card payment — full payment by card
  • Cash payment — full payment in cash
  • Mixed payment — part by card, part in cash
  • Partial payment — payment goes into partialAmount
  • Prepayment usage — payment goes into prePaymentAmount

Base setup for the WooCommerce examples. The amount and prepared items must use the order currency:

$eHDM = apply_filters( 'planetstudio-payment-gateway-ehdm', null );

$wc_order_id = 2708;
$order       = wc_get_order( $wc_order_id );
$amount      = (float) $order->get_total();
$currency    = (string) $order->get_currency();
$order_id    = '3b2a2c1b-a850-4edd-abf9-bb2a36e1078a';

$eHDM->getWoocommersOrderItems( $wc_order_id );
$items = $eHDM->getWoocommersOrderItemsFromDB( $wc_order_id );

The response examples for Scenarios 1-5 illustrate AMD receipts with a total of 1 AMD, not fixed results for order 2708. Actual identifiers and totals come from the Tax Service. Foreign-currency orders produce AMD receipt amounts after conversion.

Scenario 1: Card payment

$result = $eHDM->print(
	array(
		'orderId'          => $order_id,
		'wc_orderId'       => $wc_order_id,
		'currency'         => $currency,
		'cardAmount'       => $amount,
		'cashAmount'       => 0,
		'partialAmount'    => 0,
		'prePaymentAmount' => 0,
		'cashierId'        => (int) get_option( 'ehdm_default_cashierId', 1 ),
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Expected result:

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "824",
		"crn": "52014201",
		"sn": "2FECD1F8",
		"tin": "00493113",
		"taxpayer": "...",
		"address": "...",
		"time": 1774363086000,
		"fiscal": "30247466",
		"total": 1,
		"change": 0,
		"qr": "TIN: ..., TOTAL_CASH: 0, TOTAL_NONCASH: 1, PREP_USAGE: 0, PARTIAL: 0, TOTAL: 1"
	}
}

Scenario 2: Cash payment

$result = $eHDM->print(
	array(
		'orderId'          => '',
		'wc_orderId'       => $wc_order_id,
		'currency'         => $currency,
		'cardAmount'       => 0,
		'cashAmount'       => $amount,
		'partialAmount'    => 0,
		'prePaymentAmount' => 0,
		'cashierId'        => (int) get_option( 'ehdm_default_cashierId', 1 ),
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Expected result:

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "825",
		"crn": "52014201",
		"sn": "2FECD1F8",
		"tin": "00493113",
		"taxpayer": "...",
		"address": "...",
		"time": 1774363177000,
		"fiscal": "46682581",
		"total": 1,
		"change": 0,
		"qr": "TIN: ..., TOTAL_CASH: 1, TOTAL_NONCASH: 0, PREP_USAGE: 0, PARTIAL: 0, TOTAL: 1"
	}
}

Scenario 3: Mixed payment

$card_amount = round( $amount * 0.6, 2 );
$cash_amount = round( $amount - $card_amount, 2 );

$result = $eHDM->print(
	array(
		'orderId'          => $order_id,
		'wc_orderId'       => $wc_order_id,
		'currency'         => $currency,
		'cardAmount'       => $card_amount,
		'cashAmount'       => $cash_amount,
		'partialAmount'    => 0,
		'prePaymentAmount' => 0,
		'cashierId'        => (int) get_option( 'ehdm_default_cashierId', 1 ),
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Expected result:

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "826",
		"crn": "52014201",
		"sn": "2FECD1F8",
		"tin": "00493113",
		"taxpayer": "...",
		"address": "...",
		"time": 1774363289000,
		"fiscal": "25105467",
		"total": 1,
		"change": 0,
		"qr": "TIN: ..., TOTAL_CASH: 0.4, TOTAL_NONCASH: 0.6, PREP_USAGE: 0, PARTIAL: 0, TOTAL: 1"
	}
}

Scenario 4: Partial payment

$result = $eHDM->print(
	array(
		'orderId'          => $order_id,
		'wc_orderId'       => $wc_order_id,
		'currency'         => $currency,
		'cardAmount'       => 0,
		'cashAmount'       => 0,
		'partialAmount'    => $amount,
		'prePaymentAmount' => 0,
		'cashierId'        => (int) get_option( 'ehdm_default_cashierId', 1 ),
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Expected result:

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "827",
		"crn": "52014201",
		"sn": "2FECD1F8",
		"tin": "00493113",
		"taxpayer": "...",
		"address": "...",
		"time": 1774363388000,
		"fiscal": "35778064",
		"total": 1,
		"change": 0,
		"qr": "TIN: ..., TOTAL_CASH: 0, TOTAL_NONCASH: 0, PREP_USAGE: 0, PARTIAL: 1, TOTAL: 1"
	}
}

Scenario 5: Prepayment usage

$result = $eHDM->print(
	array(
		'orderId'          => $order_id,
		'wc_orderId'       => $wc_order_id,
		'currency'         => $currency,
		'cardAmount'       => 0,
		'cashAmount'       => 0,
		'partialAmount'    => 0,
		'prePaymentAmount' => $amount,
		'cashierId'        => (int) get_option( 'ehdm_default_cashierId', 1 ),
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Expected result:

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "828",
		"crn": "52014201",
		"sn": "2FECD1F8",
		"tin": "00493113",
		"taxpayer": "...",
		"address": "...",
		"time": 1774363457000,
		"fiscal": "10900220",
		"total": 1,
		"change": 0,
		"qr": "TIN: ..., TOTAL_CASH: 0, TOTAL_NONCASH: 0, PREP_USAGE: 1, PARTIAL: 0, TOTAL: 1"
	}
}

Prepayment note

Receiving a prepayment and using an existing prepayment are different scenarios. The prepayment-usage example above prints a goods receipt with mode = 2, an item list and the used amount in prePaymentAmount.

  • Goods receipts (mode = 2) use an item list, including the prepayment-usage example above.
  • To receive a prepayment without goods (mode = 3), pass the received payment in cardAmount or cashAmount.
  • Do not use prePaymentAmount as a substitute for a newly received card or cash payment. The fiscal scenario must match the actual transaction.

Prepayment without items

The following example receives a new non-cash prepayment of 1 AMD, without goods. PayLink, PayForm and WPForms use this itemless prepayment-style flow. Replace the example bank order ID with the actual payment identifier.

$result = $eHDM->print([
	'orderId'          => 'BANK-ORDER-ID',
	'wc_orderId'       => 0,
	'currency'         => 'AMD',
	'cardAmount'       => 1,
	'cashAmount'       => 0,
	'partialAmount'    => 0,
	'prePaymentAmount' => 0,
	'cashierId'        => 1,
	'mode'             => 3,
	'partnerTin'       => null,
	'items'            => [],
]);

Items source

You can provide items for print() in two different ways depending on your flow.

  • 1. From WooCommerce order (real-time)

    Use this method when you want to generate items directly from the WooCommerce order.
$items = $eHDM->getWoocommersOrderItems( $wc_order_id );

This will build the items list from the current order data without relying on previously saved receipts.

  • 2. From eHDM prepared items storage

    Use this method when items were already prepared and stored earlier.
$items = $eHDM->getWoocommersOrderItemsFromDB( $wc_order_id );

This reads the previously prepared WooCommerce items, not the fiscal receipt returned by the Tax Service. The prepared items remain in the source order currency. Pass that same currency to print(); do not convert or relabel the cached prices as AMD.

Custom items list

If you are not using WooCommerce order items, you can pass a custom items array manually.

$items = array(
	array(
		'adgCode'                => '9205',
		'dep'                    => 1,
		'goodCode'               => '9205-13',
		'goodName'               => 'երգեհոն',
		'quantity'               => 2,
		'unit'                   => 'հատ',
		'price'                  => 25000,
		'additionalDiscount'     => null,
		'additionalDiscountType' => null,
		'discount'               => null,
		'discountType'           => null,
	),
);

$result = $eHDM->print(
	array(
		'orderId'          => 'CUSTOM-ORDER-1',
		'wc_orderId'       => 0,
		'currency'         => 'AMD',
		'cardAmount'       => 50000,
		'cashAmount'       => 0,
		'partialAmount'    => 0,
		'prePaymentAmount' => 0,
		'cashierId'        => 1,
		'mode'             => 2,
		'partnerTin'       => null,
		'items'            => $items,
	)
);

Receipt returns

Use the return flow to create a fiscal return receipt for a previously issued receipt. First request the original receipt information, then create the return receipt using the returned item identifiers and quantities.

1. Get return information

$receipt_id = 824;
$return_info = $eHDM->getReturnedReceiptInfo( $receipt_id );
if ( ! is_array( $return_info ) || 0 !== (int) ( $return_info['code'] ?? -1 ) ) {
	// The Tax Service did not return information for this receipt.
	return;
}
$receipt = $return_info['result'];
$items   = $receipt['items'] ?? array();

The result contains the original receipt data. For goods receipts, result.items contains the returnable items. Use each item’s receiptProductId and the quantity to be returned when creating the return receipt.

2. Print a return receipt

// Return one unit of the first item from the original receipt.
$return_item_list = array(
	array(
		'receiptProductId' => (int) $items[0]['receiptProductId'],
		'quantity'         => 1,
	),
);
$result = $eHDM->printReturnReceipt(
	array(
		'receiptId'           => $receipt_id,
		'cardAmountForReturn' => 0,
		'cashAmountForReturn' => 5000,
		'returnItemList'      => $return_item_list,
	)
);

For a card refund, set cardAmountForReturn and leave cashAmountForReturn as 0. For a cash refund, do the opposite. The refund amount must match the selected return items and must not exceed the original receipt amount.

Return amounts are in AMD. Use the amounts and item data from the original fiscal receipt returned by getReturnedReceiptInfo(). Do not convert them again using the current rate. The currency argument is required by print(), not by printReturnReceipt().

For receipts without goods, such as a prepayment receipt, use the data returned by getReturnedReceiptInfo() and pass an empty returnItemList only when the Tax Service response does not contain returnable items.

Return result

{
	"code": 0,
	"message": "OK",
	"errorMessage": null,
	"result": {
		"receiptId": "829",
		"fiscal": "...",
		"total": 5000,
		"qr": "TIN: ..., TOTAL_CASH: 5000, TOTAL_NONCASH: 0, ..."
	}
}

A successful response has code = 0. The new return receipt identifier is available in result.receiptId, and its original QR data is in result.qr. When generating a QR image from a stored return receipt, pass the internal receipt_row_id to getReceiptQR() as described below to select that exact record.

Return parameters

ParameterTypeDescription
receiptIdintOriginal fiscal receipt ID to return.
cardAmountForReturndoubleAmount in AMD returned to a card or another non-cash payment method.
cashAmountForReturndoubleAmount in AMD returned in cash.
returnItemListarrayItems to return. Each item contains receiptProductId from getReturnedReceiptInfo() and the return quantity.

Notes

  • Always call getReturnedReceiptInfo() before printReturnReceipt().
  • Do not create receiptProductId values manually; use the values returned by the Tax Service.
  • A return receipt is a new fiscal receipt and has its own receipt ID and QR data.

Receipt QR

You can generate a QR code image for a stored receipt using getReceiptQR(), or retrieve its original fiscal QR string using getReceiptQRData(). Both methods accept an optional internal receipt_row_id to select an exact stored receipt.

Usage

getReceiptQR( $receiptId, $url = false, $receipt_row_id = 0 )
getReceiptQRData( $receiptId, $receipt_row_id = 0 )
ArgumentTypeDescription
receiptIdint|stringRequired fiscal receipt number returned by the Tax Service as result.receiptId. This number is not globally unique and may repeat for different registers or test/live modes.
urlboolOptional, for getReceiptQR() only. Pass true to return the image URL; the default false returns an HTML image tag.
receipt_row_idintOptional; defaults to 0. The internal id of the corresponding record in the eHDM receipts database table. Pass a positive ID to select that exact record. It is not a fiscal receipt number, a WooCommerce order ID or a PSPG order number.

Use receipt_row_id whenever the exact stored record is known. Take it from the corresponding record’s id in {WordPress table prefix}ehdm_receipts. The fiscal number must also match that record. The print() response does not include this internal database ID; do not use result.receiptId in its place.

For example, if the required stored record has internal id = 1234 and fiscal receiptId = 824:

$receipt_id     = 824;
$receipt_row_id = 1234; // Internal ID of the exact stored receipt; illustrative only.

$url     = $eHDM->getReceiptQR( $receipt_id, true, $receipt_row_id );
$html    = $eHDM->getReceiptQR( $receipt_id, false, $receipt_row_id );
$qr_data = $eHDM->getReceiptQRData( $receipt_id, $receipt_row_id );

$qr_data contains the original qr string from the stored Tax Service response, not an image URL. Calls without receipt_row_id remain supported when the fiscal receipt number can be resolved unambiguously:

$url = $eHDM->getReceiptQR( 824, true );

Returns QR code image URL.

$html = $eHDM->getReceiptQR( 824 );

Returns ready-to-use HTML image tag.

$qr_data = $eHDM->getReceiptQRData( 824 );

Returns the original fiscal QR string, provided the receipt can be resolved unambiguously.

Example result

https://api.qrserver.com/v1/create-qr-code/?data=...&size=300x300
<img class="ehdm-qr" src="https://api.qrserver.com/..." alt="eHDM QR Code">

How it works

  • Loads the exact receipt by receiptId and receipt_row_id when a positive internal ID is provided. Without it, resolves the fiscal number only when there is no ambiguity between different receipts.
  • Extracts qr field
  • Encodes data into QR format
  • Generates QR image via external service

Notes

  • receiptId must exist in eHDM
  • getReceiptQR() returns false if the receipt is missing, ambiguous or has no QR data. getReceiptQRData() returns an empty string in those cases.
  • If receipt_row_id is provided but does not match receiptId, the methods do not fall back to another record. Old receipts can still be selected by their exact internal ID after a register or test/live setting change.
  • A failed QR lookup does not mean fiscal printing failed. Do not print another receipt merely because QR lookup returned no result.
  • QR contains full fiscal information
  • You can use URL or ready HTML depending on your needs

Discount usage

Item discounts are passed through discount/discountType and additionalDiscount/additionalDiscountType. Monetary discount values must use the source currency; percentage values must remain percentages.

  • 1. Product discount / discount
    • % PERCENT — percentage value, discountType = 1
    • PRICE (source currency) — discount applied per product / unit, discountType = 2. Calculation: (initial_price – discount) * quantity
    • TOTAL (source currency) — discount applied to the total quantity, discountType = 4. Calculation: (price * quantity) – discount
  • 2. Additional product discount / additionalDiscount
    • additionalDiscountType = 16 — an additional monetary discount, in the source currency, subtracted from the line total after the product discount.
    • additionalDiscountType = 8 — an additional percentage discount applied to the line total after the product discount.

What the response means

  • code = 0 — success
  • message = OK — receipt was printed successfully
  • errorMessage = null — no error
  • result.receiptId — receipt ID
  • result.fiscal — fiscal number
  • result.total — total fiscal receipt amount in AMD
  • result.change — change amount in AMD
  • result.qr — fiscal QR string with payment breakdown

QR fields

  • TOTAL_CASH — cash amount
  • TOTAL_NONCASH — card amount
  • PREP_USAGE — used prepayment amount
  • PARTIAL — partial payment amount
  • TOTAL — final total

Notes

  • mode = 2 – Goods mode / 3 – Prepayment mode
  • orderId identifies the bank payment in card and mixed scenarios. It is not the PSPG order number; use pspgOrderNumber for that.
  • WooCommerce items must be prepared first via getWoocommersOrderItems()
  • Then load them via getWoocommersOrderItemsFromDB()
  • All receipts are stored inside eHDM

Parameters

ParameterTypeDescription
currencystringRequired source currency: AMD, USD, EUR or RUB. Numeric codes must also be passed as strings. All payment amounts, prices and monetary discounts use this currency.
orderIdstringBank payment ID, when applicable. Can be empty for cash WooCommerce orders.
wc_orderIdintWooCommerce order ID, when applicable.
pspgOrderNumberintPSPG order number, when applicable. Separate from the bank payment ID.
cardAmountdoubleNon-cash (card) payment amount in the source currency
cashAmountdoubleCash payment amount in the source currency
partialAmountdoublePartial payment amount in the source currency
prePaymentAmountdoubleUsed prepayment amount in the source currency
cashierIdintCashier ID
modeintReceipt mode: 2 – Goods / 3 – Prepayment
partnerTinstringBuyer tax identification number (TIN)
itemsarrayList of goods. Prices and monetary discounts use the source currency. Required for goods receipts.