Files
halobestie-clone/client_app/lib/features/payment/widgets/payment_icon.dart
Ramadhan Sjamsani 1f6d8e09ae Phase 5.x payment catalog + customer-app splash/register polish
Payment catalog (Phase 5.x — see requirement/phase5-payment-catalog-plan.md):
- New tables payment_method_groups + payment_methods with seed (3 groups,
  10 methods; GoPay seeded inactive pending Xendit channel confirmation).
- payment-catalog.service.js with two-layer cache (60s in-process + 1h
  Valkey) and config:invalidate pub/sub fanout. Mutator API + casing-
  tolerant findActiveMethodByCode for downstream validation.
- App-facing GET /api/client/payment-methods returns pre-grouped JSON,
  active-only, empty groups dropped server-side.
- POST /api/client/payment-requests now validates `method` against the
  catalog (INVALID_PAYMENT_METHOD 422) and stamps
  product_metadata.preferred_payment_code (upper-cased).
- Control-center /internal/payment-{groups,methods}{,/:id,/reorder}
  endpoints (full CRUD + idempotent reorder). New Payment Catalog page
  wired into the CC nav.
- Customer app renders the catalog as collapsible groups (first expanded)
  via paymentCatalogProvider; QRIS-only hardcoded fallback on 5xx so
  checkout never hard-fails. Replaces the hardcoded _PayMethod enum.
- 10 brand SVGs (~63KB) bundled in client_app/assets/payment_icons/ from
  github.com/hafidznoor/idn-finlogos. Xendit's per-channel media-asset
  pages were planned but found decommissioned during implementation —
  switched to idn-finlogos with the standard "channels-we-accept"
  trademark posture. See assets/payment_icons/README.md for the workflow
  to add new methods.
- 16 vitest cases covering the service + cache; full backend suite green
  (162/162).

Customer-app splash + register polish:
- Splash rewritten per figma S1: warm vertical gradient, two ImageFiltered
  radial orbs, 96×96 rounded-square logo tile, "HaloBestie" + "kamu gak
  harus ngerasain ini sendirian." Self-driving navigation via context.go
  after a 2.5s post-frame timer (native Android splash burns ~1-1.5s
  before Flutter paints — 1s timer yielded near-zero visible duration).
  Router early-returns null for isSplash so it never moves us off /splash
  on its own.
- 3-page onboarding carousel removed: user clarified the new splash
  REPLACES that carousel. Dropped /onboarding route, OnboardingScreen,
  onboardingDoneProvider + gating, dead splash_{1,2,3}.png + the
  splash_chat_hebat.png Flutter asset. Phase 4 /onboarding/* subroutes
  untouched; Android-native launch_background drawable left alone.
- Register screen (login-by-phone) polished: circular pink back button +
  72×72 logo badge (same brandLogoBg pink as splash, Transform.scale 1.4
  to fill the tile). Step-dots indicator removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 23:06:46 +08:00

57 lines
1.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../core/theme/halo_tokens.dart';
/// Slugs we ship SVGs for. Keep this in sync with `assets/payment_icons/`.
/// When a brand mark is added to the bundle (drop the SVG into the asset
/// dir), add its slug here. Anything not in this set renders the placeholder.
///
/// Source: idn-finlogos (github.com/hafidznoor/idn-finlogos) — see
/// `assets/payment_icons/NOTICE_IDN_FINLOGOS.txt` for licensing terms.
const Set<String> _kBundledSlugs = {
'qris',
'ovo',
'dana',
'shopeepay',
'gopay',
'bca',
'mandiri',
'bni',
'bri',
'permata',
};
/// Renders a payment-method brand mark by slug (`payment_methods.icon`).
/// Falls back to a generic credit-card placeholder when the slug isn't
/// bundled. Slugs are kept lower-case by convention.
class PaymentIcon extends StatelessWidget {
final String? slug;
final double size;
final Color color;
const PaymentIcon({
super.key,
required this.slug,
this.size = 24,
this.color = HaloTokens.brandDark,
});
@override
Widget build(BuildContext context) {
final isBundled = slug != null && _kBundledSlugs.contains(slug);
final asset = isBundled
? 'assets/payment_icons/$slug.svg'
: 'assets/payment_icons/placeholder.svg';
// Brand SVGs ship with their canonical colors and must NOT be tinted;
// the placeholder is mono-color and DOES want the brand-dark tint.
return SvgPicture.asset(
asset,
width: size,
height: size,
colorFilter: isBundled ? null : ColorFilter.mode(color, BlendMode.srcIn),
);
}
}