CoolCMS API

PaymentPayPageTrait

Dedicated filtered action for the payment page.

The front end page "payment execution" (/platba/p-{id}) loads the payment unconditionally, even for an anonymous visitor (PaymentShowTrait::isPaymentAccessible() deliberately leaves an account-less payment open: anyone with the link may pay). A grant on :Api:System:Payment:get is the wrong answer for two reasons:

  1. get has actionGet (from CrudTrait), so it is reachable externally over HTTP; a grant would open reading of all payments to every holder of a valid token.
  2. Payment carries email, ip, encryptedSignature, gatewayPaymentId and callback, and the read path has no field tier, so get would issue the whole extract().

This action closes both:

  • It has no actionPayPage() and must not get one. Without action* Nette rejects an external request with 404 before anything loads, so the key :Api:System:Payment:payPage is a decision about the internal path only; a guest/registrated grant on it opens no external HTTP.
  • The server authorizes, not the caller: the rule is the same as the front end isPaymentAccessible(), evaluated inside the Api. A completed/refunded payment is not issued at all and an account-bound payment only to its owner; the identity comes only from getJwtPayload(), never from a parameter.
  • The payload is narrowed positively (self::PAY_PAGE_FIELDS): a new entity column never reaches the response by itself (fail-closed; a negative list would fail open).

Table of Contents

Constants

PAY_PAGE_FIELDS  : array<string|int, mixed> = ['id', 'status', 'userId', 'paymentGateId', 'pa...
IMPORTANT: allowlist of payment page fields. Exactly what `PaymentShowTrait` (Front) and the `paymentMobilniplatby` component need. A key added here is opened to an anonymous visitor.
PAY_PAGE_CLOSED_STATUSES  : array<string|int, mixed> = [\App\UI\Api\System\Enums\PaymentStatus::COMPLE...
States in which the payment page must not be opened (parity with `actionExecute()` and the front end `isPaymentAccessible()`): reopening the URL would overwrite `gatewayPaymentId` of a finished payment.

Methods

assertPaymentPayable()  : array<string, mixed>|null
Single definition of "may this payment be opened / started at the gateway right now".
internalActionPayPage()  : array<string, mixed>
isPaymentTargetPayable()  : bool
Module-level payability ({@see PaymentCallbackRegistry::PAYABILITY_CHECKS}) by the payment `callback_key`.
payPageFailure()  : array<string, mixed>
Failure in the same shape the guard returns (`Result::failure(...)->toArray()`), so the fail-soft boundary recognizes it by one predicate {@see Result::isFailureEnvelope()} and the caller gets "no data" instead of an entity with meaningless fields.

Constants

PAY_PAGE_FIELDS

IMPORTANT: allowlist of payment page fields. Exactly what `PaymentShowTrait` (Front) and the `paymentMobilniplatby` component need. A key added here is opened to an anonymous visitor.

public array<string|int, mixed> PAY_PAGE_FIELDS = ['id', 'status', 'userId', 'paymentGateId', 'paymentRegionId', 'currencyId', 'totalPrice', 'variableSymbol', 'code', 'name', 'description', 'successUrl', 'cancelUrl', 'insertDatetime', 'paymentDatetime', 'requirePaymentEet', 'paymentGateFeePrice', 'paymentGateFeePercents', 'paymentGateFeeTotal', 'paymentGateFeePercentsPrice']

Deliberately missing: email, ip, encryptedSignature, gatewayPaymentId, callback, notifyUrl, paymentCallbackId, paymentEetId - the page does not need them and they are either personal data or gateway secrets.

PAY_PAGE_CLOSED_STATUSES

States in which the payment page must not be opened (parity with `actionExecute()` and the front end `isPaymentAccessible()`): reopening the URL would overwrite `gatewayPaymentId` of a finished payment.

private array<string|int, mixed> PAY_PAGE_CLOSED_STATUSES = [\App\UI\Api\System\Enums\PaymentStatus::COMPLETED, \App\UI\Api\System\Enums\PaymentStatus::REFUNDED]

Methods

assertPaymentPayable()

Single definition of "may this payment be opened / started at the gateway right now".

public assertPaymentPayable(Payment $payment) : array<string, mixed>|null

Used by self::internalActionPayPage() and PaymentCreateTrait::internalActionCreate(); two copies of the conditions are exactly how authorization drifts apart.

The rule equals the front end check (App\UI\Front\System\Presenters\Traits\Details\PaymentShowTrait::isPaymentAccessible()), evaluated server-side, so it also holds for a caller that bypassed the front end:

  1. A completed/refunded payment is not reopened (repeated GET would overwrite gatewayPaymentId of a finished payment; parity with actionExecute()).
  2. A payment bound to an account belongs only to its owner (identity only from the JWT, never from a parameter).
  3. A payment without an account (user_id IS NULL) stays open on purpose: it does not matter who pays, whoever has the link.

A privileged role (currently admin) bypasses the rule, because the admin must be able to open a foreign payment. PermissionResolver::isGrantedByPrivilegedRole() decides it, not a hardcoded role name.

Parameters
$payment : Payment
Return values
array<string, mixed>|null —

null = may continue; array = return it to the caller

internalActionPayPage()

public internalActionPayPage([int|null $id = null ]) : array<string, mixed>
Parameters
$id : int|null = null
Return values
array<string, mixed> —

allowlist from self::PAY_PAGE_FIELDS, or a failed Result envelope (which the fail-soft boundary turns into "no data": EntityHydrator::rejectFailureEnvelope() / FailSoftReadTrait)

isPaymentTargetPayable()

Module-level payability ({@see PaymentCallbackRegistry::PAYABILITY_CHECKS}) by the payment `callback_key`.

private isPaymentTargetPayable(Payment $payment) : bool

Fail-open: no callback, no registered check or an unresolvable service means "nothing to object", the usual guards above/below still apply.

Parameters
$payment : Payment
Return values
bool

payPageFailure()

Failure in the same shape the guard returns (`Result::failure(...)->toArray()`), so the fail-soft boundary recognizes it by one predicate {@see Result::isFailureEnvelope()} and the caller gets "no data" instead of an entity with meaningless fields.

private static payPageFailure(ResultCode $code, string $message) : array<string, mixed>
Parameters
$code : ResultCode
$message : string
Return values
array<string, mixed>
On this page

Search results