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:
    1. Include in MVP — block launch until merchant account live
    2. Ship MVP with Stripe-via-LIFF, add LINE Pay in Phase 3 follow-up
  • Recommendation: Option 2 — Stripe Checkout via liff.openWindow works today, LINE Pay merchant onboarding is unpredictable.
  • Blocks: delivery date
  • Status: open
  • Options:
    1. QR only (LINE-native, scan in LINE app)
    2. Short URL only (shareable anywhere)
    3. Both
  • Recommendation: Option 3 — Curva already supports both via LineQrCode + tracking sessions.

Tracking matrix

IDTitleTrackJiraCCRiskStatusDepends
3.P-10LINE Pay payment-method modulePPASS-?2HBacklog3.D-1 (sandbox), 3.D-2
3.P-13Replay-attack protection (timestamp+nonce)PPASS-?1MBacklog0.P-7
3.C-10Agent QR auto-link on followCCRV-?1.5MBacklog2.C-7
3.C-11LineFollower ↔ Pasukuru member_id mappingCCRV-?1MBacklog0.P-3
3.F-8Order history in LIFFFPASS-?1.5LBacklog1.F-5
3.F-9”Add to LINE” CTA on landing pageFPASS-?0.5LBacklog
3.F-12LIFF UX polish (bottom nav, share, LINE buttons)FPASS-?1.5LBacklog1.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 — add LINE_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:

  1. Checkout → create Order with payment_method=LINE_PAY
  2. Backend calls LINE Pay API /payments/request → returns paymentUrl + transactionId
  3. Frontend (LIFF) → liff.openWindow({ url: paymentUrl, external: false }) (LINE Pay opens IN LINE)
  4. User confirms in LINE Pay → redirected to confirmUrl
  5. Backend webhook receives → calls /payments/{transactionId}/confirm → marks order paid → emits order.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_method row config

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:

  1. Agent (in Pasukuru) requests their QR from new endpoint GET /api/v1/integration/agent-qr
    • Pasukuru BE returns: { shopId, agentCode, qrPayload }
  2. Agent shares QR (printed/digital)
  3. Customer scans → adds Pasukuru’s LINE OA as friend
  4. LINE webhook fires follow event → Curva FollowEventHandler
  5. Handler checks: did this user have a recent QrTrackingSession with agent_code param?
  6. If yes → calls Pasukuru POST /integration/line/agent-attribute with { lineUserId, agentCode }
  7. 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:

  1. Add column line_followers.pasukuru_member_id (nullable, indexed)
  2. On follow event → call Pasukuru /integration/line/follower-link (0.P-3) → store returned memberId
  3. 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 — uses liff.shareTargetPicker)
  • src/styles/liff.css (new — LINE design tokens)
  • src/app/layout.tsx (edit — conditional LIFF chrome)

Items:

  1. Bottom nav (Shop / Cart / Orders / Account) — only shown when useLiff().isInClient
  2. Share button on product page → liff.shareTargetPicker to send to friends
  3. LINE-green CTAs (use bg-[#06C755])
  4. liff.closeWindow() button on receipt page
  5. Disable Pasukuru’s normal navbar in LIFF mode
  6. 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:

  1. Agent A requests QR via Pasukuru admin → gets QR image
  2. Customer X scans QR → friends Pasukuru’s LINE OA → 5s later AgentReferral row created in Curva, member.referrer_agent_id set in Pasukuru
  3. Customer X opens LIFF → buys ¥3000 product → pays via LINE Pay (in-LINE)
  4. Order completes → receipt Flex arrives in chat
  5. Agent A’s commission row appears in Pasukuru agent-profit module
  6. Order history page in LIFF shows past purchases
  7. 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