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
$paymentGatewayNotificationManager
public
PaymentGatewayNotificationManager
$paymentGatewayNotificationManager
Attributes
- #[Inject]
$notificationReason
private
string|null
$notificationReason
= null
$notificationResult
private
GatewayNotificationResult|null
$notificationResult
= null
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
boolprocessPayment()
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|nullrecordClosedPaymentNotification()
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
recordGatewayNotification()
protected
recordGatewayNotification(int|null $paymentId, int|null $paymentGateId, GatewayNotificationTransport $transport, GatewayCheckResult|null $check, GatewayNotificationResult $result[, string|null $reason = null ]) : void
Parameters
- $paymentId : int|null
- $paymentGateId : int|null
- $transport : GatewayNotificationTransport
- $check : GatewayCheckResult|null
- $result : GatewayNotificationResult
- $reason : string|null = null
applyGatewayCheck()
private
applyGatewayCheck(Payment|null $payment, PaymentGate $paymentGate, GatewayCheckResult $check) : int|null
Parameters
- $payment : Payment|null
- $paymentGate : PaymentGate
- $check : GatewayCheckResult
Return values
int|nullapplyResolvedStatus()
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|nulldoCallback()
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
stringisAlternateTransaction()
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
boolisRepeatableCallback()
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
boollogRejectedCallback()
private
logRejectedCallback(Payment $payment, string $target) : void
Parameters
- $payment : Payment
- $target : string
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
processStatus()
private
processStatus(Payment $payment, int|null $paymentGateId, GatewayCheckResult $check) : bool
Parameters
- $payment : Payment
- $paymentGateId : int|null
- $check : GatewayCheckResult
Return values
boolresetNotificationOutcome()
private
resetNotificationOutcome() : void
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}|nullrunCallbackForTransaction()
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