Files
core/src/Payment/Drivers/BankTransferPaymentDriver.php
T

96 lines
4.2 KiB
PHP

<?php
namespace Modules\Core\Payment\Drivers;
use Illuminate\Support\Str;
use Lunar\DataTypes\Price;
use Modules\Core\Payment\Contracts\Configurable;
use Modules\Core\Payment\Contracts\SupportsPay;
use Modules\Core\Payment\Contracts\SupportsRefunds;
use Modules\Core\Payment\DTOs\PaymentResult;
use Modules\Core\Payment\Enums\PaymentResultStatus;
use Modules\Core\Payment\Events\PaymentDeferred;
use Modules\Core\Payment\Events\PaymentRefunded;
/**
* refund() is manual/attested, same trust model as OfflinePaymentDriver —
* there is no bank API to call, so it decides success immediately on a
* staff member's say-so (they've already sent the wire outside the
* system). Distinct from OfflinePaymentDriver in intent: this exists so a
* payment taken through a DIFFERENT method (e.g. cash-on-delivery) can
* still be REFUNDED via bank transfer — an admin chooses this driver
* explicitly in the refund action, independent of which driver the
* original payment went through (see
* Payment\Support\TransactionDriverAdapter::refundVia() and
* Order\Filament\Extensions\OrderActionsExtension).
*
* pay() is the opposite trust direction from refund(): a bank transfer
* payment requires the money to arrive BEFORE the order can be
* considered paid (unlike cash-on-delivery, where payment happens on
* delivery — see CashOnDeliveryPaymentDriver's own docblock for that
* driver's mirror-image reasoning). So pay() returns Pending and DOES
* dispatch PaymentDeferred, same as COD — without it, nothing ever sets
* Order::placed_at or fires OrderPlaced, leaving the order invisible in
* customer order history, un-decremented in stock, and the checkout
* confirmation page unable to find it (see PaymentDeferred's and
* CashOnDeliveryPaymentDriver's own docblocks for that failure mode).
* Unlike COD, though, a bank transfer order genuinely DOES have something
* to await: MarkOrderPlacedOnDeferredPayment skips
* OrderPaymentResolutionService::resolveDeferredPayment() for a bank
* transfer order (via OrderStatusFlow::isBankTransfer()), so it stays at
* 'awaiting_payment' with Order::paid false until staff confirm the wire
* arrived via OrderFulfillmentService::markPaid(), which — unlike its COD
* path — also advances the order's status, since nothing else ever will
* (see that method's own docblock). CheckoutController::placeOrder()
* already treats a Pending result with no continuation as a fully placed
* order (see its own docblock), so the order is still created and visible
* to the shopper immediately; only its payment/status is what's left
* outstanding.
*
* $reference is generated here for the same reason as OfflinePaymentDriver's
* pay(): there is no gateway to hand one back. refund()'s 'notes' (in
* $context — it has no $data parameter) is folded into PaymentResult::$meta,
* which Order\Services\TransactionRecorder::record() already writes straight
* into Transaction.meta with no extra plumbing; pay() has no equivalent
* write, since nothing ever records a Transaction from its own result (see
* above) — any notes a shopper enters at checkout would need surfacing some
* other way, e.g. when staff mark the order paid.
*/
class BankTransferPaymentDriver implements Configurable, SupportsPay, SupportsRefunds
{
/**
* Always true — no external dependency to be missing.
*/
public function isConfigured(): bool
{
return true;
}
public function pay(string $type, Price $amount, array $data = [], array $context = []): PaymentResult
{
$result = new PaymentResult(
status: PaymentResultStatus::Pending,
reference: 'bank-transfer-'.Str::uuid(),
amount: $amount,
);
PaymentDeferred::dispatch($type, $result, $context);
return $result;
}
public function refund(string $reference, Price $amount, array $context = []): PaymentResult
{
$result = new PaymentResult(
status: PaymentResultStatus::Succeeded,
reference: 'bank-transfer-'.Str::uuid(),
amount: $amount,
meta: array_filter(['notes' => $context['notes'] ?? null]),
);
PaymentRefunded::dispatch('bank-transfer', $result, $context);
return $result;
}
}