Skip to main content
Payment methods represent the ways customers can pay for their orders. Shopper provides a flexible system to manage multiple payment providers, each with its own logo, description, and customer-facing instructions.

Model

Shopper\Core\Models\PaymentMethod
use Shopper\Core\Models\PaymentMethod;

// The model implements
- Shopper\Core\Models\Contracts\PaymentMethod

// Uses traits
- HasSlug
- HasZones

Database Schema

PaymentMethod Table

ColumnTypeNullableDefaultDescription
idbigintnoautoPrimary key
titlestringno-Payment method name
slugstringyesautoURL-friendly identifier (unique)
logostringyesnullPath to the logo file
link_urlstringyesnullPayment provider website or documentation URL
descriptiontextyesnullDescription displayed to customers when choosing a payment method
instructionstextyesnullInstructions displayed to customers after placing an order
is_enabledbooleannofalsePayment method visibility
driverstringyesnullPayment driver code (stripe, manual, etc.) — added by shopper/payment
created_attimestampyesnullCreation timestamp
updated_attimestampyesnullLast update timestamp

Contract

The PaymentMethod contract defines the interface that all payment method models must implement:
namespace Shopper\Core\Models\Contracts;

use Illuminate\Database\Eloquent\Relations\MorphToMany;

interface PaymentMethod
{
    public function zones(): MorphToMany;
}
Payment methods use zones to control availability by geographic region. A payment method can be assigned to multiple zones, and a zone can have multiple payment methods.
The driver column connects a payment method to a registered payment driver (stripe, manual, paypal, etc.). When the PaymentProcessingService processes a payment, it uses this value to resolve the correct driver. See Payments for driver registration and the full payment lifecycle.

Relationships

Zones

Payment methods can be scoped to specific geographic zones using the HasZones trait:
// Get zones where this payment method is available
$paymentMethod->zones;

// Attach payment method to zones
$paymentMethod->zones()->attach([$zoneId1, $zoneId2]);

// Detach from zones
$paymentMethod->zones()->detach($zoneId);

Orders

Orders reference payment methods via the payment_method_id foreign key:
use Shopper\Core\Models\Order;

// Get the payment method used for an order
$order->paymentMethod;

// Get all orders using a specific payment method
Order::query()
    ->where('payment_method_id', $paymentMethod->id)
    ->get();

Query Scopes

use Shopper\Core\Models\PaymentMethod;

// Get only enabled payment methods
PaymentMethod::query()->enabled()->get();

Logo Handling

Payment method logos use Spatie Media Library. The logoUrl() method returns the first media URL from the thumbnail collection, with a fallback to the payment driver’s logo:
$paymentMethod->logoUrl(); // Media library URL or null

use Shopper\Payment\Services\PaymentProcessingService;

$service = resolve(PaymentProcessingService::class);
$service->getLogoUrl($paymentMethod); // Media URL → driver logo → null

Creating Payment Methods

Basic Payment Method

use Shopper\Core\Models\PaymentMethod;

$paymentMethod = PaymentMethod::query()->create([
    'title' => 'Credit Card',
    'slug' => 'credit-card',
    'driver' => 'stripe',
    'link_url' => 'https://stripe.com',
    'description' => 'Pay securely with your credit or debit card.',
    'instructions' => 'Your card will be charged immediately upon order confirmation.',
    'is_enabled' => true,
]);

Cash on Delivery

$cod = PaymentMethod::query()->create([
    'title' => 'Cash on Delivery',
    'slug' => 'cash-on-delivery',
    'driver' => 'manual',
    'description' => 'Pay with cash when your order is delivered.',
    'instructions' => 'Please have the exact amount ready for the delivery driver.',
    'is_enabled' => true,
]);

Bank Transfer

$bankTransfer = PaymentMethod::query()->create([
    'title' => 'Bank Transfer',
    'slug' => 'bank-transfer',
    'driver' => 'manual',
    'description' => 'Pay via direct bank transfer.',
    'instructions' => 'Please transfer the total amount to the following account: IBAN XX00 0000 0000 0000. Use your order number as the payment reference.',
    'is_enabled' => true,
]);

Zone-Scoped Payment Method

$mobileMoney = PaymentMethod::query()->create([
    'title' => 'Mobile Money',
    'slug' => 'mobile-money',
    'link_url' => 'https://notchpay.co',
    'description' => 'Pay with your mobile money wallet.',
    'is_enabled' => true,
]);

// Make available only in specific zones
$mobileMoney->zones()->attach([$africaZoneId]);

Retrieving Payment Methods

use Shopper\Core\Models\PaymentMethod;

// All enabled payment methods
$methods = PaymentMethod::query()
    ->enabled()
    ->get();

// Find by slug
$method = PaymentMethod::query()
    ->where('slug', 'credit-card')
    ->first();

// Payment methods available in a specific zone
$methods = PaymentMethod::query()
    ->enabled()
    ->whereHas('zones', fn ($q) => $q->where('zone_id', $zoneId))
    ->get();

// Payment methods with no zone restriction (available everywhere)
$globalMethods = PaymentMethod::query()
    ->enabled()
    ->whereDoesntHave('zones')
    ->get();

Working with Orders

When creating an order, associate it with the selected payment method:
use Shopper\Core\Models\Order;

$order = Order::query()->create([
    'number' => $orderNumber,
    'customer_id' => $customerId,
    'payment_method_id' => $paymentMethod->id,
    // ... other order fields
]);

// Access the payment method from an order
$order->paymentMethod->title;        // "Credit Card"
$order->paymentMethod->instructions; // Post-order instructions

Storefront Example

Checkout Payment Selection

namespace App\Http\Controllers;

use Shopper\Core\Models\PaymentMethod;
use Shopper\Core\Models\Zone;

class CheckoutController extends Controller
{
    public function showPayment()
    {
        $zone = $this->getCustomerZone();

        $paymentMethods = PaymentMethod::query()
            ->enabled()
            ->where(function ($query) use ($zone): void {
                $query->whereHas('zones', fn ($q) => $q->where('zone_id', $zone?->id))
                    ->orWhereDoesntHave('zones');
            })
            ->get();

        return view('checkout.payment', compact('paymentMethods'));
    }

    public function storePayment(Request $request)
    {
        $paymentMethod = PaymentMethod::query()->findOrFail(
            $request->input('payment_method_id')
        );

        session()->put('checkout.payment_method_id', $paymentMethod->id);

        return redirect()->route('checkout.review');
    }

    private function getCustomerZone(): ?Zone
    {
        $address = Cart::shippingAddress();

        return Zone::query()
            ->whereHas('countries', fn ($q) => $q->where('name', $address->country_name))
            ->first();
    }
}
Payment methods without any zone assignment are considered globally available and will be shown to all customers regardless of their location.

Use Cases

Payment MethodDescription
Credit CardOnline card payments via Stripe, PayPal, etc.
Bank TransferDirect bank wire transfer
Cash on DeliveryPayment at delivery time
Mobile MoneyMobile wallet payments (M-Pesa, Orange Money, etc.)
PayPalPayPal account or guest checkout
CryptocurrencyBitcoin, Ethereum, etc.
ScenarioImplementation
Single payment providerCreate one enabled payment method
Region-specific methodsUse zones to restrict availability
Offline paymentsUse instructions field for post-order guidance
Gateway documentationUse link_url to reference provider docs
Display at checkoutQuery enabled methods filtered by customer zone