2026-09-09 00:48:09 +03:00
|
|
|
<?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\PaymentRefunded;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-28 10:15:23 +03:00
|
|
|
* 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
|
2026-09-09 00:48:09 +03:00
|
|
|
* Payment\Support\TransactionDriverAdapter::refundVia() and
|
2026-09-28 10:15:23 +03:00
|
|
|
* 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, dispatching
|
|
|
|
|
* no event at all — no PaymentCaptured (nothing has been paid yet), and
|
|
|
|
|
* deliberately NOT PaymentDeferred either (unlike COD, whose
|
|
|
|
|
* MarkOrderPlacedOnDeferredPayment listener immediately advances the
|
|
|
|
|
* order past 'awaiting_payment' since a COD order has nothing to await at
|
|
|
|
|
* checkout). A bank transfer order genuinely DOES have something to
|
|
|
|
|
* await: 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.
|
2026-09-09 00:48:09 +03:00
|
|
|
*
|
|
|
|
|
* $reference is generated here for the same reason as OfflinePaymentDriver's
|
2026-09-28 10:15:23 +03:00
|
|
|
* 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.
|
2026-09-09 00:48:09 +03:00
|
|
|
*/
|
|
|
|
|
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
|
|
|
|
|
{
|
2026-09-28 10:15:23 +03:00
|
|
|
return new PaymentResult(
|
|
|
|
|
status: PaymentResultStatus::Pending,
|
2026-09-09 00:48:09 +03:00
|
|
|
reference: 'bank-transfer-'.Str::uuid(),
|
|
|
|
|
amount: $amount,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|