- Backend wraps idn-finlogos npm at /assets/payment-icons/<slug>.svg with
1y immutable cache. Mobile drops bundled SVGs (only placeholder remains)
and fetches via flutter_cache_manager. payment_methods.icon is now a
CSV of slugs; catalog emits icon_urls[]. CARDS tile renders Visa + MC +
JCB side by side.
- Per-method min/max amount bounds (BIGINT, nullable). Picker greys out
out-of-range tiles with subtitle; backend gates with INVALID_PAYMENT_AMOUNT
(422). Defense in depth against stale-catalog clients.
- Xendit channel codes corrected from authoritative docs
(BCA_VA -> BCA_VIRTUAL_ACCOUNT, CREDIT_CARD -> CARDS, ovo -> ovo-new,
shopeepay -> shopee-pay, ...). 18 methods x 5 groups seeded with
Xendit-published per-channel min/max.
- Re-runnable seed (ON CONFLICT DO NOTHING on payment_code + new unique
index on group name). Operator CC edits never clobbered across re-runs.
One-shot reset + inspect scripts under backend/.dev/.
- Customer redirect HTML pages at /payment/return/{success,failure},
brand-styled with "Buka HaloBestie" CTA firing halobestie:// deeplink.
URL scheme registered on Android (intent-filter w/ BROWSABLE on
MainActivity) and iOS (CFBundleURLTypes). Waiting-payment poller still
owns confirmation; deeplink just brings the activity to foreground.
- Control center payment-catalog page: min/max inputs + columns. Other
CC pages restyled with new theme tokens (separate work, bundled here).
169/169 backend tests pass. See requirement/phase5-payment-revamp-2026-05-27.md
for the full revamp doc. Stage 8 (E2E) still pending: webhook URL routing
decision + two client_app follow-ups (legacy /chat/request removal,
extension Custom Tab).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
62 lines
3.6 KiB
JavaScript
62 lines
3.6 KiB
JavaScript
import Fastify from 'fastify'
|
|
import cors from '@fastify/cors'
|
|
import sensible from '@fastify/sensible'
|
|
import { sharedAuthRoutes } from './routes/public/shared.auth.routes.js'
|
|
import { clientAuthRoutes } from './routes/public/client.auth.routes.js'
|
|
import { mitraAuthRoutes } from './routes/public/mitra.auth.routes.js'
|
|
import { sharedConfigRoutes } from './routes/public/shared.config.routes.js'
|
|
import { sharedAuthProvidersRoutes } from './routes/public/shared.auth-providers.routes.js'
|
|
import { mitraStatusRoutes } from './routes/public/mitra.status.routes.js'
|
|
import { mitraChatRoutes } from './routes/public/mitra.chat.routes.js'
|
|
import { clientChatRoutes } from './routes/public/client.chat.routes.js'
|
|
import { clientPaymentRoutes } from './routes/public/client.payment.routes.js'
|
|
import { clientPaymentMethodsRoutes } from './routes/public/client.payment-methods.routes.js'
|
|
import { clientMitraAvailabilityRoutes } from './routes/public/client.mitra-availability.routes.js'
|
|
import { publicBestieAvailabilityRoutes } from './routes/public/public.bestie-availability.routes.js'
|
|
import { clientOnboardingRoutes } from './routes/public/client.onboarding.routes.js'
|
|
import { sharedSupportRoutes } from './routes/public/shared.support.routes.js'
|
|
import { sharedChatRoutes } from './routes/public/shared.chat.routes.js'
|
|
import { paymentWebhookRoutes } from './routes/public/shared.payment-webhooks.routes.js'
|
|
import { paymentIconRoutes } from './routes/public/shared.payment-icons.routes.js'
|
|
import { paymentReturnRoutes } from './routes/public/payment-return.routes.js'
|
|
import { errorHandler } from './plugins/error-handler.js'
|
|
import { registerWebSocketPlugin, registerWebSocketRoute } from './plugins/websocket.js'
|
|
|
|
export const buildPublicApp = async () => {
|
|
const app = Fastify({ logger: true, trustProxy: true })
|
|
|
|
await app.register(cors, { origin: true })
|
|
await app.register(sensible)
|
|
await registerWebSocketPlugin(app)
|
|
app.setErrorHandler(errorHandler)
|
|
|
|
app.register(sharedAuthRoutes, { prefix: '/api/shared/auth' })
|
|
app.register(sharedConfigRoutes, { prefix: '/api/shared/config' })
|
|
app.register(sharedAuthProvidersRoutes, { prefix: '/api/shared/auth-providers' })
|
|
app.register(sharedChatRoutes, { prefix: '/api/shared' })
|
|
app.register(clientAuthRoutes, { prefix: '/api/client/auth' })
|
|
app.register(mitraAuthRoutes, { prefix: '/api/mitra/auth' })
|
|
app.register(mitraStatusRoutes, { prefix: '/api/mitra/status' })
|
|
app.register(mitraChatRoutes, { prefix: '/api/mitra/chat-requests' })
|
|
app.register(clientChatRoutes, { prefix: '/api/client/chat' })
|
|
app.register(clientPaymentRoutes, { prefix: '/api/client/payment-requests' })
|
|
app.register(clientPaymentMethodsRoutes, { prefix: '/api/client/payment-methods' })
|
|
app.register(clientMitraAvailabilityRoutes, { prefix: '/api/client/mitra-availability' })
|
|
app.register(publicBestieAvailabilityRoutes, { prefix: '/api/public/bestie' })
|
|
// Onboarding-state stays client-only (anonymous customer flow). Support
|
|
// handles are shared — both client and mitra apps link the same WA/TG.
|
|
app.register(clientOnboardingRoutes, { prefix: '/api/client' })
|
|
app.register(sharedSupportRoutes, { prefix: '/api/shared' })
|
|
// Payment provider webhooks. Public + token-authed via x-callback-token.
|
|
app.register(paymentWebhookRoutes, { prefix: '/api/shared/payment' })
|
|
// Brand-mark SVGs from idn-finlogos. Public, 1-year immutable cache.
|
|
app.register(paymentIconRoutes, { prefix: '/assets' })
|
|
// Xendit customer-redirect HTML pages. Public, no auth.
|
|
app.register(paymentReturnRoutes, { prefix: '/payment' })
|
|
|
|
// WebSocket route (registered at app level, not prefixed)
|
|
registerWebSocketRoute(app)
|
|
|
|
return app
|
|
}
|