nava/dinlr-php

PHP client library for the Dinlr API

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 2

Open Issues: 1

pkg:composer/nava/dinlr-php

0.2.3 2025-10-23 01:15 UTC

This package is auto-updated.

Last update: 2025-11-16 03:23:18 UTC


README

A comprehensive PHP client library for integrating with the Dinlr Online Order API. This library provides a clean, object-oriented interface to interact with all Dinlr API endpoints including orders, customers, inventory, reservations, loyalty programs, and more.

Table of Contents

Requirements

  • PHP 7.1 or higher
  • Guzzle HTTP library (^6.3|^7.0)
  • JSON extension

Installation

Install via Composer:

composer require nava/dinlr-php

Quick Start

<?php
require_once 'vendor/autoload.php';

use Nava\Dinlr\Client;

// Initialize the client
$client = new Client([
    'api_key' => 'your_api_key',
    'restaurant_id' => 'your_restaurant_id',
    'debug' => true,
]);

// Get restaurant details
$restaurant = $client->restaurant()->get();
echo "Restaurant: " . $restaurant->getName() . "\n";

// List locations
$locations = $client->locations()->list();
foreach ($locations as $location) {
    echo "Location: " . $location->getName() . "\n";
}

// Get menu items for a location
if (count($locations) > 0) {
    $locationId = $locations->first()->getId();
    $items = $client->items()->list($locationId);

    echo "Found " . count($items) . " menu items\n";
}

Authentication

API Key Authentication

The simplest way to authenticate with the Dinlr API:

use Nava\Dinlr\Client;

$client = new Client([
    'api_key' => 'your_api_key',
    'restaurant_id' => 'your_restaurant_id',
    'api_url' => 'https://api.dinlr.com/v1', // optional
    'timeout' => 30, // optional, default: 30 seconds
    'debug' => true, // optional, default: false
]);

OAuth 2.0 Authentication

For applications that need to access multiple restaurants or require user authorization:

use Nava\Dinlr\OAuthClient;
use Nava\Dinlr\OAuthConfig;

// Step 1: Initialize OAuth client
$config = new OAuthConfig([
    'client_id' => 'your_client_id',
    'client_secret' => 'your_client_secret',
    'redirect_uri' => 'https://yourapp.com/oauth/callback',
    'api_url' => 'https://api.dinlr.com/v1',
]);

$client = new OAuthClient($config);

// Step 2: Redirect user to authorization URL
$state = bin2hex(random_bytes(16)); // Generate random state
session_start();
$_SESSION['oauth_state'] = $state;

$authUrl = $client->getAuthorizationUrl($state);
header("Location: {$authUrl}");
exit;

OAuth Callback Handler

// callback.php
session_start();

try {
    // Step 3: Handle the callback
    $callbackData = $client->handleCallback($_GET, $_SESSION['oauth_state']);

    // Step 4: Exchange code for access token
    $tokens = $client->getAccessToken(
        $callbackData['code'],
        $callbackData['restaurant_id']
    );

    // Step 5: Store tokens and use the client
    $_SESSION['access_token'] = $tokens['access_token'];
    $_SESSION['refresh_token'] = $tokens['refresh_token'];
    $_SESSION['restaurant_id'] = $callbackData['restaurant_id'];

    // Now you can use the client with the access token
    $client->setAccessToken($tokens['access_token']);

    // Make API calls
    $restaurant = $client->restaurant()->get();
    echo "Connected to: " . $restaurant->getName();

} catch (\Nava\Dinlr\Exception\ApiException $e) {
    echo "OAuth error: " . $e->getMessage();
}

Core Features

Restaurant & Settings Management

// Get restaurant information
$restaurant = $client->restaurant()->get();
echo "Restaurant: " . $restaurant->getName() . " (" . $restaurant->getCurrency() . ")\n";
echo "Updated: " . $restaurant->getUpdatedAt() . "\n";

// Get all locations
$locations = $client->locations()->list();
foreach ($locations as $location) {
    echo "Location: " . $location->getName() . " (ID: " . $location->getId() . ")\n";
}

// Get dining options for a location
$locationId = $locations->first()->getId();
$diningOptions = $client->diningOptions()->list($locationId);

foreach ($diningOptions as $option) {
    echo "Dining Option: " . $option->getName() . " (Sort: " . $option->getSort() . ")\n";
}

// Get payment methods
$paymentMethods = $client->paymentMethods()->list($locationId);
foreach ($paymentMethods as $method) {
    echo "Payment Method: " . $method->getName() . "\n";

    // Check for additional inputs required
    $inputs = $method->getPaymentInputs();
    if (!empty($inputs)) {
        echo "  Required inputs: " . count($inputs) . "\n";
    }
}

// Get additional charges
$charges = $client->charges()->list($locationId);
foreach ($charges as $charge) {
    echo "Charge: " . $charge->getName() . "\n";

    // Check which dining options this charge applies to
    $applicableOptions = $charge->getDiningOptions();
    echo "  Applies to " . count($applicableOptions) . " dining options\n";
}

Menu Management

// Get menu items with detailed information
$items = $client->items()->list($locationId);

foreach ($items as $item) {
    echo "Item: " . $item->getName() . "\n";
    echo "  Description: " . ($item->getDescription() ?: 'N/A') . "\n";
    echo "  Category: " . $item->getCategory() . "\n";

    // Display variants with pricing
    $variants = $item->getVariants();
    echo "  Variants (" . count($variants) . "):\n";

    foreach ($variants as $variant) {
        $price = $variant['price'] ? '$' . $variant['price'] : 'Open price';
        echo "    - " . $variant['name'] . ": {$price}\n";

        if (!empty($variant['sku'])) {
            echo "      SKU: " . $variant['sku'] . "\n";
        }
    }

    // Display available modifiers
    $modifiers = $item->getModifiers();
    if (!empty($modifiers)) {
        echo "  Available modifiers: " . count($modifiers) . "\n";
    }
}

// Get categories with hierarchy
$categories = $client->categories()->list();

// Organize categories by hierarchy
$topLevel = [];
$subCategories = [];

foreach ($categories as $category) {
    if ($category->isTopLevel()) {
        $topLevel[] = $category;
    } else {
        $parentId = $category->getParentCategory();
        $subCategories[$parentId][] = $category;
    }
}

// Display hierarchical structure
foreach ($topLevel as $category) {
    echo "Category: " . $category->getName() . "\n";

    $categoryId = $category->getId();
    if (isset($subCategories[$categoryId])) {
        foreach ($subCategories[$categoryId] as $subCategory) {
            echo "  └─ " . $subCategory->getName() . "\n";
        }
    }
}

// Get modifiers with options
$modifiers = $client->modifiers()->list($locationId);

foreach ($modifiers as $modifier) {
    echo "Modifier: " . $modifier->getName() . "\n";
    echo "  Required: " . ($modifier->isRequired() ? 'Yes' : 'No') . "\n";
    echo "  Min selection: " . ($modifier->getMinSelection() ?: 'None') . "\n";
    echo "  Max selection: " . ($modifier->getMaxSelection() ?: 'Unlimited') . "\n";

    $options = $modifier->getModifierOptions();
    echo "  Options (" . count($options) . "):\n";

    foreach ($options as $option) {
        $price = $option['price'] > 0 ? ' (+$' . $option['price'] . ')' : ' (Free)';
        $default = $option['default_selected'] ? ' [Default]' : '';
        echo "    - " . $option['name'] . $price . $default . "\n";
    }
}

// Get complete menu structure with times
$menus = $client->menu()->list($locationId);

foreach ($menus as $menu) {
    echo "Menu: " . $menu->getName() . " (" . $menu->getItemCount() . " items)\n";

    // Display menu availability
    $times = $menu->getTimes();
    if (!empty($times)) {
        echo "  Available:\n";
        foreach ($times as $time) {
            echo "    " . $time['day'] . ": " . $time['start_time'] . " - " . $time['end_time'] . "\n";
        }
    }

    // Check availability for specific day
    $isAvailableToday = $menu->isAvailableOnDay(date('l')); // Current day
    echo "  Available today: " . ($isAvailableToday ? 'Yes' : 'No') . "\n";
}

Customer Management

// Create a new customer with full details
$customerData = [
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => 'john.doe@example.com',
    'phone' => '+1234567890',
    'company_name' => 'Example Corp',
    'dob' => '1990-01-15',
    'gender' => 'M',
    'address1' => '123 Main Street',
    'address2' => 'Apt 4B',
    'city' => 'New York',
    'country' => 'US',
    'postal' => '10001',
    'notes' => 'VIP customer',
    'marketing_consent_email' => true,
    'marketing_consent_text' => false,
    'marketing_consent_phone' => false,
];

$customer = $client->customers()->create($customerData);

echo "Customer created: " . $customer->getId() . "\n";
echo "Full name: " . $customer->getFullName() . "\n";
echo "Display name: " . $customer->getDisplayName() . "\n";

// Use business logic methods
if ($customer->hasCompleteProfile()) {
    echo "✓ Customer has complete profile\n";
} else {
    echo "⚠ Customer profile incomplete\n";
}

if ($customer->canReceiveMarketing('email')) {
    echo "✓ Can receive email marketing\n";
}

$age = $customer->getAge();
if ($age !== null) {
    echo "Customer age: " . $age . " years\n";

    if ($customer->isInAgeRange(18, 65)) {
        echo "✓ Customer is in target age range\n";
    }
}

// Search for customers
$searchResults = $client->customers()->search([
    'email' => 'john.doe@example.com'
]);

if (count($searchResults) > 0) {
    echo "Found customer by email\n";
}

// Search by phone
$phoneResults = $client->customers()->search([
    'phone' => '+1234567890'
]);

// Update customer information
$updatedCustomer = $client->customers()->update($customer->getId(), [
    'notes' => 'VIP customer - updated profile',
    'marketing_consent_email' => false,
]);

// Get all customers with pagination
$customers = $client->customers()->list(null, [
    'limit' => 50,
    'page' => 1,
]);

echo "Total customers retrieved: " . count($customers) . "\n";

// Analyze customer data
foreach ($customers as $customer) {
    $summary = $customer->getSummary();
    echo "Customer: " . $summary['display_name'] . "\n";
    echo "  Complete profile: " . ($summary['complete_profile'] ? 'Yes' : 'No') . "\n";
    echo "  Marketing consents: " . $summary['marketing_consents'] . "\n";
}

// Get customer groups
$customerGroups = $client->customerGroups()->list();
foreach ($customerGroups as $group) {
    echo "Customer Group: " . $group->getName() . " (ID: " . $group->getId() . ")\n";
}

Order Processing & Cart Management

// Calculate cart total before placing order
$cartData = [
    'location' => $locationId,
    'items' => [
        [
            'item' => $itemId,
            'variant' => $variantId,
            'qty' => 2,
            'modifier_options' => [
                [
                    'modifier_option' => $modifierOptionId,
                    'qty' => 1,
                ],
            ],
            'notes' => 'Extra spicy please',
        ],
    ],
    'discounts' => [
        [
            'discount' => $discountId,
            'value' => 10.00, // For open discounts
        ],
    ],
    'charges' => [
        [
            'charge' => $chargeId,
            'amount' => 5.00,
        ],
    ],
];

// Calculate cart summary
$summary = $client->cart()->calculate($cartData);

echo "Cart Summary:\n";
echo "Subtotal: $" . $summary->getSubtotal() . "\n";
echo "Total: $" . $summary->getTotal() . "\n";
echo "Financial status: " . $summary->getFinancialStatus() . "\n";

// Display item details
$items = $summary->getItems();
foreach ($items as $item) {
    echo "Item: " . $item['item'] . " x" . $item['qty'] . " = $" . $item['price'] . "\n";

    // Show modifier options
    if (!empty($item['modifier_options'])) {
        foreach ($item['modifier_options'] as $modOption) {
            echo "  + Modifier x" . $modOption['qty'] . " = $" . $modOption['price'] . "\n";
        }
    }
}

// Display discounts applied
$discounts = $summary->getDiscounts();
foreach ($discounts as $discount) {
    echo "Discount: " . $discount['name'] . " = -$" . $discount['amount'] . "\n";
}

// Display charges
$charges = $summary->getCharges();
foreach ($charges as $charge) {
    echo "Charge: " . $charge['name'] . " = +$" . $charge['amount'] . "\n";
}

// Place the order
$orderData = $cartData;
$orderData['order_info'] = [
    'dining_option' => $diningOptionId,
    'order_no' => 'ORD' . time(),
    'pax' => 2,
    'customer' => $customerId,
    'notes' => 'Please prepare quickly',
    'status' => 'pending',
];

$order = $client->cart()->submit($orderData);

echo "Order placed successfully!\n";
echo "Order ID: " . $order->getId() . "\n";
echo "Order Number: " . $order->getOrderNumber() . "\n";
echo "Total: $" . $order->getTotal() . "\n";
echo "Status: " . $order->getStatus() . "\n";

Advanced Order Management

// List orders with filtering
$orders = $client->orders()->list(null, [
    'status' => 'open',
    'financial_status' => 'paid',
    'location_id' => $locationId,
    'detail' => 'all', // Include full order details
    'limit' => 25,
]);

echo "Found " . count($orders) . " orders\n";

// Analyze orders using collection methods
$openOrders = $orders->getByStatus('open');
$paidOrders = $orders->getByFinancialStatus('paid');
$totalRevenue = $orders->getTotalRevenue();

echo "Open orders: " . count($openOrders) . "\n";
echo "Paid orders: " . count($paidOrders) . "\n";
echo "Total revenue: $" . $totalRevenue . "\n";

// Get detailed order information
$order = $client->orders()->get($orderId);

echo "Order Details:\n";
echo "Customer: " . ($order->getCustomerId() ?: 'Walk-in') . "\n";
echo "Dining Option: " . $order->getDiningOptionName() . "\n";
echo "Pax: " . $order->getPax() . "\n";
echo "Notes: " . ($order->getNotes() ?: 'None') . "\n";

// Check order status
if ($order->isPaid()) {
    echo "✓ Order is fully paid\n";
} elseif ($order->isPartiallyPaid()) {
    echo "⚠ Order is partially paid\n";
} else {
    echo "❌ Order is unpaid\n";
}

// Display order items
$items = $order->getItems();
foreach ($items as $item) {
    echo "Item: " . $item['name'] . " x" . $item['qty'] . "\n";
    echo "  Price: $" . $item['price'] . "\n";
    echo "  Notes: " . ($item['notes'] ?: 'None') . "\n";
}

// Add payment to order
$paymentData = [
    'payment' => $paymentMethodId,
    'amount' => 50.00,
    'receipt_no' => 'RCP' . time(),
    'payment_inputs' => [
        [
            'payment_input' => $paymentInputId,
            'value' => 'Additional payment info',
        ],
    ],
];

$updatedOrder = $client->orders()->addPayment($orderId, $paymentData);

// Manage order status
$client->orders()->close($orderId);          // Close order
$client->orders()->reopen($orderId);         // Reopen if needed
$client->orders()->setPending($orderId);     // Set to pending
$client->orders()->setPendingPayment($orderId); // Waiting for payment

// Kitchen and expedite management (requires KDS subscription)
$orderItemId = $items[0]['id'];

// Kitchen workflow
$client->orders()->setItemKitchenStatusPending($orderId, $orderItemId);
$client->orders()->setItemKitchenStatusFulfilled($orderId, $orderItemId);
$client->orders()->setItemKitchenStatusDefault($orderId, $orderItemId);

// Expedite workflow
$client->orders()->setItemExpediteStatusPending($orderId, $orderItemId);
$client->orders()->setItemExpediteStatusExpedited($orderId, $orderItemId);
$client->orders()->setItemExpediteStatusDefault($orderId, $orderItemId);

// Date range filtering
$startDate = (new DateTime('-7 days'))->format('c');
$endDate = (new DateTime())->format('c');

$recentOrders = $client->orders()->listByDateRange($startDate, $endDate);
echo "Orders in last 7 days: " . count($recentOrders) . "\n";

Loyalty Program Management

// Get all loyalty programs
$programs = $client->loyalty()->getPrograms();

foreach ($programs as $program) {
    echo "Loyalty Program: " . $program->getName() . "\n";
    echo "  Terms: " . $program->getTermSingle() . "/" . $program->getTermPlural() . "\n";

    // Get program rewards
    $rewards = $client->loyalty()->getRewards($program->getId());
    echo "  Rewards available: " . count($rewards) . "\n";

    foreach ($rewards as $reward) {
        echo "    - " . $reward->getName() . ": " . $reward->getPoint() . " " .
             $program->getPointTerm($reward->getPoint()) . "\n";
    }
}

// Work with a specific program
$programId = $programs->first()->getId();

// Enroll a customer
$memberData = ['customer' => $customerId];
$member = $client->loyalty()->enrolMember($programId, $memberData);

echo "Member enrolled:\n";
echo "Member ID: " . $member->getId() . "\n";
echo "Starting points: " . $member->getPoint() . "\n";

// Award points for an order
$transaction = $client->loyalty()->awardPointsForOrder(
    $programId,
    $member->getId(),
    $orderId,
    100, // points to award
    $locationId
);

echo "Points awarded: " . $transaction->getPoint() . "\n";

// Add points manually
$addTransaction = $client->loyalty()->addPoints(
    $programId,
    $member->getId(),
    50,
    'Welcome bonus',
    $locationId
);

// Redeem points for reward
$reward = $rewards->first();
if ($member->hasSufficientPoints($reward->getPoint())) {
    $redeemTransaction = $client->loyalty()->redeemReward(
        $programId,
        $member->getId(),
        $reward->getId(),
        $reward->getPoint(),
        $locationId
    );

    echo "Reward redeemed: " . $reward->getName() . "\n";
}

// Get member transactions
$transactions = $client->loyalty()->getMemberTransactions($programId, $member->getId());

echo "Member transaction history:\n";
foreach ($transactions as $transaction) {
    $type = $transaction->isPointAddition() ? 'Added' : 'Redeemed';
    echo "  " . $type . ": " . $transaction->getAbsolutePoints() . " points\n";
    echo "    Date: " . $transaction->getCreatedAt() . "\n";
    echo "    Notes: " . ($transaction->getNotes() ?: 'N/A') . "\n";
}

// Get all members
$members = $client->loyalty()->getMembers($programId);
$totalPoints = $members->getTotalPoints();

echo "Total members: " . count($members) . "\n";
echo "Total points in circulation: " . $totalPoints . "\n";

Store Credit Management

// Get customer store credit balance
$balance = $client->storeCredit()->getCustomerBalance($customerId);

echo "Store Credit Balance:\n";
echo "Customer: " . $balance->getId() . "\n";
echo "Balance: $" . $balance->getStoreCredit() . "\n";
echo "Has credit: " . ($balance->hasStoreCredit() ? 'Yes' : 'No') . "\n";

// Check if customer can afford a purchase
$purchaseAmount = 25.00;
if ($balance->hasSufficientCredit($purchaseAmount)) {
    echo "✓ Customer can afford $" . $purchaseAmount . " purchase\n";
} else {
    echo "❌ Insufficient store credit\n";
}

// Add store credit
$creditTransaction = $client->storeCredit()->addCredit(
    $customerId,
    100.00,
    'Refund for cancelled order',
    $locationId
);

echo "Credit added:\n";
echo "Amount: $" . $creditTransaction->getAmount() . "\n";
echo "Transaction ID: " . $creditTransaction->getId() . "\n";

// Deduct store credit
$debitTransaction = $client->storeCredit()->deductCredit(
    $customerId,
    25.50,
    'Used for order payment',
    $locationId
);

// Create store credit topup
$topupData = [
    'customer' => $customerId,
    'topup_no' => 'TOP' . time(),
    'topup_amount' => 100.00,
    'payment' => $paymentMethodId,
    'payment_amount' => 90.00, // 10% bonus
];

$topup = $client->storeCredit()->createTopup($topupData);

echo "Topup created:\n";
echo "Topup amount: $" . $topup->getTopupAmount() . "\n";
echo "Payment amount: $" . $topup->getPaymentAmount() . "\n";
echo "Bonus amount: $" . $topup->getBonusAmount() . "\n";
echo "Has bonus: " . ($topup->hasBonus() ? 'Yes' : 'No') . "\n";

// Search store credit transactions
$transactions = $client->storeCredit()->searchTransactions([
    'customer_id' => $customerId,
    'limit' => 10,
]);

echo "Customer transaction history:\n";
foreach ($transactions as $transaction) {
    $type = $transaction->isCreditAddition() ? 'Credit' : 'Debit';
    echo "  " . $type . ": $" . $transaction->getAbsoluteAmount() . "\n";
    echo "    Date: " . $transaction->getCreatedAt() . "\n";
    echo "    Notes: " . ($transaction->getNotes() ?: 'N/A') . "\n";
}

// Analyze transactions using collection methods
$creditAdditions = $transactions->getCreditAdditions();
$creditDeductions = $transactions->getCreditDeductions();
$totalCreditAmount = $transactions->getTotalCreditAmount();

echo "Statistics:\n";
echo "  Credit additions: " . count($creditAdditions) . "\n";
echo "  Credit deductions: " . count($creditDeductions) . "\n";
echo "  Net amount: $" . $totalCreditAmount . "\n";

Reservations Management

// Get available reservation times
$date = (new DateTime('+1 day'))->format('Y-m-d');
$adult = 4;
$children = 2;

$services = $client->reservations()->getAvailableServices($locationId, $date, $adult, $children);

echo "Available services for " . $date . ":\n";
foreach ($services as $service) {
    echo "Service: " . $service->getName() . "\n";
    echo "  Experience: " . $service->getExperienceId() . "\n";
    echo "  Table Section: " . $service->getTableSectionId() . "\n";

    $availableTimes = $service->getAvailableTimes();
    echo "  Available times: " . count($availableTimes) . "\n";

    foreach ($availableTimes as $time) {
        echo "    - " . $time['time'] . "\n";
    }
}

// Book a reservation
if (count($services) > 0 && $services->first()->hasAvailability()) {
    $service = $services->first();
    $availableTimes = $service->getAvailableTimes();

    $reservationData = [
        'location' => $locationId,
        'objects' => [
            [
                'object' => 'table_1',
                'pax' => 6,
            ],
        ],
        'reservation_info' => [
            'reservation_no' => 'RES' . time(),
            'reservation_time' => $availableTimes[0]['time'],
            'service' => $service->getId(),
            'customer' => $customerId,
            'first_name' => 'John',
            'last_name' => 'Doe',
            'email' => 'john@example.com',
            'phone' => '+1234567890',
            'pax' => 6,
            'adult' => $adult,
            'children' => $children,
            'notes' => 'Special occasion dinner',
            'confirm_by' => 'restaurant',
        ],
    ];

    $reservation = $client->reservations()->book($reservationData);

    echo "Reservation booked:\n";
    echo "Reservation ID: " . $reservation->getId() . "\n";
    echo "Reservation Number: " . $reservation->getReservationNumber() . "\n";
    echo "Time: " . $reservation->getReservationTime() . "\n";
    echo "Pax: " . $reservation->getPax() . "\n";
    echo "Status: " . $reservation->getStatus() . "\n";
    echo "Requires deposit: " . ($reservation->requiresDeposit() ? 'Yes' : 'No') . "\n";
}

// List all reservations
$reservations = $client->reservations()->list();

echo "All reservations: " . count($reservations) . "\n";

// Filter reservations using collection methods
$bookedReservations = $reservations->getByStatus('booked');
$upcomingReservations = $reservations->getUpcoming();
$totalPax = $reservations->getTotalPax();

echo "Booked: " . count($bookedReservations) . "\n";
echo "Upcoming: " . count($upcomingReservations) . "\n";
echo "Total guests expected: " . $totalPax . "\n";

// Get experiences and table sections
$experiences = $client->experiences()->list($locationId);
foreach ($experiences as $experience) {
    echo "Experience: " . $experience->getName() . " (Sort: " . $experience->getSort() . ")\n";
}

$tableSections = $client->tableSections()->list($locationId);
foreach ($tableSections as $section) {
    echo "Table Section: " . $section->getName() . "\n";
}

Discounts, Promotions & Vouchers

// Get discounts
$discounts = $client->discounts()->list($locationId);

foreach ($discounts as $discount) {
    echo "Discount: " . $discount->getName() . "\n";
    echo "  Type: " . $discount->getType() . "\n";
    echo "  Stackable: " . ($discount->isStackable() ? 'Yes' : 'No') . "\n";
    echo "  Manufacturer discount: " . ($discount->isManufacturerDiscount() ? 'Yes' : 'No') . "\n";

    if ($discount->isOpenDiscount()) {
        echo "  Value: Open (custom value)\n";
    } else {
        echo "  Value: " . $discount->getValue();
        echo $discount->isPercentDiscount() ? "%" : " (fixed amount)\n";
    }

    if ($discount->getMaxValue()) {
        echo "  Max value: " . $discount->getMaxValue() . "\n";
    }
}

// Get promotions
$promotions = $client->promotions()->list($locationId);

foreach ($promotions as $promotion) {
    echo "Promotion: " . $promotion->getName() . "\n";
    echo "  Summary: " . $promotion->getSummary() . "\n";
    echo "  Type: " . $promotion->getType() . "\n";
    echo "  Start: " . $promotion->getStartDate() . "\n";
    echo "  End: " . ($promotion->getEndDate() ?: 'No end date') . "\n";
    echo "  Active: " . ($promotion->isActive() ? 'Yes' : 'No') . "\n";
    echo "  Auto promotion: " . ($promotion->isAutoPromotion() ? 'Yes' : 'No') . "\n";
}

// Create a voucher
$voucherData = [
    'voucher_code' => 'SAVE20',
    'type' => 'discount',
    'discount' => $discounts->first()->getId(),
    'max_redemptions' => 100,
    'start_date' => (new DateTime())->format('c'),
    'end_date' => (new DateTime('+30 days'))->format('c'),
];

$voucher = $client->vouchers()->create($voucherData);

echo "Voucher created:\n";
echo "Code: " . $voucher->getVoucherCode() . "\n";
echo "Type: " . $voucher->getType() . "\n";
echo "Max redemptions: " . $voucher->getMaxRedemptions() . "\n";

// Search for vouchers
$voucherSearch = $client->vouchers()->search([
    'voucher_code' => 'SAVE20',
]);

if (count($voucherSearch) > 0) {
    $foundVoucher = $voucherSearch->first();
    echo "Found voucher: " . $foundVoucher->getVoucherCode() . "\n";
    echo "Can be redeemed: " . ($foundVoucher->canBeRedeemed() ? 'Yes' : 'No') . "\n";
    echo "Redeemed: " . $foundVoucher->getRedeemed() . " times\n";
}

// Create customer-specific voucher
$customerVoucherData = [
    'voucher_code' => 'VIP' . $customerId,
    'applicable' => 'customer',
    'customer' => $customerId,
    'type' => 'discount',
    'discount' => $discounts->first()->getId(),
    'max_redemptions' => 1,
    'start_date' => (new DateTime())->format('c'),
    'end_date' => (new DateTime('+7 days'))->format('c'),
];

$customerVoucher = $client->vouchers()->create($customerVoucherData);
echo "Customer-specific voucher created: " . $customerVoucher->getVoucherCode() . "\n";

Inventory Management

// Get materials
$materials = $client->materials()->list($locationId);

echo "Materials (" . count($materials) . "):\n";
foreach ($materials as $material) {
    echo "Material: " . $material->getName() . "\n";
    echo "  SKU: " . ($material->getSku() ?: 'N/A') . "\n";
    echo "  Unit: " . $material->getUnit() . "\n";
    echo "  Has SKU: " . ($material->hasSku() ? 'Yes' : 'No') . "\n";
}

// Get current stock levels
$stockLevels = $client->materials()->getStockLevels($locationId);

echo "Stock Analysis:\n";
echo "Total entries: " . count($stockLevels) . "\n";
echo "Total quantity: " . $stockLevels->getTotalQuantity() . "\n";

// Analyze stock status
$outOfStock = $stockLevels->getOutOfStock();
$lowStock = $stockLevels->getLowStock(10); // Items with 10 or fewer units

echo "Out of stock: " . count($outOfStock) . " items\n";
echo "Low stock (≤10): " . count($lowStock) . " items\n";

// Display stock details
foreach ($stockLevels as $stock) {
    echo "Material " . $stock->getMaterialId() . ": " . $stock->getQuantity() . " units\n";

    if ($stock->isOutOfStock()) {
        echo "  ❌ OUT OF STOCK\n";
    } elseif ($stock->isLowStock(10)) {
        echo "  ⚠ LOW STOCK\n";
    } else {
        echo "  ✓ In stock\n";
    }
}

// Get stock takes
$stockTakes = $client->materials()->getStockTakes();

echo "Stock Takes:\n";
$ongoing = $stockTakes->getOngoing();
$completed = $stockTakes->getCompleted();

echo "Ongoing: " . count($ongoing) . "\n";
echo "Completed: " . count($completed) . "\n";

foreach ($stockTakes as $stockTake) {
    echo "Stock Take: " . $stockTake->getId() . "\n";
    echo "  Location: " . $stockTake->getLocationId() . "\n";
    echo "  Status: " . ($stockTake->isOngoing() ? 'Ongoing' : 'Completed') . "\n";
    echo "  Start: " . $stockTake->getStartDate() . "\n";
    echo "  End: " . ($stockTake->getEndDate() ?: 'In progress') . "\n";
    echo "  Materials counted: " . $stockTake->getMaterialCount() . "\n";

    if ($stockTake->isCompleted()) {
        $duration = $stockTake->getDuration();
        echo "  Duration: " . round($duration / 3600, 2) . " hours\n";
    }

    echo "  Notes: " . ($stockTake->getNotes() ?: 'None') . "\n";
}

// Find materials by unit type
$materialsByUnit = [];
foreach ($materials as $material) {
    $unit = $material->getUnit();
    $materialsByUnit[$unit] = ($materialsByUnit[$unit] ?? 0) + 1;
}

echo "Materials by unit type:\n";
foreach ($materialsByUnit as $unit => $count) {
    echo "  " . $unit . ": " . $count . " items\n";
}

Available Resources

The Dinlr PHP client provides access to all API resources through dedicated classes:

Core Resources

Resource Description Key Methods
Restaurant Restaurant information get()
Location Restaurant locations list(), get($locationId)
DiningOption Dining options (dine-in, takeaway, etc.) list($locationId), get($diningOptionId)
PaymentMethod Available payment methods list($locationId), get($paymentMethodId)
Charge Additional charges list($locationId), get($chargeId)

Menu & Inventory

Resource Description Key Methods
Item Menu items list($locationId), get($itemId)
Category Item categories list(), get($categoryId)
Modifier Item modifiers list($locationId), get($modifierId)
Menu Complete menu structure list($locationId)
Material Inventory materials list($locationId), getStockLevels($locationId)
Floorplan Table and seating plans list($locationId), get($floorplanId)

Customer & Loyalty

Resource Description Key Methods
Customer Customer management list(), get($customerId), create($data), update($customerId, $data), search($params)
CustomerGroup Customer segmentation list(), get($groupId)
Loyalty Loyalty programs getPrograms(), getRewards($programId), enrolMember($programId, $data), addPoints($programId, $memberId, $points)
StoreCredit Store credit management getCustomerBalance($customerId), addCredit($customerId, $amount), createTopup($data)

Orders & Transactions

Resource Description Key Methods
Cart Cart calculation and order placement calculate($cartData), submit($cartData)
Order Order management list(), get($orderId), update($orderId, $data), addPayment($orderId, $data), close($orderId)
Discount Discount management list($locationId), get($discountId)
Promotion Promotional campaigns list($locationId), get($promotionId)
Voucher Voucher creation and management list(), get($voucherId), create($data), search($params)

Reservations

Resource Description Key Methods
Experience Dining experiences list($locationId), get($experienceId)
TableSection Table section management list($locationId), get($tableSectionId)
Reservation Reservation booking getAvailableServices($locationId, $date, $adult, $children), book($data), list(), get($reservationId)

Advanced Usage

Custom Configuration

use Nava\Dinlr\Config;
use Nava\Dinlr\Client;

$config = new Config([
    'api_key' => 'your_key',
    'restaurant_id' => 'your_id',
    'api_url' => 'https://api.dinlr.com/v1',
    'timeout' => 60,
    'debug' => true,
]);

$client = new Client($config);

Using Different Restaurant IDs

// Override restaurant ID for specific calls
$orders = $client->orders()->list('different_restaurant_id');
$customer = $client->customers()->get($customerId, 'different_restaurant_id');

Model Helper Methods

Models include business logic methods beyond basic getters:

// Customer business logic
$customer = $client->customers()->get($customerId);

if (!$customer->hasCompleteProfile()) {
    // Request missing information
}

if ($customer->canReceiveMarketing('email')) {
    // Send promotional emails
}

if ($customer->isInAgeRange(18, 65)) {
    // Show age-appropriate content
}

// Order business logic
$order = $client->orders()->get($orderId);
if ($order->isPaid() && $order->isOpen()) {
    // Ready for fulfillment
}

// Voucher validation
$voucher = $client->vouchers()->get($voucherId);
if ($voucher->canBeRedeemed()) {
    // Apply voucher to cart
}

Pagination and Filtering

// Paginated results
$customers = $client->customers()->list(null, [
    'limit' => 50,
    'page' => 2,
]);

// Date filtering
$orders = $client->orders()->list(null, [
    'created_at_min' => '2024-01-01T00:00:00Z',
    'created_at_max' => '2024-12-31T23:59:59Z',
    'status' => 'open',
    'detail' => 'all',
]);

// Location-specific data
$items = $client->items()->list($locationId);
$orders = $client->orders()->listForLocation($locationId);

Collection Methods

Collections provide convenient methods for data analysis:

// Customer collection
$customers = $client->customers()->list();
$vipCustomers = $customers->filter(function($customer) {
    return $customer->hasAttribute('vip_status');
});

// Order collection analysis
$orders = $client->orders()->list();
$openOrders = $orders->getByStatus('open');
$totalRevenue = $orders->getTotalRevenue();

// Loyalty member collection
$members = $client->loyalty()->getMembers($programId);
$totalPoints = $members->getTotalPoints();

Laravel Integration

Installation

Add the service provider to config/app.php:

'providers' => [
    // Other service providers...
    Nava\Dinlr\Laravel\DinlrServiceProvider::class,
],

'aliases' => [
    // Other aliases...
    'Dinlr' => Nava\Dinlr\Laravel\Facades\Dinlr::class,
],

Publish Configuration

php artisan vendor:publish --provider="Nava\Dinlr\Laravel\DinlrServiceProvider"

Configuration

Add to your .env file:

DINLR_API_KEY=your_api_key
DINLR_RESTAURANT_ID=your_restaurant_id
DINLR_API_URL=https://api.dinlr.com/v1
DINLR_TIMEOUT=30
DINLR_DEBUG=false

Usage in Laravel

use Dinlr;

class OrderController extends Controller
{
    public function index()
    {
        // Use the facade
        $restaurant = Dinlr::restaurant()->get();
        $customers = Dinlr::customers()->list();

        return view('orders.index', compact('restaurant', 'customers'));
    }

    public function placeOrder(Request $request)
    {
        $cartData = [
            'location' => $request->location_id,
            'items' => $request->items,
            'order_info' => [
                'order_no' => 'WEB' . time(),
                'customer' => $request->customer_id,
                'notes' => $request->notes,
            ],
        ];

        try {
            $order = Dinlr::cart()->submit($cartData);

            return response()->json([
                'success' => true,
                'order_id' => $order->getId(),
                'order_number' => $order->getOrderNumber(),
                'total' => $order->getTotal(),
            ]);
        } catch (\Nava\Dinlr\Exception\ApiException $e) {
            return response()->json([
                'success' => false,
                'error' => $e->getMessage(),
            ], 400);
        }
    }
}

OAuth Configuration for Laravel

For OAuth, publish the OAuth config:

php artisan vendor:publish --provider="Nava\Dinlr\Laravel\OAuthServiceProvider"

Add OAuth settings to .env:

DINLR_CLIENT_ID=your_client_id
DINLR_CLIENT_SECRET=your_client_secret
DINLR_REDIRECT_URI=https://yourapp.com/oauth/callback

OAuth Controller Example

use Nava\Dinlr\Laravel\Facades\DinlrOAuth;

class OAuthController extends Controller
{
    public function authorize()
    {
        $state = Str::random(40);
        session(['oauth_state' => $state]);

        $authUrl = DinlrOAuth::getAuthorizationUrl($state);

        return redirect($authUrl);
    }

    public function callback(Request $request)
    {
        try {
            $callbackData = DinlrOAuth::handleCallback(
                $request->all(),
                session('oauth_state')
            );

            $tokens = DinlrOAuth::getAccessToken(
                $callbackData['code'],
                $callbackData['restaurant_id']
            );

            // Store tokens in session or database
            session([
                'dinlr_access_token' => $tokens['access_token'],
                'dinlr_refresh_token' => $tokens['refresh_token'],
                'dinlr_restaurant_id' => $callbackData['restaurant_id'],
            ]);

            return redirect()->route('dashboard')
                ->with('success', 'Successfully connected to Dinlr!');

        } catch (\Nava\Dinlr\Exception\ApiException $e) {
            return redirect()->route('oauth.authorize')
                ->with('error', 'OAuth failed: ' . $e->getMessage());
        }
    }
}

Webhook Handling

The library includes comprehensive webhook handling capabilities:

use Nava\Dinlr\Webhook\WebhookValidator;
use Nava\Dinlr\Exception\WebhookException;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        $validator = new WebhookValidator(config('dinlr.webhook_secret'));

        try {
            $event = $validator->constructEvent(
                $request->getContent(),
                $request->header('Dinlr-Signature')
            );

            // Handle different event types
            if ($event->isOrderEvent()) {
                $this->handleOrderEvent($event);
            } elseif ($event->isCustomerEvent()) {
                $this->handleCustomerEvent($event);
            }

            return response('Webhook handled', 200);

        } catch (WebhookException $e) {
            Log::warning('Invalid webhook received', [
                'error' => $e->getMessage(),
                'signature' => $request->header('Dinlr-Signature'),
            ]);

            return response('Invalid webhook', 400);
        }
    }

    private function handleOrderEvent($event)
    {
        $orderData = $event->getData();

        if ($event->isCreateEvent()) {
            // Handle new order
            Log::info('New order received', ['order_id' => $orderData['id']]);

            // Trigger notifications, update inventory, etc.
            event(new OrderCreated($orderData));

        } elseif ($event->isUpdateEvent()) {
            // Handle order update
            Log::info('Order updated', ['order_id' => $orderData['id']]);

            // Update local database, notify staff, etc.
            event(new OrderUpdated($orderData));
        }
    }

    private function handleCustomerEvent($event)
    {
        $customerData = $event->getData();

        if ($event->isCreateEvent()) {
            // Handle new customer registration
            event(new CustomerRegistered($customerData));
        }
    }
}

Error Handling

The library provides comprehensive error handling with specific exception types:

use Nava\Dinlr\Exception\ApiException;
use Nava\Dinlr\Exception\ValidationException;
use Nava\Dinlr\Exception\ConfigException;
use Nava\Dinlr\Exception\RateLimitException;
use Nava\Dinlr\Exception\WebhookException;

try {
    // API operations that might fail
    $customer = $client->customers()->create($customerData);
    $order = $client->cart()->submit($cartData);

} catch (ValidationException $e) {
    // Handle validation errors
    echo "Validation failed: " . $e->getMessage() . "\n";

    $errors = $e->getErrors();
    foreach ($errors as $field => $error) {
        echo "  {$field}: {$error}\n";
    }

} catch (RateLimitException $e) {
    // Handle rate limiting
    echo "Rate limit exceeded. Waiting before retry...\n";
    sleep(60); // Wait 60 seconds

    // Implement exponential backoff
    $retryAfter = $e->getErrorData()['retry_after'] ?? 60;
    sleep($retryAfter);

} catch (ApiException $e) {
    // Handle general API errors
    echo "API Error: " . $e->getMessage() . "\n";
    echo "Status Code: " . $e->getCode() . "\n";

    $errorData = $e->getErrorData();
    if ($errorData) {
        echo "Additional info: " . json_encode($errorData, JSON_PRETTY_PRINT) . "\n";
    }

    // Handle specific status codes
    switch ($e->getCode()) {
        case 401:
            echo "Authentication failed. Check your API key.\n";
            break;
        case 403:
            echo "Access forbidden. Check your permissions.\n";
            break;
        case 404:
            echo "Resource not found.\n";
            break;
        case 422:
            echo "Validation error. Check your data.\n";
            break;
        case 500:
            echo "Server error. Try again later.\n";
            break;
    }

} catch (ConfigException $e) {
    // Handle configuration errors
    echo "Configuration Error: " . $e->getMessage() . "\n";
    echo "Please check your API credentials and settings.\n";

} catch (WebhookException $e) {
    // Handle webhook validation errors
    echo "Webhook Error: " . $e->getMessage() . "\n";

} catch (\Exception $e) {
    // Handle unexpected errors
    echo "Unexpected Error: " . $e->getMessage() . "\n";
    error_log("Dinlr API Error: " . $e->getTraceAsString());
}

Error Recovery and Retry Logic

function makeApiCallWithRetry($callable, $maxRetries = 3, $baseDelay = 1)
{
    $attempt = 0;

    while ($attempt < $maxRetries) {
        try {
            return $callable();

        } catch (RateLimitException $e) {
            $attempt++;
            if ($attempt >= $maxRetries) {
                throw $e;
            }

            // Exponential backoff
            $delay = $baseDelay * pow(2, $attempt - 1);
            sleep($delay);

        } catch (ApiException $e) {
            // Only retry on certain error codes
            if (in_array($e->getCode(), [500, 502, 503, 504])) {
                $attempt++;
                if ($attempt >= $maxRetries) {
                    throw $e;
                }
                sleep($baseDelay);
            } else {
                throw $e; // Don't retry on client errors
            }
        }
    }
}

// Usage
$orders = makeApiCallWithRetry(function() use ($client) {
    return $client->orders()->list();
});

Testing

Running Tests

Run the full test suite:

composer test

# Run with verbose output
./vendor/bin/phpunit --verbose

# Run specific test files
./vendor/bin/phpunit tests/CustomerApiTest.php
./vendor/bin/phpunit tests/OrderTest.php
./vendor/bin/phpunit tests/LoyaltyTest.php

# Run with coverage report
./vendor/bin/phpunit --coverage-html coverage/

Test Configuration

Create a test configuration file at tests/config.php:

<?php
return [
    'api_key' => getenv('DINLR_TEST_API_KEY') ?: 'your_test_api_key',
    'api_url' => getenv('DINLR_TEST_API_URL') ?: 'https://api.dinlr.com/v1',
    'restaurant_id' => getenv('DINLR_TEST_RESTAURANT_ID') ?: 'your_test_restaurant_id',
    'timeout' => (int) (getenv('DINLR_TEST_TIMEOUT') ?: 30),
    'debug' => (bool) (getenv('DINLR_TEST_DEBUG') ?: true),
];

Environment Variables for Testing

Set these environment variables for testing:

export DINLR_TEST_API_KEY="your_test_api_key"
export DINLR_TEST_RESTAURANT_ID="your_test_restaurant_id"
export DINLR_TEST_API_URL="https://api.dinlr.com/v1"
export DINLR_TEST_DEBUG="true"

Writing Custom Tests

<?php
namespace YourApp\Tests;

use Nava\Dinlr\Client;
use PHPUnit\Framework\TestCase;

class CustomDinlrTest extends TestCase
{
    private $client;

    protected function setUp(): void
    {
        $this->client = new Client([
            'api_key' => 'test_key',
            'restaurant_id' => 'test_restaurant',
            'api_url' => 'https://api.dinlr.com/v1',
        ]);
    }

    public function testCustomerCreation()
    {
        $customerData = [
            'first_name' => 'Test',
            'last_name' => 'Customer',
            'email' => 'test@example.com',
        ];

        $customer = $this->client->customers()->create($customerData);

        $this->assertInstanceOf(\Nava\Dinlr\Models\Customer::class, $customer);
        $this->assertEquals('Test Customer', $customer->getFullName());
    }
}

Static Analysis

Run static analysis with PHPStan:

composer analyse

# Or directly
./vendor/bin/phpstan analyse src tests --level=7

Configuration Options

Complete Configuration Reference

Option Type Description Default Required
api_key string Your Dinlr API key - Yes
restaurant_id string Your restaurant ID - Yes
api_url string Base URL for Dinlr API https://api.dinlr.com/v1 No
timeout int Request timeout (seconds) 30 No
debug bool Enable debug mode false No

OAuth Configuration Options

Option Type Description Default Required
client_id string OAuth client ID - Yes
client_secret string OAuth client secret - Yes
redirect_uri string OAuth redirect URI - Yes
access_token string Current access token - No
refresh_token string Current refresh token - No
expires_at int Token expiration timestamp - No

Contributing

We welcome contributions to the Dinlr PHP client library! Here's how you can help:

Development Setup

  1. Fork the repository
  2. Clone your fork:
git clone https://github.com/NavanithanS/dinlr-php.git
cd dinlr-php
  1. Install dependencies:
composer install
  1. Create a branch for your feature:
git checkout -b feature/amazing-feature

Coding Standards

  • Follow PSR-4 autoloading standards
  • Use PSR-12 coding style
  • Write comprehensive PHPDoc comments
  • Include unit tests for new features
  • Maintain backward compatibility

Running Quality Checks

# Run tests
composer test

# Check coding standards
composer cs

# Fix coding standards
composer cs-fix

# Run static analysis
composer analyse

Submitting Changes

  1. Make your changes
  2. Add tests for new functionality
  3. Ensure all tests pass
  4. Update documentation if needed
  5. Commit your changes:
git commit -am 'Add amazing feature'
  1. Push to your fork:
git push origin feature/amazing-feature
  1. Open a Pull Request

Pull Request Guidelines

  • Provide a clear description of the changes
  • Include relevant tests
  • Update documentation if necessary
  • Ensure CI checks pass
  • Link to any related issues

Reporting Issues

When reporting issues, please include:

  • PHP version
  • Library version
  • Detailed error messages
  • Code examples that reproduce the issue
  • Expected vs actual behavior

Security

Security is a top priority for the Dinlr PHP client library.

Reporting Security Vulnerabilities

Please do not report security vulnerabilities through public GitHub issues. Instead, email security@dinlr.com with:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact
  • Suggested fix (if any)

We will respond to security reports within 24 hours and provide updates on our progress.

Security Features

The library includes several security features:

  • Input Validation: All user inputs are validated and sanitized
  • Rate Limiting: Built-in handling for API rate limits
  • Webhook Validation: Cryptographic verification of webhook signatures
  • Secure Authentication: Support for OAuth 2.0 with secure token handling
  • Error Sanitization: Sensitive data is removed from logs and error messages

Security Best Practices

When using this library:

  • Store credentials securely: Never commit API keys to version control
  • Use environment variables: Store sensitive configuration in environment variables
  • Validate webhook signatures: Always verify webhook authenticity
  • Handle errors gracefully: Don't expose sensitive error details to end users
  • Keep dependencies updated: Regularly update the library and its dependencies
  • Use HTTPS: Always communicate with the API over HTTPS
  • Implement rate limiting: Respect API rate limits to avoid being blocked

License

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

Support

Documentation and Resources

Getting Help

For library-specific issues:

  • Check the GitHub Issues page
  • Search existing issues before creating new ones
  • Provide detailed information when reporting bugs

For API and business questions:

Community

  • GitHub Discussions: Share ideas and get help from the community