boboko-core · competitive gap survey · 03
Lunar's payment layer (Lunar\Facades\Payments, Transaction, the offline
driver) is wired for a single "pay on delivery / bank transfer" flow. Everything downstream of
that — cards, wallets, saved methods, self-service refunds, retries — is either scaffolded in
Lunar core and unused here, or absent from the stack entirely. This is a research survey, not a
build plan.
boboko currently ships one payment type: cash-in-hand via the offline driver. Every card/wallet/BNPL path below is theoretically pluggable but has zero live implementation.
config/lunar/payments.php (3dealer's published copy): 'cash-in-hand' => ['driver' => 'offline', 'authorized' => 'payment-offline'], backed by Lunar\PaymentTypes\OfflinePayment.lunarphp/stripe is not present in either boboko-core/vendor/lunarphp or 3dealer/vendor/lunarphp, and not listed in either composer.json. docs/lunar.md's Stripe section documents Lunar's general capability, not something wired into this project.Lunar\Managers\PaymentManager extends Laravel's Manager; Payments::extend('custom', fn ($app) => ...) registers a new driver, and any class extending Lunar\PaymentTypes\AbstractPayment implementing authorize()/capture()/refund() plugs in. The scaffolding is solid — nothing beyond offline is plugged into it yet.The core primitives (intent/capture/refund, partial amounts, transaction chaining) exist in Lunar and are exposed in the Filament admin — but nothing calls them outside cash-in-hand, and none of it is customer-facing.
Lunar\Base\PaymentTypeInterface defines authorize(), capture(Transaction $t, $amount), refund(Transaction $t, int $amount, $notes); Transaction::capture()/refund() forward to the transaction's own driver() via Payments::driver($this->driver).OfflinePayment::capture() just returns new PaymentCapture(true) unconditionally — there's no real deferred-capture gateway wired up to exercise the distinction.$data['amount'] to $transaction->capture(bcmul($data['amount'], $record->currency->factor)) in ManageOrder.php — the plumbing supports partial amounts, but only staff can trigger it, and only against a real (non-offline) driver would it mean anything.$response = $transaction->refund(bcmul($data['amount'], ...), $data['notes']), and isPartiallyRefunded() / order status logic (partial-refund, refunded) compares refundTotal against captureTotal/intentTotal. This genuinely works today through the offline driver's no-op refund().Transaction.parent_transaction_id chains captures to intents and refunds to captures, and nothing in the model stops multiple transaction rows per order — but no code path in this repo actually retries a failed attempt with a second transaction; it's schema support, not a driven flow.Lunar\Observers\TransactionObserver::created() logs every transaction (amount, type, status, card_type, last_four, reference, notes) via Spatie activity log automatically — this is real and unconditional, independent of driver.stripe/webhook route, but that package isn't installed here, so there is no webhook endpoint of any kind in this project today.Lunar\Events\PaymentAttemptEvent is dispatched from OfflinePayment::authorize() with the resulting PaymentAuthorize DTO — a real, listenable event, though only one driver currently fires it.Everything a shopper would touch directly — saved cards, one-click repeat purchase, self-service refunds — is absent. Lunar's payment layer is staff/checkout-oriented, not account-oriented.
Lunar\Models — no PaymentMethod/Card model, no field on Customer. 2026 trend research (Nuvei, Checkout.com) treats network-tokenized saved cards as baseline for one-click checkout.ManageOrder.php (Actions\Action::make('refund')), gated behind admin auth. WooCommerce/PrestaShop ecosystems commonly expose a customer-initiated return/refund request flow; nothing equivalent exists here.PaymentTypeInterface or the offline driver. getPaymentChecks() exists as an extension point (Lunar\Base\DataTransferObjects\PaymentChecks, an iterable of pass/fail PaymentCheck DTOs) but AbstractPayment::getPaymentChecks() just returns an empty collection — real fraud tooling (Stripe Radar-style) isn't behind it.Transaction::paymentChecks() → driver's getPaymentChecks($transaction) is real, typed infrastructure for surfacing checks (e.g. "AVS matched") in the admin UI — but the default implementation is a no-op, so nothing populates it today.Lunar's multi-currency model covers pricing display, not multi-currency payment settlement; recurring billing/dunning has no representation at all.
Lunar\Models\Currency (code, exchange_rate, decimal_places, default) with sync_prices-gated conversion, documented in docs/lunar.md "Channels and Currencies" — this is genuinely wired, cart/pricing layer already uses it.Lunar\Models or boboko-core. This is a one-time-purchase order/cart model end to end.