bagisto/bagisto-reward-points
Bagisto Reward Points adds a loyalty points system to your Bagisto store. Customers earn points on purchases and redeem them at checkout, while admins configure earning rules, point values, and manage customer balances.
Verify compatibility before installing. Open the package's composer.json and match the bagisto/bagisto (or webkul/*) and PHP constraints against the core version you run. Bagisto majors differ in admin UI (Vue 2 vs Vue 3) and service-provider conventions, and this package is small enough that upstream version coverage is thin.
Install into a staging instance that mirrors production, then publish and migrate:
composer require bagisto/bagisto-reward-points
php artisan vendor:publish --provider="Webkul\RewardPoints\Providers\RewardPointsServiceProvider"
php artisan migrate
php artisan optimize:clear
Publishing matters — config, migrations, translations, and admin assets ship in vendor/. Publish so you can read and override them.
Read in this order on day one:
src/Providers/*ServiceProvider.php — note whether it is a Concord ModuleServiceProvider (Bagisto module) or a raw Laravel ServiceProvider. Look for event listeners, migration registration, and $moduleName.src/Config/ and any Concord module declaration — routes, admin menu, ACL.src/Models/ + src/Database/Migrations/ — the ledger schema. Is it append-only, or a mutated balance column? Does it carry customer_id, order_id, type/reason?src/Listeners/ + EventServiceProvider — which order event actually awards points (order.placed vs order.paid vs order.shipped).src/Repositories/ — Bagisto convention is contracts plus Eloquent repos; accrual and redemption logic usually lives here.First use case to get working end-to-end: admin creates a reward rule → a customer places a qualifying paid order → points land in their account → the same customer redeems at checkout for a discount. That single loop exercises ~80% of the risk surface (checkout integration, refund clawback, expiry, concurrency) in one pass. Do it against a seeded test customer in staging before touching real traffic.
Follow the Bagisto grain. Treat this as a Bagisto module, not a Laravel package bolted on. Keep your customizations in a separate local/ package that depends on this one. Never edit vendor/.
Wrap the ledger behind one seam. Whether or not the package ships a repository contract, define a thin adapter so every credit/debit flows through a single audited path:
interface PointsLedger
{
public function award(int $customerId, int $points, string $reason, ?int $orderId = null): void;
public function revoke(int $customerId, int $points, string $reason, ?int $orderId = null): void;
public function balance(int $customerId): int;
public function hasEntry(int $customerId, string $reason, ?int $orderId = null): bool;
public function assertReconciles(int $customerId): bool; // SUM(ledger) === stored balance
}
Everything — checkout redemption, refund clawback, manual admin adjustments, expiry — calls this. When the business adds tiers or referrals later, you change one class.
Award on payment confirmed, not on order placed. Hook the payment-confirmed event (sales.order.payment or your gateway's success event) and gate every write with a dedupe key so listener double-fires and queue retries cannot double-mint:
public function handle($order): void
{
if ($this->ledger->hasEntry($order->customer_id, 'order_paid', $order->id)) {
return;
}
$points = $this->calculator->for($order);
$this->ledger->award($order->customer_id, $points, 'order_paid', $order->id);
}
Own checkout redemption. The package's stated scope is admin-side reward creation; assume there is no cart-level integration and plan to build it. Store a points_to_redeem value on the quote, and subtract points value as a cart total (the way a discount does), not as a line item — so tax and shipping math stay correct. Cap redemption against the live customer balance and any per-order max from config.
Seed ACL and admin menu as a deploy step. Bagisto's ACL and menu registrations live in the database, not boot-from-code. A fresh environment without a seeder loses the admin menu item:
php artisan db:seed --class=RewardPointsPermissionSeeder
Expose a read-only support view. Support will be asked "why don't I have my points?" A scoped, read-only per-customer ledger view plus an ACL permission for the support role saves hours a week. It also doubles as your reconciliation UI.
Schedule expiry as a real job. If expiry rules exist in admin settings but no enforcement job ships, you own it. Make it idempotent (mark entries expired, never re-expire), resumable (checkpoint per chunk), and monitored (alert if daily expiry count is outside an expected band — silence usually means liability is quietly inflating).
order.placed, failed and abandoned payments mint free points. Confirm the listener event on your first read, and verify a reversal path exists for refunds and cancellations.SELECT ... FOR UPDATE, two parallel checkouts can both pass the "enough points?" check. Wrap redemption in a transaction, lock the balance row, and test with parallel requests.SUM(amount) FROM ledger WHERE customer_id = ?) is always truthful but slow on cart loads. A stored balance is fast but drifts. Either way, run a nightly sum(ledger) == balance reconciliation and alert on mismatch.channel_id. If points are global across stores selling in different currencies, earn rate and redemption conversion must be currency-aware or you will redeem USD-earned points as EUR value. Retrofitting live balances later is painful — add the column and filters now if missing.package.json / webpack.mix.js, then recompile or republish admin assets after any composer update touching the package.Log::info in the provider boot() to confirm registration, then a log line on every listener invocation. Check the chain: event fires → guard isn't short-circuiting → calculator returns non-zero. Concord $moduleName mismatches are the classic silent skip.config/reward-points.php (or similar). Read it after publishing before assuming defaults — redemption ratio (points → currency), min/max redemption per order, and earn rate usually live there. Clear config:cache after changes; a stale cache is a frequent "config change did nothing" report.php artisan migrate:rollback in staging. If the package alters or drops tables you depend on, your deploy needs a backup-and-restore step, not just a rollback.RedemptionRule validator (caps, combinability as policy rather than if branches), and the ledger repository are each a feature request within six months. Designing them on day one costs little; bolting them on costs a lot.How can I help you explore Laravel packages today?