Phase 2 — UX Glue (1-2 days parallel, ~8 CC sessions)

Goal: Curva admins can build LINE flows that drive Pasukuru transactions. Product feed cached, Flex Msg picker, RichMenu LIFF presets, abandoned cart + post-purchase Scenarios.

Exit gate: Admin builds a Flex carousel of Pasukuru products in 60 seconds. Sends to followers. Followers tap → LIFF → buy → receipt arrives back in LINE chat.


Decisions in this phase

2.D-1 — Product feed sync model

  • Status: open
  • Options:
    1. Pull on-demand — Curva calls Pasukuru /integration/items per Flex build (no cache)
    2. Webhook + cache — Pasukuru pushes product.* events (already wired in Phase 0), Curva caches in pasukuru_products table
    3. Hybrid — pull on-demand, cache for 5 min
  • Recommendation: Option 2 — events already flow, caching is cheap, no rate-limit risk on flex builds.
  • Blocks: 2.C-4

2.D-2 — Flex Msg image hosting

  • Status: open
  • Context: LINE Flex carousels need HTTPS image URLs reachable from LINE servers.
  • Options:
    1. Use Pasukuru’s existing CDN URLs (assumes public)
    2. Curva proxies images via own CDN
  • Recommendation: Option 1 — Pasukuru file service already serves public URLs. Document as contract requirement.

Tracking matrix

IDTitleTrackJiraCCRiskStatusDepends
2.P-8GET /integration/items (product feed)PPASS-?1LBacklog
2.P-11Order trigger improvements (post-checkout hook)PPASS-?0.5LBacklog0.P-4
2.C-4Product cache table + sync from webhookCCRV-?1MBacklog0.C-3, 2.D-1
2.C-5Pasukuru product picker in FlexMsg builderCCRV-?1.5MBacklog2.C-4, 2.P-8
2.C-6RichMenu LIFF preset buttonsCCRV-?1LBacklog1.C-12
2.C-7Order receipt Flex push (fill 0.C-3 stub)CCRV-?1MBacklog0.C-3
2.C-8Abandoned cart Scenario nodeCCRV-?1LBacklog2.C-7
2.C-9Post-purchase upsell Scenario nodeCCRV-?0.5LBacklog2.C-7

Total: 7.5 CC sessions = ~1-2 days parallel.


Items detail (selected)

2.P-8 — Product feed endpoint

Track: Pasukuru BE CC: M (1) Goal: Curva fetches paginated product list per shop.

Endpoint: GET /api/v1/integration/items?cursor=&limit=50 Auth: KuruApiKeyGuard (resolves shop from API key) Returns: { data: ProductData[], cursor: string|null, hasMore: bool }

Files:

  • integration.controller.ts (edit — add listItems)
  • integration.service.ts (edit — add getItemsForCurva)

Acceptance:

  • Cursor pagination (no offset)
  • Only is_published=true items
  • Image URLs resolved to public CDN
  • Test with 100+ items, 3 pages

2.C-4 — Product cache table + sync

Track: Curva CC: M (1) Depends: 0.C-3 Goal: Curva-side mirror of Pasukuru products, kept fresh by webhooks.

Files:

  • database/migrations/...add_pasukuru_products_table.php (new)
  • app/Models/PasukuruProduct.php (new)
  • app/Actions/Integration/Pasukuru/HandleProductEvent.php (edit — fill 0.C-3 stub)
  • app/Console/Commands/SyncPasukuruProducts.php (new — manual sync from feed endpoint)

Schema pasukuru_products:

id (pk)
partner_connection_id (fk, indexed)
pasukuru_item_id (string)
name
description
price (int)
image_url
category
synced_at
deleted_at (soft)
unique (partner_connection_id, pasukuru_item_id)

Action logic:

  • created → insert
  • updated → upsert
  • deleted → soft delete

Manual sync command:

  • php artisan pasukuru:sync-products {line_account_id} → calls feed endpoint, upserts all
  • For initial seed + recovery from missed webhooks

Acceptance:

  • Webhook events update table within 2s of receipt
  • Sync command refreshes everything
  • Soft delete preserves history
  • Pest test for each event handler

2.C-5 — FlexMsg Pasukuru product picker

Track: Curva CC: L (1.5) Depends: 2.C-4, 2.P-8 Goal: Vue component in FlexMessage builder lets admin search + multi-select Pasukuru products → injects Flex carousel JSON.

Files:

  • resources/js/Pages/Member/LineAccounts/LineFlexMessages/PasukuruProductPicker.vue (new)
  • resources/js/Pages/Member/LineAccounts/LineFlexMessages/Edit.vue (edit — add picker button)
  • app/Http/Controllers/Member/LineAccount/PasukuruProductController.php (new — search endpoint)
  • routes/web-member.php (edit)

UI flow:

  1. Click ”+ Pasukuru products” button in Flex editor
  2. Modal opens with search box + grid of products from pasukuru_products (current LineAccount only)
  3. Multi-select up to 10 (LINE carousel limit)
  4. Click “Insert” → generates Flex bubble per product:
    • Hero image (image_url)
    • Title (name)
    • Price (price)
    • Button “View” → action: uri → LIFF shop URL with ?item={pasukuru_item_id} query
  5. Inserted JSON appears in editor

Acceptance:

  • Search filters results (debounced 300ms)
  • Carousel max 10 enforced
  • Generated JSON valid (LINE Flex spec)
  • Preview shows correctly
  • e2e Pest test for search endpoint

2.C-6 — RichMenu LIFF preset buttons

Track: Curva CC: M (1) Depends: 1.C-12 Goal: RichMenu builder UI dropdown “Open Pasukuru shop / cart / account” → autofills LIFF URL.

Files:

  • resources/js/Pages/Member/LineAccounts/LineRichMenus/components/ActionEditor.vue (edit)

Logic:

  1. Add new action type “Open Pasukuru” beside existing “URL”
  2. Dropdown reads from LineLiff table where usage_type LIKE ‘pasukuru_%’
  3. On select → sets action.uri = liff.url

Acceptance:

  • Dropdown shows 3 options (shop/cart/account) per registered Pasukuru LIFF
  • No options → button disabled with hint “Run Auto-register first” (links to 1.C-12)
  • Saved RichMenu has correct URI

2.C-7 — Order receipt Flex push (fill 0.C-3 stub)

Track: Curva CC: M (1) Depends: 0.C-3 Goal: When order webhook arrives → push pre-built Flex Msg to follower.

Files:

  • app/Actions/Integration/Pasukuru/HandleOrderEvent.php (edit — replace stub)
  • app/Services/Member/LineAccount/PasukuruOrderFlexBuilder.php (new)
  • app/Services/LineMessagingService.php (edit if needed — add pushTo method)

Logic:

  1. Read OrderData from webhook payload
  2. Lookup LineFollower by (line_account_id, line_user_id) from data
  3. If found → build Flex Msg per event:
    • created — “Order received” with item summary
    • paid — “Payment confirmed” with total
    • shipped — “Shipped” with tracking + ETA
    • cancelled — “Cancelled” with reason
  4. Push via Messaging API
  5. If follower not found (user bought without being friend) → log + skip (don’t fail webhook)

Acceptance:

  • All 4 event types build correct Flex
  • Push succeeds in staging end-to-end test
  • Missing follower handled gracefully
  • Templates use existing FlexMessage builder format

2.C-8 — Abandoned cart Scenario node

Track: Curva CC: M (1) Depends: 2.C-7 Goal: Pre-built Scenario template “Abandoned cart” — if order created but not paid within 30min → nudge follower with reminder Flex.

Files:

  • database/seeders/AbandonedCartScenarioTemplateSeeder.php (new)
  • app/Models/Scenario.php (no schema change — uses existing trigger system)
  • app/Services/Scenario/Triggers/PasukuruOrderCreatedTrigger.php (new)
  • app/Services/Scenario/Triggers/PasukuruOrderPaidTrigger.php (new — cancellation event)

Logic:

  1. New trigger types listenable by Scenario engine: pasukuru.order.created, pasukuru.order.paid
  2. Webhook handler (2.C-7) emits internal event → Scenario engine catches
  3. Template Scenario:
    • On pasukuru.order.created → wait 30min → check if pasukuru.order.paid for same orderId fired → if not → push Flex with “Complete purchase” CTA → LIFF cart URL
  4. Admin can clone + customize the template

Acceptance:

  • Template appears in Scenario library
  • Cloning creates editable copy
  • Trigger fires on real order events
  • Wait + cancel logic works
  • Pest test with time mock

2.C-9 — Post-purchase upsell Scenario node

Track: Curva CC: S (0.5) Depends: 2.C-7, 2.C-8 Goal: Same pattern as 2.C-8, fires on paid, sends related products Flex 24hr later.

Reuses 2.C-8’s trigger infrastructure. Just a new template seeder.


Phase 2 smoke test

Procedure:

  1. Admin in Curva opens FlexMessage builder
  2. Click ”+ Pasukuru products” → picks 3 products
  3. Sends to test follower
  4. Follower (LINE user) sees carousel
  5. Tap product → LIFF opens to product page (item param)
  6. Add to cart → checkout → pay
  7. Receipt Flex arrives in LINE chat (2.C-7 working)
  8. Cancel test: create order, don’t pay, wait 30min → reminder Flex arrives (2.C-8)
  9. Repeat with completed purchase, wait 24hr → upsell Flex arrives (2.C-9)

Pass: all 9 steps green.


Phase 2 exit checklist

  • All items DoD
  • Smoke test green
  • Admin training doc written (1-page Notion or vault page)
  • Vault updated
  • User sign-off → Phase 3