Architecture Overview: UnivaPay Integration in Yoyacoo

How the UnivaPay provider structure, the Yoyacoo feature structure, and the supplier
connection/storage flows fit together. Companion to spec.md and research.md.

1. UnivaPay structure (provider side)

UnivaPay Merchant (加盟店)      ← supplier's contract with UnivaPay
└── Store (店舗)               ← where payments happen (1 merchant = 1 store in practice)
    ├── App Token (店舗トークン)  ← identifies which store processes payments
    │     ├── Token (JWT)      ← browser-side auth (inline form)
    │     ├── Secret           ← shown ONCE at creation; backend API auth (Bearer {secret}.{jwt})
    │     └── Registered domains ← inline form ONLY loads from these domains
    └── Webhooks               ← POST events: charge_finished / refund_finished / cancel_finished
          └── auth_token       ← merchant-defined value sent as Authorization header (per-store)

Key facts (see research.md for sources):

  • The app token IS the store identifier → Store ID may be auto-retrieved from the token
  • Store-type tokens only (merchant-type tokens have no practical value)
  • Test mode = separate test-mode app token; payloads carry mode: test|live

2. Yoyacoo feature structure

The feature mirrors the existing Stripe architecture 1:1, with one structural difference
(webhook verification is per-store, not a global env secret).

Layer Stripe today (existing) UnivaPay (new)
Common package — API wrapper common/src/Base/Traits/StripeConnectPayment.php UnivapayPlatform trait: verifyCredentials, createCharge, refundCharge, cancelCharge
Common package — model UserPaymentSetting (stripe_connect_id) UserPaymentUnivapay model (new table)
Supplier app (user) — settings UI/API user/app/Domains/Account/... payment settings Payment settings screen: entry point, credential form, status display, webhook guidance
Sales settings ServicePaymentMethod add univapay payment type + installment ON/OFF
Customer app — checkout customer/.../PaymentSelection + Stripe JS inline form embed (registered domain) + “Credit Card” label
Webhook receiver admin host only: /stripe/webhook etc. + env secrets shared admin-host endpoint /univapay/webhook, verified per-store auth_token
Order reference orders.stripe_* columns orders.univapay_charge_id
Side effects Order status + emails Same flow (Stripe parity)

3. How a supplier connects (MVP: manual registration, no OAuth)

Unlike Stripe Connect (Yoyacoo creates connected accounts via OAuth), the supplier is their own
UnivaPay merchant and enters credentials manually:

  1. Apply for a UnivaPay merchant account (via UnivaPay’s website, outside Yoyacoo) — there is
    no self-service signup. The supplier contacts UnivaPay (導入のご相談: univapay.com/contact/,
    or デモアカウント申請 for an instant test account: univapay.com/form-demo/), UnivaPay sends an
    application form, in-house staff perform human screening (審査), and the production merchant
    account is issued. A test account usable for staging is issued immediately, before screening
    completes. ips-support@univapay.com is existing-merchant support only — do not list it as the
    signup channel. See research.md §8.
  2. Supplier gets console access (merchant.univapay.com)
  3. Console: create a store app token (Token + Secret — copy the Secret immediately), register
    the Yoyacoo customer-site domain (mandatory for the inline form)
  4. Yoyacoo payment settings: enter Store ID, App Token, Secret
  5. Yoyacoo verifies via authenticated API call → status Connected / Unavailable / Not Connected
  6. Yoyacoo displays webhook receiver URL + generated auth_token; supplier registers the
    webhook in the UnivaPay console with triggers: charge, refund, cancel
  7. Repeat with a test-mode token for staging (FR-015)

4. Where UnivaPay information is stored

Data Location Notes
Per-supplier connection user_payment_univapays store_id, api_key, secret (encrypted), webhook_auth_token, charges_enabled, status, mode
Transaction reference orders.univapay_charge_id charge UUID for webhook matching + refund/cancel calls
Per-service enablement ServicePaymentMethod (+ installment flag) FR-005/FR-006/FR-014
Webhook replay guard idempotency record keyed by event id duplicates are guaranteed possible
Shared config (not per-supplier) .env / config/services.php API base URL, test/live mode switch, log channels

Detailed schema: data-model.md.

5. Where the supplier gets each credential

Field Source
Merchant account Apply on univapay.com — 導入のご相談 (univapay.com/contact/) → 申込フォーム → screening (審査) → account issued (not self-service)
Instant test account univapay.com/form-demo/ (issued immediately, usable before/during screening)
Store ID UnivaPay console → store settings (or auto-retrieved from token — simplification candidate)
App Token (JWT) Console → アプリトークン → 新規作成 (select 店舗)
Secret Same screen — shown only once; lost = rotate token
Domain registration Same token screen — Yoyacoo customer-site domain (staging + production separately)
Webhook URL + auth_token Generated/displayed by Yoyacoo (payment settings screen, FR-004)
Test credentials Console → test-mode app token; test card numbers under テスト課金 (or the instant demo account above)

6. Key differences vs Stripe to keep in mind

  1. No OAuth onboarding — manual credential entry; Yoyacoo cannot create stores on the
    supplier’s behalf in the MVP
  2. Webhook auth is per-supplier (auth_token stored per connection), not a single
    STRIPE_WEBHOOK_SECRET-style env value — verification looks up the connection by store_id
  3. Domain registration gates the inline form — if missing, payment silently fails to load
  4. 3-second 200 rule — acknowledge webhooks fast, process asynchronously (queue)
  5. Webhooks are best-effort — UnivaPay recommends API confirmation as the reliable path; MVP
    stays webhook-centric with a manual “check status” enhancement candidate