Phase 3 — Payment + Agent + Polish (~2 days parallel, ~9 CC sessions)
Goal: Close LINE-native loop with LINE Pay (no external redirect). Wire agent QR → automatic referral attribution. Polish LIFF UX (bottom nav, share, LINE-style).
Exit gate: User pays inside LINE without leaving. Agent shares unique QR → referred user’s purchases attribute commission. LIFF feels native (not a web shoehorn).
Decisions in this phase
3.D-1 — LINE Pay merchant account
- Status: open — REQUIRES BUSINESS ACTION
- Owner: r_goto / Cocon-inc legal/finance
- Context: LINE Pay merchant onboarding takes 2-4 weeks (KYB, contract, sandbox keys → prod keys).
- Action: start application now if not already started; tech work can use sandbox keys in parallel.
- Blocks: 3.P-10 production deploy (sandbox dev unblocked)
3.D-2 — LINE Pay in MVP scope?
- Status: open
- Options:
- Include in MVP — block launch until merchant account live
- Ship MVP with Stripe-via-LIFF, add LINE Pay in Phase 3 follow-up
- Recommendation: Option 2 — Stripe Checkout via
liff.openWindowworks today, LINE Pay merchant onboarding is unpredictable. - Blocks: delivery date
3.D-3 — Agent QR vs invite link
- Status: open
- Options:
- QR only (LINE-native, scan in LINE app)
- Short URL only (shareable anywhere)
- Both
- Recommendation: Option 3 — Curva already supports both via LineQrCode + tracking sessions.
Tracking matrix
| ID | Title | Track | Jira | CC | Risk | Status | Depends |
|---|---|---|---|---|---|---|---|
| 3.P-10 | LINE Pay payment-method module | P | PASS-? | 2 | H | Backlog | 3.D-1 (sandbox), 3.D-2 |
| 3.P-13 | Replay-attack protection (timestamp+nonce) | P | PASS-? | 1 | M | Backlog | 0.P-7 |
| 3.C-10 | Agent QR auto-link on follow | C | CRV-? | 1.5 | M | Backlog | 2.C-7 |
| 3.C-11 | LineFollower ↔ Pasukuru member_id mapping | C | CRV-? | 1 | M | Backlog | 0.P-3 |
| 3.F-8 | Order history in LIFF | F | PASS-? | 1.5 | L | Backlog | 1.F-5 |
| 3.F-9 | ”Add to LINE” CTA on landing page | F | PASS-? | 0.5 | L | Backlog | — |
| 3.F-12 | LIFF UX polish (bottom nav, share, LINE buttons) | F | PASS-? | 1.5 | L | Backlog | 1.F-7 |
Total: 9 CC sessions = ~2 days parallel.
Items detail (selected)
3.P-10 — LINE Pay payment-method
Track: Pasukuru BE CC: XL (2) Risk: H Goal: New payment method “LINE Pay”, parallel to existing Stripe.
Files:
src/app/master/payment-method/services/line-pay.service.ts(new)src/app/master/payment-method/enums/payment-method-enum.ts(edit — addLINE_PAY)src/app/master/payment-method/entities/payment-method.entity.ts(no schema change)src/app/order/order.service.ts(edit — branch on payment method)src/app/order/line-pay-webhook.controller.ts(new)src/migrations/...-add-line-pay-events-table.ts(new — like order-stripe-event)src/app/order/entities/order-line-pay-event.entity.ts(new)
Flow:
- Checkout → create Order with payment_method=LINE_PAY
- Backend calls LINE Pay API
/payments/request→ returns paymentUrl + transactionId - Frontend (LIFF) →
liff.openWindow({ url: paymentUrl, external: false })(LINE Pay opens IN LINE) - User confirms in LINE Pay → redirected to confirmUrl
- Backend webhook receives → calls
/payments/{transactionId}/confirm→ marks order paid → emitsorder.paid(Phase 0 wiring auto-pushes Flex receipt)
Acceptance:
- Sandbox keys work end-to-end
- Switch to prod keys via env (no code change)
- Cancel/refund flow handled
- Idempotent webhook
- e2e test against LINE Pay sandbox
Rollout:
- Feature flag:
payment.line_pay - Merchant per-tenant via
payment_methodrow config
3.C-10 — Agent QR auto-link
Track: Curva CC: L (1.5) Depends: 2.C-7 Goal: Agent shares LINE QR → followee user attributed to that agent → all purchases credit commission.
Files:
app/Models/AgentReferral.php(new — Curva side mapping)app/Actions/Member/LineAccount/Scenario/AssignAgentOnFollowAction.php(new)app/Services/Api/Line/Webhook/Handlers/FollowEventHandler.php(edit — check QR session)app/Http/Controllers/Api/IntegrationController.php(edit — add agent context to register payload)
Flow:
- Agent (in Pasukuru) requests their QR from new endpoint
GET /api/v1/integration/agent-qr- Pasukuru BE returns:
{ shopId, agentCode, qrPayload }
- Pasukuru BE returns:
- Agent shares QR (printed/digital)
- Customer scans → adds Pasukuru’s LINE OA as friend
- LINE webhook fires
followevent → Curva FollowEventHandler - Handler checks: did this user have a recent QrTrackingSession with
agent_codeparam? - If yes → calls Pasukuru
POST /integration/line/agent-attributewith{ lineUserId, agentCode } - Pasukuru sets member.referrer_agent_id
Acceptance:
- QR encodes shop + agent
- Scan → friend add → attribution within 5s
- Existing member doesn’t override referrer
- Commission calc (existing agent-profit module) works on next purchase
- e2e: scan → buy → verify commission row created
3.C-11 — Follower ↔ member_id mapping
Track: Curva CC: M (1) Depends: 0.P-3 Goal: Curva LineFollower table stores Pasukuru member_id for cross-system queries.
Files:
database/migrations/...add_pasukuru_member_id_to_line_followers.php(new)app/Models/LineFollower.php(edit)app/Actions/Member/LineAccount/LineFollower/SyncWithPasukuruAction.php(new)
Logic:
- Add column
line_followers.pasukuru_member_id(nullable, indexed) - On follow event → call Pasukuru
/integration/line/follower-link(0.P-3) → store returned memberId - Existing followers backfilled by command
php artisan curva:backfill-pasukuru-mapping
Acceptance:
- New follow → link auto-created
- Backfill handles 10K+ followers paginated
- Schema rollback safe
- No PII leak in logs
3.F-12 — LIFF UX polish
Track: Pasukuru FE CC: L (1.5) Goal: LIFF feels like LINE Mini App, not a web page in webview.
Files:
src/components/LiffBottomNav.tsx(new)src/components/LiffShareButton.tsx(new — usesliff.shareTargetPicker)src/styles/liff.css(new — LINE design tokens)src/app/layout.tsx(edit — conditional LIFF chrome)
Items:
- Bottom nav (Shop / Cart / Orders / Account) — only shown when
useLiff().isInClient - Share button on product page →
liff.shareTargetPickerto send to friends - LINE-green CTAs (use
bg-[#06C755]) liff.closeWindow()button on receipt page- Disable Pasukuru’s normal navbar in LIFF mode
- Adjust safe-area insets for iOS notch
Acceptance:
- Mobile testing on both platforms (iOS + Android LINE app)
- No layout breaks
- Share works → opens LINE picker
- Bottom nav hidden in non-LIFF web mode
3.P-13 — Replay-attack hardening
Track: Pasukuru BE CC: M (1) Depends: 0.P-7 Goal: Reject duplicate webhook nonces (mirror Curva’s protection from 0.C-2).
Files:
src/app/integration/services/nonce-store.service.ts(new — Redis SETNX with TTL)src/app/integration/guards/kuru-api-key.guard.ts(edit — add nonce check on inbound from Curva, when Curva starts pushing)
This becomes important when Curva → Pasukuru direction also has webhooks (currently mostly RPC, but future-proofs).
Phase 3 smoke test
Procedure:
- Agent A requests QR via Pasukuru admin → gets QR image
- Customer X scans QR → friends Pasukuru’s LINE OA → 5s later AgentReferral row created in Curva, member.referrer_agent_id set in Pasukuru
- Customer X opens LIFF → buys ¥3000 product → pays via LINE Pay (in-LINE)
- Order completes → receipt Flex arrives in chat
- Agent A’s commission row appears in Pasukuru agent-profit module
- Order history page in LIFF shows past purchases
- Polish QA: bottom nav visible, share button works, LINE Pay UI feels native
Pass: all 7 steps green.
Phase 3 exit checklist
- All items DoD
- LINE Pay sandbox green; prod keys ready (or noted as future)
- Agent referral end-to-end demo recorded
- LIFF UX QA on iOS + Android
- Vault + memory updated
- User sign-off → optional Phase 4 OR ship to prod