Loyalty & Rewards App
Overview
The loyalty app is a point- or stamp-card and reward redemption system delivered through LINE. It is built from these pieces:
| Component | Meaning |
|---|---|
| Program | The parent program — point or stamp mode, card size, and on/off status |
| Milestones | Accumulation goals; reaching one earns the associated reward |
| Tiers | Membership levels, available in point mode only, ordered by rank |
| Branches | Locations where points can be earned and rewards redeemed |
| Staff | Employees authorized to grant points, managed through invitations |
| Activity / Customers | Transaction history and member customer records |
The module uses the same error code set as client-api, so both sides speak an identical error vocabulary.
Business Flow
Configuring the Program
GET /api/apps/loyalty/programreads the current program. If none exists, the request fails withLOYALTY_PROGRAM_MISSING.PUT /api/apps/loyalty/programsaves the program (mode, card size, and so on). An invalid card size returnsLOYALTY_CARD_SIZE_INVALID.PUT /api/apps/loyalty/program/statusturns the program on or off.
Building the Reward Ladder
- Milestones are managed through
GET /api/apps/loyalty/milestones,POST /api/apps/loyalty/milestones,PUT /api/apps/loyalty/milestones/:id, andDELETE /api/apps/loyalty/milestones/:id. - Tiers are managed through
GET /api/apps/loyalty/tiers,POST /api/apps/loyalty/tiers,PUT /api/apps/loyalty/tiers/:id, andDELETE /api/apps/loyalty/tiers/:id.- Tiers work in point mode only; any other mode returns
LOYALTY_TIER_POINT_MODE_ONLY. - Inconsistent numbers return
LOYALTY_TIER_INVALID. - A rank that collides with an existing tier returns
LOYALTY_TIER_RANK_TAKEN.
- Tiers work in point mode only; any other mode returns
POST /api/apps/loyalty/tiers/recalculaterecomputes membership levels immediately, in addition to the nightly job. It exists because a shop that has just finished configuring its ladder should not have to wait until 3 AM to see whether the setup is correct.
Branches and Staff
- Branches are managed through
GET /api/apps/loyalty/branches,POST /api/apps/loyalty/branches, andDELETE /api/apps/loyalty/branches/:id. - Staff are managed through
GET /api/apps/loyalty/staff,POST /api/apps/loyalty/staff/inviteto send an invitation,PUT /api/apps/loyalty/staff/:idto edit, andDELETE /api/apps/loyalty/staff/:idto revoke an invitation.
Viewing Customer Data
GET /api/apps/loyalty/activityshows the history of point grants and redemptions.GET /api/apps/loyalty/customerssearches member customers.GET /api/apps/loyalty/customerlooks up a single customer.
End User and Frontline Staff Side
Granting points and redeeming rewards happen through client-api in the LIFF app, not through the endpoints documented here.
Key Files & Functions
The code lives in internal/modules/loyalty/.
| File | Role |
|---|---|
controller.go | Route registration; every route carries apps.AppEnabledGuard(d) |
service_program.go | ProgramService — the parent program plus error code definitions |
service_catalog.go | CatalogService — reward ladder, branches, staff allowlist |
Every route sits on the authed group, followed by appEnabled and the policy check (PolicyModuleLineOa).
| Method | Route | Handler | Policy |
|---|---|---|---|
| GET | /api/apps/loyalty/program | ct.getProgram | read |
| PUT | /api/apps/loyalty/program | ct.saveProgram | update |
| PUT | /api/apps/loyalty/program/status | ct.setProgramStatus | update |
| GET | /api/apps/loyalty/milestones | ct.listMilestones | readAll |
| POST | /api/apps/loyalty/milestones | ct.createMilestone | create |
| PUT | /api/apps/loyalty/milestones/:id | ct.updateMilestone | update |
| DELETE | /api/apps/loyalty/milestones/:id | ct.deleteMilestone | delete |
| GET | /api/apps/loyalty/tiers | ct.listTiers | readAll |
| POST | /api/apps/loyalty/tiers | ct.createTier | create |
| PUT | /api/apps/loyalty/tiers/:id | ct.updateTier | update |
| DELETE | /api/apps/loyalty/tiers/:id | ct.deleteTier | delete |
| POST | /api/apps/loyalty/tiers/recalculate | ct.recalculateTiers | update |
| GET | /api/apps/loyalty/branches | ct.listBranches | readAll |
| POST | /api/apps/loyalty/branches | ct.createBranch | create |
| DELETE | /api/apps/loyalty/branches/:id | ct.deleteBranch | delete |
| GET | /api/apps/loyalty/activity | ct.listActivity | readAll |
| GET | /api/apps/loyalty/customers | ct.searchCustomers | readAll |
| GET | /api/apps/loyalty/customer | ct.lookupCustomer | readAll |
| GET | /api/apps/loyalty/staff | ct.listStaff | readAll |
| POST | /api/apps/loyalty/staff/invite | ct.createInvite | create |
| PUT | /api/apps/loyalty/staff/:id | ct.updateStaff | update |
| DELETE | /api/apps/loyalty/staff/:id | ct.revokeInvite | delete |
All error codes are defined in service_program.go: LOYALTY_PROGRAM_MISSING, LOYALTY_TIER_POINT_MODE_ONLY, LOYALTY_TIER_INVALID, LOYALTY_TIER_RANK_TAKEN, and LOYALTY_CARD_SIZE_INVALID.
Connections to Other Services
- Access control — Every route passes
apps.AppEnabledGuard(d); the app must be enabled for the organization first (see the Add-on Apps Platform). Policy metadata usesPolicyModuleLineOa. - Tables — The loyalty group defined in
internal/entities/loyalty.go, covering program, milestone, tier, branch, staff, activity/transaction, and customer membership, plusline_oa_app,line_oa, andline_user. - Nightly job — Tier recalculation runs at 03:00 in the worker.
- Cross-service —
line-management-client-api-goshares the same error code set. - Related modules — Add-on Apps Platform, the Appointment Booking App, the Bulletin Board App, and LINE User Management.