Releases: shaxzodbek-uzb/pay-uz
4.0.0 — Uzbekistan fintech toolkit
4.0.0 - 2026-06-21
Expands the package from "Payme + Click webhooks" into a multi-rail Uzbekistan
fintech toolkit: new OFD fiscalization, card tokenization / recurring
(Payme Subscribe, ATMOS), card-acquiring aggregators (Octo, Multicard/Rahmat),
BNPL (Uzum Nasiya), and e-invoicing / ЭСФ (Didox) layers, each behind its
own facade on a shared Support\Http transport, with null-driver defaults so a
fresh install works offline. Major version because the runtime code "editor"
and its require-based payment hooks are removed in favour of a resolver + events
(see Breaking).
Added
-
E-invoicing layer (the
Einvoicefacade) with Didox as the first driver
— issue ЭСФ and other e-documents (create → sign → submit/accept/reject/cancel →
status), its own layer (not a payment flow):EinvoiceDrivercontract resolved byEinvoiceManagerbehind theEinvoice
facade (config underpayuz.einvoice). Value objectsDocument,InvoiceItem,
Counterparty,DocumentStatus,EinvoiceResult; aSomhelper for the
single tiyin↔decimal-som-string boundary; eventsDocumentCreated/
DocumentSigned/DocumentRejected/DocumentCancelled; anulldefault.- The package ships zero cryptography. E-IMZO PKCS#7 signing is a
Signer
seam (NullSignerdefault throws;CallableSignerwraps a closure); the
manager'ssignAndSubmit()wires toSign → sign → submit, or you pass a
pre-signed blob to the driver directly. DidoxDriver:Partner-Authorization+user-keyauth, decimal-som amounts,
poll-only status (no webhook). Wire details not byte-confirmed in public docs
are flagged UNCERTAIN inline. (Live use needs a Didox partner token.)
-
Note: Alif Nasiya was evaluated as a second BNPL driver but deferred — its
Nasiya API is partner-gated with no public spec, so no driver was fabricated. -
BNPL / installments layer (the
Bnplfacade) with Uzum Nasiya as the
first driver — a credit-contract lifecycle (its own layer, not Checkout/Subscribe):BnplDrivercontract resolved byBnplManagerbehind theBnplfacade
(config underpayuz.bnpl):checkEligibility→calculate→
createContract→confirm/cancel→status, withextend()for new BNPL
gateways. Value objectsEligibility,InstallmentPlan,Contract,
ContractStatus,ContractResult.UzumNasiyaDriver(coded against Uzum's official Nasiya Partner API OpenAPI):
partner Bearer JWT auth, tiyin↔som conversion at the boundary, the two-id
handling (confirm/status usecontract_id, cancel uses theorderid), and
response_code→retryability for confirm/cancel. Two onboarding-time unknowns
are flagged inline (som-vs-tiyin wire unit; WebView vs/v3OTP activation).- Events
ContractCreated,ContractConfirmed,ContractCancelled, and a
nulldriver default. (Live/E2E needs an Uzum partner token; the unit-tested
driver ships now.)
-
Multicard driver for the
Checkoutlayer. A second card-acquiring
aggregator (hosted checkout + saved-card charge + hold-capture + full/partial
refund + webhook) over Multicard's REST API: a cached bearer token (POST /auth,
refreshed + retried once on 401), the real HTTP verbs (invoicePOST, capture
PUT, refundDELETE, statusGET), the{success}envelope mapped to typed
exceptions, tiyin pass-through (no conversion), and both webhook signature
schemes selected bycallback_scheme. -
Support\Http\HttpClient::request($method, …)— the shared transport now
supports arbitrary HTTP verbs (GET/PUT/DELETE), not just POST. -
rahmatCheckout alias. "Rahmat Pay" is not a separate processor — it is the
Multicard acquiring rail (its checkout renders on app.rhmt.uz), so
Checkout::driver('rahmat')resolves to the Multicard driver with the
multicardconfig. No separate driver is shipped (none is warranted). -
ATMOS driver for the
Subscribelayer. ATMOS's verified API is a server-side
card-vault + OTP flow (OAuth2 → card bind/confirm → create/pre-apply/apply), which
matches theSubscribeDrivercontract — so ATMOS is a Subscribe driver, not a
Checkout one. The driver handles the OAuthclient_credentialstoken (cached +
auto-refreshed), passes amounts through as tiyin (no conversion), maps the
result.code == 'OK'envelope to typed exceptions, transposes theMMYY→YYmm
expiry, and swaps the bind'sbinding_idfor the confirmedcard_token.
confirmHold()and partial refunds are unsupported (no verified ATMOS endpoint)
and throw / fall back to whole-transactioncancelReceipt. ATMOS-specific
verifyCallback()/parseCallback()handle the merchant-cabinet webhook. -
Support\Http\HttpClient::postForm()— form-encoded POST on the shared
transport (for OAuthclient_credentialstoken requests and similar). -
Card-acquiring aggregator layer (the
Checkoutfacade) with Octo as the
first driver — one REST integration for Uzcard/Humo/Visa/Mastercard with hosted
checkout, saved-card charges, two-stage capture, refunds and webhooks:CheckoutDrivercontract resolved byCheckoutManagerbehind theCheckout
facade (config underpayuz.checkout), withextend()and the shared
Support\Httptransport.Payment(request) +PaymentResult(normalized,
vendor-agnostic status) value objects.OctoDriver: converts the package's tiyin to Octo's som, branches on the
errorenvelope (HTTP is always 200), maps Octo's status set to the
normalized statuses, and verifies webhook signatures (hash_equals).webhook()verifies the signature before emittingPaymentSucceeded/
PaymentFailed/PaymentRefunded; an unverified payload raises
WebhookExceptionand is never acted on.
-
Card tokenization + recurring charges (the
Subscribefacade). A
gateway-agnostic layer for save-card / one-click / subscription billing, with
the Payme Subscribe API as the first driver:SubscribeDrivercontract resolved bySubscribeManagerbehind the
Subscribefacade (config underpayuz.subscribe), withextend()for new
gateways and the sharedSupport\Httptransport.Card,VerifyCodeandChargevalue objects (tiyin amounts, receipt state
codes); the lifecycle is createCard → sendVerifyCode → verify → charge, plus
two-stageauthorize/capture/release(holds).PaymeDriverenforcing the Subscribe X-Auth rule (cashboxidalone for the
browser-safecards.create/get_verify_code/verify;id:keyfor
cards.check/removeand allreceipts.*), JSON-RPC error → typed
exceptions, and anulldriver default.- Events
CardVerified,ChargePaid,HoldConfirmed,ChargeCancelled. - Never persists the PAN — only the returned token.
-
Shared HTTP layer
Support\Http(HttpClientseam +CurlHttpClient+
JsonRpcClient,TransportException/JsonRpcException), now used by the
fiscalization and subscribe layers (and future aggregator/e-invoice drivers). -
OFD fiscalization layer (онлайн-ККМ). A gateway-agnostic way to register
fiscal receipts with a Fiscal Data Operator (PKM No. 943) and obtain the fiscal
sign / QR:- Value objects
Fiscalization\Receipt/ReceiptItem/FiscalResult—
tiyin-integer amounts, VAT-inclusive prices, andReceipt::sale()/refund()
factories with a cash/card payment split. Mxik(17-digit IKPU/MXIK validation) andVat(rate set{0, 12},
pure-integer round-half-up VAT extraction) helpers.- A
FiscalDrivercontract resolved byFiscalizationManagerbehind the new
Fiscalizerfacade, withextend()for custom OFD providers and an
HttpClienttransport seam (default cURL, no new dependencies). - Drivers:
null(validates + returns a synthetic sign; the safe default) and
ofd(generic soliq fiscal-receipt HTTP gateway with tolerant response
parsing and QR-from-parts derivation). ReceiptFiscalized/FiscalizationFailedevents, and a
Transaction::attachFiscalReceipt()helper that stores the result under the
transaction'sdetailJSON. Configure underpayuz.fiscalization.
- Value objects
-
Event/resolver-based payment hooks. Application behaviour now lives in
versioned code instead of runtime-writable PHP files:- A
Payments\Contracts\PaymentResolver(default
Payments\DefaultPaymentResolver, configured underpayuz.payments.resolver)
for the value-returning operations:convertModelToKey,convertKeyToModel,
isProperModelAndAmount,beforeResponse. - Lifecycle events
Payments\Events\PaymentBeforePay,PaymentProcessing,
PaymentPaidandPaymentCancelled— subscribe from yourEventServiceProvider
instead of editingpaying.php/after_pay.php/ etc.
- A
Removed
- The runtime code "editor" and its
require-based hooks. Deleted
ApiController::file_put, thePOST /payment/api/editable/updateroute, the
editorspage/controller action andeditors.blade.php, and the published
app/Http/Controllers/Payments/*.phphook files (no longerrequired). The
PaymentServicemethods that used torequirethose files now call the
configured resolver / dispatch events (see Added).
Security
- Removed the residual authenticated-CSRF → RCE surface left after 3.0.0.
3.0.0 fixed the unauthenticated RCE (CVE-2026-31843) but the
write-PHP-then-requirearchitecture remained, gated only behindauth.
Because the writer route usedRoute::any(so it acceptedGET, which
Laravel's CSRF middleware does not cover), a logged-in user could be lured into
writing a hook file via a crafted link. Deleting the editor and the
require-based hooks removes the write-and-execute primitive entirely.
Breaking
- Payment hooks moved from editable files to a resolver + events.
`Pa...
3.0.0 — Security release (CVE-2026-31843)
🔐 Security release — please upgrade
This release fixes CVE-2026-31843 (GHSA-m5wg-cjgh-223j) — a critical, unauthenticated remote code execution via POST /payment/api/editable/update, which overwrote executable PHP payment-hook files. Affected: all versions ≤ 2.2.24. Fixed in 3.0.0.
composer require goodoneuz/pay-uz:^3.0⚠️ Breaking change
The control-panel routes (dashboard, settings, transactions, payment-systems CRUD and the code editors) now require the auth middleware by default. If you published config/payuz.php, set control_panel.middleware to your own admin/authorization guard — the editor writes executable PHP and must be protected.
Highlights
- Fixed the RCE — strict filename allow-list + realpath containment in the editor writer, and auth-by-default on the control panel.
- Constant-time signature/credential checks (
hash_equals) for Click/Payme/Paynet/Oson. - Paynet now validates the callback amount against the order.
- Payme — removed the
APP_ENV=testingauth bypass; atomic compare-and-set prevents double-perform. - Stripe — fixed reflected XSS / header injection / open redirect.
- Octane / RoadRunner request body fix (#71).
DataFormat::timestamp2datetimeno longer returns1970-01-01for numeric timestamps.- New: Uzum Bank gateway (Merchant API —
check/create/confirm/reverse/status).
See the CHANGELOG for the full list.
Bug fixes
Merge pull request #69 from Sodiqmirzo/fix_get_statement fix: get statement method payme
Multi transaction condition
2.2.23 multi transaction condition
2.2.22
Expiring transactions
Minor changes
Minor changes
Payme redirection form key
2.2.20 change payme form account key
Minor changes
2.2.19 changes
Payme: fix system date formating
Merge pull request #63 from asadbek-fayzulloev/master fix: remove date formatter
Change Payme@CheckPerformTransaction default value true
2.2.17 set allow default true in payme CheckPerformTransaction response