Phase 0 Research: UnivaPay Card Payment Integration (MVP)
All items below were unknowns or open design questions in the spec. Each is resolved by grounding
in the official UnivaPay documentation (https://docs.univapay.com/, fetched 2026-08-03), the
existing Yoyacoo Stripe implementation (referenced via docs/956-Customer-Subscription/ and
docs/1023-grace-period-7-days/), and UnivaPay’s answers to the Yoyacoo team (recorded in the
feature request).
1. What is UnivaPay’s object hierarchy?
- Decision: Model the integration around UnivaPay’s hierarchy: Merchant (加盟店) → Store (店舗)
→ App Token → Webhook. In current UnivaPay operations one merchant registers one store, so in
practice a supplier = one store. - Evidence (
认证/docs/api/general/authentication/): “現在は1つの「加盟店」に対し複数の「店舗」を登録しない運用をしていますので、現実的には「加盟店」権限のアプリトークンは発行・活用の価値がありません。” — merchant-type app tokens are useless in practice; always use store tokens. - Implication: The app token is the store’s key — “アプリトークンとは、どの店舗で決済を行うかの判別のためのキー”. The Store ID may be auto-retrieved from the token via the Store API (GET/LIST), which is a candidate simplification for the settings form (see spec Assumptions).
2. How is authentication done?
- Decision: Backend API calls use HTTP
Authorization: Bearer {secret}.{jwt}(secret + token
concatenated with a dot). Browser/inline-form requests use the JWT only, and are additionally
authenticated by registered domain. - Evidence:
docs/api/general/authentication/— “Bearer {secret}.{jwt}”; “ウィジェットやインラインフォーム出力リクエストは、登録したドメインからであることを認証しています。管理画面で「店舗」のアプリトークンを作成する際には、ドメインを登録してください。” - Key consequences:
- The Secret is shown only once at token creation (「シークレットは、アプリトークンを作成した時だけ表示されます」). Yoyacoo’s settings screen must tell the supplier to copy it immediately; if lost, the token must be rotated.
- The Secret is NOT needed for the inline form itself, but IS required for backend API calls in scope (refunds, cancels, charge GET) — collecting it is justified.
- Domain registration is mandatory for the inline form to load. The supplier must register the Yoyacoo customer-site domain (staging and production separately) on the store app token in the UnivaPay console. This is FR-005 in the spec.
- Verification check (FR-003): feasibility confirmed — an authenticated Store GET request with the submitted credentials either succeeds (Connected) or fails with an auth error (Unavailable).
3. What is the webhook contract?
Decision: Webhooks are created by the supplier in the UnivaPay console (管理画面>ウェブフック>ウェブフック追加). Yoyacoo displays the receiver URL and an Authorization value; the supplier pastes them in, selects triggers, and saves. Webhooks can also be managed via API (GET/UPDATE) — not required for MVP.
Evidence:
docs/guide/detail/webhook/— webhooks POST to a merchant-specified URL; local URLs are not allowed; the merchant defines an arbitraryauth_token(6+ chars) that UnivaPay sends in theAuthorizationheader on every delivery.Events relevant to this feature (from the event list):
Event Trigger Status values used charge_finishedOne-time charge processing completes successful,failed,error,canceled(canceled = refunded)refund_finishedRefund completes successful,failedcancel_finishedCancel completes successful,failed,errorcharge_updatedCharge reaches authorized(not used in MVP — immediate capture)— Reliability rules (must be honored):
- Best-effort delivery — UnivaPay officially recommends not relying solely on webhooks for payment determination; the spec keeps webhook-centric operation per UnivaPay’s answer, with a manual “check status” enhancement candidate (spec Edge Cases).
- Retry policy: 2xx = no retry; 3xx = stop; 4xx/500-502 = up to 10 retries then stop; other 5xx/timeouts = up to 10 retries, never stops. Duplicates are possible → receiver MUST be idempotent (record request IDs, return 200 for replays).
- 3-second rule: receiver must return 200 within 3 seconds or UnivaPay treats it as a failure and retries → acknowledge fast, process asynchronously afterwards (queue, per constitution).
Per-store verification implication: unlike Stripe (one env-configured webhook secret), each supplier’s webhook has its own
auth_token. Yoyacoo must store it per connection and verify incoming webhooks against thestore_id+ storedauth_token. This is FR-004 and must not be overlooked in planning.Identifying the application: the payload contains
metadata(passthrough of the metadata set at transaction-token creation) — used to match the Yoyacoo application ID (FR-009/FR-010).
4. What does the inline form require?
- Decision: Use the UnivaPay inline form (インラインフォーム), which is credit-card only
(「インラインフォームはクレジットカード決済のみを行うことができ、他の決済は利用できません」). - Evidence:
docs/guide/implement/iframe/— inline form displays the card input UI in an iframe; card data never touches Yoyacoo’s servers (PCI handled by UnivaPay); card tokenization + processing happen on UnivaPay’s side; completion flows to form submit, webhook, and API retrieval. - Key consequences:
- Scoping the MVP to card-only is not just a product decision — it is what the inline form supports.
- The inline form must be served from a registered domain (see #2).
- Installments: count selection happens inside the form (per UnivaPay’s answer) — Yoyacoo only needs the ON/OFF flag (FR-014).
- 3-D Secure follows UnivaPay’s default rules (no custom control in MVP).
5. How are refunds and cancellations executed?
- Decision: Use the UnivaPay Refund API for refunds and the Cancel API for cancels
(one-time charge). Results arrive viarefund_finished/cancel_finishedwebhooks and are
reflected in application status + emails, mirroring the existing Stripe flow (FR-012/FR-013). - Evidence: API reference has
docs/api/refunds/anddocs/api/cancels/with CREATE endpoints andsuccessful/failedstatuses; webhook samples for both events exist indocs/guide/detail/webhook/. - Note: A charge whose status becomes
canceledmeans the charge was canceled/refunded — status mapping during webhook handling must treatcanceledas refunded.
6. How do test and production environments work?
- Decision: Separate app tokens per environment — test-mode tokens for staging (FR-015),
production tokens for production. Webhook payloads carry amodefield (test/live/
live_test) which should be validated to prevent cross-environment contamination. - Evidence:
docs/guide/first/setup/— 「テストモードのアプリトークンを作成することでテスト決済が可能です」; test card numbers are available in the console (テスト課金); payload samples show"mode": "test". - Consequence: The supplier must create at least two store app tokens (test + live), each with
its own domain registration and webhook configuration.
7. What is the existing Stripe pattern this feature mirrors?
- Evidence (Yoyacoo codebase docs):
- Per-supplier credentials live in
user_payment_settings.stripe_connect_id(docs/956-Customer-Subscription/Summary.md). - Stripe webhooks land on the admin host only (
/stripe/webhook,/stripe/connect,/stripe/account); customer/user apps 404 the default webhook routes (docs/956-Customer-Subscription/deployment/subscription-staging-deploy-checklist.md). - Webhook signatures verified via env-configured secrets (Cashier / custom middleware).
- API wrapper = traits in
common/src/Base/Traits/(StripeConnectPayment,StripeConnectSubscription,StripeInvoiceHelper). - Orders carry Stripe refs (
stripe_subscription_id,stripe_invoice_id);ServiceMenucarriesstripe_price_id.
- Per-supplier credentials live in
- Implication for UnivaPay: introduce a
UnivapayPlatformtrait (createCharge/refund/cancel/verifyCredentials) in the same location, a per-supplieruser_payment_univapaystable,orders.univapay_charge_id, and a shared admin-host webhook endpoint. The one structural difference: webhook verification is per-store (auth_token stored per supplier), not a global env secret.
8. How does a supplier sign up for UnivaPay?
- Decision: There is no self-service signup. The production merchant account is issued by
UnivaPay through an application + human screening (審査) process. Yoyacoo cannot create it on
the supplier’s behalf in the MVP (no OAuth/onboarding API). The Yoyacoo payment settings screen
must therefore provide an application entry point (FR-001) linking the supplier to UnivaPay’s
signup channels, then have them return with the issued credentials. - Entry channels (UnivaPay official site, univapay.com):
- 導入のご相談 / お問い合わせ: https://univapay.com/contact/
- デモアカウント申請 (instant demo/test account): https://univapay.com/form-demo/
- After contact, UnivaPay sends the formal application form (申込フォーム) with manuals; in-house
staff perform the screening (“有人での審査サポート”).
- Instant test account: UnivaPay issues an account for operational testing immediately
(審査前・審査中に運用テストができるアカウントを即時発行) — a supplier can obtain test
credentials before production screening completes. Directly supports FR-016 (staging test
tokens) without blocking on screening. - Do NOT point suppliers at
ips-support@univapay.comfor signup: per the docs’ お問い合わせ
page that mailbox is for existing merchants only (login ID + merchant ID required for
identity verification). - Notes:
- The FAQ states each separate service requires its own screening/account — a supplier running
multiple UnivaPay stores registers each connection separately. - UnivaPay’s usage guide mentions that “depending on the system integration partner, simple
settings only may suffice” — Yoyacoo establishing a 連携先 (integration partner) arrangement
with UnivaPay may simplify the supplier’s setup; worth confirming with UnivaPay during planning.
- The FAQ states each separate service requires its own screening/account — a supplier running
Open questions to resolve in planning
- Whether Store ID remains a manual input or is auto-retrieved from the app token (Store API).
- Webhook receiver endpoint placement (admin host, matching Stripe) vs. a dedicated route on the customer host.
- Whether a reconciliation job (charge GET for applications stuck in “payment processing”) ships in MVP or is deferred.