@hello-pay/react 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.cjs +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -6
- package/dist/index.d.ts +124 -6
- package/dist/index.js +44 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
CardExpiryElement,
|
|
22
22
|
CardCvcElement,
|
|
23
23
|
CardholderNameElement,
|
|
24
|
+
CardPasswordElement,
|
|
24
25
|
useHelloPay,
|
|
25
26
|
useElements,
|
|
26
27
|
} from '@hello-pay/react';
|
|
@@ -71,6 +72,8 @@ function PaymentForm() {
|
|
|
71
72
|
<CardExpiryElement />
|
|
72
73
|
<CardCvcElement />
|
|
73
74
|
<CardholderNameElement />
|
|
75
|
+
{/* 韓国発行カードのみ: 暗証番号の先頭 2 桁 */}
|
|
76
|
+
<CardPasswordElement />
|
|
74
77
|
<button type="submit">支払う</button>
|
|
75
78
|
</form>
|
|
76
79
|
);
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
CardCvcElement: () => CardCvcElement,
|
|
25
25
|
CardExpiryElement: () => CardExpiryElement,
|
|
26
26
|
CardNumberElement: () => CardNumberElement,
|
|
27
|
+
CardPasswordElement: () => CardPasswordElement,
|
|
27
28
|
CardholderNameElement: () => CardholderNameElement,
|
|
28
29
|
Elements: () => Elements,
|
|
29
30
|
loadHelloPay: () => loadHelloPay,
|
|
@@ -145,7 +146,8 @@ var EMPTY_CONTEXT = {
|
|
|
145
146
|
elements: null
|
|
146
147
|
};
|
|
147
148
|
var ElementsContext = (0, import_react.createContext)(EMPTY_CONTEXT);
|
|
148
|
-
function Elements({ hellopay: hellopayProp, children }) {
|
|
149
|
+
function Elements({ hellopay: hellopayProp, options, children }) {
|
|
150
|
+
const sessionClientSecret = options?.sessionClientSecret;
|
|
149
151
|
const [ctx, setCtx] = (0, import_react.useState)(EMPTY_CONTEXT);
|
|
150
152
|
(0, import_react.useEffect)(() => {
|
|
151
153
|
let cancelled = false;
|
|
@@ -156,7 +158,10 @@ function Elements({ hellopay: hellopayProp, children }) {
|
|
|
156
158
|
setCtx(EMPTY_CONTEXT);
|
|
157
159
|
return;
|
|
158
160
|
}
|
|
159
|
-
setCtx({
|
|
161
|
+
setCtx({
|
|
162
|
+
hellopay: sdk,
|
|
163
|
+
elements: sdk.elements(sessionClientSecret ? { sessionClientSecret } : void 0)
|
|
164
|
+
});
|
|
160
165
|
}).catch(() => {
|
|
161
166
|
if (cancelled) return;
|
|
162
167
|
setCtx(EMPTY_CONTEXT);
|
|
@@ -164,7 +169,7 @@ function Elements({ hellopay: hellopayProp, children }) {
|
|
|
164
169
|
return () => {
|
|
165
170
|
cancelled = true;
|
|
166
171
|
};
|
|
167
|
-
}, [hellopayProp]);
|
|
172
|
+
}, [hellopayProp, sessionClientSecret]);
|
|
168
173
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ElementsContext.Provider, { value: ctx, children });
|
|
169
174
|
}
|
|
170
175
|
|
|
@@ -182,6 +187,24 @@ function useElements() {
|
|
|
182
187
|
|
|
183
188
|
// src/ElementComponents.tsx
|
|
184
189
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
190
|
+
var PROBE_PX = 1e5;
|
|
191
|
+
function contentSizedAxes(container) {
|
|
192
|
+
if (container.getClientRects().length === 0) {
|
|
193
|
+
const computed = getComputedStyle(container);
|
|
194
|
+
const isAuto = (v) => v === "" || v === "auto";
|
|
195
|
+
return { width: isAuto(computed.width), height: isAuto(computed.height) };
|
|
196
|
+
}
|
|
197
|
+
const before = { width: container.offsetWidth, height: container.offsetHeight };
|
|
198
|
+
const probe = container.ownerDocument.createElement("div");
|
|
199
|
+
probe.style.cssText = `width: ${PROBE_PX}px; height: ${PROBE_PX}px;`;
|
|
200
|
+
container.appendChild(probe);
|
|
201
|
+
const grows = {
|
|
202
|
+
width: container.offsetWidth > before.width,
|
|
203
|
+
height: container.offsetHeight > before.height
|
|
204
|
+
};
|
|
205
|
+
probe.remove();
|
|
206
|
+
return grows;
|
|
207
|
+
}
|
|
185
208
|
function createElementComponent(type) {
|
|
186
209
|
function ElementComponent({
|
|
187
210
|
options,
|
|
@@ -194,12 +217,19 @@ function createElementComponent(type) {
|
|
|
194
217
|
}) {
|
|
195
218
|
const elements = useElements();
|
|
196
219
|
const containerRef = (0, import_react3.useRef)(null);
|
|
220
|
+
const [fill, setFill] = (0, import_react3.useState)({ width: false, height: false });
|
|
197
221
|
const handlersRef = (0, import_react3.useRef)({ onReady, onChange, onFocus, onBlur });
|
|
198
222
|
handlersRef.current = { onReady, onChange, onFocus, onBlur };
|
|
199
223
|
(0, import_react3.useEffect)(() => {
|
|
200
|
-
|
|
224
|
+
const container = containerRef.current;
|
|
225
|
+
if (!elements || !container) return;
|
|
226
|
+
const unset = contentSizedAxes(container);
|
|
227
|
+
setFill((prev) => ({
|
|
228
|
+
width: prev.width || unset.width,
|
|
229
|
+
height: prev.height || unset.height
|
|
230
|
+
}));
|
|
201
231
|
const element = elements.create(type, options);
|
|
202
|
-
element.mount(
|
|
232
|
+
element.mount(container);
|
|
203
233
|
const unsubs = [
|
|
204
234
|
element.on("ready", (e) => handlersRef.current.onReady?.(e)),
|
|
205
235
|
element.on("change", (e) => handlersRef.current.onChange?.(e)),
|
|
@@ -216,7 +246,11 @@ function createElementComponent(type) {
|
|
|
216
246
|
{
|
|
217
247
|
ref: containerRef,
|
|
218
248
|
className,
|
|
219
|
-
style: {
|
|
249
|
+
style: {
|
|
250
|
+
...fill.width && { width: "100%" },
|
|
251
|
+
...fill.height && { height: "100%" },
|
|
252
|
+
...style
|
|
253
|
+
}
|
|
220
254
|
}
|
|
221
255
|
);
|
|
222
256
|
}
|
|
@@ -228,6 +262,7 @@ var CardNumberElement = createElementComponent("cardNumber");
|
|
|
228
262
|
var CardExpiryElement = createElementComponent("cardExpiry");
|
|
229
263
|
var CardCvcElement = createElementComponent("cardCvc");
|
|
230
264
|
var CardholderNameElement = createElementComponent("cardholderName");
|
|
265
|
+
var CardPasswordElement = createElementComponent("cardPassword");
|
|
231
266
|
|
|
232
267
|
// src/ApplePayButton.tsx
|
|
233
268
|
var import_react4 = require("react");
|
|
@@ -297,6 +332,7 @@ ApplePayButton.displayName = "HelloPay(ApplePayButton)";
|
|
|
297
332
|
CardCvcElement,
|
|
298
333
|
CardExpiryElement,
|
|
299
334
|
CardNumberElement,
|
|
335
|
+
CardPasswordElement,
|
|
300
336
|
CardholderNameElement,
|
|
301
337
|
Elements,
|
|
302
338
|
loadHelloPay,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/loadHelloPay.ts","../src/Elements.tsx","../src/ElementComponents.tsx","../src/hooks.ts","../src/ApplePayButton.tsx"],"sourcesContent":["export { loadHelloPay } from './loadHelloPay';\nexport type { LoadHelloPayOptions } from './loadHelloPay';\n\nexport { Elements } from './Elements';\nexport type { ElementsProps } from './Elements';\n\nexport {\n CardNumberElement,\n CardExpiryElement,\n CardCvcElement,\n CardholderNameElement,\n} from './ElementComponents';\nexport type { ElementProps } from './ElementComponents';\n\nexport { ApplePayButton } from './ApplePayButton';\nexport type { ApplePayButtonProps } from './ApplePayButton';\n\nexport { useHelloPay, useElements } from './hooks';\n\nexport type {\n HelloPay,\n Elements as ElementsSession,\n Element,\n ElementType,\n ElementOptions,\n ElementStyle,\n ElementChangeEvent,\n ElementFocusEvent,\n ElementReadyEvent,\n TokenCard,\n HelloPayError,\n CreateTokenResult,\n TokenProvider,\n ApplePayCard,\n ApplePayOptions,\n ApplePayResult,\n ApplePayError,\n ApplePayCancelError,\n Unsubscribe,\n} from './types';\n","/**\n * HelloPay SDK ローダー。\n *\n * HelloPay 管理下のドメイン(js.hello-pay.app 等)から SDK を動的ロードし、\n * `window.HelloPay` ファクトリを解決する。\n *\n * - 同一 sdkUrl への呼び出しは script tag を 1 回だけ挿入(dedup)\n * - 異なる sdkUrl は独立したロードを維持(E2E / ローカル切り替え対応)\n * - SSR 環境では Promise<null> を解決(加盟店側で null チェック)\n * - 一時的な失敗はキャッシュせず次回呼び出しで再試行可能\n */\n\nimport type { HelloPay, HelloPayFactory } from './types';\n\n// SDK 本体は test / live どちらの publishable key でも同一ドメイン\n// (js.hello-pay.app) から配信する。test / live mode の切り替えは SDK 内部で\n// publishable key の prefix から解決し、test mode では接続先を test 環境\n// (*.stg.hello-pay.app) に向ける(hellopay.js と同じ方式)。\nconst DEFAULT_SDK_URL = 'https://js.hello-pay.app/v1/hellopay-elements.js';\n\n// sdkUrl 単位で script ロード Promise をキャッシュ。\n// loadPromise を単一変数にするとすべての URL で共有されてしまい、\n// 異なる sdkUrl(E2E / ローカル開発)が誤って同一ロードを共有する。\nconst scriptPromiseCache = new Map<string, Promise<HelloPayFactory | null>>();\n\n// publicKey + locale + sdkUrl の組み合わせごとに `HelloPaySDK` インスタンスをキャッシュ。\n// 加盟店が誤って inline で loadHelloPay を呼び出した場合でも同一 SDK インスタンスが\n// 返るようにして、Elements セッションの BroadcastChannel session ID ミスマッチを防ぐ。\nconst sdkCache = new Map<string, Promise<HelloPay | null>>();\n\n/** テスト専用: module-scope のキャッシュをすべて初期状態に戻す。 */\nexport function __resetLoadPromiseForTest(): void {\n scriptPromiseCache.clear();\n sdkCache.clear();\n}\n\nfunction normalizeSdkUrl(url: string): string {\n if (typeof window === 'undefined') return url;\n\n try {\n return new URL(url, window.location.href).href;\n } catch {\n return url;\n }\n}\n\nfunction normalizeLocale(locale?: string): string {\n return locale || 'ja';\n}\n\nfunction loadScript(url: string): Promise<HelloPayFactory | null> {\n if (typeof window === 'undefined') return Promise.resolve(null);\n\n // URL を正規化してプロトコル省略形などを統一する。\n // テスト / SSR 環境では window.location が http URL でない場合があるため try-catch で保護。\n const normalizedUrl = normalizeSdkUrl(url);\n\n return new Promise((resolve, reject) => {\n // script は URL 単位で管理する。異なる URL の script を削除せず共存させることで、\n // E2E / ローカル開発で異なる sdkUrl が並列で呼ばれても Promise が宙吊りにならない。\n // 状態は dataset.hellopayState に \"loading\" | \"loaded\" | \"error\" で刻む。\n const managedScripts = Array.from(\n document.querySelectorAll<HTMLScriptElement>('script[data-hellopay-sdk]'),\n );\n\n const existing = managedScripts.find((script) => {\n const rawUrl = script.dataset.hellopayUrl || script.src;\n if (!rawUrl) return false;\n try {\n return new URL(rawUrl, window.location.href).href === normalizedUrl;\n } catch {\n return rawUrl === normalizedUrl;\n }\n });\n\n if (existing) {\n const state = existing.dataset.hellopayState;\n if (state === 'loaded') {\n resolve(window.HelloPay ?? null);\n return;\n }\n if (state === 'loading') {\n existing.addEventListener('load', () => resolve(window.HelloPay ?? null), {\n once: true,\n });\n existing.addEventListener(\n 'error',\n () => reject(new Error('HelloPay SDK failed to load')),\n { once: true },\n );\n return;\n }\n // state === \"error\" or undefined: 失敗済み or 未知の状態なので作り直す\n existing.remove();\n }\n\n const script = document.createElement('script');\n script.src = normalizedUrl;\n script.async = true;\n script.dataset.hellopaySdk = 'true';\n script.dataset.hellopayUrl = normalizedUrl; // URL 比較用\n script.dataset.hellopayState = 'loading';\n script.addEventListener(\n 'load',\n () => {\n if (!window.HelloPay) {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK loaded but window.HelloPay is missing'));\n return;\n }\n script.dataset.hellopayState = 'loaded';\n resolve(window.HelloPay);\n },\n { once: true },\n );\n script.addEventListener(\n 'error',\n () => {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK failed to load'));\n },\n { once: true },\n );\n document.head.appendChild(script);\n });\n}\n\nexport interface LoadHelloPayOptions {\n /** SDK 配信 URL(ローカル開発・E2E で差し替える用。省略時は js.hello-pay.app から配信し、test/live は SDK 内部で key prefix から解決) */\n sdkUrl?: string;\n locale?: string;\n}\n\n/**\n * HelloPay SDK をロードして初期化する。\n * モジュールスコープで 1 回だけ呼び、返り値の Promise を `<Elements>` に渡す。\n */\nexport function loadHelloPay(\n publicKey: string,\n options: LoadHelloPayOptions = {},\n): Promise<HelloPay | null> {\n const sdkUrl = normalizeSdkUrl(options.sdkUrl ?? DEFAULT_SDK_URL);\n const locale = normalizeLocale(options.locale);\n const cacheKey = `${publicKey}::${locale}::${sdkUrl}`;\n const cached = sdkCache.get(cacheKey);\n if (cached) return cached;\n\n let scriptPromise = scriptPromiseCache.get(sdkUrl);\n if (!scriptPromise) {\n scriptPromise = loadScript(sdkUrl).catch((err) => {\n // 一時的なロード失敗はキャッシュせず次回呼び出しで再試行可能にする\n scriptPromiseCache.delete(sdkUrl);\n throw err;\n });\n scriptPromiseCache.set(sdkUrl, scriptPromise);\n }\n\n const sdkPromise = scriptPromise\n .then((factory) => {\n if (!factory) return null; // SSR\n return factory(publicKey, { locale: options.locale });\n })\n .catch((err) => {\n // SDK インスタンス生成失敗は sdkCache から退避して次回呼び出しで再試行可能にする\n sdkCache.delete(cacheKey);\n throw err;\n });\n sdkCache.set(cacheKey, sdkPromise);\n return sdkPromise;\n}\n","/**\n * <Elements> Provider.\n *\n * loadHelloPay() の Promise を受け取り、SDK インスタンスと Elements セッションを\n * 子孫に Context 経由で供給する。\n */\n\nimport { createContext, useEffect, useState, type ReactNode } from 'react';\nimport type { HelloPay, Elements as ElementsSession } from './types';\n\nexport interface ElementsContextValue {\n hellopay: HelloPay | null;\n elements: ElementsSession | null;\n}\n\nconst EMPTY_CONTEXT: ElementsContextValue = {\n hellopay: null,\n elements: null,\n};\n\nexport const ElementsContext = createContext<ElementsContextValue>(EMPTY_CONTEXT);\n\nexport interface ElementsProps {\n /** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */\n hellopay: Promise<HelloPay | null> | HelloPay | null;\n children: ReactNode;\n}\n\nexport function Elements({ hellopay: hellopayProp, children }: ElementsProps) {\n const [ctx, setCtx] = useState<ElementsContextValue>(EMPTY_CONTEXT);\n\n useEffect(() => {\n let cancelled = false;\n setCtx(EMPTY_CONTEXT);\n\n Promise.resolve(hellopayProp)\n .then((sdk) => {\n if (cancelled) return;\n if (!sdk) {\n setCtx(EMPTY_CONTEXT);\n return;\n }\n setCtx({ hellopay: sdk, elements: sdk.elements() });\n })\n .catch(() => {\n // SDK ロード失敗時は null のまま(unhandled rejection を防ぐ)\n if (cancelled) return;\n setCtx(EMPTY_CONTEXT);\n });\n\n return () => {\n cancelled = true;\n };\n }, [hellopayProp]);\n\n return <ElementsContext.Provider value={ctx}>{children}</ElementsContext.Provider>;\n}\n","/**\n * Element コンポーネント。\n *\n * 各コンポーネントは 1 つの `<div>` コンテナを描画するだけで、\n * 実体(カード情報が入る iframe)はコア SDK が差し込む。\n *\n * React Strict Mode の二重 mount 対応:\n * - effect の cleanup で element.unmount() を必ず呼ぶ\n * - unmount 後は Elements._elements 配列から除外される(コア SDK 側で対応済)\n * - 結果として Strict Mode で effect が再実行されても古い iframe は残らない\n */\n\nimport { useEffect, useRef, type CSSProperties } from 'react';\nimport { useElements } from './hooks';\nimport type {\n ElementType,\n ElementOptions,\n ElementChangeEvent,\n ElementFocusEvent,\n ElementReadyEvent,\n} from './types';\n\nexport interface ElementProps {\n options?: ElementOptions;\n className?: string;\n style?: CSSProperties;\n onReady?: (e: ElementReadyEvent) => void;\n onChange?: (e: ElementChangeEvent) => void;\n onFocus?: (e: ElementFocusEvent) => void;\n onBlur?: (e: ElementFocusEvent) => void;\n}\n\n/**\n * 指定 type の Element を管理する React コンポーネントを生成する。\n */\nfunction createElementComponent(type: ElementType) {\n function ElementComponent({\n options,\n className,\n style,\n onReady,\n onChange,\n onFocus,\n onBlur,\n }: ElementProps) {\n const elements = useElements();\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n // 最新のハンドラを ref で保持(effect を毎回 re-run させないため)\n const handlersRef = useRef({ onReady, onChange, onFocus, onBlur });\n handlersRef.current = { onReady, onChange, onFocus, onBlur };\n\n useEffect(() => {\n if (!elements || !containerRef.current) return;\n\n const element = elements.create(type, options);\n element.mount(containerRef.current);\n\n const unsubs = [\n element.on('ready', (e) => handlersRef.current.onReady?.(e)),\n element.on('change', (e) => handlersRef.current.onChange?.(e)),\n element.on('focus', (e) => handlersRef.current.onFocus?.(e)),\n element.on('blur', (e) => handlersRef.current.onBlur?.(e)),\n ];\n\n return () => {\n unsubs.forEach((u) => u());\n element.unmount();\n };\n // options はマウント時にしか反映しない方針(Stripe と同じ挙動)。\n // 動的に style を変えたい場合は後続 PR で element.update() を追加。\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [elements]);\n\n // 親コンテナ(加盟店が指定した `.element-container` 等)の高さを\n // iframe に伝播させるため、wrapper は親のサイズを塗りつぶす。\n // style prop で上書き可能。\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n }\n ElementComponent.displayName = `HelloPay(${type})`;\n // getElement(CardNumberElement) のようにコンポーネント自身を型指定子として使えるよう、\n // 静的プロパティに type を生やしておく\n ElementComponent.__elementType = type as ElementType;\n return ElementComponent;\n}\n\nexport const CardNumberElement = createElementComponent('cardNumber');\nexport const CardExpiryElement = createElementComponent('cardExpiry');\nexport const CardCvcElement = createElementComponent('cardCvc');\nexport const CardholderNameElement = createElementComponent('cardholderName');\n\nexport type ElementComponent = typeof CardNumberElement;\n","/**\n * React hooks.\n *\n * useHelloPay / useElements は <Elements> Provider の Context から\n * SDK インスタンスと Elements セッションを取り出す。\n * SDK ロード完了までは null を返すので、ハンドラ内で null チェックする。\n */\n\nimport { useContext } from 'react';\nimport { ElementsContext } from './Elements';\nimport type { HelloPay, Elements } from './types';\n\nexport function useHelloPay(): HelloPay | null {\n return useContext(ElementsContext).hellopay;\n}\n\nexport function useElements(): Elements | null {\n return useContext(ElementsContext).elements;\n}\n","/**\n * Apple Pay ボタン。\n *\n * `useHelloPay()` から SDK を取り、対応端末(Safari + Apple Pay 利用可能)でのみ\n * Apple 公式スタイルのボタンを描画する。非対応端末では何も描画しない。\n *\n * click で `hellopay.applePay()` を同期呼び出しし(`new ApplePaySession` の\n * user gesture 要件のため)、結果を `onToken` / `onError` / `onCancel` に振り分ける。\n * SDK は Apple Pay トークンを返すだけで課金はしないので、加盟店は `onToken` で\n * 受けたトークンを自社バックエンドから決済 API に送って課金する。\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useHelloPay } from './hooks';\nimport type { ApplePayCard, ApplePayOptions, HelloPayError } from './types';\n\nexport interface ApplePayButtonProps {\n options: ApplePayOptions;\n /** トークン取得成功時。トークンを自社バックエンド経由で決済 API に送る。 */\n onToken: (token: string, card: ApplePayCard) => void;\n /** validation / network エラー時。 */\n onError?: (error: HelloPayError) => void;\n /** ユーザーが Apple Pay シートを閉じた時。 */\n onCancel?: () => void;\n /** Apple Pay ボタンの色。既定 `black`。 */\n buttonStyle?: 'black' | 'white' | 'white-outline';\n /** Apple Pay ボタンの種別(`plain` / `buy` / `pay` 等)。既定 `plain`。 */\n buttonType?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function ApplePayButton({\n options,\n onToken,\n onError,\n onCancel,\n buttonStyle = 'black',\n buttonType = 'plain',\n className,\n style,\n}: ApplePayButtonProps) {\n const hellopay = useHelloPay();\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n const [supported, setSupported] = useState(false);\n\n // canMakeApplePayPayments は ApplePaySession 依存で client-only のため effect で判定。\n useEffect(() => {\n setSupported(!!hellopay && hellopay.canMakeApplePayPayments());\n }, [hellopay]);\n\n // Apple Pay ボタンの見た目はベンダー CSS プロパティで指定する。React の style 型に\n // 無いため ref 経由で setProperty する。\n useEffect(() => {\n const el = buttonRef.current;\n if (!el) return;\n el.style.setProperty('-webkit-appearance', '-apple-pay-button');\n el.style.setProperty('-apple-pay-button-type', buttonType);\n el.style.setProperty('-apple-pay-button-style', buttonStyle);\n }, [supported, buttonType, buttonStyle]);\n\n // 最新のハンドラ / options を ref に保持し、click ハンドラを毎回作り直さない。\n const latest = useRef({ options, onToken, onError, onCancel, hellopay });\n latest.current = { options, onToken, onError, onCancel, hellopay };\n\n if (!supported) return null;\n\n const handleClick = () => {\n const current = latest.current;\n if (!current.hellopay) return;\n // user gesture 要件のため click ハンドラから同期呼び出しする。\n current.hellopay\n .applePay(current.options)\n .then((result) => {\n if (result.error) {\n if (result.error.type === 'cancel') {\n current.onCancel?.();\n } else {\n current.onError?.(result.error);\n }\n return;\n }\n current.onToken(result.token, result.card);\n })\n .catch((err: unknown) => {\n current.onError?.({\n type: 'config_error',\n code: 'apple_pay_error',\n message: err instanceof Error ? err.message : 'Apple Pay error',\n });\n });\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleClick}\n className={className}\n style={{ width: '100%', height: '44px', cursor: 'pointer', ...style }}\n />\n );\n}\n\nApplePayButton.displayName = 'HelloPay(ApplePayButton)';\n"],"mappings":"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","names":["script","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/loadHelloPay.ts","../src/Elements.tsx","../src/ElementComponents.tsx","../src/hooks.ts","../src/ApplePayButton.tsx"],"sourcesContent":["export { loadHelloPay } from './loadHelloPay';\nexport type { LoadHelloPayOptions } from './loadHelloPay';\n\nexport { Elements } from './Elements';\nexport type { ElementsProps } from './Elements';\n\nexport {\n CardNumberElement,\n CardExpiryElement,\n CardCvcElement,\n CardholderNameElement,\n CardPasswordElement,\n} from './ElementComponents';\nexport type { ElementProps } from './ElementComponents';\n\nexport { ApplePayButton } from './ApplePayButton';\nexport type { ApplePayButtonProps } from './ApplePayButton';\n\nexport { useHelloPay, useElements } from './hooks';\n\nexport type {\n HelloPay,\n Elements as ElementsSession,\n Element,\n ElementType,\n ElementOptions,\n ElementStyle,\n ElementChangeEvent,\n CardNumberChangeEvent,\n OtherElementChangeEvent,\n IssuerCountryResult,\n ElementFocusEvent,\n ElementReadyEvent,\n ElementsOptions,\n TokenCard,\n HelloPayError,\n CreateTokenResult,\n TokenProvider,\n ApplePayCard,\n ApplePayOptions,\n ApplePayResult,\n ApplePayError,\n ApplePayCancelError,\n Unsubscribe,\n} from './types';\n","/**\n * HelloPay SDK ローダー。\n *\n * HelloPay 管理下のドメイン(js.hello-pay.app 等)から SDK を動的ロードし、\n * `window.HelloPay` ファクトリを解決する。\n *\n * - 同一 sdkUrl への呼び出しは script tag を 1 回だけ挿入(dedup)\n * - 異なる sdkUrl は独立したロードを維持(E2E / ローカル切り替え対応)\n * - SSR 環境では Promise<null> を解決(加盟店側で null チェック)\n * - 一時的な失敗はキャッシュせず次回呼び出しで再試行可能\n */\n\nimport type { HelloPay, HelloPayFactory } from './types';\n\n// SDK 本体は test / live どちらの publishable key でも同一ドメイン\n// (js.hello-pay.app) から配信する。test / live mode の切り替えは SDK 内部で\n// publishable key の prefix から解決し、test mode では接続先を test 環境\n// (*.stg.hello-pay.app) に向ける(hellopay.js と同じ方式)。\nconst DEFAULT_SDK_URL = 'https://js.hello-pay.app/v1/hellopay-elements.js';\n\n// sdkUrl 単位で script ロード Promise をキャッシュ。\n// loadPromise を単一変数にするとすべての URL で共有されてしまい、\n// 異なる sdkUrl(E2E / ローカル開発)が誤って同一ロードを共有する。\nconst scriptPromiseCache = new Map<string, Promise<HelloPayFactory | null>>();\n\n// publicKey + locale + sdkUrl の組み合わせごとに `HelloPaySDK` インスタンスをキャッシュ。\n// 加盟店が誤って inline で loadHelloPay を呼び出した場合でも同一 SDK インスタンスが\n// 返るようにして、Elements セッションの BroadcastChannel session ID ミスマッチを防ぐ。\nconst sdkCache = new Map<string, Promise<HelloPay | null>>();\n\n/** テスト専用: module-scope のキャッシュをすべて初期状態に戻す。 */\nexport function __resetLoadPromiseForTest(): void {\n scriptPromiseCache.clear();\n sdkCache.clear();\n}\n\nfunction normalizeSdkUrl(url: string): string {\n if (typeof window === 'undefined') return url;\n\n try {\n return new URL(url, window.location.href).href;\n } catch {\n return url;\n }\n}\n\nfunction normalizeLocale(locale?: string): string {\n return locale || 'ja';\n}\n\nfunction loadScript(url: string): Promise<HelloPayFactory | null> {\n if (typeof window === 'undefined') return Promise.resolve(null);\n\n // URL を正規化してプロトコル省略形などを統一する。\n // テスト / SSR 環境では window.location が http URL でない場合があるため try-catch で保護。\n const normalizedUrl = normalizeSdkUrl(url);\n\n return new Promise((resolve, reject) => {\n // script は URL 単位で管理する。異なる URL の script を削除せず共存させることで、\n // E2E / ローカル開発で異なる sdkUrl が並列で呼ばれても Promise が宙吊りにならない。\n // 状態は dataset.hellopayState に \"loading\" | \"loaded\" | \"error\" で刻む。\n const managedScripts = Array.from(\n document.querySelectorAll<HTMLScriptElement>('script[data-hellopay-sdk]'),\n );\n\n const existing = managedScripts.find((script) => {\n const rawUrl = script.dataset.hellopayUrl || script.src;\n if (!rawUrl) return false;\n try {\n return new URL(rawUrl, window.location.href).href === normalizedUrl;\n } catch {\n return rawUrl === normalizedUrl;\n }\n });\n\n if (existing) {\n const state = existing.dataset.hellopayState;\n if (state === 'loaded') {\n resolve(window.HelloPay ?? null);\n return;\n }\n if (state === 'loading') {\n existing.addEventListener('load', () => resolve(window.HelloPay ?? null), {\n once: true,\n });\n existing.addEventListener(\n 'error',\n () => reject(new Error('HelloPay SDK failed to load')),\n { once: true },\n );\n return;\n }\n // state === \"error\" or undefined: 失敗済み or 未知の状態なので作り直す\n existing.remove();\n }\n\n const script = document.createElement('script');\n script.src = normalizedUrl;\n script.async = true;\n script.dataset.hellopaySdk = 'true';\n script.dataset.hellopayUrl = normalizedUrl; // URL 比較用\n script.dataset.hellopayState = 'loading';\n script.addEventListener(\n 'load',\n () => {\n if (!window.HelloPay) {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK loaded but window.HelloPay is missing'));\n return;\n }\n script.dataset.hellopayState = 'loaded';\n resolve(window.HelloPay);\n },\n { once: true },\n );\n script.addEventListener(\n 'error',\n () => {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK failed to load'));\n },\n { once: true },\n );\n document.head.appendChild(script);\n });\n}\n\nexport interface LoadHelloPayOptions {\n /** SDK 配信 URL(ローカル開発・E2E で差し替える用。省略時は js.hello-pay.app から配信し、test/live は SDK 内部で key prefix から解決) */\n sdkUrl?: string;\n locale?: string;\n}\n\n/**\n * HelloPay SDK をロードして初期化する。\n * モジュールスコープで 1 回だけ呼び、返り値の Promise を `<Elements>` に渡す。\n */\nexport function loadHelloPay(\n publicKey: string,\n options: LoadHelloPayOptions = {},\n): Promise<HelloPay | null> {\n const sdkUrl = normalizeSdkUrl(options.sdkUrl ?? DEFAULT_SDK_URL);\n const locale = normalizeLocale(options.locale);\n const cacheKey = `${publicKey}::${locale}::${sdkUrl}`;\n const cached = sdkCache.get(cacheKey);\n if (cached) return cached;\n\n let scriptPromise = scriptPromiseCache.get(sdkUrl);\n if (!scriptPromise) {\n scriptPromise = loadScript(sdkUrl).catch((err) => {\n // 一時的なロード失敗はキャッシュせず次回呼び出しで再試行可能にする\n scriptPromiseCache.delete(sdkUrl);\n throw err;\n });\n scriptPromiseCache.set(sdkUrl, scriptPromise);\n }\n\n const sdkPromise = scriptPromise\n .then((factory) => {\n if (!factory) return null; // SSR\n return factory(publicKey, { locale: options.locale });\n })\n .catch((err) => {\n // SDK インスタンス生成失敗は sdkCache から退避して次回呼び出しで再試行可能にする\n sdkCache.delete(cacheKey);\n throw err;\n });\n sdkCache.set(cacheKey, sdkPromise);\n return sdkPromise;\n}\n","/**\n * <Elements> Provider.\n *\n * loadHelloPay() の Promise を受け取り、SDK インスタンスと Elements セッションを\n * 子孫に Context 経由で供給する。\n */\n\nimport { createContext, useEffect, useState, type ReactNode } from 'react';\nimport type { HelloPay, Elements as ElementsSession, ElementsOptions } from './types';\n\nexport interface ElementsContextValue {\n hellopay: HelloPay | null;\n elements: ElementsSession | null;\n}\n\nconst EMPTY_CONTEXT: ElementsContextValue = {\n hellopay: null,\n elements: null,\n};\n\nexport const ElementsContext = createContext<ElementsContextValue>(EMPTY_CONTEXT);\n\nexport interface ElementsProps {\n /** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */\n hellopay: Promise<HelloPay | null> | HelloPay | null;\n /** `sessionClientSecret` を渡すと、預かりカードのセッションで要る欄だけを出す */\n options?: ElementsOptions;\n children: ReactNode;\n}\n\nexport function Elements({ hellopay: hellopayProp, options, children }: ElementsProps) {\n const sessionClientSecret = options?.sessionClientSecret;\n const [ctx, setCtx] = useState<ElementsContextValue>(EMPTY_CONTEXT);\n\n useEffect(() => {\n let cancelled = false;\n setCtx(EMPTY_CONTEXT);\n\n Promise.resolve(hellopayProp)\n .then((sdk) => {\n if (cancelled) return;\n if (!sdk) {\n setCtx(EMPTY_CONTEXT);\n return;\n }\n setCtx({\n hellopay: sdk,\n elements: sdk.elements(sessionClientSecret ? { sessionClientSecret } : undefined),\n });\n })\n .catch(() => {\n // SDK ロード失敗時は null のまま(unhandled rejection を防ぐ)\n if (cancelled) return;\n setCtx(EMPTY_CONTEXT);\n });\n\n return () => {\n cancelled = true;\n };\n }, [hellopayProp, sessionClientSecret]);\n\n return <ElementsContext.Provider value={ctx}>{children}</ElementsContext.Provider>;\n}\n","/**\n * Element コンポーネント。\n *\n * 各コンポーネントは 1 つの `<div>` コンテナを描画するだけで、\n * 実体(カード情報が入る iframe)はコア SDK が差し込む。\n *\n * React Strict Mode の二重 mount 対応:\n * - effect の cleanup で element.unmount() を必ず呼ぶ\n * - unmount 後は Elements._elements 配列から除外される(コア SDK 側で対応済)\n * - 結果として Strict Mode で effect が再実行されても古い iframe は残らない\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useElements } from './hooks';\nimport type {\n ElementType,\n ElementOptions,\n ElementChangeEvent,\n ElementFocusEvent,\n ElementReadyEvent,\n} from './types';\n\nexport interface ElementProps {\n options?: ElementOptions;\n className?: string;\n style?: CSSProperties;\n onReady?: (e: ElementReadyEvent) => void;\n onChange?: (e: ElementChangeEvent) => void;\n onFocus?: (e: ElementFocusEvent) => void;\n onBlur?: (e: ElementFocusEvent) => void;\n}\n\nconst PROBE_PX = 100000;\n\n/**\n * コンテナの幅・高さのうち、中身に追従する(加盟店が寸法を決めていない)軸を返す。\n */\nfunction contentSizedAxes(container: HTMLElement) {\n // display: none の祖先の中ではレイアウトが無いので、computed value で見る。\n if (container.getClientRects().length === 0) {\n const computed = getComputedStyle(container);\n const isAuto = (v: string) => v === '' || v === 'auto';\n return { width: isAuto(computed.width), height: isAuto(computed.height) };\n }\n // computed の height で判定すると、border-box の padding・border や min-height で\n // 0 にならず見誤るので、大きな子を一時的に入れて寸法が伸びるかで確かめる。\n // 祖先の transform(モーダルの scale のアニメーションなど)に左右されないよう、\n // getBoundingClientRect ではなくレイアウト上の寸法の offset* で比べる。\n const before = { width: container.offsetWidth, height: container.offsetHeight };\n const probe = container.ownerDocument.createElement('div');\n probe.style.cssText = `width: ${PROBE_PX}px; height: ${PROBE_PX}px;`;\n container.appendChild(probe);\n const grows = {\n width: container.offsetWidth > before.width,\n height: container.offsetHeight > before.height,\n };\n probe.remove();\n return grows;\n}\n\n/**\n * 指定 type の Element を管理する React コンポーネントを生成する。\n */\nfunction createElementComponent(type: ElementType) {\n function ElementComponent({\n options,\n className,\n style,\n onReady,\n onChange,\n onFocus,\n onBlur,\n }: ElementProps) {\n const elements = useElements();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [fill, setFill] = useState({ width: false, height: false });\n\n // 最新のハンドラを ref で保持(effect を毎回 re-run させないため)\n const handlersRef = useRef({ onReady, onChange, onFocus, onBlur });\n handlersRef.current = { onReady, onChange, onFocus, onBlur };\n\n useEffect(() => {\n const container = containerRef.current;\n if (!elements || !container) return;\n\n // 100% を当てた後に測り直すと親の寸法に追従して見えるので、立てた軸は戻さない。\n const unset = contentSizedAxes(container);\n setFill((prev) => ({\n width: prev.width || unset.width,\n height: prev.height || unset.height,\n }));\n\n const element = elements.create(type, options);\n element.mount(container);\n\n const unsubs = [\n element.on('ready', (e) => handlersRef.current.onReady?.(e)),\n element.on('change', (e) => handlersRef.current.onChange?.(e)),\n element.on('focus', (e) => handlersRef.current.onFocus?.(e)),\n element.on('blur', (e) => handlersRef.current.onBlur?.(e)),\n ];\n\n return () => {\n unsubs.forEach((u) => u());\n element.unmount();\n };\n // options はマウント時にしか反映しない方針(Stripe と同じ挙動)。\n // 動的に style を変えたい場合は後続 PR で element.update() を追加。\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [elements]);\n\n // インラインの既定値は className の寸法に勝つので、加盟店が決めていない軸だけに付ける。\n // 判定は mount 時の 1 回だけなので、後から寸法を変えるなら style で渡す。\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n ...(fill.width && { width: '100%' }),\n ...(fill.height && { height: '100%' }),\n ...style,\n }}\n />\n );\n }\n ElementComponent.displayName = `HelloPay(${type})`;\n // getElement(CardNumberElement) のようにコンポーネント自身を型指定子として使えるよう、\n // 静的プロパティに type を生やしておく\n ElementComponent.__elementType = type as ElementType;\n return ElementComponent;\n}\n\nexport const CardNumberElement = createElementComponent('cardNumber');\nexport const CardExpiryElement = createElementComponent('cardExpiry');\nexport const CardCvcElement = createElementComponent('cardCvc');\nexport const CardholderNameElement = createElementComponent('cardholderName');\n// 韓国発行カードの Toss 登録に必要な暗証番号の先頭 2 桁。表示・非表示は\n// 発行国の判定結果に応じて呼び出し側が mount / unmount で制御する。\nexport const CardPasswordElement = createElementComponent('cardPassword');\n\nexport type ElementComponent = typeof CardNumberElement;\n","/**\n * React hooks.\n *\n * useHelloPay / useElements は <Elements> Provider の Context から\n * SDK インスタンスと Elements セッションを取り出す。\n * SDK ロード完了までは null を返すので、ハンドラ内で null チェックする。\n */\n\nimport { useContext } from 'react';\nimport { ElementsContext } from './Elements';\nimport type { HelloPay, Elements } from './types';\n\nexport function useHelloPay(): HelloPay | null {\n return useContext(ElementsContext).hellopay;\n}\n\nexport function useElements(): Elements | null {\n return useContext(ElementsContext).elements;\n}\n","/**\n * Apple Pay ボタン。\n *\n * `useHelloPay()` から SDK を取り、対応端末(Safari + Apple Pay 利用可能)でのみ\n * Apple 公式スタイルのボタンを描画する。非対応端末では何も描画しない。\n *\n * click で `hellopay.applePay()` を同期呼び出しし(`new ApplePaySession` の\n * user gesture 要件のため)、結果を `onToken` / `onError` / `onCancel` に振り分ける。\n * SDK は Apple Pay トークンを返すだけで課金はしないので、加盟店は `onToken` で\n * 受けたトークンを自社バックエンドから決済 API に送って課金する。\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useHelloPay } from './hooks';\nimport type { ApplePayCard, ApplePayOptions, HelloPayError } from './types';\n\nexport interface ApplePayButtonProps {\n options: ApplePayOptions;\n /** トークン取得成功時。トークンを自社バックエンド経由で決済 API に送る。 */\n onToken: (token: string, card: ApplePayCard) => void;\n /** validation / network エラー時。 */\n onError?: (error: HelloPayError) => void;\n /** ユーザーが Apple Pay シートを閉じた時。 */\n onCancel?: () => void;\n /** Apple Pay ボタンの色。既定 `black`。 */\n buttonStyle?: 'black' | 'white' | 'white-outline';\n /** Apple Pay ボタンの種別(`plain` / `buy` / `pay` 等)。既定 `plain`。 */\n buttonType?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function ApplePayButton({\n options,\n onToken,\n onError,\n onCancel,\n buttonStyle = 'black',\n buttonType = 'plain',\n className,\n style,\n}: ApplePayButtonProps) {\n const hellopay = useHelloPay();\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n const [supported, setSupported] = useState(false);\n\n // canMakeApplePayPayments は ApplePaySession 依存で client-only のため effect で判定。\n useEffect(() => {\n setSupported(!!hellopay && hellopay.canMakeApplePayPayments());\n }, [hellopay]);\n\n // Apple Pay ボタンの見た目はベンダー CSS プロパティで指定する。React の style 型に\n // 無いため ref 経由で setProperty する。\n useEffect(() => {\n const el = buttonRef.current;\n if (!el) return;\n el.style.setProperty('-webkit-appearance', '-apple-pay-button');\n el.style.setProperty('-apple-pay-button-type', buttonType);\n el.style.setProperty('-apple-pay-button-style', buttonStyle);\n }, [supported, buttonType, buttonStyle]);\n\n // 最新のハンドラ / options を ref に保持し、click ハンドラを毎回作り直さない。\n const latest = useRef({ options, onToken, onError, onCancel, hellopay });\n latest.current = { options, onToken, onError, onCancel, hellopay };\n\n if (!supported) return null;\n\n const handleClick = () => {\n const current = latest.current;\n if (!current.hellopay) return;\n // user gesture 要件のため click ハンドラから同期呼び出しする。\n current.hellopay\n .applePay(current.options)\n .then((result) => {\n if (result.error) {\n if (result.error.type === 'cancel') {\n current.onCancel?.();\n } else {\n current.onError?.(result.error);\n }\n return;\n }\n current.onToken(result.token, result.card);\n })\n .catch((err: unknown) => {\n current.onError?.({\n type: 'config_error',\n code: 'apple_pay_error',\n message: err instanceof Error ? err.message : 'Apple Pay error',\n });\n });\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleClick}\n className={className}\n style={{ width: '100%', height: '44px', cursor: 'pointer', ...style }}\n />\n );\n}\n\nApplePayButton.displayName = 'HelloPay(ApplePayButton)';\n"],"mappings":"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","names":["script","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -9,7 +9,7 @@ import { ReactNode, CSSProperties } from 'react';
|
|
|
9
9
|
* HelloPay 管理ドメインから配信)を満たすため。
|
|
10
10
|
* npm パッケージは型定義とローダーのみを提供する。
|
|
11
11
|
*/
|
|
12
|
-
type ElementType = 'cardNumber' | 'cardExpiry' | 'cardCvc' | 'cardholderName';
|
|
12
|
+
type ElementType = 'cardNumber' | 'cardExpiry' | 'cardCvc' | 'cardholderName' | 'cardPassword';
|
|
13
13
|
interface ElementStyle {
|
|
14
14
|
base?: {
|
|
15
15
|
color?: string;
|
|
@@ -29,17 +29,65 @@ interface ElementStyle {
|
|
|
29
29
|
interface ElementOptions {
|
|
30
30
|
style?: ElementStyle;
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* カード発行国の判定結果。
|
|
34
|
+
*
|
|
35
|
+
* `null` を使わず 3 状態で返すのは、呼び出し側の挙動が異なるため。
|
|
36
|
+
* `pending` はフォームを組み替えずに現状の表示を維持し、`unknown` は
|
|
37
|
+
* どの発行国グループにも該当しないもの(`default`)として組み替える。
|
|
38
|
+
*
|
|
39
|
+
* カード番号の先頭 8 桁が揃うと HelloPay に問い合わせ、KSNET の国内カード
|
|
40
|
+
* BIN 一覧にあれば `determined`(`country: 'KR'`)、無ければ `unknown` になる。
|
|
41
|
+
* 8 桁未満の間と問い合わせ中は `pending`、問い合わせに失敗したら `unknown`。
|
|
42
|
+
*/
|
|
43
|
+
type IssuerCountryResult = {
|
|
44
|
+
status: 'pending';
|
|
45
|
+
} | {
|
|
46
|
+
status: 'unknown';
|
|
47
|
+
} | {
|
|
48
|
+
status: 'determined';
|
|
49
|
+
country: string;
|
|
50
|
+
};
|
|
51
|
+
interface CardNumberChangeEvent {
|
|
52
|
+
elementType: 'cardNumber';
|
|
53
|
+
empty: boolean;
|
|
54
|
+
complete: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* `'unionpay'` はこのイベントにだけ現れる。トークン化結果 (`TokenCard.brand`) は
|
|
57
|
+
* acquirer 側の見え方を返すので、同じカードで別の値になりうる。
|
|
58
|
+
*/
|
|
59
|
+
brand?: string | null;
|
|
60
|
+
issuerCountry: IssuerCountryResult;
|
|
61
|
+
}
|
|
62
|
+
interface OtherElementChangeEvent {
|
|
63
|
+
elementType: Exclude<ElementType, 'cardNumber'>;
|
|
34
64
|
empty: boolean;
|
|
35
65
|
complete: boolean;
|
|
36
66
|
brand?: string | null;
|
|
37
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* `elementType` で分岐する判別可能ユニオン。`issuerCountry` は cardNumber
|
|
70
|
+
* にしか存在しないため、読むには `elementType === 'cardNumber'` での
|
|
71
|
+
* narrowing が必要。
|
|
72
|
+
*/
|
|
73
|
+
type ElementChangeEvent = CardNumberChangeEvent | OtherElementChangeEvent;
|
|
38
74
|
interface ElementFocusEvent {
|
|
39
75
|
elementType: ElementType;
|
|
40
76
|
}
|
|
41
77
|
interface ElementReadyEvent {
|
|
42
78
|
elementType: ElementType;
|
|
79
|
+
/**
|
|
80
|
+
* 預かりカードの登録セッションで、この欄を出さないとき true。SDK は iframe を隠すので、
|
|
81
|
+
* 加盟店は見出しなど周りの要素を隠す。
|
|
82
|
+
*/
|
|
83
|
+
hidden: boolean;
|
|
84
|
+
}
|
|
85
|
+
interface ElementsOptions {
|
|
86
|
+
/**
|
|
87
|
+
* 登録セッションの `client_secret`。預かりカードのセッションなら、カード番号と有効期限の
|
|
88
|
+
* 欄を出さず、CVC か暗証番号の欄だけを出す。
|
|
89
|
+
*/
|
|
90
|
+
sessionClientSecret?: string;
|
|
43
91
|
}
|
|
44
92
|
type ElementEventMap = {
|
|
45
93
|
ready: ElementReadyEvent;
|
|
@@ -110,6 +158,59 @@ type CreateTokenResult = {
|
|
|
110
158
|
tokenExpiryDate?: undefined;
|
|
111
159
|
error: HelloPayError;
|
|
112
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* 複数プロバイダへのカード登録 (#2747) の返り値。
|
|
163
|
+
*
|
|
164
|
+
* `CreateTokenResult` と違い **トークンを持たない** — 登録結果 (Toss の BillingKey など) は
|
|
165
|
+
* 加盟店バックエンドがサーバ間 API で取得する。判別は `status` で行う。
|
|
166
|
+
*
|
|
167
|
+
* ```ts
|
|
168
|
+
* const result = await hellopay.createToken(el, { sessionClientSecret });
|
|
169
|
+
* if (result.status === 'failed') {
|
|
170
|
+
* // 発行国の申告と BIN 判定が食い違った場合はフォームを組み替える
|
|
171
|
+
* if (result.error.code === 'issuer_country_mismatch') {
|
|
172
|
+
* rebuildForm(result.error.issuerCountryGroup);
|
|
173
|
+
* }
|
|
174
|
+
* return showError(result.error.message);
|
|
175
|
+
* }
|
|
176
|
+
* showRegisteredCard(result.card);
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
type CardRegistrationResult = {
|
|
180
|
+
status: 'succeeded';
|
|
181
|
+
card: CardRegistrationCard | null;
|
|
182
|
+
error?: undefined;
|
|
183
|
+
} | {
|
|
184
|
+
status: 'failed';
|
|
185
|
+
card: null;
|
|
186
|
+
error: CardRegistrationError;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* 登録できたカードのメタデータ。
|
|
190
|
+
*
|
|
191
|
+
* `brand` が `TokenCard` と違って `null` を許すのは、韓国の国内専用カード (`9410` 始まり) が
|
|
192
|
+
* 国際ブランドを持たないため。サーバーの BIN 判定がブランドを確定できないとそのまま `null` で
|
|
193
|
+
* 返るので、型でも表す。
|
|
194
|
+
*/
|
|
195
|
+
interface CardRegistrationCard {
|
|
196
|
+
brand: string | null;
|
|
197
|
+
last4: string;
|
|
198
|
+
expMonth: string;
|
|
199
|
+
expYear: string;
|
|
200
|
+
}
|
|
201
|
+
interface CardRegistrationError extends HelloPayError {
|
|
202
|
+
/**
|
|
203
|
+
* `issuer_country_mismatch` のときだけ入る、BIN 判定で確定した発行国グループ
|
|
204
|
+
* (該当なしは `'default'`)。呼び出し側はこれを使って入力フォームを組み替える。
|
|
205
|
+
*/
|
|
206
|
+
issuerCountryGroup?: string;
|
|
207
|
+
/**
|
|
208
|
+
* `card_brand_mismatch` のときだけ入る、判定したカードブランド (`visa` / `unionpay` など)。
|
|
209
|
+
* ブランドを判別できないカード (`9410` 始まり) では入らない。呼び出し側はこのブランドで
|
|
210
|
+
* 登録セッションを作り直す。
|
|
211
|
+
*/
|
|
212
|
+
cardBrand?: string;
|
|
213
|
+
}
|
|
113
214
|
/**
|
|
114
215
|
* Apple Pay で決済したカードのメタデータ。Apple Pay は client 側で last4 /
|
|
115
216
|
* 有効期限を確実に取得できないため、カード決済の `TokenCard` とは別形状。
|
|
@@ -164,8 +265,18 @@ type ApplePayResult = {
|
|
|
164
265
|
error: ApplePayError;
|
|
165
266
|
};
|
|
166
267
|
interface HelloPay {
|
|
167
|
-
elements(): Elements$1;
|
|
268
|
+
elements(options?: ElementsOptions): Elements$1;
|
|
168
269
|
createToken(cardNumberElement: Element): Promise<CreateTokenResult>;
|
|
270
|
+
/**
|
|
271
|
+
* 登録セッション経路 (#2747)。`sessionClientSecret` を渡すと、加盟店バックエンドが
|
|
272
|
+
* 宣言した複数の決済プロバイダへまとめてカードを登録する。
|
|
273
|
+
*
|
|
274
|
+
* **トークンは返らない。** BillingKey も PSP のトークンもブラウザには渡らず、
|
|
275
|
+
* 加盟店バックエンドがサーバ間 API で取得する。
|
|
276
|
+
*/
|
|
277
|
+
createToken(cardNumberElement: Element, options: {
|
|
278
|
+
sessionClientSecret: string;
|
|
279
|
+
}): Promise<CardRegistrationResult>;
|
|
169
280
|
/** 対応端末(Safari + Apple Pay 利用可能)でのみ true。 */
|
|
170
281
|
canMakeApplePayPayments(): boolean;
|
|
171
282
|
/** Apple Pay シートを開き、都度課金トークンを取得する。 */
|
|
@@ -211,9 +322,11 @@ declare function loadHelloPay(publicKey: string, options?: LoadHelloPayOptions):
|
|
|
211
322
|
interface ElementsProps {
|
|
212
323
|
/** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */
|
|
213
324
|
hellopay: Promise<HelloPay | null> | HelloPay | null;
|
|
325
|
+
/** `sessionClientSecret` を渡すと、預かりカードのセッションで要る欄だけを出す */
|
|
326
|
+
options?: ElementsOptions;
|
|
214
327
|
children: ReactNode;
|
|
215
328
|
}
|
|
216
|
-
declare function Elements({ hellopay: hellopayProp, children }: ElementsProps): react_jsx_runtime.JSX.Element;
|
|
329
|
+
declare function Elements({ hellopay: hellopayProp, options, children }: ElementsProps): react_jsx_runtime.JSX.Element;
|
|
217
330
|
|
|
218
331
|
interface ElementProps {
|
|
219
332
|
options?: ElementOptions;
|
|
@@ -244,6 +357,11 @@ declare const CardholderNameElement: {
|
|
|
244
357
|
displayName: string;
|
|
245
358
|
__elementType: ElementType;
|
|
246
359
|
};
|
|
360
|
+
declare const CardPasswordElement: {
|
|
361
|
+
({ options, className, style, onReady, onChange, onFocus, onBlur, }: ElementProps): react_jsx_runtime.JSX.Element;
|
|
362
|
+
displayName: string;
|
|
363
|
+
__elementType: ElementType;
|
|
364
|
+
};
|
|
247
365
|
|
|
248
366
|
interface ApplePayButtonProps {
|
|
249
367
|
options: ApplePayOptions;
|
|
@@ -276,4 +394,4 @@ declare namespace ApplePayButton {
|
|
|
276
394
|
declare function useHelloPay(): HelloPay | null;
|
|
277
395
|
declare function useElements(): Elements$1 | null;
|
|
278
396
|
|
|
279
|
-
export { ApplePayButton, type ApplePayButtonProps, type ApplePayCancelError, type ApplePayCard, type ApplePayError, type ApplePayOptions, type ApplePayResult, CardCvcElement, CardExpiryElement, CardNumberElement, CardholderNameElement, type CreateTokenResult, type Element, type ElementChangeEvent, type ElementFocusEvent, type ElementOptions, type ElementProps, type ElementReadyEvent, type ElementStyle, type ElementType, Elements, type ElementsProps, type Elements$1 as ElementsSession, type HelloPay, type HelloPayError, type LoadHelloPayOptions, type TokenCard, type TokenProvider, type Unsubscribe, loadHelloPay, useElements, useHelloPay };
|
|
397
|
+
export { ApplePayButton, type ApplePayButtonProps, type ApplePayCancelError, type ApplePayCard, type ApplePayError, type ApplePayOptions, type ApplePayResult, CardCvcElement, CardExpiryElement, type CardNumberChangeEvent, CardNumberElement, CardPasswordElement, CardholderNameElement, type CreateTokenResult, type Element, type ElementChangeEvent, type ElementFocusEvent, type ElementOptions, type ElementProps, type ElementReadyEvent, type ElementStyle, type ElementType, Elements, type ElementsOptions, type ElementsProps, type Elements$1 as ElementsSession, type HelloPay, type HelloPayError, type IssuerCountryResult, type LoadHelloPayOptions, type OtherElementChangeEvent, type TokenCard, type TokenProvider, type Unsubscribe, loadHelloPay, useElements, useHelloPay };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { ReactNode, CSSProperties } from 'react';
|
|
|
9
9
|
* HelloPay 管理ドメインから配信)を満たすため。
|
|
10
10
|
* npm パッケージは型定義とローダーのみを提供する。
|
|
11
11
|
*/
|
|
12
|
-
type ElementType = 'cardNumber' | 'cardExpiry' | 'cardCvc' | 'cardholderName';
|
|
12
|
+
type ElementType = 'cardNumber' | 'cardExpiry' | 'cardCvc' | 'cardholderName' | 'cardPassword';
|
|
13
13
|
interface ElementStyle {
|
|
14
14
|
base?: {
|
|
15
15
|
color?: string;
|
|
@@ -29,17 +29,65 @@ interface ElementStyle {
|
|
|
29
29
|
interface ElementOptions {
|
|
30
30
|
style?: ElementStyle;
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* カード発行国の判定結果。
|
|
34
|
+
*
|
|
35
|
+
* `null` を使わず 3 状態で返すのは、呼び出し側の挙動が異なるため。
|
|
36
|
+
* `pending` はフォームを組み替えずに現状の表示を維持し、`unknown` は
|
|
37
|
+
* どの発行国グループにも該当しないもの(`default`)として組み替える。
|
|
38
|
+
*
|
|
39
|
+
* カード番号の先頭 8 桁が揃うと HelloPay に問い合わせ、KSNET の国内カード
|
|
40
|
+
* BIN 一覧にあれば `determined`(`country: 'KR'`)、無ければ `unknown` になる。
|
|
41
|
+
* 8 桁未満の間と問い合わせ中は `pending`、問い合わせに失敗したら `unknown`。
|
|
42
|
+
*/
|
|
43
|
+
type IssuerCountryResult = {
|
|
44
|
+
status: 'pending';
|
|
45
|
+
} | {
|
|
46
|
+
status: 'unknown';
|
|
47
|
+
} | {
|
|
48
|
+
status: 'determined';
|
|
49
|
+
country: string;
|
|
50
|
+
};
|
|
51
|
+
interface CardNumberChangeEvent {
|
|
52
|
+
elementType: 'cardNumber';
|
|
53
|
+
empty: boolean;
|
|
54
|
+
complete: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* `'unionpay'` はこのイベントにだけ現れる。トークン化結果 (`TokenCard.brand`) は
|
|
57
|
+
* acquirer 側の見え方を返すので、同じカードで別の値になりうる。
|
|
58
|
+
*/
|
|
59
|
+
brand?: string | null;
|
|
60
|
+
issuerCountry: IssuerCountryResult;
|
|
61
|
+
}
|
|
62
|
+
interface OtherElementChangeEvent {
|
|
63
|
+
elementType: Exclude<ElementType, 'cardNumber'>;
|
|
34
64
|
empty: boolean;
|
|
35
65
|
complete: boolean;
|
|
36
66
|
brand?: string | null;
|
|
37
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* `elementType` で分岐する判別可能ユニオン。`issuerCountry` は cardNumber
|
|
70
|
+
* にしか存在しないため、読むには `elementType === 'cardNumber'` での
|
|
71
|
+
* narrowing が必要。
|
|
72
|
+
*/
|
|
73
|
+
type ElementChangeEvent = CardNumberChangeEvent | OtherElementChangeEvent;
|
|
38
74
|
interface ElementFocusEvent {
|
|
39
75
|
elementType: ElementType;
|
|
40
76
|
}
|
|
41
77
|
interface ElementReadyEvent {
|
|
42
78
|
elementType: ElementType;
|
|
79
|
+
/**
|
|
80
|
+
* 預かりカードの登録セッションで、この欄を出さないとき true。SDK は iframe を隠すので、
|
|
81
|
+
* 加盟店は見出しなど周りの要素を隠す。
|
|
82
|
+
*/
|
|
83
|
+
hidden: boolean;
|
|
84
|
+
}
|
|
85
|
+
interface ElementsOptions {
|
|
86
|
+
/**
|
|
87
|
+
* 登録セッションの `client_secret`。預かりカードのセッションなら、カード番号と有効期限の
|
|
88
|
+
* 欄を出さず、CVC か暗証番号の欄だけを出す。
|
|
89
|
+
*/
|
|
90
|
+
sessionClientSecret?: string;
|
|
43
91
|
}
|
|
44
92
|
type ElementEventMap = {
|
|
45
93
|
ready: ElementReadyEvent;
|
|
@@ -110,6 +158,59 @@ type CreateTokenResult = {
|
|
|
110
158
|
tokenExpiryDate?: undefined;
|
|
111
159
|
error: HelloPayError;
|
|
112
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* 複数プロバイダへのカード登録 (#2747) の返り値。
|
|
163
|
+
*
|
|
164
|
+
* `CreateTokenResult` と違い **トークンを持たない** — 登録結果 (Toss の BillingKey など) は
|
|
165
|
+
* 加盟店バックエンドがサーバ間 API で取得する。判別は `status` で行う。
|
|
166
|
+
*
|
|
167
|
+
* ```ts
|
|
168
|
+
* const result = await hellopay.createToken(el, { sessionClientSecret });
|
|
169
|
+
* if (result.status === 'failed') {
|
|
170
|
+
* // 発行国の申告と BIN 判定が食い違った場合はフォームを組み替える
|
|
171
|
+
* if (result.error.code === 'issuer_country_mismatch') {
|
|
172
|
+
* rebuildForm(result.error.issuerCountryGroup);
|
|
173
|
+
* }
|
|
174
|
+
* return showError(result.error.message);
|
|
175
|
+
* }
|
|
176
|
+
* showRegisteredCard(result.card);
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
type CardRegistrationResult = {
|
|
180
|
+
status: 'succeeded';
|
|
181
|
+
card: CardRegistrationCard | null;
|
|
182
|
+
error?: undefined;
|
|
183
|
+
} | {
|
|
184
|
+
status: 'failed';
|
|
185
|
+
card: null;
|
|
186
|
+
error: CardRegistrationError;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* 登録できたカードのメタデータ。
|
|
190
|
+
*
|
|
191
|
+
* `brand` が `TokenCard` と違って `null` を許すのは、韓国の国内専用カード (`9410` 始まり) が
|
|
192
|
+
* 国際ブランドを持たないため。サーバーの BIN 判定がブランドを確定できないとそのまま `null` で
|
|
193
|
+
* 返るので、型でも表す。
|
|
194
|
+
*/
|
|
195
|
+
interface CardRegistrationCard {
|
|
196
|
+
brand: string | null;
|
|
197
|
+
last4: string;
|
|
198
|
+
expMonth: string;
|
|
199
|
+
expYear: string;
|
|
200
|
+
}
|
|
201
|
+
interface CardRegistrationError extends HelloPayError {
|
|
202
|
+
/**
|
|
203
|
+
* `issuer_country_mismatch` のときだけ入る、BIN 判定で確定した発行国グループ
|
|
204
|
+
* (該当なしは `'default'`)。呼び出し側はこれを使って入力フォームを組み替える。
|
|
205
|
+
*/
|
|
206
|
+
issuerCountryGroup?: string;
|
|
207
|
+
/**
|
|
208
|
+
* `card_brand_mismatch` のときだけ入る、判定したカードブランド (`visa` / `unionpay` など)。
|
|
209
|
+
* ブランドを判別できないカード (`9410` 始まり) では入らない。呼び出し側はこのブランドで
|
|
210
|
+
* 登録セッションを作り直す。
|
|
211
|
+
*/
|
|
212
|
+
cardBrand?: string;
|
|
213
|
+
}
|
|
113
214
|
/**
|
|
114
215
|
* Apple Pay で決済したカードのメタデータ。Apple Pay は client 側で last4 /
|
|
115
216
|
* 有効期限を確実に取得できないため、カード決済の `TokenCard` とは別形状。
|
|
@@ -164,8 +265,18 @@ type ApplePayResult = {
|
|
|
164
265
|
error: ApplePayError;
|
|
165
266
|
};
|
|
166
267
|
interface HelloPay {
|
|
167
|
-
elements(): Elements$1;
|
|
268
|
+
elements(options?: ElementsOptions): Elements$1;
|
|
168
269
|
createToken(cardNumberElement: Element): Promise<CreateTokenResult>;
|
|
270
|
+
/**
|
|
271
|
+
* 登録セッション経路 (#2747)。`sessionClientSecret` を渡すと、加盟店バックエンドが
|
|
272
|
+
* 宣言した複数の決済プロバイダへまとめてカードを登録する。
|
|
273
|
+
*
|
|
274
|
+
* **トークンは返らない。** BillingKey も PSP のトークンもブラウザには渡らず、
|
|
275
|
+
* 加盟店バックエンドがサーバ間 API で取得する。
|
|
276
|
+
*/
|
|
277
|
+
createToken(cardNumberElement: Element, options: {
|
|
278
|
+
sessionClientSecret: string;
|
|
279
|
+
}): Promise<CardRegistrationResult>;
|
|
169
280
|
/** 対応端末(Safari + Apple Pay 利用可能)でのみ true。 */
|
|
170
281
|
canMakeApplePayPayments(): boolean;
|
|
171
282
|
/** Apple Pay シートを開き、都度課金トークンを取得する。 */
|
|
@@ -211,9 +322,11 @@ declare function loadHelloPay(publicKey: string, options?: LoadHelloPayOptions):
|
|
|
211
322
|
interface ElementsProps {
|
|
212
323
|
/** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */
|
|
213
324
|
hellopay: Promise<HelloPay | null> | HelloPay | null;
|
|
325
|
+
/** `sessionClientSecret` を渡すと、預かりカードのセッションで要る欄だけを出す */
|
|
326
|
+
options?: ElementsOptions;
|
|
214
327
|
children: ReactNode;
|
|
215
328
|
}
|
|
216
|
-
declare function Elements({ hellopay: hellopayProp, children }: ElementsProps): react_jsx_runtime.JSX.Element;
|
|
329
|
+
declare function Elements({ hellopay: hellopayProp, options, children }: ElementsProps): react_jsx_runtime.JSX.Element;
|
|
217
330
|
|
|
218
331
|
interface ElementProps {
|
|
219
332
|
options?: ElementOptions;
|
|
@@ -244,6 +357,11 @@ declare const CardholderNameElement: {
|
|
|
244
357
|
displayName: string;
|
|
245
358
|
__elementType: ElementType;
|
|
246
359
|
};
|
|
360
|
+
declare const CardPasswordElement: {
|
|
361
|
+
({ options, className, style, onReady, onChange, onFocus, onBlur, }: ElementProps): react_jsx_runtime.JSX.Element;
|
|
362
|
+
displayName: string;
|
|
363
|
+
__elementType: ElementType;
|
|
364
|
+
};
|
|
247
365
|
|
|
248
366
|
interface ApplePayButtonProps {
|
|
249
367
|
options: ApplePayOptions;
|
|
@@ -276,4 +394,4 @@ declare namespace ApplePayButton {
|
|
|
276
394
|
declare function useHelloPay(): HelloPay | null;
|
|
277
395
|
declare function useElements(): Elements$1 | null;
|
|
278
396
|
|
|
279
|
-
export { ApplePayButton, type ApplePayButtonProps, type ApplePayCancelError, type ApplePayCard, type ApplePayError, type ApplePayOptions, type ApplePayResult, CardCvcElement, CardExpiryElement, CardNumberElement, CardholderNameElement, type CreateTokenResult, type Element, type ElementChangeEvent, type ElementFocusEvent, type ElementOptions, type ElementProps, type ElementReadyEvent, type ElementStyle, type ElementType, Elements, type ElementsProps, type Elements$1 as ElementsSession, type HelloPay, type HelloPayError, type LoadHelloPayOptions, type TokenCard, type TokenProvider, type Unsubscribe, loadHelloPay, useElements, useHelloPay };
|
|
397
|
+
export { ApplePayButton, type ApplePayButtonProps, type ApplePayCancelError, type ApplePayCard, type ApplePayError, type ApplePayOptions, type ApplePayResult, CardCvcElement, CardExpiryElement, type CardNumberChangeEvent, CardNumberElement, CardPasswordElement, CardholderNameElement, type CreateTokenResult, type Element, type ElementChangeEvent, type ElementFocusEvent, type ElementOptions, type ElementProps, type ElementReadyEvent, type ElementStyle, type ElementType, Elements, type ElementsOptions, type ElementsProps, type Elements$1 as ElementsSession, type HelloPay, type HelloPayError, type IssuerCountryResult, type LoadHelloPayOptions, type OtherElementChangeEvent, type TokenCard, type TokenProvider, type Unsubscribe, loadHelloPay, useElements, useHelloPay };
|
package/dist/index.js
CHANGED
|
@@ -111,7 +111,8 @@ var EMPTY_CONTEXT = {
|
|
|
111
111
|
elements: null
|
|
112
112
|
};
|
|
113
113
|
var ElementsContext = createContext(EMPTY_CONTEXT);
|
|
114
|
-
function Elements({ hellopay: hellopayProp, children }) {
|
|
114
|
+
function Elements({ hellopay: hellopayProp, options, children }) {
|
|
115
|
+
const sessionClientSecret = options?.sessionClientSecret;
|
|
115
116
|
const [ctx, setCtx] = useState(EMPTY_CONTEXT);
|
|
116
117
|
useEffect(() => {
|
|
117
118
|
let cancelled = false;
|
|
@@ -122,7 +123,10 @@ function Elements({ hellopay: hellopayProp, children }) {
|
|
|
122
123
|
setCtx(EMPTY_CONTEXT);
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
125
|
-
setCtx({
|
|
126
|
+
setCtx({
|
|
127
|
+
hellopay: sdk,
|
|
128
|
+
elements: sdk.elements(sessionClientSecret ? { sessionClientSecret } : void 0)
|
|
129
|
+
});
|
|
126
130
|
}).catch(() => {
|
|
127
131
|
if (cancelled) return;
|
|
128
132
|
setCtx(EMPTY_CONTEXT);
|
|
@@ -130,12 +134,12 @@ function Elements({ hellopay: hellopayProp, children }) {
|
|
|
130
134
|
return () => {
|
|
131
135
|
cancelled = true;
|
|
132
136
|
};
|
|
133
|
-
}, [hellopayProp]);
|
|
137
|
+
}, [hellopayProp, sessionClientSecret]);
|
|
134
138
|
return /* @__PURE__ */ jsx(ElementsContext.Provider, { value: ctx, children });
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
// src/ElementComponents.tsx
|
|
138
|
-
import { useEffect as useEffect2, useRef } from "react";
|
|
142
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
139
143
|
|
|
140
144
|
// src/hooks.ts
|
|
141
145
|
import { useContext } from "react";
|
|
@@ -148,6 +152,24 @@ function useElements() {
|
|
|
148
152
|
|
|
149
153
|
// src/ElementComponents.tsx
|
|
150
154
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
155
|
+
var PROBE_PX = 1e5;
|
|
156
|
+
function contentSizedAxes(container) {
|
|
157
|
+
if (container.getClientRects().length === 0) {
|
|
158
|
+
const computed = getComputedStyle(container);
|
|
159
|
+
const isAuto = (v) => v === "" || v === "auto";
|
|
160
|
+
return { width: isAuto(computed.width), height: isAuto(computed.height) };
|
|
161
|
+
}
|
|
162
|
+
const before = { width: container.offsetWidth, height: container.offsetHeight };
|
|
163
|
+
const probe = container.ownerDocument.createElement("div");
|
|
164
|
+
probe.style.cssText = `width: ${PROBE_PX}px; height: ${PROBE_PX}px;`;
|
|
165
|
+
container.appendChild(probe);
|
|
166
|
+
const grows = {
|
|
167
|
+
width: container.offsetWidth > before.width,
|
|
168
|
+
height: container.offsetHeight > before.height
|
|
169
|
+
};
|
|
170
|
+
probe.remove();
|
|
171
|
+
return grows;
|
|
172
|
+
}
|
|
151
173
|
function createElementComponent(type) {
|
|
152
174
|
function ElementComponent({
|
|
153
175
|
options,
|
|
@@ -160,12 +182,19 @@ function createElementComponent(type) {
|
|
|
160
182
|
}) {
|
|
161
183
|
const elements = useElements();
|
|
162
184
|
const containerRef = useRef(null);
|
|
185
|
+
const [fill, setFill] = useState2({ width: false, height: false });
|
|
163
186
|
const handlersRef = useRef({ onReady, onChange, onFocus, onBlur });
|
|
164
187
|
handlersRef.current = { onReady, onChange, onFocus, onBlur };
|
|
165
188
|
useEffect2(() => {
|
|
166
|
-
|
|
189
|
+
const container = containerRef.current;
|
|
190
|
+
if (!elements || !container) return;
|
|
191
|
+
const unset = contentSizedAxes(container);
|
|
192
|
+
setFill((prev) => ({
|
|
193
|
+
width: prev.width || unset.width,
|
|
194
|
+
height: prev.height || unset.height
|
|
195
|
+
}));
|
|
167
196
|
const element = elements.create(type, options);
|
|
168
|
-
element.mount(
|
|
197
|
+
element.mount(container);
|
|
169
198
|
const unsubs = [
|
|
170
199
|
element.on("ready", (e) => handlersRef.current.onReady?.(e)),
|
|
171
200
|
element.on("change", (e) => handlersRef.current.onChange?.(e)),
|
|
@@ -182,7 +211,11 @@ function createElementComponent(type) {
|
|
|
182
211
|
{
|
|
183
212
|
ref: containerRef,
|
|
184
213
|
className,
|
|
185
|
-
style: {
|
|
214
|
+
style: {
|
|
215
|
+
...fill.width && { width: "100%" },
|
|
216
|
+
...fill.height && { height: "100%" },
|
|
217
|
+
...style
|
|
218
|
+
}
|
|
186
219
|
}
|
|
187
220
|
);
|
|
188
221
|
}
|
|
@@ -194,9 +227,10 @@ var CardNumberElement = createElementComponent("cardNumber");
|
|
|
194
227
|
var CardExpiryElement = createElementComponent("cardExpiry");
|
|
195
228
|
var CardCvcElement = createElementComponent("cardCvc");
|
|
196
229
|
var CardholderNameElement = createElementComponent("cardholderName");
|
|
230
|
+
var CardPasswordElement = createElementComponent("cardPassword");
|
|
197
231
|
|
|
198
232
|
// src/ApplePayButton.tsx
|
|
199
|
-
import { useEffect as useEffect3, useRef as useRef2, useState as
|
|
233
|
+
import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
200
234
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
201
235
|
function ApplePayButton({
|
|
202
236
|
options,
|
|
@@ -210,7 +244,7 @@ function ApplePayButton({
|
|
|
210
244
|
}) {
|
|
211
245
|
const hellopay = useHelloPay();
|
|
212
246
|
const buttonRef = useRef2(null);
|
|
213
|
-
const [supported, setSupported] =
|
|
247
|
+
const [supported, setSupported] = useState3(false);
|
|
214
248
|
useEffect3(() => {
|
|
215
249
|
setSupported(!!hellopay && hellopay.canMakeApplePayPayments());
|
|
216
250
|
}, [hellopay]);
|
|
@@ -262,6 +296,7 @@ export {
|
|
|
262
296
|
CardCvcElement,
|
|
263
297
|
CardExpiryElement,
|
|
264
298
|
CardNumberElement,
|
|
299
|
+
CardPasswordElement,
|
|
265
300
|
CardholderNameElement,
|
|
266
301
|
Elements,
|
|
267
302
|
loadHelloPay,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loadHelloPay.ts","../src/Elements.tsx","../src/ElementComponents.tsx","../src/hooks.ts","../src/ApplePayButton.tsx"],"sourcesContent":["/**\n * HelloPay SDK ローダー。\n *\n * HelloPay 管理下のドメイン(js.hello-pay.app 等)から SDK を動的ロードし、\n * `window.HelloPay` ファクトリを解決する。\n *\n * - 同一 sdkUrl への呼び出しは script tag を 1 回だけ挿入(dedup)\n * - 異なる sdkUrl は独立したロードを維持(E2E / ローカル切り替え対応)\n * - SSR 環境では Promise<null> を解決(加盟店側で null チェック)\n * - 一時的な失敗はキャッシュせず次回呼び出しで再試行可能\n */\n\nimport type { HelloPay, HelloPayFactory } from './types';\n\n// SDK 本体は test / live どちらの publishable key でも同一ドメイン\n// (js.hello-pay.app) から配信する。test / live mode の切り替えは SDK 内部で\n// publishable key の prefix から解決し、test mode では接続先を test 環境\n// (*.stg.hello-pay.app) に向ける(hellopay.js と同じ方式)。\nconst DEFAULT_SDK_URL = 'https://js.hello-pay.app/v1/hellopay-elements.js';\n\n// sdkUrl 単位で script ロード Promise をキャッシュ。\n// loadPromise を単一変数にするとすべての URL で共有されてしまい、\n// 異なる sdkUrl(E2E / ローカル開発)が誤って同一ロードを共有する。\nconst scriptPromiseCache = new Map<string, Promise<HelloPayFactory | null>>();\n\n// publicKey + locale + sdkUrl の組み合わせごとに `HelloPaySDK` インスタンスをキャッシュ。\n// 加盟店が誤って inline で loadHelloPay を呼び出した場合でも同一 SDK インスタンスが\n// 返るようにして、Elements セッションの BroadcastChannel session ID ミスマッチを防ぐ。\nconst sdkCache = new Map<string, Promise<HelloPay | null>>();\n\n/** テスト専用: module-scope のキャッシュをすべて初期状態に戻す。 */\nexport function __resetLoadPromiseForTest(): void {\n scriptPromiseCache.clear();\n sdkCache.clear();\n}\n\nfunction normalizeSdkUrl(url: string): string {\n if (typeof window === 'undefined') return url;\n\n try {\n return new URL(url, window.location.href).href;\n } catch {\n return url;\n }\n}\n\nfunction normalizeLocale(locale?: string): string {\n return locale || 'ja';\n}\n\nfunction loadScript(url: string): Promise<HelloPayFactory | null> {\n if (typeof window === 'undefined') return Promise.resolve(null);\n\n // URL を正規化してプロトコル省略形などを統一する。\n // テスト / SSR 環境では window.location が http URL でない場合があるため try-catch で保護。\n const normalizedUrl = normalizeSdkUrl(url);\n\n return new Promise((resolve, reject) => {\n // script は URL 単位で管理する。異なる URL の script を削除せず共存させることで、\n // E2E / ローカル開発で異なる sdkUrl が並列で呼ばれても Promise が宙吊りにならない。\n // 状態は dataset.hellopayState に \"loading\" | \"loaded\" | \"error\" で刻む。\n const managedScripts = Array.from(\n document.querySelectorAll<HTMLScriptElement>('script[data-hellopay-sdk]'),\n );\n\n const existing = managedScripts.find((script) => {\n const rawUrl = script.dataset.hellopayUrl || script.src;\n if (!rawUrl) return false;\n try {\n return new URL(rawUrl, window.location.href).href === normalizedUrl;\n } catch {\n return rawUrl === normalizedUrl;\n }\n });\n\n if (existing) {\n const state = existing.dataset.hellopayState;\n if (state === 'loaded') {\n resolve(window.HelloPay ?? null);\n return;\n }\n if (state === 'loading') {\n existing.addEventListener('load', () => resolve(window.HelloPay ?? null), {\n once: true,\n });\n existing.addEventListener(\n 'error',\n () => reject(new Error('HelloPay SDK failed to load')),\n { once: true },\n );\n return;\n }\n // state === \"error\" or undefined: 失敗済み or 未知の状態なので作り直す\n existing.remove();\n }\n\n const script = document.createElement('script');\n script.src = normalizedUrl;\n script.async = true;\n script.dataset.hellopaySdk = 'true';\n script.dataset.hellopayUrl = normalizedUrl; // URL 比較用\n script.dataset.hellopayState = 'loading';\n script.addEventListener(\n 'load',\n () => {\n if (!window.HelloPay) {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK loaded but window.HelloPay is missing'));\n return;\n }\n script.dataset.hellopayState = 'loaded';\n resolve(window.HelloPay);\n },\n { once: true },\n );\n script.addEventListener(\n 'error',\n () => {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK failed to load'));\n },\n { once: true },\n );\n document.head.appendChild(script);\n });\n}\n\nexport interface LoadHelloPayOptions {\n /** SDK 配信 URL(ローカル開発・E2E で差し替える用。省略時は js.hello-pay.app から配信し、test/live は SDK 内部で key prefix から解決) */\n sdkUrl?: string;\n locale?: string;\n}\n\n/**\n * HelloPay SDK をロードして初期化する。\n * モジュールスコープで 1 回だけ呼び、返り値の Promise を `<Elements>` に渡す。\n */\nexport function loadHelloPay(\n publicKey: string,\n options: LoadHelloPayOptions = {},\n): Promise<HelloPay | null> {\n const sdkUrl = normalizeSdkUrl(options.sdkUrl ?? DEFAULT_SDK_URL);\n const locale = normalizeLocale(options.locale);\n const cacheKey = `${publicKey}::${locale}::${sdkUrl}`;\n const cached = sdkCache.get(cacheKey);\n if (cached) return cached;\n\n let scriptPromise = scriptPromiseCache.get(sdkUrl);\n if (!scriptPromise) {\n scriptPromise = loadScript(sdkUrl).catch((err) => {\n // 一時的なロード失敗はキャッシュせず次回呼び出しで再試行可能にする\n scriptPromiseCache.delete(sdkUrl);\n throw err;\n });\n scriptPromiseCache.set(sdkUrl, scriptPromise);\n }\n\n const sdkPromise = scriptPromise\n .then((factory) => {\n if (!factory) return null; // SSR\n return factory(publicKey, { locale: options.locale });\n })\n .catch((err) => {\n // SDK インスタンス生成失敗は sdkCache から退避して次回呼び出しで再試行可能にする\n sdkCache.delete(cacheKey);\n throw err;\n });\n sdkCache.set(cacheKey, sdkPromise);\n return sdkPromise;\n}\n","/**\n * <Elements> Provider.\n *\n * loadHelloPay() の Promise を受け取り、SDK インスタンスと Elements セッションを\n * 子孫に Context 経由で供給する。\n */\n\nimport { createContext, useEffect, useState, type ReactNode } from 'react';\nimport type { HelloPay, Elements as ElementsSession } from './types';\n\nexport interface ElementsContextValue {\n hellopay: HelloPay | null;\n elements: ElementsSession | null;\n}\n\nconst EMPTY_CONTEXT: ElementsContextValue = {\n hellopay: null,\n elements: null,\n};\n\nexport const ElementsContext = createContext<ElementsContextValue>(EMPTY_CONTEXT);\n\nexport interface ElementsProps {\n /** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */\n hellopay: Promise<HelloPay | null> | HelloPay | null;\n children: ReactNode;\n}\n\nexport function Elements({ hellopay: hellopayProp, children }: ElementsProps) {\n const [ctx, setCtx] = useState<ElementsContextValue>(EMPTY_CONTEXT);\n\n useEffect(() => {\n let cancelled = false;\n setCtx(EMPTY_CONTEXT);\n\n Promise.resolve(hellopayProp)\n .then((sdk) => {\n if (cancelled) return;\n if (!sdk) {\n setCtx(EMPTY_CONTEXT);\n return;\n }\n setCtx({ hellopay: sdk, elements: sdk.elements() });\n })\n .catch(() => {\n // SDK ロード失敗時は null のまま(unhandled rejection を防ぐ)\n if (cancelled) return;\n setCtx(EMPTY_CONTEXT);\n });\n\n return () => {\n cancelled = true;\n };\n }, [hellopayProp]);\n\n return <ElementsContext.Provider value={ctx}>{children}</ElementsContext.Provider>;\n}\n","/**\n * Element コンポーネント。\n *\n * 各コンポーネントは 1 つの `<div>` コンテナを描画するだけで、\n * 実体(カード情報が入る iframe)はコア SDK が差し込む。\n *\n * React Strict Mode の二重 mount 対応:\n * - effect の cleanup で element.unmount() を必ず呼ぶ\n * - unmount 後は Elements._elements 配列から除外される(コア SDK 側で対応済)\n * - 結果として Strict Mode で effect が再実行されても古い iframe は残らない\n */\n\nimport { useEffect, useRef, type CSSProperties } from 'react';\nimport { useElements } from './hooks';\nimport type {\n ElementType,\n ElementOptions,\n ElementChangeEvent,\n ElementFocusEvent,\n ElementReadyEvent,\n} from './types';\n\nexport interface ElementProps {\n options?: ElementOptions;\n className?: string;\n style?: CSSProperties;\n onReady?: (e: ElementReadyEvent) => void;\n onChange?: (e: ElementChangeEvent) => void;\n onFocus?: (e: ElementFocusEvent) => void;\n onBlur?: (e: ElementFocusEvent) => void;\n}\n\n/**\n * 指定 type の Element を管理する React コンポーネントを生成する。\n */\nfunction createElementComponent(type: ElementType) {\n function ElementComponent({\n options,\n className,\n style,\n onReady,\n onChange,\n onFocus,\n onBlur,\n }: ElementProps) {\n const elements = useElements();\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n // 最新のハンドラを ref で保持(effect を毎回 re-run させないため)\n const handlersRef = useRef({ onReady, onChange, onFocus, onBlur });\n handlersRef.current = { onReady, onChange, onFocus, onBlur };\n\n useEffect(() => {\n if (!elements || !containerRef.current) return;\n\n const element = elements.create(type, options);\n element.mount(containerRef.current);\n\n const unsubs = [\n element.on('ready', (e) => handlersRef.current.onReady?.(e)),\n element.on('change', (e) => handlersRef.current.onChange?.(e)),\n element.on('focus', (e) => handlersRef.current.onFocus?.(e)),\n element.on('blur', (e) => handlersRef.current.onBlur?.(e)),\n ];\n\n return () => {\n unsubs.forEach((u) => u());\n element.unmount();\n };\n // options はマウント時にしか反映しない方針(Stripe と同じ挙動)。\n // 動的に style を変えたい場合は後続 PR で element.update() を追加。\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [elements]);\n\n // 親コンテナ(加盟店が指定した `.element-container` 等)の高さを\n // iframe に伝播させるため、wrapper は親のサイズを塗りつぶす。\n // style prop で上書き可能。\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n }\n ElementComponent.displayName = `HelloPay(${type})`;\n // getElement(CardNumberElement) のようにコンポーネント自身を型指定子として使えるよう、\n // 静的プロパティに type を生やしておく\n ElementComponent.__elementType = type as ElementType;\n return ElementComponent;\n}\n\nexport const CardNumberElement = createElementComponent('cardNumber');\nexport const CardExpiryElement = createElementComponent('cardExpiry');\nexport const CardCvcElement = createElementComponent('cardCvc');\nexport const CardholderNameElement = createElementComponent('cardholderName');\n\nexport type ElementComponent = typeof CardNumberElement;\n","/**\n * React hooks.\n *\n * useHelloPay / useElements は <Elements> Provider の Context から\n * SDK インスタンスと Elements セッションを取り出す。\n * SDK ロード完了までは null を返すので、ハンドラ内で null チェックする。\n */\n\nimport { useContext } from 'react';\nimport { ElementsContext } from './Elements';\nimport type { HelloPay, Elements } from './types';\n\nexport function useHelloPay(): HelloPay | null {\n return useContext(ElementsContext).hellopay;\n}\n\nexport function useElements(): Elements | null {\n return useContext(ElementsContext).elements;\n}\n","/**\n * Apple Pay ボタン。\n *\n * `useHelloPay()` から SDK を取り、対応端末(Safari + Apple Pay 利用可能)でのみ\n * Apple 公式スタイルのボタンを描画する。非対応端末では何も描画しない。\n *\n * click で `hellopay.applePay()` を同期呼び出しし(`new ApplePaySession` の\n * user gesture 要件のため)、結果を `onToken` / `onError` / `onCancel` に振り分ける。\n * SDK は Apple Pay トークンを返すだけで課金はしないので、加盟店は `onToken` で\n * 受けたトークンを自社バックエンドから決済 API に送って課金する。\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useHelloPay } from './hooks';\nimport type { ApplePayCard, ApplePayOptions, HelloPayError } from './types';\n\nexport interface ApplePayButtonProps {\n options: ApplePayOptions;\n /** トークン取得成功時。トークンを自社バックエンド経由で決済 API に送る。 */\n onToken: (token: string, card: ApplePayCard) => void;\n /** validation / network エラー時。 */\n onError?: (error: HelloPayError) => void;\n /** ユーザーが Apple Pay シートを閉じた時。 */\n onCancel?: () => void;\n /** Apple Pay ボタンの色。既定 `black`。 */\n buttonStyle?: 'black' | 'white' | 'white-outline';\n /** Apple Pay ボタンの種別(`plain` / `buy` / `pay` 等)。既定 `plain`。 */\n buttonType?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function ApplePayButton({\n options,\n onToken,\n onError,\n onCancel,\n buttonStyle = 'black',\n buttonType = 'plain',\n className,\n style,\n}: ApplePayButtonProps) {\n const hellopay = useHelloPay();\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n const [supported, setSupported] = useState(false);\n\n // canMakeApplePayPayments は ApplePaySession 依存で client-only のため effect で判定。\n useEffect(() => {\n setSupported(!!hellopay && hellopay.canMakeApplePayPayments());\n }, [hellopay]);\n\n // Apple Pay ボタンの見た目はベンダー CSS プロパティで指定する。React の style 型に\n // 無いため ref 経由で setProperty する。\n useEffect(() => {\n const el = buttonRef.current;\n if (!el) return;\n el.style.setProperty('-webkit-appearance', '-apple-pay-button');\n el.style.setProperty('-apple-pay-button-type', buttonType);\n el.style.setProperty('-apple-pay-button-style', buttonStyle);\n }, [supported, buttonType, buttonStyle]);\n\n // 最新のハンドラ / options を ref に保持し、click ハンドラを毎回作り直さない。\n const latest = useRef({ options, onToken, onError, onCancel, hellopay });\n latest.current = { options, onToken, onError, onCancel, hellopay };\n\n if (!supported) return null;\n\n const handleClick = () => {\n const current = latest.current;\n if (!current.hellopay) return;\n // user gesture 要件のため click ハンドラから同期呼び出しする。\n current.hellopay\n .applePay(current.options)\n .then((result) => {\n if (result.error) {\n if (result.error.type === 'cancel') {\n current.onCancel?.();\n } else {\n current.onError?.(result.error);\n }\n return;\n }\n current.onToken(result.token, result.card);\n })\n .catch((err: unknown) => {\n current.onError?.({\n type: 'config_error',\n code: 'apple_pay_error',\n message: err instanceof Error ? err.message : 'Apple Pay error',\n });\n });\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleClick}\n className={className}\n style={{ width: '100%', height: '44px', cursor: 'pointer', ...style }}\n />\n );\n}\n\nApplePayButton.displayName = 'HelloPay(ApplePayButton)';\n"],"mappings":"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","names":["script","useEffect","jsx","useEffect","useEffect","useRef","useState","jsx","useRef","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/loadHelloPay.ts","../src/Elements.tsx","../src/ElementComponents.tsx","../src/hooks.ts","../src/ApplePayButton.tsx"],"sourcesContent":["/**\n * HelloPay SDK ローダー。\n *\n * HelloPay 管理下のドメイン(js.hello-pay.app 等)から SDK を動的ロードし、\n * `window.HelloPay` ファクトリを解決する。\n *\n * - 同一 sdkUrl への呼び出しは script tag を 1 回だけ挿入(dedup)\n * - 異なる sdkUrl は独立したロードを維持(E2E / ローカル切り替え対応)\n * - SSR 環境では Promise<null> を解決(加盟店側で null チェック)\n * - 一時的な失敗はキャッシュせず次回呼び出しで再試行可能\n */\n\nimport type { HelloPay, HelloPayFactory } from './types';\n\n// SDK 本体は test / live どちらの publishable key でも同一ドメイン\n// (js.hello-pay.app) から配信する。test / live mode の切り替えは SDK 内部で\n// publishable key の prefix から解決し、test mode では接続先を test 環境\n// (*.stg.hello-pay.app) に向ける(hellopay.js と同じ方式)。\nconst DEFAULT_SDK_URL = 'https://js.hello-pay.app/v1/hellopay-elements.js';\n\n// sdkUrl 単位で script ロード Promise をキャッシュ。\n// loadPromise を単一変数にするとすべての URL で共有されてしまい、\n// 異なる sdkUrl(E2E / ローカル開発)が誤って同一ロードを共有する。\nconst scriptPromiseCache = new Map<string, Promise<HelloPayFactory | null>>();\n\n// publicKey + locale + sdkUrl の組み合わせごとに `HelloPaySDK` インスタンスをキャッシュ。\n// 加盟店が誤って inline で loadHelloPay を呼び出した場合でも同一 SDK インスタンスが\n// 返るようにして、Elements セッションの BroadcastChannel session ID ミスマッチを防ぐ。\nconst sdkCache = new Map<string, Promise<HelloPay | null>>();\n\n/** テスト専用: module-scope のキャッシュをすべて初期状態に戻す。 */\nexport function __resetLoadPromiseForTest(): void {\n scriptPromiseCache.clear();\n sdkCache.clear();\n}\n\nfunction normalizeSdkUrl(url: string): string {\n if (typeof window === 'undefined') return url;\n\n try {\n return new URL(url, window.location.href).href;\n } catch {\n return url;\n }\n}\n\nfunction normalizeLocale(locale?: string): string {\n return locale || 'ja';\n}\n\nfunction loadScript(url: string): Promise<HelloPayFactory | null> {\n if (typeof window === 'undefined') return Promise.resolve(null);\n\n // URL を正規化してプロトコル省略形などを統一する。\n // テスト / SSR 環境では window.location が http URL でない場合があるため try-catch で保護。\n const normalizedUrl = normalizeSdkUrl(url);\n\n return new Promise((resolve, reject) => {\n // script は URL 単位で管理する。異なる URL の script を削除せず共存させることで、\n // E2E / ローカル開発で異なる sdkUrl が並列で呼ばれても Promise が宙吊りにならない。\n // 状態は dataset.hellopayState に \"loading\" | \"loaded\" | \"error\" で刻む。\n const managedScripts = Array.from(\n document.querySelectorAll<HTMLScriptElement>('script[data-hellopay-sdk]'),\n );\n\n const existing = managedScripts.find((script) => {\n const rawUrl = script.dataset.hellopayUrl || script.src;\n if (!rawUrl) return false;\n try {\n return new URL(rawUrl, window.location.href).href === normalizedUrl;\n } catch {\n return rawUrl === normalizedUrl;\n }\n });\n\n if (existing) {\n const state = existing.dataset.hellopayState;\n if (state === 'loaded') {\n resolve(window.HelloPay ?? null);\n return;\n }\n if (state === 'loading') {\n existing.addEventListener('load', () => resolve(window.HelloPay ?? null), {\n once: true,\n });\n existing.addEventListener(\n 'error',\n () => reject(new Error('HelloPay SDK failed to load')),\n { once: true },\n );\n return;\n }\n // state === \"error\" or undefined: 失敗済み or 未知の状態なので作り直す\n existing.remove();\n }\n\n const script = document.createElement('script');\n script.src = normalizedUrl;\n script.async = true;\n script.dataset.hellopaySdk = 'true';\n script.dataset.hellopayUrl = normalizedUrl; // URL 比較用\n script.dataset.hellopayState = 'loading';\n script.addEventListener(\n 'load',\n () => {\n if (!window.HelloPay) {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK loaded but window.HelloPay is missing'));\n return;\n }\n script.dataset.hellopayState = 'loaded';\n resolve(window.HelloPay);\n },\n { once: true },\n );\n script.addEventListener(\n 'error',\n () => {\n script.dataset.hellopayState = 'error';\n reject(new Error('HelloPay SDK failed to load'));\n },\n { once: true },\n );\n document.head.appendChild(script);\n });\n}\n\nexport interface LoadHelloPayOptions {\n /** SDK 配信 URL(ローカル開発・E2E で差し替える用。省略時は js.hello-pay.app から配信し、test/live は SDK 内部で key prefix から解決) */\n sdkUrl?: string;\n locale?: string;\n}\n\n/**\n * HelloPay SDK をロードして初期化する。\n * モジュールスコープで 1 回だけ呼び、返り値の Promise を `<Elements>` に渡す。\n */\nexport function loadHelloPay(\n publicKey: string,\n options: LoadHelloPayOptions = {},\n): Promise<HelloPay | null> {\n const sdkUrl = normalizeSdkUrl(options.sdkUrl ?? DEFAULT_SDK_URL);\n const locale = normalizeLocale(options.locale);\n const cacheKey = `${publicKey}::${locale}::${sdkUrl}`;\n const cached = sdkCache.get(cacheKey);\n if (cached) return cached;\n\n let scriptPromise = scriptPromiseCache.get(sdkUrl);\n if (!scriptPromise) {\n scriptPromise = loadScript(sdkUrl).catch((err) => {\n // 一時的なロード失敗はキャッシュせず次回呼び出しで再試行可能にする\n scriptPromiseCache.delete(sdkUrl);\n throw err;\n });\n scriptPromiseCache.set(sdkUrl, scriptPromise);\n }\n\n const sdkPromise = scriptPromise\n .then((factory) => {\n if (!factory) return null; // SSR\n return factory(publicKey, { locale: options.locale });\n })\n .catch((err) => {\n // SDK インスタンス生成失敗は sdkCache から退避して次回呼び出しで再試行可能にする\n sdkCache.delete(cacheKey);\n throw err;\n });\n sdkCache.set(cacheKey, sdkPromise);\n return sdkPromise;\n}\n","/**\n * <Elements> Provider.\n *\n * loadHelloPay() の Promise を受け取り、SDK インスタンスと Elements セッションを\n * 子孫に Context 経由で供給する。\n */\n\nimport { createContext, useEffect, useState, type ReactNode } from 'react';\nimport type { HelloPay, Elements as ElementsSession, ElementsOptions } from './types';\n\nexport interface ElementsContextValue {\n hellopay: HelloPay | null;\n elements: ElementsSession | null;\n}\n\nconst EMPTY_CONTEXT: ElementsContextValue = {\n hellopay: null,\n elements: null,\n};\n\nexport const ElementsContext = createContext<ElementsContextValue>(EMPTY_CONTEXT);\n\nexport interface ElementsProps {\n /** loadHelloPay() の戻り値。モジュールスコープで 1 回だけ生成すること */\n hellopay: Promise<HelloPay | null> | HelloPay | null;\n /** `sessionClientSecret` を渡すと、預かりカードのセッションで要る欄だけを出す */\n options?: ElementsOptions;\n children: ReactNode;\n}\n\nexport function Elements({ hellopay: hellopayProp, options, children }: ElementsProps) {\n const sessionClientSecret = options?.sessionClientSecret;\n const [ctx, setCtx] = useState<ElementsContextValue>(EMPTY_CONTEXT);\n\n useEffect(() => {\n let cancelled = false;\n setCtx(EMPTY_CONTEXT);\n\n Promise.resolve(hellopayProp)\n .then((sdk) => {\n if (cancelled) return;\n if (!sdk) {\n setCtx(EMPTY_CONTEXT);\n return;\n }\n setCtx({\n hellopay: sdk,\n elements: sdk.elements(sessionClientSecret ? { sessionClientSecret } : undefined),\n });\n })\n .catch(() => {\n // SDK ロード失敗時は null のまま(unhandled rejection を防ぐ)\n if (cancelled) return;\n setCtx(EMPTY_CONTEXT);\n });\n\n return () => {\n cancelled = true;\n };\n }, [hellopayProp, sessionClientSecret]);\n\n return <ElementsContext.Provider value={ctx}>{children}</ElementsContext.Provider>;\n}\n","/**\n * Element コンポーネント。\n *\n * 各コンポーネントは 1 つの `<div>` コンテナを描画するだけで、\n * 実体(カード情報が入る iframe)はコア SDK が差し込む。\n *\n * React Strict Mode の二重 mount 対応:\n * - effect の cleanup で element.unmount() を必ず呼ぶ\n * - unmount 後は Elements._elements 配列から除外される(コア SDK 側で対応済)\n * - 結果として Strict Mode で effect が再実行されても古い iframe は残らない\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useElements } from './hooks';\nimport type {\n ElementType,\n ElementOptions,\n ElementChangeEvent,\n ElementFocusEvent,\n ElementReadyEvent,\n} from './types';\n\nexport interface ElementProps {\n options?: ElementOptions;\n className?: string;\n style?: CSSProperties;\n onReady?: (e: ElementReadyEvent) => void;\n onChange?: (e: ElementChangeEvent) => void;\n onFocus?: (e: ElementFocusEvent) => void;\n onBlur?: (e: ElementFocusEvent) => void;\n}\n\nconst PROBE_PX = 100000;\n\n/**\n * コンテナの幅・高さのうち、中身に追従する(加盟店が寸法を決めていない)軸を返す。\n */\nfunction contentSizedAxes(container: HTMLElement) {\n // display: none の祖先の中ではレイアウトが無いので、computed value で見る。\n if (container.getClientRects().length === 0) {\n const computed = getComputedStyle(container);\n const isAuto = (v: string) => v === '' || v === 'auto';\n return { width: isAuto(computed.width), height: isAuto(computed.height) };\n }\n // computed の height で判定すると、border-box の padding・border や min-height で\n // 0 にならず見誤るので、大きな子を一時的に入れて寸法が伸びるかで確かめる。\n // 祖先の transform(モーダルの scale のアニメーションなど)に左右されないよう、\n // getBoundingClientRect ではなくレイアウト上の寸法の offset* で比べる。\n const before = { width: container.offsetWidth, height: container.offsetHeight };\n const probe = container.ownerDocument.createElement('div');\n probe.style.cssText = `width: ${PROBE_PX}px; height: ${PROBE_PX}px;`;\n container.appendChild(probe);\n const grows = {\n width: container.offsetWidth > before.width,\n height: container.offsetHeight > before.height,\n };\n probe.remove();\n return grows;\n}\n\n/**\n * 指定 type の Element を管理する React コンポーネントを生成する。\n */\nfunction createElementComponent(type: ElementType) {\n function ElementComponent({\n options,\n className,\n style,\n onReady,\n onChange,\n onFocus,\n onBlur,\n }: ElementProps) {\n const elements = useElements();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [fill, setFill] = useState({ width: false, height: false });\n\n // 最新のハンドラを ref で保持(effect を毎回 re-run させないため)\n const handlersRef = useRef({ onReady, onChange, onFocus, onBlur });\n handlersRef.current = { onReady, onChange, onFocus, onBlur };\n\n useEffect(() => {\n const container = containerRef.current;\n if (!elements || !container) return;\n\n // 100% を当てた後に測り直すと親の寸法に追従して見えるので、立てた軸は戻さない。\n const unset = contentSizedAxes(container);\n setFill((prev) => ({\n width: prev.width || unset.width,\n height: prev.height || unset.height,\n }));\n\n const element = elements.create(type, options);\n element.mount(container);\n\n const unsubs = [\n element.on('ready', (e) => handlersRef.current.onReady?.(e)),\n element.on('change', (e) => handlersRef.current.onChange?.(e)),\n element.on('focus', (e) => handlersRef.current.onFocus?.(e)),\n element.on('blur', (e) => handlersRef.current.onBlur?.(e)),\n ];\n\n return () => {\n unsubs.forEach((u) => u());\n element.unmount();\n };\n // options はマウント時にしか反映しない方針(Stripe と同じ挙動)。\n // 動的に style を変えたい場合は後続 PR で element.update() を追加。\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [elements]);\n\n // インラインの既定値は className の寸法に勝つので、加盟店が決めていない軸だけに付ける。\n // 判定は mount 時の 1 回だけなので、後から寸法を変えるなら style で渡す。\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n ...(fill.width && { width: '100%' }),\n ...(fill.height && { height: '100%' }),\n ...style,\n }}\n />\n );\n }\n ElementComponent.displayName = `HelloPay(${type})`;\n // getElement(CardNumberElement) のようにコンポーネント自身を型指定子として使えるよう、\n // 静的プロパティに type を生やしておく\n ElementComponent.__elementType = type as ElementType;\n return ElementComponent;\n}\n\nexport const CardNumberElement = createElementComponent('cardNumber');\nexport const CardExpiryElement = createElementComponent('cardExpiry');\nexport const CardCvcElement = createElementComponent('cardCvc');\nexport const CardholderNameElement = createElementComponent('cardholderName');\n// 韓国発行カードの Toss 登録に必要な暗証番号の先頭 2 桁。表示・非表示は\n// 発行国の判定結果に応じて呼び出し側が mount / unmount で制御する。\nexport const CardPasswordElement = createElementComponent('cardPassword');\n\nexport type ElementComponent = typeof CardNumberElement;\n","/**\n * React hooks.\n *\n * useHelloPay / useElements は <Elements> Provider の Context から\n * SDK インスタンスと Elements セッションを取り出す。\n * SDK ロード完了までは null を返すので、ハンドラ内で null チェックする。\n */\n\nimport { useContext } from 'react';\nimport { ElementsContext } from './Elements';\nimport type { HelloPay, Elements } from './types';\n\nexport function useHelloPay(): HelloPay | null {\n return useContext(ElementsContext).hellopay;\n}\n\nexport function useElements(): Elements | null {\n return useContext(ElementsContext).elements;\n}\n","/**\n * Apple Pay ボタン。\n *\n * `useHelloPay()` から SDK を取り、対応端末(Safari + Apple Pay 利用可能)でのみ\n * Apple 公式スタイルのボタンを描画する。非対応端末では何も描画しない。\n *\n * click で `hellopay.applePay()` を同期呼び出しし(`new ApplePaySession` の\n * user gesture 要件のため)、結果を `onToken` / `onError` / `onCancel` に振り分ける。\n * SDK は Apple Pay トークンを返すだけで課金はしないので、加盟店は `onToken` で\n * 受けたトークンを自社バックエンドから決済 API に送って課金する。\n */\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { useHelloPay } from './hooks';\nimport type { ApplePayCard, ApplePayOptions, HelloPayError } from './types';\n\nexport interface ApplePayButtonProps {\n options: ApplePayOptions;\n /** トークン取得成功時。トークンを自社バックエンド経由で決済 API に送る。 */\n onToken: (token: string, card: ApplePayCard) => void;\n /** validation / network エラー時。 */\n onError?: (error: HelloPayError) => void;\n /** ユーザーが Apple Pay シートを閉じた時。 */\n onCancel?: () => void;\n /** Apple Pay ボタンの色。既定 `black`。 */\n buttonStyle?: 'black' | 'white' | 'white-outline';\n /** Apple Pay ボタンの種別(`plain` / `buy` / `pay` 等)。既定 `plain`。 */\n buttonType?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function ApplePayButton({\n options,\n onToken,\n onError,\n onCancel,\n buttonStyle = 'black',\n buttonType = 'plain',\n className,\n style,\n}: ApplePayButtonProps) {\n const hellopay = useHelloPay();\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n const [supported, setSupported] = useState(false);\n\n // canMakeApplePayPayments は ApplePaySession 依存で client-only のため effect で判定。\n useEffect(() => {\n setSupported(!!hellopay && hellopay.canMakeApplePayPayments());\n }, [hellopay]);\n\n // Apple Pay ボタンの見た目はベンダー CSS プロパティで指定する。React の style 型に\n // 無いため ref 経由で setProperty する。\n useEffect(() => {\n const el = buttonRef.current;\n if (!el) return;\n el.style.setProperty('-webkit-appearance', '-apple-pay-button');\n el.style.setProperty('-apple-pay-button-type', buttonType);\n el.style.setProperty('-apple-pay-button-style', buttonStyle);\n }, [supported, buttonType, buttonStyle]);\n\n // 最新のハンドラ / options を ref に保持し、click ハンドラを毎回作り直さない。\n const latest = useRef({ options, onToken, onError, onCancel, hellopay });\n latest.current = { options, onToken, onError, onCancel, hellopay };\n\n if (!supported) return null;\n\n const handleClick = () => {\n const current = latest.current;\n if (!current.hellopay) return;\n // user gesture 要件のため click ハンドラから同期呼び出しする。\n current.hellopay\n .applePay(current.options)\n .then((result) => {\n if (result.error) {\n if (result.error.type === 'cancel') {\n current.onCancel?.();\n } else {\n current.onError?.(result.error);\n }\n return;\n }\n current.onToken(result.token, result.card);\n })\n .catch((err: unknown) => {\n current.onError?.({\n type: 'config_error',\n code: 'apple_pay_error',\n message: err instanceof Error ? err.message : 'Apple Pay error',\n });\n });\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleClick}\n className={className}\n style={{ width: '100%', height: '44px', cursor: 'pointer', ...style }}\n />\n );\n}\n\nApplePayButton.displayName = 'HelloPay(ApplePayButton)';\n"],"mappings":"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","names":["script","useEffect","useState","jsx","useState","useEffect","useEffect","useRef","useState","jsx","useRef","useState","useEffect"]}
|