Complete guide to integrating Loyalty.lt with your PrestaShop store
Install Module
API Configuration
// Configuration in module settings $config = [ 'api_key' => 'your_api_key', 'shop_id' => 'your_shop_id', 'environment' => 'production', // or 'staging' 'webhook_secret' => 'your_webhook_secret' ];
Point Rules Setup
// Point earning configuration $earning_rules = [ 'purchase_rate' => 1, // 1 point per €1 'signup_bonus' => 100, 'review_bonus' => 50, 'referral_bonus' => 500 ]; $redemption_rules = [ 'min_points' => 100, 'conversion_rate' => 100, // 100 points = €1 'max_discount_percent' => 50 ];
// Register hooks in module public function install() { return parent::install() && $this->registerHook('actionCustomerAccountAdd') && $this->registerHook('actionValidateOrder') && $this->registerHook('actionOrderStatusUpdate') && $this->registerHook('displayCustomerAccount') && $this->registerHook('displayShoppingCart') && $this->registerHook('displayProductButtons'); }
// Multi-store support if (Shop::isFeatureActive()) { $shops = Shop::getShops(); foreach ($shops as $shop) { Configuration::updateValue( 'LOYALTY_SHOP_ID', $shop['loyalty_id'], false, null, $shop['id_shop'] ); } }
{* Customer account template *} {if $customer.logged} <div class="loyalty-status"> <h3>{l s='Loyalty Program' mod='loyaltylt'}</h3> <p>{l s='Points Balance:' mod='loyaltylt'} <strong>{$customer_points}</strong></p> <p>{l s='Current Tier:' mod='loyaltylt'} <strong>{$customer_tier}</strong></p> </div> {/if}
// Hook for order validation public function hookActionValidateOrder($params) { $order = $params['order']; $customer = new Customer($order->id_customer); // Calculate points $points = $this->calculatePurchasePoints($order); // Award points via API $this->loyaltyAPI->awardPoints([ 'customer_id' => $customer->id, 'points' => $points, 'order_id' => $order->id, 'reason' => 'purchase' ]); }
// Award points for reviews public function hookActionProductCommentAdd($params) { $comment = $params['comment']; $this->loyaltyAPI->awardPoints([ 'customer_id' => $comment['id_customer'], 'points' => 50, 'reason' => 'product_review', 'reference_id' => $comment['id'] ]); }
{* Shopping cart loyalty section *} <div class="loyalty-cart-section"> <h4>{l s='Use Loyalty Points' mod='loyaltylt'}</h4> <p>{l s='Available Points:' mod='loyaltylt'} {$customer_points}</p> <p>{l s='Value:' mod='loyaltylt'} {$points_value|string_format:"%.2f"} {$currency.sign}</p> <form action="{$cart_update_url}" method="post"> <input type="number" name="points_to_redeem" max="{$customer_points}" min="0"> <button type="submit" class="btn btn-success"> {l s='Apply Points' mod='loyaltylt'} </button> </form> </div>
// Process point redemption public function processPointRedemption($points, $customer_id, $cart_id) { // Validate redemption if (!$this->validateRedemption($points, $customer_id)) { return false; } // Create discount $discount = $this->createLoyaltyDiscount($points); // Apply to cart $cart = new Cart($cart_id); $cart->addDiscount($discount->id); // Deduct points via API return $this->loyaltyAPI->redeemPoints([ 'customer_id' => $customer_id, 'points' => $points, 'cart_id' => $cart_id ]); }
// Add loyalty columns to customer list public function hookActionAdminCustomersListingFieldsModifier($params) { $params['fields']['loyalty_points'] = [ 'title' => $this->l('Loyalty Points'), 'type' => 'text', 'search' => false, 'orderby' => false ]; $params['fields']['loyalty_tier'] = [ 'title' => $this->l('Tier'), 'type' => 'text', 'search' => false, 'orderby' => false ]; }
// Loyalty analytics for admin dashboard public function getLoyaltyAnalytics($date_from, $date_to) { return [ 'total_members' => $this->getTotalMembers(), 'points_awarded' => $this->getPointsAwarded($date_from, $date_to), 'points_redeemed' => $this->getPointsRedeemed($date_from, $date_to), 'revenue_impact' => $this->getRevenueImpact($date_from, $date_to), 'top_customers' => $this->getTopLoyaltyCustomers() ]; }
// Export customer loyalty data public function exportLoyaltyData($format = 'csv') { $customers = $this->getLoyaltyCustomers(); switch ($format) { case 'csv': return $this->generateCSV($customers); case 'excel': return $this->generateExcel($customers); default: return $this->generateJSON($customers); } }
{* Product page points preview *} <div class="loyalty-product-info"> <p class="loyalty-points-earning"> <i class="icon-star"></i> {l s='Earn' mod='loyaltylt'} <strong>{$product_points}</strong> {l s='points with this purchase' mod='loyaltylt'} </p> </div>
{* Category page loyalty promotion *} <div class="loyalty-category-banner"> <div class="alert alert-info"> <i class="icon-gift"></i> {l s='Earn points on every purchase! Join our loyalty program today.' mod='loyaltylt'} <a href="{$loyalty_signup_url}" class="btn btn-sm btn-primary"> {l s='Learn More' mod='loyaltylt'} </a> </div> </div>
{* Loyalty widget for account page *} <div class="loyalty-widget" id="loyalty-dashboard"> <div class="row"> <div class="col-md-4"> <div class="loyalty-stat"> <h4>{$customer_points}</h4> <p>{l s='Available Points' mod='loyaltylt'}</p> </div> </div> <div class="col-md-4"> <div class="loyalty-stat"> <h4>{$customer_tier}</h4> <p>{l s='Current Tier' mod='loyaltylt'}</p> </div> </div> <div class="col-md-4"> <div class="loyalty-stat"> <h4>{$next_tier_points}</h4> <p>{l s='Points to Next Tier' mod='loyaltylt'}</p> </div> </div> </div> </div>
// Custom point calculation class CustomLoyaltyRules extends LoyaltyRules { public function calculatePurchasePoints($order) { $base_points = parent::calculatePurchasePoints($order); // Bonus for first-time customers if ($this->isFirstOrder($order->id_customer)) { $base_points *= 2; } // Category-specific bonuses foreach ($order->getProducts() as $product) { if ($product['id_category_default'] == 5) { // Electronics $base_points += 50; } } return $base_points; } }
{* Custom loyalty card template *} <div class="loyalty-card"> <div class="card-header"> <h3>{$shop_name} Loyalty Card</h3> </div> <div class="card-body"> <div class="qr-code"> <img src="{$qr_code_url}" alt="Loyalty QR Code"> </div> <div class="card-info"> <p><strong>{l s='Member:' mod='loyaltylt'}</strong> {$customer.firstname} {$customer.lastname}</p> <p><strong>{l s='Points:' mod='loyaltylt'}</strong> {$customer_points}</p> <p><strong>{l s='Member Since:' mod='loyaltylt'}</strong> {$member_since|date_format}</p> </div> </div> </div>
// Loyalty API client for PrestaShop class LoyaltyAPIClient { private $api_key; private $shop_id; private $base_url; public function __construct($config) { $this->api_key = $config['api_key']; $this->shop_id = $config['shop_id']; $this->base_url = $config['base_url']; } public function awardPoints($data) { return $this->makeRequest('POST', '/points/award', $data); } public function redeemPoints($data) { return $this->makeRequest('POST', '/points/redeem', $data); } public function getCustomerBalance($customer_id) { return $this->makeRequest('GET', "/customers/{$customer_id}/balance"); } private function makeRequest($method, $endpoint, $data = null) { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => $this->base_url . $endpoint, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->api_key, 'Content-Type: application/json', 'Shop-ID: ' . $this->shop_id ] ]); if ($data && in_array($method, ['POST', 'PUT', 'PATCH'])) { curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); } $response = curl_exec($curl); curl_close($curl); return json_decode($response, true); } }
Module Installation Fails
# Fix file permissions chmod -R 755 modules/loyaltylt/ find modules/loyaltylt/ -type f -exec chmod 644 {} \;
Points Not Awarded
// Debug point calculation public function debugPointCalculation($order_id) { $order = new Order($order_id); error_log('Order total: ' . $order->total_paid); error_log('Calculated points: ' . $this->calculatePurchasePoints($order)); }
Widget Not Displaying
// Debug hook execution public function hookDisplayCustomerAccount($params) { error_log('Hook executed: displayCustomerAccount'); return parent::hookDisplayCustomerAccount($params); }
// Enable debug mode in module class LoyaltyLT extends Module { const DEBUG_MODE = true; private function debug($message) { if (self::DEBUG_MODE) { error_log('[LoyaltyLT] ' . $message); } } public function awardPoints($data) { $this->debug('Awarding points: ' . json_encode($data)); // Award points logic } }
// Cache customer loyalty data class LoyaltyCache { const CACHE_TTL = 300; // 5 minutes public static function getCustomerPoints($customer_id) { $cache_key = 'loyalty_points_' . $customer_id; if (!Cache::isStored($cache_key)) { $points = LoyaltyAPI::getCustomerBalance($customer_id); Cache::store($cache_key, $points, self::CACHE_TTL); return $points; } return Cache::retrieve($cache_key); } }
-- Add indexes for loyalty queries ALTER TABLE `ps_loyalty_transactions` ADD INDEX `idx_customer_date` (`id_customer`, `date_add`); ALTER TABLE `ps_loyalty_customers` ADD INDEX `idx_points_tier` (`points`, `tier`);
// Secure API communication class SecureLoyaltyAPI extends LoyaltyAPIClient { public function makeSecureRequest($method, $endpoint, $data = null) { // Add request signing $timestamp = time(); $signature = hash_hmac('sha256', $method . $endpoint . json_encode($data) . $timestamp, $this->webhook_secret ); $headers = [ 'Authorization: Bearer ' . $this->api_key, 'Content-Type: application/json', 'Shop-ID: ' . $this->shop_id, 'X-Timestamp: ' . $timestamp, 'X-Signature: ' . $signature ]; return $this->makeRequest($method, $endpoint, $data, $headers); } }
// Validate customer input public function validatePointRedemption($points, $customer_id) { // Sanitize input $points = (int) $points; $customer_id = (int) $customer_id; // Validate customer exists if (!Customer::existsInDatabase($customer_id, 'customer')) { return false; } // Validate point amount if ($points <= 0 || $points > $this->getCustomerPoints($customer_id)) { return false; } return true; }
// Language file structure $_MODULE['<{loyaltylt}prestashop>loyaltylt'] = []; // English translations $_MODULE['<{loyaltylt}prestashop>loyaltylt']['loyalty_program'] = 'Loyalty Program'; $_MODULE['<{loyaltylt}prestashop>loyaltylt']['points_balance'] = 'Points Balance'; $_MODULE['<{loyaltylt}prestashop>loyaltylt']['earn_points'] = 'Earn %d points'; $_MODULE['<{loyaltylt}prestashop>loyaltylt']['redeem_points'] = 'Redeem Points'; // Template usage {l s='loyalty_program' mod='loyaltylt'}
// Handle multiple currencies public function convertPointsToValue($points, $currency_id = null) { if (!$currency_id) { $currency_id = Context::getContext()->currency->id; } $base_rate = Configuration::get('LOYALTY_CONVERSION_RATE'); $currency = new Currency($currency_id); return ($points / $base_rate) * $currency->conversion_rate; }
// Customer tier calculation class LoyaltyTierManager { const TIERS = [ 'bronze' => ['min_points' => 0, 'multiplier' => 1.0], 'silver' => ['min_points' => 1000, 'multiplier' => 1.2], 'gold' => ['min_points' => 5000, 'multiplier' => 1.5], 'platinum' => ['min_points' => 10000, 'multiplier' => 2.0] ]; public function calculateTier($total_points) { $tier = 'bronze'; foreach (self::TIERS as $tier_name => $tier_data) { if ($total_points >= $tier_data['min_points']) { $tier = $tier_name; } } return $tier; } }
// Birthday bonus automation public function checkBirthdayBonuses() { $today = date('Y-m-d'); $customers = Customer::getBirthdayCustomers($today); foreach ($customers as $customer) { $this->loyaltyAPI->awardPoints([ 'customer_id' => $customer['id_customer'], 'points' => 200, 'reason' => 'birthday_bonus', 'expires_at' => date('Y-m-d', strtotime('+1 year')) ]); } }