Feat: Creating PaymentMethods, Setting Fees, Availabilities
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Payment\Drivers;
|
||||
|
||||
use Lunar\Exceptions\Carts\CartException;
|
||||
use Lunar\Exceptions\DisallowMultipleCartOrdersException;
|
||||
use Lunar\Exceptions\FingerprintMismatchException;
|
||||
use Lunar\Models\Cart;
|
||||
use Lunar\Models\Order;
|
||||
use Modules\Core\Checkout\Contracts\PaymentDriver;
|
||||
use Modules\Core\Checkout\Services\CheckoutService;
|
||||
|
||||
/**
|
||||
* Cash-on-delivery has no gateway to confirm against — the shopper pays the
|
||||
* courier on delivery, not at checkout — so confirm() has nothing to wait
|
||||
* on and places the order immediately, same as Lunar's own OfflinePayment
|
||||
* would, but through CheckoutService::placeOrder() so it goes through the
|
||||
* same fingerprint check every other driver does. $data is unused: nothing
|
||||
* about this confirmation depends on gateway-specific payload.
|
||||
*
|
||||
* Sets the order status to config('lunar.payments.types.cash-on-delivery.authorized')
|
||||
* afterward — placeOrder() itself leaves the order at Lunar's configured
|
||||
* draft_status, same as every driver is responsible for moving it on from.
|
||||
*/
|
||||
class CashOnDeliveryPaymentDriver implements PaymentDriver
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CheckoutService $checkout,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws FingerprintMismatchException
|
||||
* @throws CartException
|
||||
* @throws DisallowMultipleCartOrdersException
|
||||
*/
|
||||
public function confirm(Cart $cart, string $fingerprint, array $data): Order
|
||||
{
|
||||
$order = $this->checkout->placeOrder($fingerprint);
|
||||
|
||||
$order->update([
|
||||
'status' => config('lunar.payments.types.cash-on-delivery.authorized', $order->status),
|
||||
]);
|
||||
|
||||
return $order->refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Payment\Filament\Resources;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Core\Payment\Filament\Resources\PaymentMethodResource\Pages\ListPaymentMethods;
|
||||
use Modules\Core\Payment\Models\PaymentMethod;
|
||||
|
||||
/**
|
||||
* One row per payment type key (config('lunar.payments.types')), seeded by
|
||||
* InstallLunarCommand — never created/deleted here, only edited. `enabled`
|
||||
* toggles inline; `data.fee` (currently the only type-specific setting, for
|
||||
* cash-on-delivery's flat surcharge — see ApplyCashOnDeliveryFee) is edited
|
||||
* via a modal action rather than a dedicated form field, since not every
|
||||
* type has the same data keys.
|
||||
*/
|
||||
class PaymentMethodResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PaymentMethod::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-credit-card';
|
||||
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Settings';
|
||||
|
||||
protected static ?string $modelLabel = 'Payment Method';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Payment Methods';
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('type')
|
||||
->label('Type'),
|
||||
ToggleColumn::make('enabled')
|
||||
->label('Enabled'),
|
||||
TextColumn::make('data.fee')
|
||||
->label('Fee')
|
||||
->formatStateUsing(fn (?int $state) => $state
|
||||
? number_format($state / 100, 2)
|
||||
: '—'),
|
||||
TextColumn::make('updated_at')
|
||||
->label('Last updated')
|
||||
->dateTime(),
|
||||
])
|
||||
->recordActions([
|
||||
static::editFeeAction(),
|
||||
])
|
||||
->defaultSort('type');
|
||||
}
|
||||
|
||||
/**
|
||||
* $data['fee'] is stored as an integer minor unit (cents), matching
|
||||
* Lunar's own Price convention everywhere else in this codebase — the
|
||||
* form collects/displays a decimal and converts at the boundary.
|
||||
*/
|
||||
private static function editFeeAction(): Action
|
||||
{
|
||||
return Action::make('edit_fee')
|
||||
->label('Edit fee')
|
||||
->icon('heroicon-o-pencil')
|
||||
->schema([
|
||||
TextInput::make('fee')
|
||||
->label('Fee')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->step(0.01)
|
||||
->helperText('Flat surcharge added when this payment method is selected.'),
|
||||
])
|
||||
->fillForm(fn (PaymentMethod $record) => [
|
||||
'fee' => filled($record->data['fee'] ?? null) ? $record->data['fee'] / 100 : null,
|
||||
])
|
||||
->action(function (PaymentMethod $record, array $data) {
|
||||
$record->update([
|
||||
'data' => [
|
||||
...$record->data->toArray(),
|
||||
'fee' => filled($data['fee']) ? (int) round($data['fee'] * 100) : null,
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListPaymentMethods::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDelete($record = null): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Payment\Filament\Resources\PaymentMethodResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Modules\Core\Payment\Filament\Resources\PaymentMethodResource;
|
||||
|
||||
class ListPaymentMethods extends ListRecords
|
||||
{
|
||||
protected static string $resource = PaymentMethodResource::class;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Payment\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Admin-editable settings for one payment type key (matching a key in
|
||||
* config('lunar.payments.types')) — enabled/disabled, and whatever type-
|
||||
* specific data it needs (starts with 'fee' for cash-on-delivery's flat
|
||||
* surcharge). Mirrors Lunar's own Discount model: a single jsonb 'data'
|
||||
* column holding keyed settings, rather than a fixed column per setting or
|
||||
* a separate conditions table — new settings are a code change (a new key
|
||||
* read from data), not a migration.
|
||||
*
|
||||
* Seeded once per type by InstallLunarCommand (skip-if-exists, same
|
||||
* idempotent convention as seedStorefrontLabels()) — never auto-created on
|
||||
* read, so a read path stays a pure read.
|
||||
*/
|
||||
class PaymentMethod extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'enabled' => 'boolean',
|
||||
'data' => AsArrayObject::class,
|
||||
];
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace Modules\Core\Payment\Pipelines\Cart;
|
||||
use Closure;
|
||||
use Lunar\DataTypes\Price;
|
||||
use Lunar\Models\Contracts\Cart as CartContract;
|
||||
use Modules\Core\Payment\Models\PaymentMethod;
|
||||
|
||||
final class ApplyCashOnDeliveryFee
|
||||
{
|
||||
@@ -16,7 +17,7 @@ final class ApplyCashOnDeliveryFee
|
||||
public function handle(CartContract $cart, Closure $next): mixed
|
||||
{
|
||||
if (($cart->meta['payment_method'] ?? null) === 'cash-on-delivery') {
|
||||
$fee = (int) config('lunar.payments.types.cash-on-delivery.fee', 0);
|
||||
$fee = (int) (PaymentMethod::where('type', 'cash-on-delivery')->value('data->fee') ?? 0);
|
||||
|
||||
$cart->shippingTotal = new Price(
|
||||
($cart->shippingTotal?->value ?? 0) + $fee,
|
||||
|
||||
Reference in New Issue
Block a user