@thinkingcat/subscription-ui 1.0.8 → 1.1.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/dist/index.d.mts +16 -9
- package/dist/index.d.ts +16 -9
- package/dist/index.js +36 -83
- package/dist/index.mjs +26 -83
- package/package.json +6 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
|
|
3
3
|
interface Plan {
|
|
4
4
|
id: string;
|
|
@@ -13,12 +13,12 @@ interface Plan {
|
|
|
13
13
|
}
|
|
14
14
|
interface Card {
|
|
15
15
|
id: string;
|
|
16
|
-
maskedCardNumber: string;
|
|
17
|
-
cardBrand: string;
|
|
18
|
-
cardType?: string;
|
|
19
|
-
cardHolderName
|
|
20
|
-
expiryMonth
|
|
21
|
-
expiryYear
|
|
16
|
+
maskedCardNumber: string | null;
|
|
17
|
+
cardBrand: string | null;
|
|
18
|
+
cardType?: string | null;
|
|
19
|
+
cardHolderName?: string | null;
|
|
20
|
+
expiryMonth?: string | null;
|
|
21
|
+
expiryYear?: string | null;
|
|
22
22
|
isDefault: boolean;
|
|
23
23
|
}
|
|
24
24
|
interface SubscribePageConfig {
|
|
@@ -43,6 +43,13 @@ interface SubscribePageConfig {
|
|
|
43
43
|
* - "system": OS/HTML 다크모드 설정 자동 적용
|
|
44
44
|
*/
|
|
45
45
|
theme?: "light" | "dark" | "system";
|
|
46
|
+
/**
|
|
47
|
+
* 포트원 상점 ID (공개값, 제품의 NEXT_PUBLIC_PORTONE_STORE_ID).
|
|
48
|
+
* 카드 등록을 포트원 결제창으로 진행하는 데 필요하다 — 없으면 카드 추가가 막힌다.
|
|
49
|
+
*/
|
|
50
|
+
portOneStoreId?: string;
|
|
51
|
+
/** 포트원 채널 키 (공개값, NEXT_PUBLIC_PORTONE_CHANNEL_KEY). */
|
|
52
|
+
portOneChannelKey?: string;
|
|
46
53
|
}
|
|
47
54
|
interface Subscription {
|
|
48
55
|
id: string;
|
|
@@ -93,8 +100,8 @@ interface SubscriptionManagementConfig {
|
|
|
93
100
|
theme?: "light" | "dark" | "system";
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig):
|
|
103
|
+
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, portOneStoreId, portOneChannelKey, }: SubscribePageConfig): react.JSX.Element;
|
|
97
104
|
|
|
98
|
-
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig):
|
|
105
|
+
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react.JSX.Element;
|
|
99
106
|
|
|
100
107
|
export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
|
|
3
3
|
interface Plan {
|
|
4
4
|
id: string;
|
|
@@ -13,12 +13,12 @@ interface Plan {
|
|
|
13
13
|
}
|
|
14
14
|
interface Card {
|
|
15
15
|
id: string;
|
|
16
|
-
maskedCardNumber: string;
|
|
17
|
-
cardBrand: string;
|
|
18
|
-
cardType?: string;
|
|
19
|
-
cardHolderName
|
|
20
|
-
expiryMonth
|
|
21
|
-
expiryYear
|
|
16
|
+
maskedCardNumber: string | null;
|
|
17
|
+
cardBrand: string | null;
|
|
18
|
+
cardType?: string | null;
|
|
19
|
+
cardHolderName?: string | null;
|
|
20
|
+
expiryMonth?: string | null;
|
|
21
|
+
expiryYear?: string | null;
|
|
22
22
|
isDefault: boolean;
|
|
23
23
|
}
|
|
24
24
|
interface SubscribePageConfig {
|
|
@@ -43,6 +43,13 @@ interface SubscribePageConfig {
|
|
|
43
43
|
* - "system": OS/HTML 다크모드 설정 자동 적용
|
|
44
44
|
*/
|
|
45
45
|
theme?: "light" | "dark" | "system";
|
|
46
|
+
/**
|
|
47
|
+
* 포트원 상점 ID (공개값, 제품의 NEXT_PUBLIC_PORTONE_STORE_ID).
|
|
48
|
+
* 카드 등록을 포트원 결제창으로 진행하는 데 필요하다 — 없으면 카드 추가가 막힌다.
|
|
49
|
+
*/
|
|
50
|
+
portOneStoreId?: string;
|
|
51
|
+
/** 포트원 채널 키 (공개값, NEXT_PUBLIC_PORTONE_CHANNEL_KEY). */
|
|
52
|
+
portOneChannelKey?: string;
|
|
46
53
|
}
|
|
47
54
|
interface Subscription {
|
|
48
55
|
id: string;
|
|
@@ -93,8 +100,8 @@ interface SubscriptionManagementConfig {
|
|
|
93
100
|
theme?: "light" | "dark" | "system";
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig):
|
|
103
|
+
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, portOneStoreId, portOneChannelKey, }: SubscribePageConfig): react.JSX.Element;
|
|
97
104
|
|
|
98
|
-
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig):
|
|
105
|
+
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react.JSX.Element;
|
|
99
106
|
|
|
100
107
|
export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
9
|
var __export = (target, all) => {
|
|
8
10
|
for (var name in all)
|
|
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/index.ts
|
|
@@ -28,6 +38,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
38
|
|
|
29
39
|
// src/components/SubscribePage.tsx
|
|
30
40
|
var import_react = require("react");
|
|
41
|
+
var import_v2 = __toESM(require("@portone/browser-sdk/v2"));
|
|
31
42
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
32
43
|
var DEFAULT_PRIMARY = "#60A5FA";
|
|
33
44
|
function SubscribePage({
|
|
@@ -38,7 +49,9 @@ function SubscribePage({
|
|
|
38
49
|
serviceName = "\uC11C\uBE44\uC2A4",
|
|
39
50
|
primaryColor = DEFAULT_PRIMARY,
|
|
40
51
|
showExpiredBanner = true,
|
|
41
|
-
theme = "system"
|
|
52
|
+
theme = "system",
|
|
53
|
+
portOneStoreId,
|
|
54
|
+
portOneChannelKey
|
|
42
55
|
}) {
|
|
43
56
|
const [step, setStep] = (0, import_react.useState)("plan");
|
|
44
57
|
const [plans, setPlans] = (0, import_react.useState)([]);
|
|
@@ -49,15 +62,6 @@ function SubscribePage({
|
|
|
49
62
|
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
50
63
|
const [error, setError] = (0, import_react.useState)("");
|
|
51
64
|
const [success, setSuccess] = (0, import_react.useState)(false);
|
|
52
|
-
const [showCardForm, setShowCardForm] = (0, import_react.useState)(false);
|
|
53
|
-
const [cardForm, setCardForm] = (0, import_react.useState)({
|
|
54
|
-
cardNumber: "",
|
|
55
|
-
expiryDate: "",
|
|
56
|
-
cvc: "",
|
|
57
|
-
cardPassword: "",
|
|
58
|
-
cardHolderName: "",
|
|
59
|
-
birthNumber: ""
|
|
60
|
-
});
|
|
61
65
|
const [cardError, setCardError] = (0, import_react.useState)("");
|
|
62
66
|
const [cardSubmitting, setCardSubmitting] = (0, import_react.useState)(false);
|
|
63
67
|
(0, import_react.useEffect)(() => {
|
|
@@ -90,15 +94,25 @@ function SubscribePage({
|
|
|
90
94
|
}
|
|
91
95
|
async function handleRegisterCard() {
|
|
92
96
|
setCardError("");
|
|
97
|
+
if (!portOneStoreId || !portOneChannelKey) {
|
|
98
|
+
setCardError("\uACB0\uC81C \uC124\uC815\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
93
101
|
setCardSubmitting(true);
|
|
94
102
|
try {
|
|
103
|
+
const issue = await import_v2.default.requestIssueBillingKey({
|
|
104
|
+
storeId: portOneStoreId,
|
|
105
|
+
channelKey: portOneChannelKey,
|
|
106
|
+
billingKeyMethod: "CARD"
|
|
107
|
+
});
|
|
108
|
+
if (!issue || issue.code !== void 0) {
|
|
109
|
+
setCardError(issue?.message || "\uCE74\uB4DC \uB4F1\uB85D\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
95
112
|
const res = await fetch(cardsApiPath, {
|
|
96
113
|
method: "POST",
|
|
97
114
|
headers: { "Content-Type": "application/json" },
|
|
98
|
-
body: JSON.stringify({
|
|
99
|
-
...cardForm,
|
|
100
|
-
cardNumber: cardForm.cardNumber.replace(/\s/g, "")
|
|
101
|
-
})
|
|
115
|
+
body: JSON.stringify({ billingKey: issue.billingKey })
|
|
102
116
|
});
|
|
103
117
|
const data = await res.json();
|
|
104
118
|
if (!data.success) {
|
|
@@ -107,8 +121,6 @@ function SubscribePage({
|
|
|
107
121
|
}
|
|
108
122
|
await fetchCards();
|
|
109
123
|
setSelectedCard(data.card);
|
|
110
|
-
setShowCardForm(false);
|
|
111
|
-
setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
|
|
112
124
|
} catch {
|
|
113
125
|
setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
114
126
|
} finally {
|
|
@@ -255,7 +267,7 @@ function SubscribePage({
|
|
|
255
267
|
] }),
|
|
256
268
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
257
269
|
card.isDefault && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "px-2 py-0.5 bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400 text-xs rounded-full border border-blue-200 dark:border-blue-500/30", children: "\uAE30\uBCF8" }),
|
|
258
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
|
|
270
|
+
card.expiryMonth && card.expiryYear && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
|
|
259
271
|
card.expiryMonth,
|
|
260
272
|
"/",
|
|
261
273
|
card.expiryYear
|
|
@@ -265,75 +277,16 @@ function SubscribePage({
|
|
|
265
277
|
},
|
|
266
278
|
card.id
|
|
267
279
|
)) }),
|
|
268
|
-
|
|
280
|
+
cardError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-3 p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
269
282
|
"button",
|
|
270
283
|
{
|
|
271
|
-
onClick:
|
|
272
|
-
|
|
273
|
-
|
|
284
|
+
onClick: handleRegisterCard,
|
|
285
|
+
disabled: cardSubmitting,
|
|
286
|
+
className: "w-full p-4 rounded-xl border border-dashed border-gray-300 dark:border-white/20 text-gray-400 dark:text-gray-500 hover:border-gray-400 dark:hover:border-white/40 hover:text-gray-500 dark:hover:text-gray-300 disabled:opacity-50 transition-all text-sm",
|
|
287
|
+
children: cardSubmitting ? "\uCE74\uB4DC \uB4F1\uB85D \uCC3D\uC744 \uC5EC\uB294 \uC911..." : "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
|
|
274
288
|
}
|
|
275
|
-
)
|
|
276
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
|
|
277
|
-
cardError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
|
|
278
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
|
|
279
|
-
{ label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
|
|
280
|
-
{ label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
|
|
281
|
-
{ label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
|
|
282
|
-
{ label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
|
|
283
|
-
{ label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
|
|
284
|
-
{ label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
|
|
285
|
-
].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: colSpan ? "sm:col-span-2" : "", children: [
|
|
286
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
|
|
287
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
288
|
-
"input",
|
|
289
|
-
{
|
|
290
|
-
type,
|
|
291
|
-
placeholder,
|
|
292
|
-
value: cardForm[key],
|
|
293
|
-
onChange: (e) => {
|
|
294
|
-
const val = e.target.value;
|
|
295
|
-
if (key === "cardNumber") {
|
|
296
|
-
const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
|
|
297
|
-
setCardForm((f) => ({ ...f, [key]: formatted }));
|
|
298
|
-
} else if (key === "expiryDate") {
|
|
299
|
-
const digits = val.replace(/\D/g, "");
|
|
300
|
-
let formatted = digits;
|
|
301
|
-
if (digits.length > 2) {
|
|
302
|
-
formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
|
|
303
|
-
}
|
|
304
|
-
setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
|
|
305
|
-
} else {
|
|
306
|
-
setCardForm((f) => ({ ...f, [key]: val }));
|
|
307
|
-
}
|
|
308
|
-
},
|
|
309
|
-
className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 dark:focus:border-blue-500 transition-colors"
|
|
310
|
-
}
|
|
311
|
-
)
|
|
312
|
-
] }, key)) }),
|
|
313
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2 pt-1", children: [
|
|
314
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
315
|
-
"button",
|
|
316
|
-
{
|
|
317
|
-
onClick: () => {
|
|
318
|
-
setShowCardForm(false);
|
|
319
|
-
setCardError("");
|
|
320
|
-
},
|
|
321
|
-
className: "flex-1 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
|
|
322
|
-
children: "\uCDE8\uC18C"
|
|
323
|
-
}
|
|
324
|
-
),
|
|
325
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
326
|
-
"button",
|
|
327
|
-
{
|
|
328
|
-
onClick: handleRegisterCard,
|
|
329
|
-
disabled: cardSubmitting,
|
|
330
|
-
style: { backgroundColor: primaryColor },
|
|
331
|
-
className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
|
|
332
|
-
children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
|
|
333
|
-
}
|
|
334
|
-
)
|
|
335
|
-
] })
|
|
336
|
-
] }),
|
|
289
|
+
),
|
|
337
290
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3 mt-6", children: [
|
|
338
291
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
339
292
|
"button",
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/components/SubscribePage.tsx
|
|
4
4
|
import { useState, useEffect } from "react";
|
|
5
|
+
import PortOne from "@portone/browser-sdk/v2";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
var DEFAULT_PRIMARY = "#60A5FA";
|
|
7
8
|
function SubscribePage({
|
|
@@ -12,7 +13,9 @@ function SubscribePage({
|
|
|
12
13
|
serviceName = "\uC11C\uBE44\uC2A4",
|
|
13
14
|
primaryColor = DEFAULT_PRIMARY,
|
|
14
15
|
showExpiredBanner = true,
|
|
15
|
-
theme = "system"
|
|
16
|
+
theme = "system",
|
|
17
|
+
portOneStoreId,
|
|
18
|
+
portOneChannelKey
|
|
16
19
|
}) {
|
|
17
20
|
const [step, setStep] = useState("plan");
|
|
18
21
|
const [plans, setPlans] = useState([]);
|
|
@@ -23,15 +26,6 @@ function SubscribePage({
|
|
|
23
26
|
const [submitting, setSubmitting] = useState(false);
|
|
24
27
|
const [error, setError] = useState("");
|
|
25
28
|
const [success, setSuccess] = useState(false);
|
|
26
|
-
const [showCardForm, setShowCardForm] = useState(false);
|
|
27
|
-
const [cardForm, setCardForm] = useState({
|
|
28
|
-
cardNumber: "",
|
|
29
|
-
expiryDate: "",
|
|
30
|
-
cvc: "",
|
|
31
|
-
cardPassword: "",
|
|
32
|
-
cardHolderName: "",
|
|
33
|
-
birthNumber: ""
|
|
34
|
-
});
|
|
35
29
|
const [cardError, setCardError] = useState("");
|
|
36
30
|
const [cardSubmitting, setCardSubmitting] = useState(false);
|
|
37
31
|
useEffect(() => {
|
|
@@ -64,15 +58,25 @@ function SubscribePage({
|
|
|
64
58
|
}
|
|
65
59
|
async function handleRegisterCard() {
|
|
66
60
|
setCardError("");
|
|
61
|
+
if (!portOneStoreId || !portOneChannelKey) {
|
|
62
|
+
setCardError("\uACB0\uC81C \uC124\uC815\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
67
65
|
setCardSubmitting(true);
|
|
68
66
|
try {
|
|
67
|
+
const issue = await PortOne.requestIssueBillingKey({
|
|
68
|
+
storeId: portOneStoreId,
|
|
69
|
+
channelKey: portOneChannelKey,
|
|
70
|
+
billingKeyMethod: "CARD"
|
|
71
|
+
});
|
|
72
|
+
if (!issue || issue.code !== void 0) {
|
|
73
|
+
setCardError(issue?.message || "\uCE74\uB4DC \uB4F1\uB85D\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
69
76
|
const res = await fetch(cardsApiPath, {
|
|
70
77
|
method: "POST",
|
|
71
78
|
headers: { "Content-Type": "application/json" },
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
...cardForm,
|
|
74
|
-
cardNumber: cardForm.cardNumber.replace(/\s/g, "")
|
|
75
|
-
})
|
|
79
|
+
body: JSON.stringify({ billingKey: issue.billingKey })
|
|
76
80
|
});
|
|
77
81
|
const data = await res.json();
|
|
78
82
|
if (!data.success) {
|
|
@@ -81,8 +85,6 @@ function SubscribePage({
|
|
|
81
85
|
}
|
|
82
86
|
await fetchCards();
|
|
83
87
|
setSelectedCard(data.card);
|
|
84
|
-
setShowCardForm(false);
|
|
85
|
-
setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
|
|
86
88
|
} catch {
|
|
87
89
|
setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
88
90
|
} finally {
|
|
@@ -229,7 +231,7 @@ function SubscribePage({
|
|
|
229
231
|
] }),
|
|
230
232
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
231
233
|
card.isDefault && /* @__PURE__ */ jsx("span", { className: "px-2 py-0.5 bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400 text-xs rounded-full border border-blue-200 dark:border-blue-500/30", children: "\uAE30\uBCF8" }),
|
|
232
|
-
/* @__PURE__ */ jsxs("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
|
|
234
|
+
card.expiryMonth && card.expiryYear && /* @__PURE__ */ jsxs("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
|
|
233
235
|
card.expiryMonth,
|
|
234
236
|
"/",
|
|
235
237
|
card.expiryYear
|
|
@@ -239,75 +241,16 @@ function SubscribePage({
|
|
|
239
241
|
},
|
|
240
242
|
card.id
|
|
241
243
|
)) }),
|
|
242
|
-
|
|
244
|
+
cardError && /* @__PURE__ */ jsx("div", { className: "mb-3 p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
243
246
|
"button",
|
|
244
247
|
{
|
|
245
|
-
onClick:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
onClick: handleRegisterCard,
|
|
249
|
+
disabled: cardSubmitting,
|
|
250
|
+
className: "w-full p-4 rounded-xl border border-dashed border-gray-300 dark:border-white/20 text-gray-400 dark:text-gray-500 hover:border-gray-400 dark:hover:border-white/40 hover:text-gray-500 dark:hover:text-gray-300 disabled:opacity-50 transition-all text-sm",
|
|
251
|
+
children: cardSubmitting ? "\uCE74\uB4DC \uB4F1\uB85D \uCC3D\uC744 \uC5EC\uB294 \uC911..." : "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
|
|
248
252
|
}
|
|
249
|
-
)
|
|
250
|
-
/* @__PURE__ */ jsx("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
|
|
251
|
-
cardError && /* @__PURE__ */ jsx("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
|
|
252
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
|
|
253
|
-
{ label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
|
|
254
|
-
{ label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
|
|
255
|
-
{ label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
|
|
256
|
-
{ label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
|
|
257
|
-
{ label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
|
|
258
|
-
{ label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
|
|
259
|
-
].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ jsxs("div", { className: colSpan ? "sm:col-span-2" : "", children: [
|
|
260
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
|
|
261
|
-
/* @__PURE__ */ jsx(
|
|
262
|
-
"input",
|
|
263
|
-
{
|
|
264
|
-
type,
|
|
265
|
-
placeholder,
|
|
266
|
-
value: cardForm[key],
|
|
267
|
-
onChange: (e) => {
|
|
268
|
-
const val = e.target.value;
|
|
269
|
-
if (key === "cardNumber") {
|
|
270
|
-
const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
|
|
271
|
-
setCardForm((f) => ({ ...f, [key]: formatted }));
|
|
272
|
-
} else if (key === "expiryDate") {
|
|
273
|
-
const digits = val.replace(/\D/g, "");
|
|
274
|
-
let formatted = digits;
|
|
275
|
-
if (digits.length > 2) {
|
|
276
|
-
formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
|
|
277
|
-
}
|
|
278
|
-
setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
|
|
279
|
-
} else {
|
|
280
|
-
setCardForm((f) => ({ ...f, [key]: val }));
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 dark:focus:border-blue-500 transition-colors"
|
|
284
|
-
}
|
|
285
|
-
)
|
|
286
|
-
] }, key)) }),
|
|
287
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
288
|
-
/* @__PURE__ */ jsx(
|
|
289
|
-
"button",
|
|
290
|
-
{
|
|
291
|
-
onClick: () => {
|
|
292
|
-
setShowCardForm(false);
|
|
293
|
-
setCardError("");
|
|
294
|
-
},
|
|
295
|
-
className: "flex-1 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
|
|
296
|
-
children: "\uCDE8\uC18C"
|
|
297
|
-
}
|
|
298
|
-
),
|
|
299
|
-
/* @__PURE__ */ jsx(
|
|
300
|
-
"button",
|
|
301
|
-
{
|
|
302
|
-
onClick: handleRegisterCard,
|
|
303
|
-
disabled: cardSubmitting,
|
|
304
|
-
style: { backgroundColor: primaryColor },
|
|
305
|
-
className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
|
|
306
|
-
children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
] })
|
|
310
|
-
] }),
|
|
253
|
+
),
|
|
311
254
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-3 mt-6", children: [
|
|
312
255
|
/* @__PURE__ */ jsx(
|
|
313
256
|
"button",
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.0
|
|
6
|
+
"version": "1.1.0",
|
|
7
7
|
"description": "Subscription UI components for ThinkingCat services",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
@@ -45,5 +45,8 @@
|
|
|
45
45
|
"files": [
|
|
46
46
|
"dist",
|
|
47
47
|
"README.md"
|
|
48
|
-
]
|
|
49
|
-
|
|
48
|
+
],
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@portone/browser-sdk": "^0.0.19"
|
|
51
|
+
}
|
|
52
|
+
}
|