sashalenz/vchasno-kasa-api

Laravel client for Вчасно.Каса Orders API (deferred fiscal receipts / ПРРО)

Maintainers

Package info

github.com/sashalenz/vchasno-kasa-api

pkg:composer/sashalenz/vchasno-kasa-api

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-04-26 09:41 UTC

This package is auto-updated.

Last update: 2026-04-26 09:43:11 UTC


README

Latest Version on Packagist Tests PHP Version License

Laravel package for integrating with the Vchasno.Kasa Orders API.

Supports deferred fiscal receipts (ETTN) via Nova Poshta, Ukrposhta and Meest, as well as direct cash and IBAN payments.

Requirements

  • PHP 8.4+
  • Laravel 11 / 12 / 13

Installation

composer require sashalenz/vchasno-kasa-api

Publish the config file:

php artisan vendor:publish --tag="vchasno-kasa-config"

Add variables to your .env:

VCHASNO_KASA_TOKEN=your-api-token
VCHASNO_KASA_RRO_FN=1234567890

Configuration

// config/vchasno-kasa.php

return [
    'token'    => env('VCHASNO_KASA_TOKEN'),
    'rro_fn'   => env('VCHASNO_KASA_RRO_FN'),
    'base_url' => env('VCHASNO_KASA_URL', 'https://kasa.vchasno.ua/api/v1'),
    'timeout'  => 30,
    'retry'    => [
        'times' => 3,
        'sleep' => 150,
    ],
];
Option Description
token API token from the Vchasno cabinet: Settings → API
rro_fn Fiscal number of the software cash register (PRRO). Visible in the cabinet or via GET /api/v1/rro
base_url Orders API base URL (change only if necessary)

Usage

Via Facade

use Sashalenz\VchasnoKasaApi\Facades\VchasnoKasa;

Via DI / app()

use Sashalenz\VchasnoKasaApi\VchasnoKasaApi;

$api = app(VchasnoKasaApi::class);

Orders API

1. Deferred receipt — Nova Poshta (COD)

The primary use case: the customer pays upon parcel pickup. The receipt is fiscalized automatically when the customer collects the shipment.

use Sashalenz\VchasnoKasaApi\Facades\VchasnoKasa;
use Sashalenz\VchasnoKasaApi\Data\Requests\Orders\CreatePostalOrderData;
use Sashalenz\VchasnoKasaApi\Data\Requests\Orders\GoodItemData;
use Sashalenz\VchasnoKasaApi\Enums\PostalType;

$order = VchasnoKasa::orders()->createPostal(
    new CreatePostalOrderData(
        ttn: '59000123456789',                     // Nova Poshta tracking number
        rroFn: config('vchasno-kasa.rro_fn'),
        tag: "order-{$order->id}-{$shipment->id}", // unique idempotency key
        postalType: PostalType::NovaPoshta,
        goods: [
            new GoodItemData(
                name: 'BMW E46 front left headlight',
                quantity: 1.0,
                price: 350000,    // in kopecks (3500.00 UAH)
                code: 'SKU-12345',
            ),
        ],
        totalSum: 350000,         // in kopecks
    )
);

echo $order->id;     // order ID in Vchasno
echo $order->status; // OrderStatus::Created

2. Deferred receipt — Ukrposhta / Meest

use Sashalenz\VchasnoKasaApi\Enums\PostalType;

$order = VchasnoKasa::orders()->createPostal(
    new CreatePostalOrderData(
        ttn: '0312345678901',
        rroFn: config('vchasno-kasa.rro_fn'),
        tag: "order-{$order->id}-ukrposhta",
        postalType: PostalType::UkrPoshta,   // or PostalType::Meest
        goods: [...],
        totalSum: 120000,
    )
);

3. Direct cash / card payment

use Sashalenz\VchasnoKasaApi\Data\Requests\Orders\CreateOrderData;
use Sashalenz\VchasnoKasaApi\Enums\PaymentType;

$order = VchasnoKasa::orders()->create(
    new CreateOrderData(
        rroFn: config('vchasno-kasa.rro_fn'),
        tag: "cash-order-{$order->id}",
        goods: [
            new GoodItemData(name: 'Rear bumper', quantity: 1.0, price: 180000),
        ],
        totalSum: 180000,
        paymentType: PaymentType::Cash,   // or PaymentType::Card
    )
);

4. IBAN bank transfer

use Sashalenz\VchasnoKasaApi\Data\Requests\Orders\CreateIbanOrderData;

$order = VchasnoKasa::orders()->createIban(
    new CreateIbanOrderData(
        rroFn: config('vchasno-kasa.rro_fn'),
        tag: "iban-payment-{$payment->id}",
        goods: [...],
        totalSum: 250000,
        iban: 'UA123456789012345678901234567',
        comment: 'Payment for order #12345',
    )
);

5. Get order status

$order = VchasnoKasa::orders()->get($fiscalOrderId);

if ($order->status->isSuccess()) {
    $yourOrder->update([
        'fiscal_receipt_url' => $order->receiptUrl,
        'fiscal_code'        => $order->fiscalCode,
        'fiscalized_at'      => $order->fiscalizedAt,
    ]);
}

6. List orders (cursor pagination)

use Sashalenz\VchasnoKasaApi\Data\Requests\Orders\ListOrdersData;
use Sashalenz\VchasnoKasaApi\Enums\OrderStatus;

$result = VchasnoKasa::orders()->list(
    new ListOrdersData(
        pageSize: 50,
        status: OrderStatus::Fiscalized,
        dateFrom: '2026-04-01T00:00:00Z',
        dateTo: '2026-04-30T23:59:59Z',
    )
);

foreach ($result->items as $order) {
    echo "{$order->id}: {$order->status->label()}";
}

// Next page
if ($result->hasMore) {
    $next = VchasnoKasa::orders()->list(
        new ListOrdersData(cursor: $result->nextCursor, pageSize: 50)
    );
}

7. Cancel an order

$cancelled = VchasnoKasa::orders()->cancel($fiscalOrderId);
echo $cancelled->status->label(); // "Cancelled"

Receipts API

Get a receipt by ID

$receipt = VchasnoKasa::receipts()->get($receiptId);

echo $receipt->receiptUrl;  // PDF URL
echo $receipt->fiscalCode;  // fiscal code from the tax authority

Poll for new receipts

Useful for a background job that syncs fiscalization statuses:

use Sashalenz\VchasnoKasaApi\Data\Requests\Receipts\ListReceiptsData;

$cursor = cache('vchasno_receipts_cursor');

do {
    $result = VchasnoKasa::receipts()->list(
        new ListReceiptsData(cursor: $cursor, pageSize: 100)
    );

    foreach ($result->items as $receipt) {
        Order::where('fiscal_order_id', $receipt->orderId)
            ->update([
                'fiscal_receipt_url' => $receipt->receiptUrl,
                'fiscal_code'        => $receipt->fiscalCode,
                'fiscalized_at'      => $receipt->createdAt,
            ]);
    }

    $cursor = $result->nextCursor;

} while ($result->hasMore);

cache()->put('vchasno_receipts_cursor', $cursor);

Enums

OrderStatus

Case Value Description
Created 0 Created, awaiting processing
WaitingForPickup 10 NP: waiting for customer pickup
Delivered 11 NP: delivered, fiscalization in progress
Fiscalized 101 ✅ Successfully fiscalized
Cancelled 2000 Cancelled
Returned 2001 Returned
Expired 2002 Expired
$status->isSuccess();  // true if Fiscalized
$status->isFinal();    // true if Fiscalized / Cancelled / Returned / Expired
$status->isPending();  // true if Created / WaitingForPickup / Delivered
$status->label();      // 'Fiscalized'

PostalType

Case Value paymentType()
NovaPoshta nova_poshta PaymentType::NovaPoshta (20)
UkrPoshta ukr_poshta PaymentType::PostalOther (15)
Meest meest PaymentType::PostalOther (15)

PaymentType

Case Value Description
Cash 1 Cash
Card 2 Card / terminal
PostalOther 15 Ukrposhta / Meest COD
NovaPoshta 20 Nova Poshta COD

Error handling

use Sashalenz\VchasnoKasaApi\Exceptions\VchasnoKasaRequestException;

try {
    $order = VchasnoKasa::orders()->createPostal($data);
} catch (VchasnoKasaRequestException $e) {
    if ($e->shouldRetry()) {
        // res_action=1 or 2: request can be retried
        // res_action=2 (collision): retry with the same tag
        retry($job);
    }

    if ($e->needsDataFix()) {
        // res_action=3: bad request data, retrying will not help
        Log::error('Vchasno fiscal error', [
            'message'    => $e->getMessage(),
            'res_action' => $e->getResAction(),
        ]);
    }
}

res_action codes

Value Method Action
0 Success
1 shouldRetry() Retry the request
2 isCollision() Collision — retry with the same tag
3 needsDataFix() Bad data — fix the request payload

Idempotency (tag)

The tag field is a required unique key for every order. If a request is repeated with the same tag, Vchasno returns the existing order instead of creating a new one.

Recommended format:

// Nova Poshta shipment
$tag = "order-{$order->id}-{$shipment->id}";

// Cash payment
$tag = "cash-{$order->id}";

// IBAN payment
$tag = "iban-{$payment->id}";

Testing

composer test
composer analyse    # PHPStan
composer format     # Pint

License

MIT. See LICENSE for details.