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:
- Pull on-demand — Curva calls Pasukuru
/integration/itemsper Flex build (no cache) - Webhook + cache — Pasukuru pushes product.* events (already wired in Phase 0), Curva caches in
pasukuru_productstable - Hybrid — pull on-demand, cache for 5 min
- Pull on-demand — Curva calls Pasukuru
- 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:
- Use Pasukuru’s existing CDN URLs (assumes public)
- Curva proxies images via own CDN
- Recommendation: Option 1 — Pasukuru file service already serves public URLs. Document as contract requirement.
Tracking matrix
| ID | Title | Track | Jira | CC | Risk | Status | Depends |
|---|---|---|---|---|---|---|---|
| 2.P-8 | GET /integration/items (product feed) | P | PASS-? | 1 | L | Backlog | — |
| 2.P-11 | Order trigger improvements (post-checkout hook) | P | PASS-? | 0.5 | L | Backlog | 0.P-4 |
| 2.C-4 | Product cache table + sync from webhook | C | CRV-? | 1 | M | Backlog | 0.C-3, 2.D-1 |
| 2.C-5 | Pasukuru product picker in FlexMsg builder | C | CRV-? | 1.5 | M | Backlog | 2.C-4, 2.P-8 |
| 2.C-6 | RichMenu LIFF preset buttons | C | CRV-? | 1 | L | Backlog | 1.C-12 |
| 2.C-7 | Order receipt Flex push (fill 0.C-3 stub) | C | CRV-? | 1 | M | Backlog | 0.C-3 |
| 2.C-8 | Abandoned cart Scenario node | C | CRV-? | 1 | L | Backlog | 2.C-7 |
| 2.C-9 | Post-purchase upsell Scenario node | C | CRV-? | 0.5 | L | Backlog | 2.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 — addlistItems)integration.service.ts(edit — addgetItemsForCurva)
Acceptance:
- Cursor pagination (no offset)
- Only
is_published=trueitems - 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→ insertupdated→ upsertdeleted→ 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:
- Click ”+ Pasukuru products” button in Flex editor
- Modal opens with search box + grid of products from
pasukuru_products(current LineAccount only) - Multi-select up to 10 (LINE carousel limit)
- 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
- 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:
- Add new action type “Open Pasukuru” beside existing “URL”
- Dropdown reads from LineLiff table where usage_type LIKE ‘pasukuru_%’
- 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:
- Read OrderData from webhook payload
- Lookup LineFollower by
(line_account_id, line_user_id)from data - If found → build Flex Msg per event:
created— “Order received” with item summarypaid— “Payment confirmed” with totalshipped— “Shipped” with tracking + ETAcancelled— “Cancelled” with reason
- Push via Messaging API
- 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:
- New trigger types listenable by Scenario engine:
pasukuru.order.created,pasukuru.order.paid - Webhook handler (2.C-7) emits internal event → Scenario engine catches
- Template Scenario:
- On
pasukuru.order.created→ wait 30min → check ifpasukuru.order.paidfor same orderId fired → if not → push Flex with “Complete purchase” CTA → LIFF cart URL
- On
- 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:
- Admin in Curva opens FlexMessage builder
- Click ”+ Pasukuru products” → picks 3 products
- Sends to test follower
- Follower (LINE user) sees carousel
- Tap product → LIFF opens to product page (item param)
- Add to cart → checkout → pay
- Receipt Flex arrives in LINE chat (2.C-7 working)
- Cancel test: create order, don’t pay, wait 30min → reminder Flex arrives (2.C-8)
- 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