Architecture Decision Records

Why tappay-web-module is shaped the way it is — three decisions, three trade-offs.

ADR-001 — Unified Adapter Strategy over Raw Imperative Callbacks

Status: Accepted · Date: 2026-08-04

Context

TapPay's legacy JS SDK uses disparate imperative patterns: (err, result) callbacks for some wallets, single (result) objects for others, and DOM-scraping events for credit cards. Consumers must memorize a different shape per rail.

Decision

Wrap every payment rail in dedicated classes (CardPayment, GooglePayment, ApplePayment, SamsungPayment, WalletPayment) that expose standardized ES6 Promises and EventEmitter-style subscriptions: init(), renderButton(), getPrime().

Consequences

Gain: type-safe error handling, async/await pipelines, deterministic state testing.

Trade-off: minor object-wrapping overhead over raw window callback invocation.

ADR-002 — Universal Strategy Adapter for Digital Wallets (WalletPayment)

Status: Accepted · Date: 2026-08-04

Context

13 regional payment methods (JKO Pay, LINE Pay, Pi Wallet, Easy Wallet, iPASS MONEY, PXPay Plus, Plus Pay, GoGo Pay, OPPay, Pay Later, AFTEE, ATM, COD) share an identical lifecycle: getPrime(cb) followed by server-side payment-URL redirect.

Decision

Implement one parameterized bounded adapter (WalletPayment) mapped via metadata definitions (WALLET_METADATA) instead of 13 redundant concrete classes.

Consequences

Gain: massive reduction in bundle size and maintenance surface while keeping complete Taiwan & Asian rail coverage.

Trade-off: if TapPay introduces divergent init parameters for one specific wallet, that rail must be promoted to a standalone adapter class.

ADR-003 — Vanilla Web Components & CSS Variables vs. Framework Component Libraries

Status: Accepted · Date: 2026-08-04

Context

A modern checkout UX must work across heterogeneous consumer stacks — React, Vue, Next.js, Django, WordPress, and plain static HTML.

Decision

Zero-dependency native JS DOM assembly styled purely through CSS Custom Properties (--tp-*), avoiding Tailwind runtime requirements and framework bundlers.

Consequences

Gain: universal compatibility, instant startup, zero production dependencies, dynamic runtime dark/light theme switching.

Trade-off: UI components cannot consume application-level React/Vue context engines directly without a slim wrapper.

Trade-Off Analysis Matrix

Design ConcernSelected ApproachAlternativeWhyTrade-Off
SDK Dependency Dynamic async injection via loadTapPaySDK Hardcoded <script src> tags No global script-block delay; SRI integrity support Requires async await client.ready()
Error Handling Normalized typed TapPayError objects Raw SDK error codes & strings Standardized error classification, easy debugging Hides vendor console verbosity by default
State Management Event-driven observable hooks (statuschange) Polling DOM states / React state coupling Decouples DOM form rendering from domain validation Requires explicit listener cleanup in SPAs