PHP code example of artisanpack-ui / bookings

1. Go to this page and download the library: Download artisanpack-ui/bookings library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

artisanpack-ui / bookings example snippets


config( 'artisanpack.bookings.slot_interval' );        // 15
config( 'artisanpack.bookings.admin.gate' );           // 'bookings.manage'
config( 'artisanpack.bookings' );                      // the whole array

use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware( function ( Middleware $middleware ): void {
    $middleware->trustProxies( at: [
        '192.0.2.10', // the load balancer or CDN address requests actually arrive from
    ] );
} )

use ArtisanPackUI\Core\Facades\ArtisanPackSite;

ArtisanPackSite::forSite( $siteId, fn () => /* every bookings query answers for $siteId */ );
ArtisanPackSite::withoutSite( fn () => /* unscoped, for maintenance work */ );

use ArtisanPackUI\Bookings\Facades\Bookings;

app( 'bookings' );
Bookings::getFacadeRoot();
bookings();

use ArtisanPackUI\Bookings\Services\BookingService;

$booking = app( BookingService::class )->create( [
    'service'           => $service,
    'start_time'        => $start,          // any Carbon or parseable string
    'customer_name'     => 'Sam Rivera',
    'customer_email'    => '[email protected]',
    'customer_timezone' => 'America/Chicago',
    'intake_data'       => [ 'goal' => 'Learn to juggle' ],
] );

$bookings = app( BookingService::class );

$bookings->confirm( $booking, BookingActor::Admin );
$bookings->reschedule( $booking, $newStart, BookingActor::Customer );
$bookings->cancel( $booking, BookingActor::Customer, 'Something came up.' );
$bookings->complete( $booking, BookingActor::Provider );
$bookings->markNoShow( $booking, BookingActor::Admin );

use ArtisanPackUI\Bookings\Services\SeriesService;

$series = app( SeriesService::class )->create( [
    'service'          => $service,
    'rrule'            => 'FREQ=WEEKLY;COUNT=12',
    'dtstart_local'    => '2026-06-01 15:00:00',
    'dtstart_timezone' => 'America/Chicago',
    'customer_name'    => 'Sam Rivera',
    'customer_email'   => '[email protected]',
] );

$series->occurrences;   // twelve ordinary bookings, linked by series_id

use ArtisanPackUI\Bookings\Enums\SeriesEditScope;

$recurring = app( SeriesService::class );

// One week moves; the rule is untouched and that occurrence stops following it.
$recurring->edit( $series, SeriesEditScope::This, [ 'start_time' => $newStart ], $occurrence );

// The rule is bounded here, and the new series it returns carries the change forward.
$tail = $recurring->edit( $series, SeriesEditScope::ThisAndFollowing, [ 'rrule' => '…' ], $occurrence );

// The rule is rewritten and everything still to come is re-derived from it.
$recurring->edit( $series, SeriesEditScope::All, [ 'rrule' => '…' ] );

$recurring->cancel( $series, BookingActor::Customer, 'Moving away.' );

use ArtisanPackUI\Bookings\Services\ManageTokenService;

$tokens = app( ManageTokenService::class );

// Minted automatically when a booking is created — take it once, for the email.
$token = $booking->pullPlainManageToken();

$booking = $tokens->findBooking( $requestToken );   // null when the token is unknown
$tokens->verifyFor( $booking, $requestToken );      // hash_equals, never ==

$fresh = $tokens->issueFor( $booking );             // the old link stops working here

use Illuminate\Support\Facades\Route;

Route::get( '/bookings/manage/{token}', fn () => view( 'bookings.manage' ) )
    ->middleware( [
        'bookings.rate-limit:manage_get',
        'bookings.rate-limit:manage_token',
        'bookings.manage-token',
    ] )
    ->name( 'bookings.manage' );

addAction( 'ap.bookings.icalTokenIssued', function ( ServiceProvider $provider, string $token ) {
    Mail::to( $provider->email )->send( new CalendarFeedIssued(
        app( IcalTokenService::class )->feedUrl( $token ),
    ) );
} );

use ArtisanPackUI\Bookings\Models\Webhook;

Webhook::create( [
    'name'   => 'Zapier',
    'url'    => 'https://hooks.zapier.test/bookings',
    'secret' => Str::random( 40 ),
    'events' => [ 'booking.confirmed', 'booking.cancelled' ],
] );

use ArtisanPackUI\Bookings\Services\WebhookDispatcher;

app( WebhookDispatcher::class )->dispatch( 'booking.confirmed', $payload, $siteId );

$signed = $request->header( 'X-ArtisanPack-Timestamp' ) . '.' . $request->getContent();

if ( ! hash_equals( 'sha256=' . hash_hmac( 'sha256', $signed, $secret ), $request->header( 'X-ArtisanPack-Signature' ) ) ) {
    abort( 401 );
}

use ArtisanPackUI\Bookings\Rules\ValidWebhookUrl;

$request->validate( [
    'url' => [ '

// config/artisanpack/bookings.php
'notifications' => [
    'channels'   => [ 'mail', 'database', 'webhook', 'sms' ],
    'sms_driver' => App\Sms\TwilioSmsDriver::class,
],

use ArtisanPackUI\Bookings\Contracts\SmsDriver;

class TwilioSmsDriver implements SmsDriver
{
    public function send( string $phone, string $message ): void
    {
        // Throw if the gateway refuses it. The send is recorded against
        // booking_notification_log as failed, the other channels carry on,
        // and an operator has something to read.
    }
}

addFilter(
    'ap.bookings.notification.channels',
    function ( array $channels, string $event, Booking $booking ): array {
        if ( 'cancellation' === $event ) {
            $channels[] = 'sms';
        }

        return $channels;
    },
);

'retention' => [
    'prune_after_days'         => 365 * 3,   // read by bookings:prune
    'notification_log_days'    => 90,
    'webhook_delivery_days'    => null,   // falls back to webhooks.delivery_retention_days
    'calendar_events_ttl_days' => 30,
],

route( 'artisanpack.bookings.admin.bookings' );   // the list
route( 'artisanpack.bookings.admin.settings' );   // general config

Gate::define( 'bookings.manage', fn ( User $user ) => $user->isStaff() );

use ArtisanPackUI\Bookings\MeetingTypes\RegisteredMeetingType;

addFilter( 'ap.bookings.registeredMeetingTypes', function ( array $types ): array {
    $types[] = new RegisteredMeetingType(
        'webinar',
        'Webinar',
        'Broadcast to many attendees at once.',
        allowsMultipleAttendees: true,
    );

    return $types;
} );

use ArtisanPackUI\Bookings\Support\HookRegistry;

HookRegistry::all();      // every hook, with its type and the issue that fires it
HookRegistry::shipped();  // the ones firing today — the table above
HookRegistry::pending();  // declared, not yet fired

use ArtisanPackUI\Bookings\Support\HookSubscriptions;

HookSubscriptions::whenInstalled( 'forms', function (): void {
    addFilter( 'ap.forms.fieldTypes', /* ... */ );
} );
bash
php artisan vendor:publish --tag=bookings-config
bash
php artisan vendor:publish --tag=bookings-migrations
text
https://example.test/book?bookingService=discovery-call&bookingDate=2026-06-01
bash
php artisan vendor:publish --tag=bookings-views
text
GET  api/bookings/manage/{token}
POST api/bookings/manage/{token}/cancel        { "reason": "optional" }
POST api/bookings/manage/{token}/reschedule    { "start_time": "2026-06-01T19:00:00+00:00" }
bash
php artisan bookings:reissue-detached-manage-tokens
bash
php artisan bookings:complete-past --dry-run
php artisan bookings:prune-notification-log --dry-run