eugenefvdm/billing

A Laravel service provider and Livewire UI components for Payfast and EFT subscription billing

Installs: 30

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/eugenefvdm/billing

v1 2025-11-01 14:30 UTC

This package is auto-updated.

Last update: 2025-11-05 09:52:06 UTC


README

GitHub release (latest by date) Tests GitHub Downloads

A billing package for Laravel to use for EFT and credit card payment using Payfast Onsite Payments. Includes UI components for Livewire.

Requirements:

Installation

Install the package via composer:

composer require eugenefvdm/billing

Config file and views

Optional: Publish the config file:

php artisan vendor:publish --provider="Eugenefvdm\Billing\BillingServiceProvider" --tag="config"

Optional: Publish the views:

php artisan vendor:publish --provider="Eugenefvdm\Billing\BillingServiceProvider" --tag="views"

Add this to /resources/css/app.css to have the Tailwind CSS classes compiled properly:

@source '../../vendor/eugenefvdm/billing/resources/views/**/*.blade.php';

Setup

Add the Billable() trait to your user model.

use Eugenefvdm\Payfast\Billable;

class User extends Authenticatable
{    
    use Billable;

In your head section of your HTML you'll need this snippet:

@payfastScripts

If you're using Flux, add this to /resources/views/partials/head.blade.php say like below @fluxAppearance.

Migrations

A migration is needed to create Customers, Orders, Receipts and Subscriptions tables:

php artisan migrate

Configuration

See config/billing.php.

For testing you'll need:

PAYFAST_TEST_MODE=true
PAYFAST_MERCHANT_ID_TEST=
PAYFAST_MERCHANT_KEY_TEST=
PAYFAST_PASSPHRASE_TEST=

For live you'll need:

PAYFAST_MERCHANT_ID=
PAYFAST_MERCHANT_KEY=
PAYFAST_PASSPHRASE=

For localhost testing and to receive the Payfast ITN, you'll need Ngrok or Expose, and then add it's URL:

PAYFAST_TEST_MODE_ITN_URL=https://your-ngrok-url.ngrok-free.app

Here is an example Ngrok command to run when working on localhost:

ngrok http https://myapp.test --host-header=rewrite --domain=ngrok-supplied-url.ngrok-free.app

Pricing component

To include the pricing component on a page, do this:

In your header:

@vite(['resources/css/app.css', 'resources/js/app.js'])

In your view:

@include('billing::components.pricing')

Livewire setup

Views

Flux

/resources/views/components/layouts/app/sidebar.blade.php

<flux:menu.radio.group>
    <flux:menu.item :href="route('profile.edit')" icon="cog" wire:navigate>{{ __('Settings1') }}</flux:menu.item>
    <flux:menu.item :href="route('settings.billing')" icon="cog" wire:navigate>{{ __('Billing') }}</flux:menu.item>                        
</flux:menu.radio.group>

The Livewire views are modelled to blend into a Laravel Jetstream user profile page.

Adding the subscriptions and receipts views

When calling the Livewire component, you can override any Payfast form field by specifying a mergeFields array.

Example modification Jetstream Livewire's resources/views/profiles/show.php:

Replace $user->name with your first name and last name fields.

<!-- Subscriptions -->
<div class="mt-10 sm:mt-0">    
    @livewire('subscriptions', ['mergeFields' => [
            'name_first' => $user->name,
            'name_last' => $user->name,
            'item_description' => 'Subscription to Online Service'
        ]] )        
</div>

<x-billing::section-border />
<!-- End Subscriptions -->

<!-- Receipts -->
    <div class="mt-10 sm:mt-0">
        @livewire('receipts')
    </div>

<x-billing::section-border />
<!-- End Receipts -->

Usage

Examples

  • Generate a payment link
  • Create an adhoc token optionally specifying the amount
  • Cancel a subscription
  • Update a card
use Eugenefvdm\Payfast\Facades\Payfast;

Route::get('/payment', function() {
    return Payfast::payment(5,'Order #1');
});

Route::get('/cancel-subscription', function() {
    return Payfast::cancelSubscription('73d2a218-695e-4bb5-9f62-383e53bef68f');
});

Route::get('/create-subscription', function() {
    return Payfast::createSubscription(
        Carbon::now()->addDay()->format('Y-m-d'),
        5, // Amount
        6 // Frequency (6 = annual, 3 = monthly)
    );
});

Route::get('/create-adhoc-token', function() {
    return Payfast::createAdhocToken(5);
});

Route::get('/fetch-subscription', function() {
    return Payfast::fetchSubscription('21189d52-12eb-4108-9c0e-53343c7ac692');
});

Route::get('/update-card', function() {
    return Payfast::updateCardLink('40ab3194-20f0-4814-8c89-4d2a6b5462ed');
});

Workflows

How to determine when a user's subscription ends

$user->subscription('default')->ends_at = [date in the past]

Testing

composer test

If you're developing against a project, you can use the ./switch script to switch your project in and out of the repository.

This has the same effect as adding a repository and symlinking, and reversing it again when done.

Adding a repo manually instead of using ./switch:

"repositories": [
        {
            "type": "path",
            "url": "../billing"
        }
],

Symlinking manually:

composer require fintechsystems/payfast-onsite-subscriptions:dev-main

If you want to test trials, use this one-liner to activate a billable user and a trial using say Tinker or Tinkerwell:

$user = User::find(x)->createAsCustomer(['trial_ends_at' => now()->addDays(30)]);

EFT Billing

This package supports both Card payments (via Payfast onsite) and EFT (bank transfer) payments with automated invoice generation and reminders.

How EFT Billing Works

  1. Subscription Creation: Create an EFT subscription with a start date
  2. Forward Mechanism: A scheduled command "forwards" the subscription by one billing period
  3. Invoice Generation: When forwarding, an invoice is automatically created with a PDF
  4. Email Delivery: The invoice PDF is emailed to the customer
  5. Overdue Reminders: Automated reminders are sent at configurable intervals

Configuration

Add these to your .env:

# Invoice Settings
INVOICE_DEFAULT_DUE_DAYS=7
INVOICE_PDF_PATH=invoices

# Overdue Notice Intervals (days after due date)
INVOICE_FIRST_OVERDUE_NOTICE=3
INVOICE_SECOND_OVERDUE_NOTICE=6
INVOICE_THIRD_OVERDUE_NOTICE=9

Scheduled Commands

Add to your routes/console.php:

use Illuminate\Support\Facades\Schedule;

// Forward EFT subscriptions - creates invoices and moves subscription forward
Schedule::command('subscriptions:forward')->hourly();

// Send overdue reminders for unpaid EFT invoices
Schedule::command('invoices:check-overdue')->daily();

Important: The subscriptions:forward command will create invoices even when there are outstanding unpaid invoices. This is by design to ensure continuous billing cycles - subscriptions continue to advance and generate invoices regardless of payment status. Only manual resubscription via the UI will check for outstanding invoices before creating a new subscription.

Creating an EFT Subscription

use Eugenefvdm\Billing\Enums\PaymentMethod;

// Create EFT subscription
$subscription = $user->subscriptions()->create([
    'name' => 'default',
    'type' => 'monthly', // or 'yearly'
    'payment_method' => PaymentMethod::Eft,
    'status' => 'ACTIVE',
    'ends_at' => now()->addMonth(),
]);

// First invoice is created immediately
// Forward command will create subsequent invoices automatically

Accessing Invoices

// Get all invoices for a user
$invoices = $user->invoices;

// Find invoice by UUID (for guest viewing)
$invoice = $user->findInvoice($uuid);

// Check invoice status
if ($invoice->isPaid()) {
    // Invoice is paid
}

if ($invoice->isOverdue()) {
    $daysPastDue = $invoice->days_past_due;
}

Guest Invoice Viewing

Invoices can be viewed without authentication using their UUID:

https://yourapp.com/invoices/{uuid}
https://yourapp.com/invoices/{uuid}/download

These URLs are included in email notifications and are secure (UUID is hard to guess).

Manual Invoice Operations

use Eugenefvdm\Billing\Services\InvoiceService;

// Manually create an invoice for a subscription
$invoice = InvoiceService::createSubscriptionInvoice($subscription);

// Generate PDF
InvoiceService::createPdf($invoice);

// Stream PDF to browser
InvoiceService::createPdf($invoice, stream: true);

// Mark as paid
$invoice->markAsPaid();

Payment Instructions

Update the payment instructions in:

  • resources/views/vendor/billing/pdf/invoice.blade.php
  • resources/views/vendor/billing/mail/invoice-created.blade.php
  • resources/views/vendor/billing/invoices/show.blade.php

Replace [Your Bank Name], [Your Account Number], etc. with your actual banking details.

Reminder System

The reminder system is notification only - it never changes subscription status automatically:

  1. First Notice (3 days overdue by default): Friendly reminder
  2. Second Notice (6 days overdue): Firmer tone
  3. Third Notice (9 days overdue): Final notice

Reminders are only sent once per period. The system tracks when each reminder was sent.

Testing EFT Flow

// In Tinker or a test
$user = User::first();

// Create EFT subscription
$subscription = $user->subscriptions()->create([
    'type' => 'monthly',
    'payment_method' => \Eugenefvdm\Billing\Enums\PaymentMethod::Eft,
    'status' => 'ACTIVE',    
    'ends_at' => now()->subDay(), // Period already ended
]);

// Run forward command
php artisan subscriptions:forward

// Check that invoice was created
$user->invoices; // Should have 1 invoice

// Check that subscription moved forward
$subscription->refresh();
$subscription->ends_at; // Should be ~1 month from now

Changelog

Please see CHANGELOG for more information on what has changed recently.

Credits

License

The MIT License (MIT). Please see License File for more information.

This package includes code derived from Laravel Cashier (Paddle) which is also licensed under the MIT License. Copyright (c) Taylor Otwell.