2026-09-03 16:01:33 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Modules\Core\Payment\DTOs;
|
|
|
|
|
|
2026-09-03 17:00:24 +03:00
|
|
|
use Lunar\DataTypes\Price;
|
2026-09-03 16:01:33 +03:00
|
|
|
use Modules\Core\Payment\Enums\PaymentResultStatus;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The one shape every Payment operation (pay, authorize, capture, void,
|
|
|
|
|
* refund, handleCallback) returns, regardless of driver — a caller never
|
|
|
|
|
* writes gateway-specific branching to read the outcome.
|
|
|
|
|
*
|
|
|
|
|
* Deliberately not one-size-fits-all in richness underneath: a gateway's
|
|
|
|
|
* own response can be as sparse as Nexi's capture (just an operation id +
|
|
|
|
|
* timestamp, no echoed amount or status) or as rich as Stripe's
|
|
|
|
|
* PaymentIntent (status, amounts, decline classification, full error
|
|
|
|
|
* detail). $status/$reference/$amount are the only fields every driver can
|
|
|
|
|
* always populate — $amount from what WE requested, not necessarily
|
|
|
|
|
* echoed by the gateway. Everything else is best-effort normalization;
|
|
|
|
|
* $raw is the unconditional escape hatch for genuine audit fidelity
|
|
|
|
|
* (the untouched gateway response), so nothing is ever lost even when a
|
|
|
|
|
* gateway has no field to normalize into $failureReason/$retriable.
|
|
|
|
|
*/
|
|
|
|
|
final class PaymentResult
|
|
|
|
|
{
|
|
|
|
|
/**
|
2026-09-03 17:00:24 +03:00
|
|
|
* @param $amount Lunar's own money type (Lunar\DataTypes\Price —
|
|
|
|
|
* integer minor units bundled with its Currency), the SAME
|
|
|
|
|
* representation every contract method takes/returns — never a
|
|
|
|
|
* gateway's own minor-unit scale. Each driver converts at its own
|
|
|
|
|
* boundary (e.g. StripeManager::toStripeAmount()/fromStripeAmount())
|
|
|
|
|
* before calling out to, or after reading back from, its gateway —
|
|
|
|
|
* Payment itself only ever speaks Lunar's Price.
|
2026-09-03 16:01:33 +03:00
|
|
|
* @param $failureReason a human-readable reason, only meaningful
|
|
|
|
|
* when $status is Failed — the driver's own normalization of
|
|
|
|
|
* whatever the gateway called it (Stripe's decline_code message,
|
|
|
|
|
* Nexi's ErrorsInner::$description, ...).
|
|
|
|
|
* @param $retriable whether the caller should offer "try again" with
|
|
|
|
|
* the SAME payment method, vs. "use a different one" — real,
|
|
|
|
|
* gateway-native distinction on Stripe (decline_code soft/hard) and
|
|
|
|
|
* Mastercard (merchantAdviceCode / scheme soft-decline codes), but
|
|
|
|
|
* Nexi's OperationResult has no such signal at all. Defaults to
|
|
|
|
|
* false (assume not safely retriable) rather than guessing when a
|
|
|
|
|
* driver's gateway has no such classification.
|
|
|
|
|
* @param $raw the untouched gateway response body — always
|
|
|
|
|
* populated, even when the gateway's own fields were too sparse to
|
|
|
|
|
* normalize into anything above.
|
|
|
|
|
* @param $meta driver-specific extras that don't fit the normalized
|
|
|
|
|
* fields above (e.g. a card's last four digits).
|
2026-09-03 17:27:34 +03:00
|
|
|
* @param $continuation only meaningful when $status is Pending —
|
|
|
|
|
* what the caller does next (a redirect URL, a client secret for
|
|
|
|
|
* frontend JS), gateway-agnostic. Null for every other status, and
|
|
|
|
|
* for any driver whose pay()/authorize() never returns Pending
|
|
|
|
|
* (e.g. OfflinePaymentDriver).
|
2026-09-03 16:01:33 +03:00
|
|
|
*/
|
|
|
|
|
public function __construct(
|
|
|
|
|
public readonly PaymentResultStatus $status,
|
|
|
|
|
public readonly string $reference,
|
2026-09-03 17:00:24 +03:00
|
|
|
public readonly Price $amount,
|
2026-09-03 16:01:33 +03:00
|
|
|
public readonly ?string $failureReason = null,
|
|
|
|
|
public readonly bool $retriable = false,
|
|
|
|
|
public readonly array $raw = [],
|
|
|
|
|
public readonly array $meta = [],
|
2026-09-03 17:27:34 +03:00
|
|
|
public readonly ?PaymentContinuation $continuation = null,
|
2026-09-03 16:01:33 +03:00
|
|
|
) {}
|
|
|
|
|
}
|