Skip to content
Paybob

Development

Theme development

Build a theme for the checkout, invoice and payment-link pages.

A theme controls how customers see a Brand's checkout, invoice and payment-link pages. Paybob handles everything behind those pages: which gateways are offered, prices, form validation and payments. Your theme only decides how they look.

Package structure

Text
themes/midnight/
├── ProcessTheme.php                  ← required: the theme class
├── assets/
│   ├── preview.svg                   ← the preview shown in the theme gallery
│   ├── style.css
│   └── script.js
└── views/
    ├── checkout.blade.php            ← required
    ├── invoice.blade.php             ← required
    ├── payment-link.blade.php        ← required
    ├── payment-link-default.blade.php← required
    ├── layout.blade.php              ← optional: your shared layout
    └── partials/                     ← optional

The folder name is the theme's slug: lowercase letters, numbers, hyphens and underscores, starting with a letter or number, up to 80 characters.

The theme class

ProcessTheme.php declares ProcessTheme in the namespace Themes\{StudlySlug}Theme, extending Paybob's Theme class:

PHP
<?php

namespace Themes\MidnightTheme;

use App\Support\Themes\Forms\Field;
use App\Support\Themes\Forms\Form;
use App\Support\Themes\Forms\Section;
use App\Support\Themes\Theme;
use App\Support\Themes\ThemeMetadata;

class ProcessTheme extends Theme
{
    public static function metadata(): ThemeMetadata
    {
        return ThemeMetadata::make()
            ->name('Midnight')
            ->logo('assets/preview.svg')
            ->version('1.0.0');
    }

    public function config(): Form
    {
        return Form::make([
            Section::make('Colours')->schema([
                Field::make('primary_color')->color()->label('Primary color')->default('#146EF5')->required(),
                Field::make('show_faq')->toggle()->label('Show FAQ section')->default(true),
            ]),
        ]);
    }
}

For themes/midnight/, the namespace is Themes\MidnightTheme.

The four pages

PageViewShown at
Checkout: the customer picks a gatewaycheckout/{brand}/checkout/{t_id}
Invoice: view and pay an invoiceinvoice/{brand}/invoice/{i_id}
Payment linkpayment-link/{brand}/paymentlink/{p_id}
Default payment link: the customer enters an amountpayment-link-default/{brand}/paymentlink/default/{currency}

All four views must exist. To use a different view for a page, override checkout(), invoice(), paymentlink() or paymentlinkdefault() and return the name of another view in your package.

Your views are in their own namespace. Every view gets $themeNamespace, so you can extend and include without hard-coding the slug:

Blade
@extends($themeNamespace.'::layout')

@include($themeNamespace.'::partials.header')

Settings

config() returns the form admins see under Customize. Settings are saved per theme and per Brand, and kept if the Brand switches themes and back.

Field type
text(), textarea(), number()Text and numbers
select($options), radio($options)Choices, as value => label
toggle(), checkbox()On/off
color()A colour, as a 6-digit hex value
image()An image upload (PNG, JPEG, WebP or GIF, up to 2 MB)
password()A secret, encrypted and never passed to your public views

Modifiers: label(), placeholder(), helperText(), default(), required(), nullable(), disabled(), readOnly(), revealable(), columnSpan(), columnSpanFull(), and rules([...]) for extra validation (min, max, size, email, url, integer, numeric, alpha_dash, starts_with, ends_with). Keys are lowercase snake_case. Section::make('Title')->schema([...]) groups fields; sections are optional.

Read settings in views from $themeSettings['primary_color'], or with theme_setting('primary_color', '#146EF5'). Settings are plain text, not HTML: output them with {{ }}, never {!! !!}.

What each page receives

Every view gets:

VariableContains
$contextThe page's data (below). Read-only.
$context->merchantThe Brand's profile: name, logo, favicon, address, support contacts, social links, SEO, Tag Manager ID. Every value is display-ready.
$context->faqThe Brand's active FAQs, each with title and content.
$themeSettingsYour theme's settings.
$themeYour theme's metadata.
$localeThe customer's language.

Checkout

MethodGives
$context->transaction()t_id, amount, discount, processing_fee, total, status, payment_method, redirect_url
$context->customer()name, email, phone
$context->gateways()Every gateway available for this payment: g_id, name, logo_url, currency, amount, discount, processing_fee, total, min_amount, max_amount, checkout_url
$context->menus()The Brand's Checkout Menus, in order, each with title, icon and its gateways. Empty when the Brand has none: then show gateways() as one list.
$context->currency()The payment's currency

Each gateway's prices are already converted and include its fees and discounts. Link each gateway to its checkout_url. A menu can be empty: whether to show it is up to you.

When a transaction is already paid, show its status instead of the gateway list.

Invoice

$context->invoice() gives i_id, status, notes, due_date, overdue, items (each with description, quantity, amount, discount, vat, total) and totals (subtotal, discount, vat, shipping, grand_total), plus customer() and currency().

$context->paymentLink() gives p_id, name, description, quantity, amount and image_url (the default link has none). $context->fields() lists the form fields to show: name, email, phone, the amount (default link only) and the link's custom fields. Each field has key, label, type, required and value, and may have readonly, options and file_extensions. Name custom fields custom[field_key].

Payment forms

The invoice and payment-link pages submit a form to start the payment:

Blade
@if ($context->submitUrl())
    <form method="POST" action="{{ $context->submitUrl() }}" enctype="multipart/form-data">
        @csrf
        <input type="hidden" name="checkout_token" value="{{ $context->submitToken() }}">

        {{-- your fields --}}

        <button type="submit">Pay</button>
    </form>
@elseif ($context->paymentUnavailable())
    <p>Payments are temporarily unavailable. Please try again later.</p>
@endif
  • Only show the pay button when submitUrl() is set. It isn't on a paid invoice, for example.
  • When paymentUnavailable() is true, show that neutral message instead. Never tell customers why.
  • Include @csrf and the hidden checkout_token.
  • Use Laravel's $errors and old() to show validation errors, but never refill a read-only amount from old().

Paybob checks everything again on the server: the amount, the currency and the customer always come from the invoice or link, never from the form.

Languages

Customers can choose English, Bangla, Hindi or Urdu, and their choice is shared with gateway pages. Build a language menu from customer_locale()->supported(), linking each option to ?lang={code}, and translate your own text with customer_locale()->resolve(['en' => 'Pay', 'bn' => 'পরিশোধ করুন']). Text the merchant typed (menu titles, field labels, FAQs) is shown exactly as entered.

Tag Manager and SEO

Read the Brand's settings from $context->merchant: googleTagManagerId(), metaDescription(), seoKeywords(), ogImageUrl() and twitterImageUrl(). Each is null or empty when not set: leave the tag out entirely rather than outputting an empty one.

Assets

Put CSS, JavaScript and images in assets/ and link them with theme_assets('assets/style.css'). Paybob publishes them when the theme is activated, and adds your theme's version to every URL, such as ?v=1.0.0.

Bump the version when assets change

Browsers keep cached copies of your CSS and JavaScript until the URL changes. Raise version() every time you change a file in assets/, or customers keep seeing the old one.

Testing and packaging

  • Put the folder in themes/ on a development copy of Paybob. It appears on the Themes page; activate it to see it.
  • If your theme fails to render, Paybob falls back to the default theme and logs the error, so a broken theme never takes checkout down. Check the log while developing.
  • ZIP the folder so the ZIP contains one top-level folder named after the slug (up to 50 MB), and install it with Themes → Upload Theme.
  • Themes can't be updated by uploading yet: a ZIP whose slug is already installed is refused.

The default theme, themes/default/, is a complete example: grouped gateway menus, a support drawer with FAQs, four languages, colour settings and an invoice with "Download PDF".