CoolCMS API

PaymentProcessorTrait uses trait:short

Table of Contents

Constants

AMOUNT_TOLERANCE  : mixed = 0.01
Rounding tolerance of the amount comparison (an overpayment or underpayment below the limit passes).

Properties

$paymentGatewayNotificationManager  : PaymentGatewayNotificationManager
$notificationReason  : string|null
$notificationResult  : GatewayNotificationResult|null

Methods

isFurtherPaidTransaction()  : bool
Is a notification about a completed payment a further paid transaction worth processing?
processPayment()  : int|null
Processes one gateway notification and writes it to the notification history (one row per call, with the outcome collected along the way). An exception is recorded as an error and rethrown.
recordClosedPaymentNotification()  : void
Records a notification about a completed or refunded payment as a duplicate. `$check` = what the gateway said (a completed payment is asked, a refunded one is not).
recordGatewayNotification()  : void
applyGatewayCheck()  : int|null
applyResolvedStatus()  : bool
The payment status is derived from the transactions and freezes after `COMPLETED`.
currencyMismatchReason()  : string|null
The currency is verified only where the gateway reports it and where the ISO code of the payment can be found.
doCallback()  : void
The callback also receives the id of the transaction it runs for.
getPaymentGateSetting()  : string
isAlternateTransaction()  : bool
Does the gateway report a different transaction than the one stored on the payment?
isRepeatableCallback()  : bool
May the callback of this payment run again for a further paid transaction ({@see PaymentCallbackRegistry::REPEATABLE})?
logRejectedCallback()  : void
markNotificationOutcome()  : void
A later step overrides the result; reasons of consecutive steps with the same result are joined, so that e.g. "frozen payment" and "callback already ran" both stay visible.
persistGatewayPaymentId()  : void
Writes `gatewayPaymentId` from a verified path.
processStatus()  : bool
resetNotificationOutcome()  : void
resolveCallbackTarget()  : array{0: string, 1: string}|null
IMPORTANT: the callback target is determined only by {@see PaymentCallbackRegistry} by `callback_key`. The DB triple namespace/class/function and a direct `Payment.callback` string are no longer an authority; a direct string passes only when it points to an approved handler. A rejection is logged like an unreachable callback (still fail-open towards the gateway).
runCallbackForTransaction()  : void
The module callback may run once per transaction.
verifyAmount()  : GatewayCheckResult
Verifies the amount and the currency against `payment.total_price`.

Constants

AMOUNT_TOLERANCE

Rounding tolerance of the amount comparison (an overpayment or underpayment below the limit passes).

private mixed AMOUNT_TOLERANCE = 0.01

Properties

Methods

isFurtherPaidTransaction()

Is a notification about a completed payment a further paid transaction worth processing?

protected isFurtherPaidTransaction(PaymentGate $paymentGate, GatewayCheckResult $check) : bool

Only a transaction the gateway confirmed as paid, with its own id that has not activated yet. Anything else (a repeat of the transaction that already activated, a late failure, a rejected or unidentified notification) is only recorded in the notification history; the payment and its transactions stay untouched. The decision is a cheap pre-filter: idempotence itself is the atomic claim in runCallbackForTransaction(), and the reference and amount checks of processPayment() still apply to what passes.

A forged notification with an invented transaction id passes only if the gateway confirms that id as paid: card gateways ask their server (checkPayment()/checkDelivery()), and a paid transaction of another payment fails on its reference.

Parameters
$paymentGate : PaymentGate
$check : GatewayCheckResult
Return values
bool

processPayment()

Processes one gateway notification and writes it to the notification history (one row per call, with the outcome collected along the way). An exception is recorded as an error and rethrown.

protected processPayment(Payment|null $payment, PaymentGate $paymentGate, GatewayCheckResult $check[, GatewayNotificationTransport $transport = GatewayNotificationTransport::Execute ]) : int|null
Parameters
$payment : Payment|null
$paymentGate : PaymentGate
$check : GatewayCheckResult
$transport : GatewayNotificationTransport = GatewayNotificationTransport::Execute
Return values
int|null

recordClosedPaymentNotification()

Records a notification about a completed or refunded payment as a duplicate. `$check` = what the gateway said (a completed payment is asked, a refunded one is not).

protected recordClosedPaymentNotification(Payment $payment, GatewayNotificationTransport $transport[, GatewayCheckResult|null $check = null ]) : void
Parameters
$payment : Payment
$transport : GatewayNotificationTransport
$check : GatewayCheckResult|null = null

applyResolvedStatus()

The payment status is derived from the transactions and freezes after `COMPLETED`.

private applyResolvedStatus(Payment $payment, int|null $paymentGateId, GatewayCheckResult $check) : bool

Freezing is what prevents repeated activation: a completed payment no longer changes its status, further events are written only to the transactions. The one exception one might want, REFUNDED, is deliberately not made: a refund is handled manually in the gateway portal (mobilniplatby has no refund API at all, for cards it usually goes through the bank), so it is recorded as a transaction and is traceable, but does not overwrite the status.

Parameters
$payment : Payment
$paymentGateId : int|null
$check : GatewayCheckResult
Return values
bool —

false = COMPLETED could not be written to the DB, so the caller must not run the callback

currencyMismatchReason()

The currency is verified only where the gateway reports it and where the ISO code of the payment can be found.

private currencyMismatchReason(Payment $payment, GatewayCheckResult $check) : string|null

$this->currencyManager is already injected in the Api base presenter (CurrencyInitTrait). An unknown payment currency (missing currency_id) is no reason to fail the payment: the amount already matches and the eshop additionally guards the currency in EshopPaymentHook::coversOrderTotal().

Parameters
$payment : Payment
$check : GatewayCheckResult
Return values
string|null

doCallback()

The callback also receives the id of the transaction it runs for.

private doCallback(Payment $payment[, int $transactionId = 0 ][, bool $isDuplicateActivation = false ]) : void

$isDuplicateActivation is passed too: true means that one transaction already activated this payment and this is a further, different paid transaction (the customer paid twice). The module activates the same way; items that are already active stack on their own validity, so the period is added again.

Old callbacks need not change their signature: PHP silently drops a surplus positional argument of a userland function, so handlePayment(Payment $p) keeps working.

Parameters
$payment : Payment
$transactionId : int = 0
$isDuplicateActivation : bool = false

getPaymentGateSetting()

private getPaymentGateSetting(PaymentGate $paymentGate, string $key, string $default) : string
Parameters
$paymentGate : PaymentGate
$key : string
$default : string
Return values
string

isAlternateTransaction()

Does the gateway report a different transaction than the one stored on the payment?

private isAlternateTransaction(Payment $payment, GatewayCheckResult $check) : bool

The stored id was created when the transaction was started, i.e. on our initiated path. When the verified one differs, it is either a second transaction of the same payment (repeated attempt) or a foreign transaction in our callback; only referenceId decides.

Parameters
$payment : Payment
$check : GatewayCheckResult
Return values
bool

isRepeatableCallback()

May the callback of this payment run again for a further paid transaction ({@see PaymentCallbackRegistry::REPEATABLE})?

private isRepeatableCallback(Payment $payment) : bool
Parameters
$payment : Payment
Return values
bool

markNotificationOutcome()

A later step overrides the result; reasons of consecutive steps with the same result are joined, so that e.g. "frozen payment" and "callback already ran" both stay visible.

private markNotificationOutcome(GatewayNotificationResult $result[, string|null $reason = null ]) : void
Parameters
$result : GatewayNotificationResult
$reason : string|null = null

persistGatewayPaymentId()

Writes `gatewayPaymentId` from a verified path.

private persistGatewayPaymentId(Payment $payment, int|null $paymentGateId, GatewayCheckResult $check) : void

Written only for states that mean "the gateway knows the transaction and this is its state". Failed / invalid / unknown also come from a query error or a nonexistent id, so the id could be slipped in through them. A filled different value is never overwritten: it is no longer our transaction and belongs in the log, not in the DB.

Parameters
$payment : Payment
$paymentGateId : int|null
$check : GatewayCheckResult

resolveCallbackTarget()

IMPORTANT: the callback target is determined only by {@see PaymentCallbackRegistry} by `callback_key`. The DB triple namespace/class/function and a direct `Payment.callback` string are no longer an authority; a direct string passes only when it points to an approved handler. A rejection is logged like an unreachable callback (still fail-open towards the gateway).

private resolveCallbackTarget(Payment $payment) : array{0: string, 1: string}|null
Parameters
$payment : Payment
Return values
array{0: string, 1: string}|null

runCallbackForTransaction()

The module callback may run once per transaction.

private runCallbackForTransaction(Payment $payment, int|null $paymentGateId, GatewayCheckResult $check, int $transactionId) : void

The claim is made atomically (PaymentTransactionManager::claimExecution()), not as read-then-write: two notifications about the same transaction arrive even concurrently (a gateway retry overlaps with the customer return) and would otherwise both start the activation. A repeated notification of the same transaction therefore never activates anything again.

A further, different paid transaction of the same payment (another id at the gateway) means the money came again: a tariff callback (PaymentCallbackRegistry::REPEATABLE) activates the payment again and the period is added (credits too); other callbacks (an e-shop order) run once per payment. Such a transaction must carry its gateway id; without it it cannot be told from a replay of the one that already activated, so it is only logged. A paid payment the administrator closed afterwards (canceled/voided, frozen for the gateway) activates nothing more.

Parameters
$payment : Payment
$paymentGateId : int|null
$check : GatewayCheckResult
$transactionId : int

verifyAmount()

Verifies the amount and the currency against `payment.total_price`.

private verifyAmount(Payment $payment, PaymentGate $paymentGate, GatewayCheckResult $check) : GatewayCheckResult

Only the completed state is checked (the only one meaning "paid"); other states claim nothing about money. A mismatch gives PaymentStatus::BAD_PRICE: the payment is saved and the callback runs, but as unpaid, so an eshop order does not jump to paid.

A gateway that reports no amount at all also gives bad price (fail-closed). Escape hatch without a code change: require_amount[0] in the gateway settings; then an unreported amount is only logged and the payment passes. A mismatch of a reported amount is always bad price, even with require_amount off.

Parameters
$payment : Payment
$paymentGate : PaymentGate
$check : GatewayCheckResult
Return values
GatewayCheckResult
On this page

Search results