Skip to content

Korean Market Conventions ​

Status: Active Last reviewed: 2026-06-30

How TwoMore handles Korea-specific market requirements across phone input, discovery, KakaoTalk sharing, payments, Alimtalk, PIPA consent, marketing push, and location-based services. These are the canonical team-readable conventions extracted from CLAUDE.md; the machine-enforced subset remains in AGENTS.md.

Phone & Discovery ​

  • Phone input: no general Korean phone-input validator exists today — the korean-phone.ts wrapper and its libphonenumber-js dependency were fully removed, and profiles.phone (a plain nullable string) has no live user-editable entry point (edit-profile-screen.tsx's own comment: "present in UpdateProfileInput but not a typical user-editable field"). The only live phone-format check in the repo is packages/app/src/config/support.ts's E164_KR_PHONE regex, which validates the single owner-configured support contact number from an env var — not general phone-input validation. Verified-identity signup (Phase 8, 본인인증) will need a real validator when it ships.
  • Discovery filters: REGION_LABELS for 17 시/도. Real addresses via Daum Postcode

Sharing & Payments ​

  • KakaoTalk share: template messages only (pre-approved on Kakao Developers console)
  • Payments: always Portone v2 WebView — never per-PG SDKs directly
  • Alimtalk: always server-side via Solapi — never from client
  • PIPA: every user-identifying event gated on consent (see signal_consent_log for push-category preferences and user_consent for primary PIPA legal consent — two separate, non-overlapping tables)
  • Push title prefix for marketing → "(광고) [브랜드명] {body}" format. Sender contact (email or phone) + 1-tap unsubscribe link required in body per 정통망법 §50(4). Annual re-confirmation of marketing consent enforced by migration 00171 cron at 02:00 KST.
  • Consent-row honesty: a user_consent row must correspond to an actual user action on an item the user was actually shown — never fabricated from matrix metadata. getRequiredConsentsForJurisdiction() (packages/app/src/domain/rules/consent-matrix.ts) returns the FULL per-jurisdiction matrix, including showAtSignup: false items (settings-only, e.g. marketing_push_night); the pure buildConsentGrants(matrix, values) in the same file is the single place that decides which items get a row — showAtSignup && checked, full stop, with no item.required shortcut. Service-essential processing (contract-basis, PIPA Art. 15(1)(4)) that is never shown as a checkbox needs disclosure in the terms/privacy policy, not a consent row — recording one anyway would be a fabricated grant for a purpose the user never saw or acted on. Marketing consent stays a separate, always-optional, actively-checked opt-in with the (광고) prefix above.
  • A required consent is not a toggle (00774, batch 63; for the legal check before launch). The purposes the catalog marks required (이용약관, 개인정보 처리방침, 국외 이전) end only with 회원 탈퇴: the server refuses revoke_consent for them and refuses a client's direct user_consent withdrawal row for them (trigger trg_user_consent_refuse_required_withdrawal, both P0001, hint consent_required; account deletion runs as the function owner and still closes every consent), so neither path can leave an active account without one.
  • Active grant = one predicate (00677): the ledger is append-only, so a grant row keeps state = 'granted' after it is closed. "Active" is private.active_consent_id(user, purpose) — granted, not past expires_at, and closed by no withdrawal/expiry row (revokes_consent_id). Every consent writer and reader goes through it; never test state = 'granted' alone. trg_user_consent_guard_insert enforces one active grant per purpose (23505) and that a closure closes the active grant; supabase/tests/consent_ledger_active_grant.test.sql pins withdraw → re-grant, expiry and account deletion.
  • Consent is collected only on consent surfaces (F05, 2026-09-29): a functional toggle (push on/off) never grants or revokes a PIPA purpose; ESLint @twomore/consent-api-only-on-consent-surfaces keeps the consent-write hooks (use-consent-mutations.ts) to the consent-management screen and the onboarding finish hook (use-onboarding-finish.ts).

Location-Based Services ​

  • 위치기반서비스사업자 신고 → if the app uses GPS for any user-visible feature (GPS check-in, "find nearby clubs"), KCC business report at lbsc.kr is mandatory before launch. 1-month grace period for 소상공인.

See also ​

  • AGENTS.md — KR-1..KR-5 (machine-enforced Korean Market subset).
  • CLAUDE.md — orchestration core + the pointer index back to this doc.

Markdown remains the source of truth. Run yarn docs:check before handoff.