Customer Layer
Next.js Storefront
Framework: Next.js 15
Port: 3000
New
↕ REST
MedusaJS Backend
Framework: MedusaJS 2.x
Port: 9000
Active
HTTP
Port 3001
Service Layer
loyalty-service
Framework: NestJS
Port: 3001
New
OAuth2 REST
Client Credentials
External Layer
PDI Conexxus API
Protocol: REST + OAuth2
Provider: PDI
External
Side Integrations & Infrastructure
PostgreSQL
Primary database for orders, products, loyalty sessions & configs
Database
Redis
Session cache, rate limiting, circuit breaker state, pub/sub events
Cache
S3 Storage
Product images, media assets, static file hosting
Storage
MeiliSearch
Full-text product search, faceted filtering, typo tolerance
Search
Stripe
Primary payment processor — cards, ACH, Apple Pay
Payments
Finix
Secondary payment processor for fuel & c-store transactions
Payments
Firebase
Push notifications, customer auth, real-time loyalty updates
Notifications
SendGrid
Transactional emails — order confirmations, loyalty receipts
Email
Docker
Container orchestration for all services, dev & production parity
DevOps
Nginx
Reverse proxy, SSL termination, load balancing, static serving
DevOps
StorefrontNext.js 15
Medusa BackendMedusaJS 2.x
loyalty-serviceNestJS
PDI APIConexxus REST
IDENTIFY
1
Enter Phone #
Customer enters phone number on checkout page to link loyalty account.
2
POST /loyalty/identify
Medusa receives identify request, creates session record, forwards to loyalty-service.
3
Validate Session
Creates loyalty session, validates phone with PDI, assigns pos_sequence_id. State → ACTIVE.
identifyMember()
PDI Conexxus API validates the member phone number and returns loyalty account data.
REWARDS
4
Display Rewards
UI renders available instant and optional rewards returned from backend.
5
POST /loyalty/rewards
Backend sends cart items to loyalty-service to calculate available rewards and points.
6
Calculate Rewards
Loyalty service calls PDI calculateRewards with cart items and session. Returns instant + optional rewards.
calculateRewards()
PDI engine evaluates cart against promotions, returns eligible rewards and point multipliers.
7
Select Reward
Customer picks optional rewards from the rewards panel. Instant rewards auto-applied.
8
Apply to Cart
Backend applies selected reward as line item discount. Recalculates cart totals with tax.
Update Selection
Session rewards JSONB updated with selected reward IDs. Session state remains ACTIVE.
CHECKOUT
9
Cart Re-eval
If items change, frontend debounces then triggers reward recalculation via backend.
10
Recalculate Totals
Cart totals recalculated: subtotal, loyalty discount, tax (Numeral), delivery fee, tip.
11
Show Updated Totals
Updated breakdown: original, loyalty savings, new total. Customer reviews final amount.
12
Place Order
Customer clicks "Place Order". Payment processed via Finix/Stripe. Order created.
Process Payment
Payment captured via Finix gateway. Order record created. Triggers OrderCompleted event.
FINALIZE
13
Emit OrderCompleted
Backend emits event (SQS recommended). Loyalty service subscribes to finalize transaction.
Finalize Session
Session state → PENDING_FINALIZATION. Calls PDI finalize. On success → FINALIZED.
finalize()
PDI records final transaction: points earned, rewards redeemed. Returns confirmation ID.
14
Confirmation Page
Order confirmation with loyalty summary: points earned, rewards redeemed, new balance.
Return Summary
Backend compiles full order summary with loyalty data for confirmation display.
Points Recorded
Session finalized. Points and rewards permanently recorded. Session state = FINALIZED.
Phase 1: Identify (Steps 1-3)
Phase 2: Rewards (Steps 4-8)
Phase 3: Checkout (Steps 9-12)
Phase 4: Finalize (Steps 13-14)
Hover any step for details
Critical Path
14 Steps
4 phases, 4 service actors
PDI Calls
3 Round-trips
identify → calculateRewards → finalize
Circuit Breaker
Steps 3, 6, 13
Opossum wraps all PDI calls
Failure Mode
Graceful Bypass
Checkout succeeds without loyalty
StoreLoyaltyConfig
store_id
UUID
enabled
boolean
provider
string
client_id
string
client_secret
string
participant_id
string
site_id
string
created_at
timestamp
LoyaltySession
id
UUID
store_id
UUID
cart_id
UUID
pos_sequence_id
string
status
enum
rewards
JSONB
pdi_transaction_id
string
created_at
timestamp
LoyaltyCustomer
id
UUID
customer_id
UUID
membership_id
string
phone
string
linked_at
timestamp
LoyaltyReward
id
UUID
session_id
UUID
reward_type
string
description
text
discount_amount
decimal
applied
boolean
OrderCompleted
→
Triggered when order is placed and payment confirmed. Contains order details, customer info, and cart contents.
finalizeSession()
CustomerRegistered
→
New customer account created. Triggers automatic loyalty account creation and linking to PDI.
autoCreateAccount()
CartUpdated
→
Item added/removed or quantity changed. Re-evaluates available rewards and point eligibility.
reEvaluateRewards()
LoyaltyConfigChanged
→
Store loyalty settings modified (credentials, provider, features). Clears caches for consistency.
invalidateCache()
MemberLinked
→
Customer linked to PDI membership. Fetches initial balance and point history.
syncMemberData()
AuthTokenExpired
→
OAuth2 token from PDI has expired. Must refresh to maintain integration.
refreshToken()