Skip to content
ESC

Searching...

Quick Links

Type to search • Press to navigate • Enter to select

Keep typing to search...

No results found

No documentation matches ""

Changelog.

Track all updates, improvements, and fixes to Reward Loyalty v4.

Jun 7, 2026

💡 Always test updates in a test environment before updating production. See Staging Installation for setup instructions.

A quick star rating supports development ⭐️⭐️⭐️⭐️⭐️

Version 4.7.0

June 7, 2026

New

Wallet Search

The My Cards page has a search command palette. Press ⌘K (or Ctrl+K) or tap the search icon to open it. Results show the card name, subtitle, and current balance or progress. Cards are grouped by type when no query is entered. Full keyboard navigation supported.

Improved

Member Action Pages

The Enter Code, Generate Request Link, and Share/Send Points pages use the same hero-centered layout as the Referrals page. Centered gradient titles, ambient backgrounds, floating form cards, and gradient CTA buttons with hover animations.

Member Profile Dropdown

The profile dropdown shows descriptive subtitles for each action item, color-coded icon containers, inset hover states, and wider spacing.

Birthday Date Picker

The birthday field in member profiles uses a Flatpickr calendar instead of the browser's native picker. The calendar opens 20 years before the current year, so members start at a sensible date. All date-only fields across the platform receive the same upgrade.

OTP Verification Grace Period

After verifying identity with a one-time code, the verification is remembered for 30 minutes. Additional profile saves within that window go through without requesting a new code.

Translations

New keys added to all 11 supported locales: wallet search strings, member dropdown menu descriptions, and the Enter Code page label.


Version 4.6.0

June 4, 2026

New

Branded Partner Emails

Every email a member receives about a partner's loyalty cards, stamp cards, vouchers, or tiers now carries that partner's identity. The sender name shows the partner's business name instead of the platform name, and the reply-to directs responses to the partner's business email. Both fall back to the partner's account settings if not configured, then to the system default.

Partners set their sender identity in Business Settings > Branding, right below the business name:

Setting Purpose
Email Address Reply-to on all partner emails. Falls back to account email if empty
Show email on public card Adds a mailto: contact button to the member-facing business card. Off by default

The email address serves two separate purposes: reply-to (always active) and public display (controlled by the toggle). Existing partners keep the toggle off after upgrading, so no email is exposed without action. See Email Configuration > Partner-Level Overrides for the full fallback chain.


Improved

  • Google Wallet retry on timeout. When a member taps "Add to Google Wallet" and the Google API is slow or unreachable, the system queues provisioning in the background and shows a "Your pass is being prepared" message. Tapping the button again after a few seconds completes the save. The member no longer sees an error page.
  • Campaign compose shows read-only sender info. The email campaign compose page displays the current sender name and reply-to as fixed values with a link to Business Settings. Partners can no longer type sender values that are silently discarded on save.

Translations

  • Business email, privacy toggle, and sender info strings translated in all 11 supported locales.

Maintenance

  • 19 new automated tests covering the email sender fallback chain, privacy toggle model behavior, business card Blade component rendering, Google Wallet controller fallback path, and JWT URL-length regression.
  • Fixed 8 test files that failed on a clean database because they did not declare RefreshDatabase. All 883 tests pass from an empty database.

Version 4.5.0

June 1, 2026

New

Google Wallet Integration

Members can save loyalty cards, stamp cards, and vouchers to Google Wallet. Each pass shows the card name, current balance or stamp progress, and a scannable QR code. When points change, stamps are collected, or a voucher is redeemed, the pass updates on the member's phone.

  • Three-gate access control. The "Add to Google Wallet" button appears when three conditions are met: the system feature flag is on, the admin granted the partner permission, and the partner enabled Google Wallet on the specific card. All three default to off.
  • Automatic enrollment. Tapping the button enrolls the member if needed, provisions the pass on Google's servers, and redirects to Google Wallet. No prior enrollment required.
  • Localized buttons. The "Add to Google Wallet" button renders in the member's language using Google's official SVG assets. 74 languages ship with automatic English fallback.
  • Automatic deactivation. Turning off the Google Wallet toggle or deactivating a card revokes all member passes for that card.
  • Background pass sync. Point transactions, stamp transactions, and voucher redemptions trigger queued jobs that push updated pass data to Google.
  • Admin visibility. A new Integrations section on the admin settings page shows Google Wallet configuration status, issuer ID, and credential source. See System Settings.
  • Pass design from your existing card. Each pass reuses your logo, background color, and background image. No extra design work. See Pass Design for how each field maps to the pass.

Setup requires a Google Cloud service account with the Wallet API enabled. See Google Wallet Integration for the full guide.


Improved

  • Collapsible admin navigation. The admin sidebar groups items into collapsible sections: Dashboard, Administration, Members, Activity, and System.
  • Clearer partner and staff navigation. Partner sidebar items split into people-management and operational groups with visible dividers. The staff sidebar isolates the Scan QR action in its own section.
  • Navigation design polish. All sidebars use gradient-fade dividers, spring-physics hover transitions, and a warm active-state highlight. Inline critical CSS prevents the brief flash of unstyled text on page load.

Fixed

  • 500 error when deleting a points transaction. In rare cases, deleting a transaction could try to subtract more from a card's aggregate stats than the stored value, producing a negative number that MySQL rejected on the unsigned column. All stat decrements now clamp to zero.

Translations

  • Google Wallet UI strings (10 keys) translated in all 11 languages: button label, toggle help text, admin status indicators, and setup guidance.

Upgrade Notes

  • New environment variables. FEATURE_GOOGLE_WALLET, GOOGLE_WALLET_ISSUER_ID, and GOOGLE_WALLET_CREDENTIALS_JSON (or GOOGLE_WALLET_CREDENTIALS_PATH). All default to off or empty. No action needed unless you want to enable Google Wallet.
  • Optional queue. Google Wallet pass updates run on the google-wallet queue. If your queue driver is set to sync (the default), jobs run inline and you do not need to change anything. If you run a dedicated queue worker with a database or redis driver, add this queue to your worker: php artisan queue:work --queue=default,google-wallet. See Queue Driver for background.

Maintenance

  • 38 automated tests for Google Wallet gate logic, JWT generation, provisioning, deactivation, and pass updates.
  • Navigation design system with semantic CSS classes for sidebar components.
  • Production assets rebuilt.

Version 4.4.0

May 29, 2026

New

Member Profile: Birthday, Gender, and Phone

Members can now manage three optional fields in the Profile tab of Account Settings.

  • Birthday. Date picker that only accepts dates in the past.
  • Gender. Dropdown with Male, Female, and Prefer not to say. Leaving it blank stores "Not specified".
  • Phone. International phone input with a searchable country selector, flag icons, dial codes, and digits-only enforcement. Country names display in the member's own language via PHP intl. The number is validated against the 15-digit ITU-T E.164 limit and stored in international format.

Birthday and gender appear side by side on desktop. All three fields are included in the GDPR data export and cleared on account deletion.


Forge-Compatible Environment Template

The installer now uses .env.example instead of the previous .env.blueprint, matching Laravel conventions. Services like Laravel Forge that copy .env.example on deployment get a working Reward Loyalty configuration out of the box, with production-safe defaults (APP_DEBUG=false, APP_ENV=production).


Improved

  • GDPR Data Export. The member data export now includes birthday, gender, and phone fields. Previously only name, email, locale, timezone, and email consent were exported.
  • GDPR Account Deletion. Deleting a member account now resets gender to "Not specified" alongside clearing phone, birthday, and all other personal data.
  • Portal Homepage Card. The "Balance" label and point amount on the portal homepage card now align to the left, matching the main homepage layout.

Fixed

  • Agent API: staffId rejected valid UUIDs. The purchase and points endpoints validated staffId as a numeric value, rejecting all UUID staff IDs with a 422 error. These endpoints now accept UUIDs, matching the current staff model. The OpenAPI spec has been updated to reflect format: uuid.

Translations

  • New profile field labels (birthday, gender, phone) and phone component strings (country code, search) translated in all 11 languages.

Maintenance

  • Removed the unused Snowflake ID system. All models use Laravel's built-in UUID support.
  • Production assets rebuilt.

Version 4.3.0

May 25, 2026

New

  • Gumroad License Activation. Admins can now activate Reward Loyalty with a Gumroad license key or an Envato/CodeCanyon purchase code from the same License & Updates field. The app detects the provider, validates the key, stores the provider status, and keeps update checks tied to the current installation host. See Updating.

Improved

  • Simpler License Form. The activation form now asks for one license key. It no longer shows an editable production domain field.
  • Demo Homepage Balance. The demo homepage now shows 3 loyalty cards, 3 stamp cards, and 3 vouchers from the same business, so the default demo staff member can interact with all of them.
  • Installer Dependency Cleanup. The install flow no longer depends on HTMX. The form uses a built-in request flow and restores the submit button when validation or server errors occur.

Fixed

  • PHP 8.5 Install Compatibility. Reward Loyalty no longer triggers the PDO::MYSQL_ATTR_SSL_CA deprecation warning during database setup on PHP 8.5.
  • Dashboard Updates Without a License Token. Update checks now show a clear message when the license is valid but automatic updates aren't available yet, instead of a generic error.

Maintenance

  • Composer dependencies were updated and production assets were rebuilt.

Version 4.2.0

May 21, 2026

New

  • Monthly and Yearly Billing Interval Toggle. Partners can now toggle between monthly and yearly billing cycles when reviewing and upgrading plans on the My Plan page. The page dynamically displays the monthly-equivalent price for annual tiers alongside the annual total, and highlights yearly discounts with a styled percentage badge.
  • Multi-language Billing Intervals. Integrated localized billing interval keys (monthly, yearly, year, billing_save_percent) in Arabic, German, Spanish, French, Indonesian, Italian, Japanese, Polish, Portuguese, Turkish, and English language packs.

Improved

  • Stripe Webhook Synchronization Hardening. Re-architected the Stripe webhook plan synchronization to prevent unauthorized entitlement access. The controller now validates Cashier subscription status in the local database after processing rather than trusting the raw webhook payload. Plan tiers are only synced for active or trialing states.
  • Billing Checkout Sanitization. Hardened checkout parameter handling in BillingController to validate requested billing intervals, ensuring fallback to a monthly interval if invalid or missing parameters are submitted.
  • Staff-specific "access denied" error page. When a staff member opens a loyalty card, stamp card, or voucher from a club they are not assigned to, they now see a clear, tailored error page instead of a generic error. The page explains the issue in plain language ("You're signed in as staff, but this item is not assigned to your account") and suggests asking a manager for the correct club assignment. Translated natively in all 11 languages.
  • Faster form saves on shared hosting. Complex forms with many fields (like Stamp Cards with 30+ configuration options) now save in under a second. The previous approach ran a full HTML parsing pipeline on every field, including booleans, colors, numbers, and selects that cannot contain HTML. The sanitizer now skips constrained input types and uses a lightweight method for text fields.

Fixed

  • Pricing Display Layout Bug. Fixed a bug where pricing minor units (cents) were displayed directly without formatting on the Partner Billing Page, causing prices to render 100x inflated. Standardized the display to use the cent-aware moneyStyled() helper.
  • Points exceeded card maximum after tier multiplier. Cards with a "Max Points Per Purchase" setting did not enforce that limit after tier multipliers were applied. A card capped at 1,000 points with a 2× Gold tier could award 2,000 points. The system now clamps awarded points to the card's maximum after all multiplier calculations, across staff transactions, the Staff API, the Partner API, and the Agent API. See Point Rules.
  • Cards with unlimited points (no maximum set) awarded zero. When "Max Points Per Purchase" was left empty (meaning unlimited), purchase-amount transactions returned 0 points instead of the calculated amount. A PHP type comparison treated the empty value as a zero ceiling. This affected all channels: staff form, Staff API, Partner API, and Agent API.
  • Staff form applied tier multiplier twice for purchase-amount transactions. When a staff member entered a purchase amount, the JavaScript preview showed the multiplied point total. The server then multiplied again, doubling the tier bonus. The server now calculates points from the purchase amount directly, using the preview field only for display.
  • Transaction audit log marked all purchase-mode transactions as manually overridden. The manual_points_override flag in transaction metadata was set to true on every purchase-amount transaction, even when staff did not enter a custom point value. The flag now records only when the caller provides an explicit point amount.
  • Staff could view and redeem vouchers from other clubs. The voucher redemption prefill page, the voucher redeem POST endpoint, and the voucher validate AJAX endpoint did not verify that the voucher belonged to the staff member's assigned club. A staff member could access any voucher's details or redeem vouchers from clubs they were not assigned to. All three endpoints now enforce club ownership.
  • Staff voucher validation accepted arbitrary club IDs. The AJAX validation endpoint accepted a club_id from the request body, allowing staff to probe voucher codes from other clubs and receive discount details. The endpoint now derives the club from the authenticated staff member and ignores any client-supplied value.
  • Staff API voucher redemption lacked club authorization. The POST /api/{locale}/v1/staff/vouchers/{id}/redeem endpoint called the voucher service without verifying that the voucher belonged to the staff member's club. A staff API token could redeem vouchers across club boundaries. The endpoint now checks club ownership before processing.
  • Staff API auth guard was not configured. The Staff API routes referenced an auth:staff_api guard that was not defined in the auth configuration, which would crash at runtime. The missing Sanctum guard has been added, matching the pattern used by admin, partner, and member API guards.
  • Stamp card pages rendered with null data for missing resources. The staff stamp card transaction, add stamps, and claim reward pages used find() and first() which returned null for non-existent records, causing views to render with empty data instead of a proper 404 error. Changed to findOrFail() and firstOrFail() for consistent 404 responses.
  • Staff add-stamp page crashed for deleted stamp cards. When a staff member followed a stale bookmark or old QR code pointing to a stamp card that no longer exists, the page crashed with a server error instead of showing the friendly error state. The page title and purchase-amount script now handle missing stamp cards gracefully.
  • Inconsistent HTTP status codes for staff access errors. Some staff controllers used abort(401) (Unauthorized) and others used abort(403) (Forbidden) for the same type of error: an authenticated staff member accessing a resource outside their club. All controllers now consistently return 403 with the tailored error page.
  • Passwords containing angle brackets no longer change on save. If a member or staff password contained characters like < or >, the sanitizer stripped part of it before hashing, storing a different password than the one entered. Passwords are no longer processed by the HTML sanitizer.

Version 4.1.0

May 12, 2026

New

Self-Service Plan Upgrade via Stripe Checkout

Partners who registered through the public registration page can now upgrade or change their plan from the My Plan page without contacting an administrator.

  • Partners without a subscription see an Upgrade button on each higher-tier plan card. Clicking it starts a Stripe Checkout session that creates the Stripe customer on the fly.
  • Partners with an active subscription see a Change Plan button that redirects to the Stripe billing portal for upgrades and downgrades.
  • Admin-created and legacy partners see plan cards for reference but cannot start self-service billing. They see a contact-admin prompt instead.
  • The checkout endpoint validates plan existence, rejects same-plan requests, and blocks admin-managed accounts.
  • Manual billing mode hides all checkout actions.

Partner Billing Page → · SaaS Setup Guide →

Translations

  • 4 new billing keys (billing_upgrade, billing_change_plan, billing_checkout_unavailable, billing_invalid_plan) translated in all 11 locales

Maintenance

  • Laravel Framework 13.2.0 → 13.8.0
  • Laravel Cashier 16.5.1 → 16.5.3
  • Pest 4.4.3 → 4.7.0, PHPUnit 12.5.14 → 12.5.24
  • Symfony components updated to latest 8.0.x releases
  • All NPM dependencies updated, production assets rebuilt
  • 0 security vulnerabilities

Version 4.0.0

May 7, 2026

New

Tiered Partner Plans & Entitlement Service

Partner plan management has been rebuilt from the ground up with a clean, name-agnostic tier architecture. Four configurable tiers control resource limits, feature flags, and access across the entire platform.

  • Config-driven plans — All plan definitions live in config/plans.php. Each tier specifies resource limits (max_cards, max_staff, max_rewards, etc.), feature flags (has_vouchers, has_email_campaigns, has_agent_api, etc.), pricing, and display metadata
  • Centralized entitlement enforcement — The new EntitlementService provides a single API for all permission and limit checks: can(), limit(), withinLimit(), remaining(), and summary()
  • Meta override layer — Admins can fine-tune individual partner limits without changing the partner's plan tier
  • Billing-state awareness — Nine subscription states (billing_disabled, legacy, manual, active, trialing, past_due, cancelled, incomplete, suspended) with configurable access restrictions
  • Plan-derived metaderiveMetaFromPlan() and syncMetaFromPlan() ensure consistent entitlement enforcement across all partner creation paths (admin CRUD, self-registration, seeders)
  • Homepage publishing flaghas_cards_on_homepage controls whether partners on a given plan can feature content on the public homepage. Disabled by default across all tiers. Admins can override per partner from the Permissions tab. See Homepage Visibility Control

Partner Self-Registration

Partners can now register directly through a public-facing registration page. Disabled by default — admins enable it in Settings → Onboarding → Partner Registration.

  • Plan comparison cards — Registration page displays all active plans with resource limits, feature availability, and pricing
  • Default plan enforcement — New partners automatically receive the default plan with enforced limits from the moment of creation
  • Abuse protection — Automated bot detection and IP-based rate limiting prevent spam registrations
  • Atomic registration — Partner creation and email verification are transactional; if delivery fails, no partial account is created
  • Full localization — Registration experience fully translated in all 11 supported languages with native translations
  • Public CTA — Conditional "Register Your Business" link appears in the guest header when registration is enabled
  • Full footer — Registration page includes the complete footer with language selector, theme toggle, and navigation links

Partner Registration →


Health Center

Administrators can now monitor platform health from a dedicated dashboard page.

  • Environment checks — PHP version, required extensions, directory permissions, queue driver, mail configuration, URL and HTTPS settings
  • Database health — Connection status and migration state
  • Storage — Writable directory verification
  • Scheduler heartbeat — Detects whether the cron job is running
  • Billing provider status — Reports active billing provider and configuration state
  • Categorized display — Summary cards with OK/warning/blocked indicators across 6 categories

Billing Provider Foundation

Extensible billing provider abstraction supporting manual/offline billing (default) and Stripe via Laravel Cashier.

  • Provider contractBillingProvider interface with implementations for null (manual) and stripe modes, switchable via BILLING_PROVIDER environment variable
  • Laravel Cashier integration — Stripe subscription management with adapted migrations for the Partner model
  • Subscription state resolution — Billing provider automatically resolves partner subscription status for the EntitlementService
  • Webhook handling — Idempotent Stripe webhook controller at /api/stripe/webhook for subscription lifecycle events. Rejects all requests with 403 when STRIPE_WEBHOOK_SECRET is not configured.
  • Config-safe price IDs — Stripe Price IDs mapped through config/default.php, compatible with php artisan config:cache
  • Zero-disruption migration — All Cashier columns are nullable; existing installations work without any Stripe configuration

Partner Billing Page

A dedicated plan management page accessible to all partners from the sidebar and user menu.

  • Plan overview — Current plan name, description, subscription status badge, and feature list
  • Resource usage — Visual progress bars for all resource limits with exceeded-state highlighting
  • Adaptive rendering — Manual mode shows plan details with admin contact CTA; Stripe mode adds subscription status, billing portal link, and plan comparison
  • Status banners — Contextual alerts for trial, past due, cancelled, and suspended subscription states
  • Navigation — "My Plan" link added to desktop sidebar, desktop user menu, mobile user menu, and mobile overlay

Admin SaaS Dashboard

A super-admin-only operational dashboard for monitoring partner subscription health across the platform.

  • Full status breakdown — All 9 subscription states displayed: active, trialing, past due, cancelled, incomplete, suspended, manual, legacy, and billing disabled
  • Plan distribution — Visual progress bars showing partner distribution across plan tiers
  • Attention panel — Expiring trials (7-day window) and past-due partners with direct links to partner management
  • Recent registrations — Latest partner signups with plan, email, and time-ago display
  • Usage overview — Aggregate resource counts (partners, staff, loyalty cards, stamp cards, vouchers)
  • 5-minute cache — Automatic cache with manual clear support

SaaS Dashboard →


Demo Seed & Reset

A comprehensive demo data system for testing and demonstration purposes.

  • 4 demo partners across all plan tiers with full business profiles (names, addresses, brand colors, taglines, square business logos)
  • 13 demo members with cross-partner transaction diversity proving the shared wallet architecture
  • Deterministic content — curated card, stamp, and voucher names per business vertical (cafés, restaurants, beauty, fitness) so demo screenshots are stable across resets
  • Homepage balance — 3 loyalty cards, 3 stamp cards, and 3 vouchers visible on the homepage. One partner hidden from homepage to demonstrate the visibility control
  • php artisan demo:reset — Repeatable reset command with safety guards
  • Plan-derived meta — Demo partner permissions derived from config/plans.php to prevent contract drift

Data Export & Ledger History

Partners and admins can export loyalty program data in CSV, TSV, and JSON formats directly from the dashboard.

  • Transaction history — Exportable ledger of point transactions (credits, redemptions, adjustments) scoped to the partner's loyalty cards
  • Stamp transaction history — Full audit trail of stamp earning, redemption, and completion events for the partner's stamp cards
  • Voucher redemption history — Complete redemption log with voucher code, voucher name, member, status, and order reference
  • Admin aggregate export — Platform-level exports across all partners for admin reporting, covering all three ledger types
  • Multiple formats — CSV (default), TSV, and JSON with structured metadata
  • Partner isolation — All exports are strictly scoped to the authenticated partner's data; cross-partner access is impossible
  • Search engine — Full-text search across all ledger columns including relation fields (member name, card name, voucher code). Translated event labels (e.g., searching "Points Credited" in any locale) map back to raw database slugs for accurate filtering
  • Multi-field autocomplete — Search suggestions show composed labels (e.g., "Points Credited · Gold Card · John Smith") with a two-line dropdown layout for readability
  • Sidebar navigation — Transaction History links added to partner and admin dashboards

Data Export →


USB & Bluetooth Barcode Scanner

Staff can now use hardware QR barcode scanners (USB or Bluetooth) as an alternative to the built-in camera scanner. Plug in a scanner, point it at a customer's QR code, and the browser navigates directly to their profile.

  • Works everywhere — Dashboard, reward claims, member views. Any staff page.
  • Security — Only navigates to URLs on your own domain. External URLs are blocked.
  • No setup — Plug in (USB) or pair (Bluetooth). No software or configuration needed.
  • Smart detection — Ignores scanner input when typing in a form. No interference with keyboard shortcuts.

Staff Scanning →


Independent Stamp Card & Voucher Plan Limits

Plan tiers now control stamp card and voucher allowances independently from loyalty card limits. Operators can offer a free tier with 1 loyalty card and 1 stamp card but 0 vouchers, or a premium tier with unlimited across all card types.

  • max_stamp_cards and max_vouchers configurable per tier in config/plans.php
  • Backward compatible — falls back to max_cards if new keys are omitted

Primary Network Admin Controls

Admins can now view and manage the primary network status directly from the Networks CRUD.

  • is_primary toggle on list, view, edit, and insert actions
  • Marking a network as primary automatically clears all other networks (single-primary invariant)
  • Currency help text explains that the primary network currency becomes the default for new partner registrations

Improved

Tier-Based Plan Architecture

Plan identifiers use name-agnostic tier IDs (tier1, tier2, tier3, tier4) instead of branded names. Display names (Bronze, Silver, Gold, Platinum) are configured separately and fully localizable via translation keys. Operators can rename plans without touching code or translation keys.

  • Upgrade migration automatically converts existing partner plan values from legacy names to tier IDs
  • All fallbacks, defaults, and test fixtures use tier-based IDs
  • Translation keys follow the plan_tierX_name / plan_tierX_desc pattern

Admin Partner List

The partner list in the admin dashboard now shows only the columns that matter for quick scanning: name, email, network, plan, and the impersonate button. Detailed metadata (avatar, login count, last login, active status) is still accessible from the edit and view screens.


Staff Attribution on Stamp Card Completions

When a staff member adds the final stamp that completes a card and triggers a point bonus, the resulting point transaction now shows that staff member's name instead of "System". Automated completions (no staff involvement) continue to show "System".


Currency-Safe Price Formatting

Price displays now correctly render Unicode digits (e.g., Arabic-Indic ٠١٢٣٤) and non-standard decimal separators across all locales. Previously, some locale/currency combinations could show garbled characters in price displays.


Translations

  • Plan names and descriptions translatable via plan_tier1_name through plan_tier4_name keys
  • Registration page strings translated in all 11 locales with native translations
  • Health Center strings (22 keys) translated in all 11 locales
  • Entitlement deny reasons and resource names (14 keys) translated in all 11 locales
  • Partner Billing Page strings (16 billing_* keys) translated in all 11 locales
  • Admin SaaS Dashboard strings (23 saas_* keys) translated natively in all 11 locales
  • Data export event labels (22 transaction events, 9 stamp events) translated natively in all 11 locales

Fixed

  • Non-Latin language names — Language selector now correctly capitalizes names like "Русский" instead of "русский". Analytics chart month labels also capitalize correctly in Cyrillic, Arabic, and other non-Latin locales.
  • Stamp card enrollment — Fixed broken stamp card enrollment calls and route ordering that could prevent members from enrolling in stamp programs.
  • Voucher percentage storage — Voucher percentage values are now consistently stored as whole numbers (20 = 20%). Previously, some code paths treated them differently, causing incorrect discount calculations.
  • OpenAI client startup crash — Installations without an OpenAI API key no longer crash on application startup. The client is now lazy-loaded on first use.

Upgrade Notes (v3 → v4)

  • Plan migration — Existing partner plan values are automatically converted from legacy names to tier IDs during migration. No manual action required.
  • Billing provider — Defaults to billing_disabled mode. Existing installations continue working without any Stripe configuration. Set BILLING_PROVIDER=stripe and configure credentials only when ready.
  • Partner registration — Disabled by default. Enable at Settings → Onboarding → Partner Registration. Requires at least one active primary network.

Looking for v3 changes? See Version 3.x Changelog for all v3 releases.

Looking for v1–v2? See Changelog Archive for the historical version history.