@dj-test/payment-sdk 1.0.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 +325 -0
- package/dist/chunk-3AOMSDLI.mjs +223 -0
- package/dist/chunk-3RYOE2SM.mjs +205 -0
- package/dist/chunk-KJPPSTIW.mjs +216 -0
- package/dist/chunk-MJBDAWQX.mjs +211 -0
- package/dist/chunk-PR2U6WKA.mjs +209 -0
- package/dist/chunk-T6PKEDYJ.mjs +207 -0
- package/dist/index.d.mts +3905 -0
- package/dist/index.d.ts +3905 -0
- package/dist/index.js +3849 -0
- package/dist/index.mjs +3789 -0
- package/dist/orders-64PVYGCW.mjs +12 -0
- package/dist/orders-6A74XIYV.mjs +12 -0
- package/dist/orders-BNESRMMA.mjs +12 -0
- package/dist/orders-EWAUCVY3.mjs +12 -0
- package/dist/orders-SBMFAKCC.mjs +12 -0
- package/dist/orders-UO6XKPAX.mjs +12 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3849 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
API_ENDPOINTS: () => API_ENDPOINTS,
|
|
34
|
+
ChainTypes: () => ChainType,
|
|
35
|
+
DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
|
|
36
|
+
ENVIRONMENT_URLS: () => ENVIRONMENT_URLS,
|
|
37
|
+
ERROR_CODES: () => ERROR_CODES,
|
|
38
|
+
EvmWalletAdapter: () => EvmWalletAdapter,
|
|
39
|
+
MIN_WITHDRAWAL_AMOUNT: () => MIN_WITHDRAWAL_AMOUNT,
|
|
40
|
+
ORDER_EXPIRY_MINUTES: () => ORDER_EXPIRY_MINUTES,
|
|
41
|
+
OrderPayment: () => OrderPayment,
|
|
42
|
+
PAYMENT_DOMAINS: () => PAYMENT_DOMAINS,
|
|
43
|
+
PayWithAddress: () => PayWithAddress,
|
|
44
|
+
PayWithWallet: () => PayWithWallet,
|
|
45
|
+
PaymentButton: () => PaymentButton,
|
|
46
|
+
PaymentContext: () => PaymentContext,
|
|
47
|
+
PaymentFlow: () => PaymentFlow,
|
|
48
|
+
PaymentModal: () => PaymentModal,
|
|
49
|
+
PaymentProvider: () => PaymentProvider,
|
|
50
|
+
PaymentQRCode: () => PaymentQRCode,
|
|
51
|
+
PaymentQRModal: () => PaymentQRModal,
|
|
52
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
53
|
+
SUPPORTED_CHAINS: () => SUPPORTED_CHAINS,
|
|
54
|
+
TRON_NETWORKS: () => TRON_NETWORKS,
|
|
55
|
+
TronWalletAdapter: () => TronWalletAdapter,
|
|
56
|
+
WalletAdapterFactory: () => WalletAdapterFactory,
|
|
57
|
+
WalletProvider: () => WalletProvider,
|
|
58
|
+
WalletTypes: () => WalletType,
|
|
59
|
+
coinsAPI: () => coins_exports,
|
|
60
|
+
config: () => config,
|
|
61
|
+
createWagmiConfig: () => createWagmiConfig,
|
|
62
|
+
detectChainType: () => detectChainType,
|
|
63
|
+
generatePaymentQRUrl: () => generatePaymentQRUrl,
|
|
64
|
+
getChainById: () => getChainById,
|
|
65
|
+
getErrorMessage: () => getErrorMessage,
|
|
66
|
+
getEvmChainById: () => getEvmChainById,
|
|
67
|
+
getPaymentDomain: () => getPaymentDomain,
|
|
68
|
+
handleAPIError: () => handleAPIError,
|
|
69
|
+
isEvmAddress: () => isEvmAddress,
|
|
70
|
+
isTronAddress: () => isTronAddress,
|
|
71
|
+
isValidAddress: () => isValidAddress,
|
|
72
|
+
ordersAPI: () => orders_exports,
|
|
73
|
+
supportedChains: () => supportedChains,
|
|
74
|
+
supportedEvmChains: () => supportedEvmChains,
|
|
75
|
+
useOrder: () => useOrder,
|
|
76
|
+
usePayment: () => usePayment,
|
|
77
|
+
useWallet: () => useWallet,
|
|
78
|
+
useWalletAdapter: () => useWalletAdapter,
|
|
79
|
+
useWalletContext: () => useWalletContext,
|
|
80
|
+
validation: () => validation_exports
|
|
81
|
+
});
|
|
82
|
+
module.exports = __toCommonJS(index_exports);
|
|
83
|
+
|
|
84
|
+
// src/core/constants.ts
|
|
85
|
+
var SDK_VERSION = "1.0.0";
|
|
86
|
+
var API_ENDPOINTS = {
|
|
87
|
+
ORDERS: "/payments",
|
|
88
|
+
PRODUCTS: "/products"
|
|
89
|
+
};
|
|
90
|
+
var ENVIRONMENT_URLS = {
|
|
91
|
+
production: "https://pay.com",
|
|
92
|
+
sandbox: "http://localhost:8080"
|
|
93
|
+
};
|
|
94
|
+
var PAYMENT_DOMAINS = {
|
|
95
|
+
production: "https://pay-home.com",
|
|
96
|
+
sandbox: "http://localhost:3000"
|
|
97
|
+
};
|
|
98
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
99
|
+
var ORDER_EXPIRY_MINUTES = 30;
|
|
100
|
+
var MIN_WITHDRAWAL_AMOUNT = 0.01;
|
|
101
|
+
var SUPPORTED_CHAINS = {
|
|
102
|
+
ETHEREUM: "1",
|
|
103
|
+
SEPOLIA: "11155111",
|
|
104
|
+
POLYGON: "137"
|
|
105
|
+
};
|
|
106
|
+
var ERROR_CODES = {
|
|
107
|
+
INVALID_API_KEY: "INVALID_API_KEY",
|
|
108
|
+
INVALID_PRODUCT: "INVALID_PRODUCT",
|
|
109
|
+
ORDER_NOT_FOUND: "ORDER_NOT_FOUND",
|
|
110
|
+
ORDER_EXPIRED: "ORDER_EXPIRED",
|
|
111
|
+
INSUFFICIENT_BALANCE: "INSUFFICIENT_BALANCE",
|
|
112
|
+
SELF_TRANSFER: "SELF_TRANSFER",
|
|
113
|
+
INVALID_ADDRESS: "INVALID_ADDRESS",
|
|
114
|
+
NETWORK_ERROR: "NETWORK_ERROR"
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/core/config.ts
|
|
118
|
+
var Config = class _Config {
|
|
119
|
+
constructor() {
|
|
120
|
+
this.config = null;
|
|
121
|
+
}
|
|
122
|
+
static getInstance() {
|
|
123
|
+
if (!_Config.instance) {
|
|
124
|
+
_Config.instance = new _Config();
|
|
125
|
+
}
|
|
126
|
+
return _Config.instance;
|
|
127
|
+
}
|
|
128
|
+
initialize(config2) {
|
|
129
|
+
if (!config2.baseUrl && config2.environment) {
|
|
130
|
+
config2.baseUrl = ENVIRONMENT_URLS[config2.environment];
|
|
131
|
+
}
|
|
132
|
+
if (!config2.timeout) {
|
|
133
|
+
config2.timeout = DEFAULT_TIMEOUT;
|
|
134
|
+
}
|
|
135
|
+
this.config = config2;
|
|
136
|
+
}
|
|
137
|
+
getConfig() {
|
|
138
|
+
if (!this.config) {
|
|
139
|
+
return {
|
|
140
|
+
environment: "sandbox",
|
|
141
|
+
baseUrl: ENVIRONMENT_URLS.sandbox,
|
|
142
|
+
timeout: DEFAULT_TIMEOUT
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return this.config;
|
|
146
|
+
}
|
|
147
|
+
isInitialized() {
|
|
148
|
+
return this.config !== null;
|
|
149
|
+
}
|
|
150
|
+
reset() {
|
|
151
|
+
this.config = null;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var config = Config.getInstance();
|
|
155
|
+
|
|
156
|
+
// src/core/wagmi-config.ts
|
|
157
|
+
var import_wagmi = require("wagmi");
|
|
158
|
+
var import_chains = require("wagmi/chains");
|
|
159
|
+
var import_connectors = require("@wagmi/connectors");
|
|
160
|
+
var supportedEvmChains = [
|
|
161
|
+
import_chains.mainnet,
|
|
162
|
+
import_chains.sepolia,
|
|
163
|
+
import_chains.polygon,
|
|
164
|
+
import_chains.polygonAmoy,
|
|
165
|
+
import_chains.base,
|
|
166
|
+
import_chains.baseSepolia
|
|
167
|
+
];
|
|
168
|
+
var supportedChains = supportedEvmChains;
|
|
169
|
+
var createWagmiConfig = (projectId) => {
|
|
170
|
+
const connectors = [(0, import_connectors.injected)()];
|
|
171
|
+
if (projectId) {
|
|
172
|
+
const sdkConfig = config.getConfig();
|
|
173
|
+
const environment = sdkConfig.environment || "sandbox";
|
|
174
|
+
const paymentDomain = PAYMENT_DOMAINS[environment];
|
|
175
|
+
connectors.push(
|
|
176
|
+
(0, import_connectors.walletConnect)({
|
|
177
|
+
projectId,
|
|
178
|
+
metadata: {
|
|
179
|
+
name: "Payment SDK",
|
|
180
|
+
description: "Multi-chain Payment SDK",
|
|
181
|
+
url: paymentDomain,
|
|
182
|
+
icons: [`${paymentDomain}/icon.png`]
|
|
183
|
+
},
|
|
184
|
+
showQrModal: true
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return (0, import_wagmi.createConfig)({
|
|
189
|
+
chains: [import_chains.mainnet, import_chains.sepolia, import_chains.polygon, import_chains.polygonAmoy, import_chains.base, import_chains.baseSepolia],
|
|
190
|
+
connectors,
|
|
191
|
+
transports: {
|
|
192
|
+
[import_chains.mainnet.id]: (0, import_wagmi.http)(),
|
|
193
|
+
[import_chains.sepolia.id]: (0, import_wagmi.http)(),
|
|
194
|
+
[import_chains.polygon.id]: (0, import_wagmi.http)(),
|
|
195
|
+
[import_chains.polygonAmoy.id]: (0, import_wagmi.http)(),
|
|
196
|
+
[import_chains.base.id]: (0, import_wagmi.http)(),
|
|
197
|
+
[import_chains.baseSepolia.id]: (0, import_wagmi.http)()
|
|
198
|
+
},
|
|
199
|
+
ssr: true
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
var getEvmChainById = (chainId) => {
|
|
203
|
+
return supportedEvmChains.find((chain) => chain.id === chainId);
|
|
204
|
+
};
|
|
205
|
+
var getChainById = getEvmChainById;
|
|
206
|
+
|
|
207
|
+
// src/core/WalletContext.tsx
|
|
208
|
+
var import_react = require("react");
|
|
209
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
210
|
+
var WalletContext = (0, import_react.createContext)(null);
|
|
211
|
+
var useWalletContext = () => {
|
|
212
|
+
const context = (0, import_react.useContext)(WalletContext);
|
|
213
|
+
if (!context) {
|
|
214
|
+
throw new Error("useWalletContext must be used within WalletProvider");
|
|
215
|
+
}
|
|
216
|
+
return context;
|
|
217
|
+
};
|
|
218
|
+
var WalletContextProvider = ({ factory, children }) => {
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WalletContext.Provider, { value: { factory }, children });
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/components/PaymentProvider.tsx
|
|
223
|
+
var import_react2 = require("react");
|
|
224
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
225
|
+
var PaymentContext = (0, import_react2.createContext)(null);
|
|
226
|
+
var isValidUrl = (url) => {
|
|
227
|
+
try {
|
|
228
|
+
new URL(url);
|
|
229
|
+
return true;
|
|
230
|
+
} catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var PaymentProvider = ({
|
|
235
|
+
baseUrl,
|
|
236
|
+
paymentDomain,
|
|
237
|
+
redirectUrl,
|
|
238
|
+
webhookUrl,
|
|
239
|
+
environment = "sandbox",
|
|
240
|
+
timeout,
|
|
241
|
+
children
|
|
242
|
+
}) => {
|
|
243
|
+
(0, import_react2.useEffect)(() => {
|
|
244
|
+
if (redirectUrl && !isValidUrl(redirectUrl)) {
|
|
245
|
+
console.error("Invalid redirectUrl");
|
|
246
|
+
}
|
|
247
|
+
if (webhookUrl && !isValidUrl(webhookUrl)) {
|
|
248
|
+
console.error("Invalid webhookUrl");
|
|
249
|
+
}
|
|
250
|
+
if (paymentDomain && !isValidUrl(paymentDomain)) {
|
|
251
|
+
console.error("Invalid paymentDomain");
|
|
252
|
+
}
|
|
253
|
+
const sdkConfig = {
|
|
254
|
+
environment,
|
|
255
|
+
baseUrl,
|
|
256
|
+
paymentDomain,
|
|
257
|
+
redirectUrl,
|
|
258
|
+
webhookUrl,
|
|
259
|
+
timeout
|
|
260
|
+
};
|
|
261
|
+
config.initialize(sdkConfig);
|
|
262
|
+
}, [environment, baseUrl, paymentDomain, redirectUrl, webhookUrl, timeout]);
|
|
263
|
+
const contextValue = {
|
|
264
|
+
config: {
|
|
265
|
+
environment,
|
|
266
|
+
baseUrl,
|
|
267
|
+
paymentDomain,
|
|
268
|
+
redirectUrl,
|
|
269
|
+
webhookUrl,
|
|
270
|
+
timeout
|
|
271
|
+
},
|
|
272
|
+
isInitialized: config.isInitialized()
|
|
273
|
+
};
|
|
274
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PaymentContext.Provider, { value: contextValue, children });
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/components/PaymentButton.tsx
|
|
278
|
+
var import_react11 = require("react");
|
|
279
|
+
|
|
280
|
+
// src/hooks/usePayment.ts
|
|
281
|
+
var import_react3 = require("react");
|
|
282
|
+
|
|
283
|
+
// src/api/orders.ts
|
|
284
|
+
var orders_exports = {};
|
|
285
|
+
__export(orders_exports, {
|
|
286
|
+
createOrder: () => createOrder,
|
|
287
|
+
getOrder: () => getOrder,
|
|
288
|
+
getOrders: () => getOrders
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// src/api/client.ts
|
|
292
|
+
var import_axios = __toESM(require("axios"));
|
|
293
|
+
|
|
294
|
+
// src/types/sdk.ts
|
|
295
|
+
var SDKError = class extends Error {
|
|
296
|
+
constructor(message, code, details) {
|
|
297
|
+
super(message);
|
|
298
|
+
this.code = code;
|
|
299
|
+
this.details = details;
|
|
300
|
+
this.name = "SDKError";
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
var APIError = class extends SDKError {
|
|
304
|
+
constructor(message, statusCode, details) {
|
|
305
|
+
super(message, "API_ERROR", details);
|
|
306
|
+
this.statusCode = statusCode;
|
|
307
|
+
this.name = "APIError";
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/api/client.ts
|
|
312
|
+
var APIClient = class {
|
|
313
|
+
constructor() {
|
|
314
|
+
this.instance = null;
|
|
315
|
+
}
|
|
316
|
+
getClient() {
|
|
317
|
+
if (!this.instance) {
|
|
318
|
+
const sdkConfig = config.getConfig();
|
|
319
|
+
this.instance = import_axios.default.create({
|
|
320
|
+
baseURL: sdkConfig.baseUrl,
|
|
321
|
+
timeout: sdkConfig.timeout,
|
|
322
|
+
headers: {
|
|
323
|
+
"Content-Type": "application/json"
|
|
324
|
+
},
|
|
325
|
+
withCredentials: true
|
|
326
|
+
// 기존 프로젝트 패턴과 일치
|
|
327
|
+
});
|
|
328
|
+
this.instance.interceptors.request.use(
|
|
329
|
+
(requestConfig) => {
|
|
330
|
+
return requestConfig;
|
|
331
|
+
},
|
|
332
|
+
(error) => Promise.reject(error)
|
|
333
|
+
);
|
|
334
|
+
this.instance.interceptors.response.use(
|
|
335
|
+
(response) => response,
|
|
336
|
+
(error) => {
|
|
337
|
+
const status = error.response?.status;
|
|
338
|
+
const data = error.response?.data;
|
|
339
|
+
const message = data?.message || error.message;
|
|
340
|
+
const details = error.response?.data;
|
|
341
|
+
throw new APIError(message, status, details);
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return this.instance;
|
|
346
|
+
}
|
|
347
|
+
reset() {
|
|
348
|
+
this.instance = null;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
var apiClient = new APIClient();
|
|
352
|
+
|
|
353
|
+
// src/api/orders.ts
|
|
354
|
+
var createOrder = async (productId, redirectUrl, webhookUrl) => {
|
|
355
|
+
const request = {
|
|
356
|
+
productId,
|
|
357
|
+
redirectUrl,
|
|
358
|
+
webhookUrl
|
|
359
|
+
};
|
|
360
|
+
const response = await apiClient.getClient().post(API_ENDPOINTS.ORDERS, request);
|
|
361
|
+
const order = response.data.output;
|
|
362
|
+
return {
|
|
363
|
+
orderId: order.uuid,
|
|
364
|
+
publicOrderId: order.uuid,
|
|
365
|
+
paymentUrl: "",
|
|
366
|
+
// 사용 안 함
|
|
367
|
+
amount: 0,
|
|
368
|
+
// 주문 생성 시점에는 없음
|
|
369
|
+
coinId: "",
|
|
370
|
+
// 주문 생성 시점에는 없음
|
|
371
|
+
chainId: "",
|
|
372
|
+
// 주문 생성 시점에는 없음
|
|
373
|
+
status: order.status,
|
|
374
|
+
expiresAt: "",
|
|
375
|
+
// 주문 생성 시점에는 없음
|
|
376
|
+
redirectUrl: order.redirectUrl || null
|
|
377
|
+
// 서버 응답의 redirectUrl
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
var getOrder = async (publicOrderId) => {
|
|
381
|
+
const response = await apiClient.getClient().get(`${API_ENDPOINTS.ORDERS}/${publicOrderId}`);
|
|
382
|
+
const orderData = response.data.output;
|
|
383
|
+
if (!orderData || typeof orderData !== "object") {
|
|
384
|
+
throw new Error("Order data is null or invalid");
|
|
385
|
+
}
|
|
386
|
+
if (!orderData.orderId || orderData.orderStat === void 0 || orderData.orderStat === null) {
|
|
387
|
+
throw new Error("Order data is incomplete - missing required fields");
|
|
388
|
+
}
|
|
389
|
+
return orderData;
|
|
390
|
+
};
|
|
391
|
+
var getOrders = async () => {
|
|
392
|
+
const response = await apiClient.getClient().get(API_ENDPOINTS.ORDERS);
|
|
393
|
+
return response.data.output;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/utils/errors.ts
|
|
397
|
+
var import_axios2 = require("axios");
|
|
398
|
+
var handleAPIError = (error) => {
|
|
399
|
+
if (error instanceof SDKError) {
|
|
400
|
+
return error;
|
|
401
|
+
}
|
|
402
|
+
if (error instanceof import_axios2.AxiosError) {
|
|
403
|
+
const status = error.response?.status;
|
|
404
|
+
const message = error.response?.data?.message || error.message;
|
|
405
|
+
const details = error.response?.data;
|
|
406
|
+
return new APIError(message, status, details);
|
|
407
|
+
}
|
|
408
|
+
if (error instanceof Error) {
|
|
409
|
+
return new SDKError(error.message);
|
|
410
|
+
}
|
|
411
|
+
return new SDKError("Unknown error occurred");
|
|
412
|
+
};
|
|
413
|
+
var getErrorMessage = (error) => {
|
|
414
|
+
const sdkError = handleAPIError(error);
|
|
415
|
+
if (sdkError instanceof APIError) {
|
|
416
|
+
switch (sdkError.statusCode) {
|
|
417
|
+
case 401:
|
|
418
|
+
return "\uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.";
|
|
419
|
+
case 403:
|
|
420
|
+
return "\uC811\uADFC \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
421
|
+
case 404:
|
|
422
|
+
return "\uC694\uCCAD\uD55C \uB9AC\uC18C\uC2A4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
423
|
+
case 429:
|
|
424
|
+
return "\uC694\uCCAD \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.";
|
|
425
|
+
case 500:
|
|
426
|
+
return "\uC11C\uBC84 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.";
|
|
427
|
+
default:
|
|
428
|
+
return sdkError.message;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return sdkError.message;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/utils/validation.ts
|
|
435
|
+
var validation_exports = {};
|
|
436
|
+
__export(validation_exports, {
|
|
437
|
+
validateProductId: () => validateProductId,
|
|
438
|
+
validateWithdrawalAddress: () => validateWithdrawalAddress
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// src/utils/address.ts
|
|
442
|
+
function isEvmAddress(address) {
|
|
443
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
444
|
+
}
|
|
445
|
+
function isTronAddress(address) {
|
|
446
|
+
return /^T[A-Za-z0-9]{33}$/.test(address);
|
|
447
|
+
}
|
|
448
|
+
function detectChainType(address) {
|
|
449
|
+
if (isEvmAddress(address)) {
|
|
450
|
+
return "evm";
|
|
451
|
+
}
|
|
452
|
+
if (isTronAddress(address)) {
|
|
453
|
+
return "tron";
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
function isValidAddress(address) {
|
|
458
|
+
return isEvmAddress(address) || isTronAddress(address);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/utils/validation.ts
|
|
462
|
+
var validateWithdrawalAddress = (fromAddress, toAddress) => {
|
|
463
|
+
const normalizedFrom = fromAddress.trim();
|
|
464
|
+
const normalizedTo = toAddress.trim();
|
|
465
|
+
if (isEvmAddress(normalizedFrom) && isEvmAddress(normalizedTo)) {
|
|
466
|
+
if (normalizedFrom.toLowerCase() === normalizedTo.toLowerCase()) {
|
|
467
|
+
return {
|
|
468
|
+
valid: false,
|
|
469
|
+
error: "Cannot send to your own wallet address"
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
} else if (isTronAddress(normalizedFrom) && isTronAddress(normalizedTo)) {
|
|
473
|
+
if (normalizedFrom === normalizedTo) {
|
|
474
|
+
return {
|
|
475
|
+
valid: false,
|
|
476
|
+
error: "Cannot send to your own wallet address"
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (!isValidAddress(toAddress)) {
|
|
481
|
+
return {
|
|
482
|
+
valid: false,
|
|
483
|
+
error: "Invalid wallet address format"
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return { valid: true };
|
|
487
|
+
};
|
|
488
|
+
var validateProductId = (productId) => {
|
|
489
|
+
if (!productId || productId.trim().length === 0) {
|
|
490
|
+
return {
|
|
491
|
+
valid: false,
|
|
492
|
+
error: "Product ID is required"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return { valid: true };
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/hooks/usePayment.ts
|
|
499
|
+
var usePayment = () => {
|
|
500
|
+
const context = (0, import_react3.useContext)(PaymentContext);
|
|
501
|
+
if (!context) {
|
|
502
|
+
throw new Error("usePayment must be used within PaymentProvider");
|
|
503
|
+
}
|
|
504
|
+
const { config: config2 } = context;
|
|
505
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
506
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
507
|
+
const [order, setOrder] = (0, import_react3.useState)(null);
|
|
508
|
+
const create = (0, import_react3.useCallback)(
|
|
509
|
+
async (params) => {
|
|
510
|
+
setIsLoading(true);
|
|
511
|
+
setError(null);
|
|
512
|
+
try {
|
|
513
|
+
const validation = validateProductId(params.productId);
|
|
514
|
+
if (!validation.valid) {
|
|
515
|
+
throw new Error(validation.error);
|
|
516
|
+
}
|
|
517
|
+
const orderResponse = await createOrder(
|
|
518
|
+
params.productId,
|
|
519
|
+
config2.redirectUrl || "",
|
|
520
|
+
config2.webhookUrl || ""
|
|
521
|
+
);
|
|
522
|
+
setOrder(orderResponse);
|
|
523
|
+
return orderResponse;
|
|
524
|
+
} catch (err) {
|
|
525
|
+
const sdkError = handleAPIError(err);
|
|
526
|
+
setError(sdkError);
|
|
527
|
+
throw sdkError;
|
|
528
|
+
} finally {
|
|
529
|
+
setIsLoading(false);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
[config2.redirectUrl, config2.webhookUrl]
|
|
533
|
+
);
|
|
534
|
+
return {
|
|
535
|
+
createOrder: create,
|
|
536
|
+
isLoading,
|
|
537
|
+
error,
|
|
538
|
+
order,
|
|
539
|
+
errorMessage: error ? getErrorMessage(error) : null
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/components/PaymentModal.tsx
|
|
544
|
+
var import_react10 = require("react");
|
|
545
|
+
|
|
546
|
+
// src/components/OrderPayment.tsx
|
|
547
|
+
var import_react9 = require("react");
|
|
548
|
+
|
|
549
|
+
// src/hooks/useOrder.ts
|
|
550
|
+
var import_react4 = require("react");
|
|
551
|
+
|
|
552
|
+
// src/utils/orderStatus.ts
|
|
553
|
+
var isOrderSuccess = (status) => {
|
|
554
|
+
return status === "S" || status === "1";
|
|
555
|
+
};
|
|
556
|
+
var isOrderFailed = (status) => {
|
|
557
|
+
return status === "F" || status === "4";
|
|
558
|
+
};
|
|
559
|
+
var isOrderRefundInsufficient = (status) => {
|
|
560
|
+
return status === "R1" || status === "2";
|
|
561
|
+
};
|
|
562
|
+
var isOrderRefundExcess = (status) => {
|
|
563
|
+
return status === "R2" || status === "3";
|
|
564
|
+
};
|
|
565
|
+
var isOrderFinalState = (status) => {
|
|
566
|
+
return isOrderSuccess(status) || isOrderFailed(status) || isOrderRefundInsufficient(status) || isOrderRefundExcess(status);
|
|
567
|
+
};
|
|
568
|
+
var getOrderState = (status) => {
|
|
569
|
+
if (isOrderSuccess(status)) return "success" /* SUCCESS */;
|
|
570
|
+
if (isOrderFailed(status)) return "failed" /* FAILED */;
|
|
571
|
+
if (isOrderRefundInsufficient(status)) return "refund_insufficient" /* REFUND_INSUFFICIENT */;
|
|
572
|
+
if (isOrderRefundExcess(status)) return "refund_excess" /* REFUND_EXCESS */;
|
|
573
|
+
return "waiting" /* WAITING */;
|
|
574
|
+
};
|
|
575
|
+
var getOrderStatusMessage = (status) => {
|
|
576
|
+
const state = getOrderState(status);
|
|
577
|
+
switch (state) {
|
|
578
|
+
case "success" /* SUCCESS */:
|
|
579
|
+
return "Payment successful";
|
|
580
|
+
case "failed" /* FAILED */:
|
|
581
|
+
return "Payment failed";
|
|
582
|
+
case "refund_insufficient" /* REFUND_INSUFFICIENT */:
|
|
583
|
+
return "Payment refunded - Insufficient amount";
|
|
584
|
+
case "refund_excess" /* REFUND_EXCESS */:
|
|
585
|
+
return "Payment refunded - Excess amount";
|
|
586
|
+
case "waiting" /* WAITING */:
|
|
587
|
+
default:
|
|
588
|
+
return "Waiting for payment";
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// src/hooks/useOrder.ts
|
|
593
|
+
var useOrder = (publicOrderId, options) => {
|
|
594
|
+
const [order, setOrder] = (0, import_react4.useState)(null);
|
|
595
|
+
const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
|
|
596
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
597
|
+
const pollingInterval = options?.pollingInterval || 5e3;
|
|
598
|
+
const enablePolling = options?.enablePolling ?? true;
|
|
599
|
+
const intervalRef = (0, import_react4.useRef)(null);
|
|
600
|
+
const isInitialFetch = (0, import_react4.useRef)(true);
|
|
601
|
+
(0, import_react4.useEffect)(() => {
|
|
602
|
+
if (!publicOrderId) return;
|
|
603
|
+
let retryCount = 0;
|
|
604
|
+
const maxRetries = 5;
|
|
605
|
+
const fetchOrder = async (isInitial = false) => {
|
|
606
|
+
if (isInitial) {
|
|
607
|
+
setIsLoading(true);
|
|
608
|
+
}
|
|
609
|
+
setError(null);
|
|
610
|
+
try {
|
|
611
|
+
const orderData = await getOrder(publicOrderId);
|
|
612
|
+
setOrder(orderData);
|
|
613
|
+
retryCount = 0;
|
|
614
|
+
if (enablePolling && isOrderFinalState(orderData.orderStat)) {
|
|
615
|
+
if (intervalRef.current) {
|
|
616
|
+
clearInterval(intervalRef.current);
|
|
617
|
+
intervalRef.current = null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} catch (err) {
|
|
621
|
+
const sdkError = handleAPIError(err);
|
|
622
|
+
if (isInitial && retryCount < maxRetries && (sdkError.message.includes("incomplete") || sdkError.message.includes("null or invalid"))) {
|
|
623
|
+
retryCount++;
|
|
624
|
+
console.log(`[useOrder] Retrying order fetch (${retryCount}/${maxRetries})...`);
|
|
625
|
+
setTimeout(() => fetchOrder(true), 500);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
setError(sdkError);
|
|
629
|
+
} finally {
|
|
630
|
+
if (isInitial && retryCount === 0) {
|
|
631
|
+
setIsLoading(false);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
fetchOrder(true);
|
|
636
|
+
isInitialFetch.current = false;
|
|
637
|
+
if (enablePolling) {
|
|
638
|
+
intervalRef.current = setInterval(() => fetchOrder(false), pollingInterval);
|
|
639
|
+
}
|
|
640
|
+
return () => {
|
|
641
|
+
if (intervalRef.current) {
|
|
642
|
+
clearInterval(intervalRef.current);
|
|
643
|
+
intervalRef.current = null;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}, [publicOrderId, pollingInterval, enablePolling]);
|
|
647
|
+
const refetch = async () => {
|
|
648
|
+
if (!publicOrderId) return;
|
|
649
|
+
setIsLoading(true);
|
|
650
|
+
try {
|
|
651
|
+
const orderData = await getOrder(publicOrderId);
|
|
652
|
+
setOrder(orderData);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
const sdkError = handleAPIError(err);
|
|
655
|
+
setError(sdkError);
|
|
656
|
+
} finally {
|
|
657
|
+
setIsLoading(false);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
return {
|
|
661
|
+
order,
|
|
662
|
+
isLoading,
|
|
663
|
+
error,
|
|
664
|
+
refetch
|
|
665
|
+
};
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/components/PaymentFlow.tsx
|
|
669
|
+
var import_react8 = require("react");
|
|
670
|
+
|
|
671
|
+
// src/components/PayWithWallet.tsx
|
|
672
|
+
var import_react6 = require("react");
|
|
673
|
+
var import_viem = require("viem");
|
|
674
|
+
|
|
675
|
+
// src/hooks/useWalletAdapter.ts
|
|
676
|
+
var import_react5 = require("react");
|
|
677
|
+
var import_core2 = require("@wagmi/core");
|
|
678
|
+
|
|
679
|
+
// src/adapters/EvmWalletAdapter.ts
|
|
680
|
+
var import_core = require("@wagmi/core");
|
|
681
|
+
var ERC20_ABI = [
|
|
682
|
+
{
|
|
683
|
+
name: "transfer",
|
|
684
|
+
type: "function",
|
|
685
|
+
stateMutability: "nonpayable",
|
|
686
|
+
inputs: [
|
|
687
|
+
{ name: "to", type: "address" },
|
|
688
|
+
{ name: "amount", type: "uint256" }
|
|
689
|
+
],
|
|
690
|
+
outputs: [{ name: "", type: "bool" }]
|
|
691
|
+
}
|
|
692
|
+
];
|
|
693
|
+
var EvmWalletAdapter = class {
|
|
694
|
+
constructor(config2) {
|
|
695
|
+
this.type = "evm";
|
|
696
|
+
this.chainType = "evm";
|
|
697
|
+
this.config = config2;
|
|
698
|
+
}
|
|
699
|
+
// 연결 상태
|
|
700
|
+
isConnected() {
|
|
701
|
+
const account = (0, import_core.getAccount)(this.config);
|
|
702
|
+
return account.isConnected;
|
|
703
|
+
}
|
|
704
|
+
getAddress() {
|
|
705
|
+
const account = (0, import_core.getAccount)(this.config);
|
|
706
|
+
return account.address || null;
|
|
707
|
+
}
|
|
708
|
+
getChainId() {
|
|
709
|
+
try {
|
|
710
|
+
return (0, import_core.getChainId)(this.config);
|
|
711
|
+
} catch {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async getBalance() {
|
|
716
|
+
const account = (0, import_core.getAccount)(this.config);
|
|
717
|
+
if (!account.address) return null;
|
|
718
|
+
try {
|
|
719
|
+
const balance = await (0, import_core.getBalance)(this.config, {
|
|
720
|
+
address: account.address
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
value: balance.value,
|
|
724
|
+
decimals: balance.decimals,
|
|
725
|
+
symbol: balance.symbol,
|
|
726
|
+
formatted: balance.formatted
|
|
727
|
+
};
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.error("[EvmWalletAdapter] Failed to get balance:", error);
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
getConnectionInfo() {
|
|
734
|
+
const account = (0, import_core.getAccount)(this.config);
|
|
735
|
+
if (!account.isConnected || !account.address) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
const currentChainId = this.getChainId();
|
|
739
|
+
console.log("[EvmWalletAdapter] getConnectionInfo:", {
|
|
740
|
+
isConnected: account.isConnected,
|
|
741
|
+
address: account.address,
|
|
742
|
+
accountChainId: account.chainId,
|
|
743
|
+
currentChainId,
|
|
744
|
+
status: account.status
|
|
745
|
+
});
|
|
746
|
+
if (!currentChainId) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
address: account.address,
|
|
751
|
+
chainId: currentChainId,
|
|
752
|
+
// 실제 현재 체인 ID 사용
|
|
753
|
+
chainType: this.chainType,
|
|
754
|
+
isConnected: account.isConnected,
|
|
755
|
+
connector: account.connector ? {
|
|
756
|
+
id: account.connector.id,
|
|
757
|
+
name: account.connector.name,
|
|
758
|
+
icon: account.connector.icon
|
|
759
|
+
} : void 0
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
// 연결 관리
|
|
763
|
+
async connect(connectorId) {
|
|
764
|
+
try {
|
|
765
|
+
const connectors = (0, import_core.getConnectors)(this.config);
|
|
766
|
+
const connector = connectorId ? connectors.find((c) => c.id === connectorId) : connectors[0];
|
|
767
|
+
if (!connector) {
|
|
768
|
+
throw new Error(
|
|
769
|
+
connectorId ? `Connector ${connectorId} not found` : "No connectors available"
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
await (0, import_core.connect)(this.config, { connector });
|
|
773
|
+
} catch (error) {
|
|
774
|
+
console.error("[EvmWalletAdapter] Connection failed:", error);
|
|
775
|
+
throw error;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async disconnect() {
|
|
779
|
+
try {
|
|
780
|
+
const connections = (0, import_core.getConnections)(this.config);
|
|
781
|
+
if (connections.length > 0) {
|
|
782
|
+
await (0, import_core.disconnect)(this.config, {
|
|
783
|
+
connector: connections[0].connector
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.error("[EvmWalletAdapter] Disconnect failed:", error);
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async switchChain(chainId) {
|
|
792
|
+
if (typeof chainId === "string") {
|
|
793
|
+
throw new Error("EVM adapter expects numeric chainId");
|
|
794
|
+
}
|
|
795
|
+
const currentChainId = (0, import_core.getAccount)(this.config).chainId;
|
|
796
|
+
console.log(`[EvmWalletAdapter] Switching chain from ${currentChainId} to ${chainId}`);
|
|
797
|
+
try {
|
|
798
|
+
await (0, import_core.switchChain)(this.config, { chainId });
|
|
799
|
+
console.log(`[EvmWalletAdapter] Switch chain completed for chainId: ${chainId}`);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
console.error("[EvmWalletAdapter] Switch chain failed:", error);
|
|
802
|
+
console.error("[EvmWalletAdapter] Error details:", {
|
|
803
|
+
message: error.message,
|
|
804
|
+
code: error.code,
|
|
805
|
+
name: error.name
|
|
806
|
+
});
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// 트랜잭션
|
|
811
|
+
async sendPayment(tx) {
|
|
812
|
+
try {
|
|
813
|
+
const account = (0, import_core.getAccount)(this.config);
|
|
814
|
+
if (!account.isConnected || !account.address) {
|
|
815
|
+
throw new Error("Wallet not connected");
|
|
816
|
+
}
|
|
817
|
+
if (typeof tx.chainId === "string") {
|
|
818
|
+
throw new Error("EVM adapter expects numeric chainId");
|
|
819
|
+
}
|
|
820
|
+
const currentChainId = (0, import_core.getChainId)(this.config);
|
|
821
|
+
console.log("[EvmWalletAdapter] sendPayment - Current chain check:", {
|
|
822
|
+
currentChainId,
|
|
823
|
+
accountChainId: account.chainId,
|
|
824
|
+
requestedChainId: tx.chainId
|
|
825
|
+
});
|
|
826
|
+
if (currentChainId !== tx.chainId) {
|
|
827
|
+
console.log(`[EvmWalletAdapter] Chain mismatch! Switching from ${currentChainId} to ${tx.chainId}`);
|
|
828
|
+
await this.switchChain(tx.chainId);
|
|
829
|
+
const chainIdAfterSwitch = (0, import_core.getChainId)(this.config);
|
|
830
|
+
console.log("[EvmWalletAdapter] After switch:", {
|
|
831
|
+
chainId: chainIdAfterSwitch,
|
|
832
|
+
expected: tx.chainId
|
|
833
|
+
});
|
|
834
|
+
if (chainIdAfterSwitch !== tx.chainId) {
|
|
835
|
+
throw new Error(`Failed to switch to chain ${tx.chainId}. Current chain: ${chainIdAfterSwitch}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
let hash;
|
|
839
|
+
const chain = this.config.chains.find((c) => c.id === tx.chainId);
|
|
840
|
+
if (!chain) {
|
|
841
|
+
throw new Error(`Chain ${tx.chainId} not found in wagmi config`);
|
|
842
|
+
}
|
|
843
|
+
if (tx.tokenAddress && tx.decimals !== void 0) {
|
|
844
|
+
console.log("[EvmWalletAdapter] ERC20 Transfer:", {
|
|
845
|
+
token: tx.tokenAddress,
|
|
846
|
+
to: tx.to,
|
|
847
|
+
value: tx.value.toString(),
|
|
848
|
+
decimals: tx.decimals,
|
|
849
|
+
chainId: tx.chainId,
|
|
850
|
+
chainName: chain.name
|
|
851
|
+
});
|
|
852
|
+
hash = await (0, import_core.writeContract)(this.config, {
|
|
853
|
+
address: tx.tokenAddress,
|
|
854
|
+
abi: ERC20_ABI,
|
|
855
|
+
functionName: "transfer",
|
|
856
|
+
args: [tx.to, tx.value],
|
|
857
|
+
chain
|
|
858
|
+
});
|
|
859
|
+
console.log("[EvmWalletAdapter] ERC20 Transfer hash:", hash);
|
|
860
|
+
} else {
|
|
861
|
+
console.log("[EvmWalletAdapter] Native Transfer:", {
|
|
862
|
+
to: tx.to,
|
|
863
|
+
value: tx.value.toString(),
|
|
864
|
+
chainId: tx.chainId,
|
|
865
|
+
chainName: chain.name
|
|
866
|
+
});
|
|
867
|
+
hash = await (0, import_core.sendTransaction)(this.config, {
|
|
868
|
+
to: tx.to,
|
|
869
|
+
value: tx.value,
|
|
870
|
+
chainId: tx.chainId
|
|
871
|
+
});
|
|
872
|
+
console.log("[EvmWalletAdapter] Native Transfer hash:", hash);
|
|
873
|
+
}
|
|
874
|
+
return { success: true, hash };
|
|
875
|
+
} catch (error) {
|
|
876
|
+
console.error("[EvmWalletAdapter] Payment failed:", error);
|
|
877
|
+
return {
|
|
878
|
+
success: false,
|
|
879
|
+
hash: "0x",
|
|
880
|
+
error: error?.message ?? "Transaction failed"
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// 지원 여부 확인
|
|
885
|
+
isChainSupported(chainId) {
|
|
886
|
+
if (typeof chainId === "string") return false;
|
|
887
|
+
return this.config.chains.some((chain) => chain.id === chainId);
|
|
888
|
+
}
|
|
889
|
+
getChainType(_chainId) {
|
|
890
|
+
return this.chainType;
|
|
891
|
+
}
|
|
892
|
+
// 커넥터 정보
|
|
893
|
+
getConnectors() {
|
|
894
|
+
const connectors = (0, import_core.getConnectors)(this.config);
|
|
895
|
+
return connectors.map((c) => ({
|
|
896
|
+
id: c.id,
|
|
897
|
+
name: c.name,
|
|
898
|
+
icon: c.icon
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
901
|
+
checkWalletInstalled(connectorId) {
|
|
902
|
+
const connectors = (0, import_core.getConnectors)(this.config);
|
|
903
|
+
return connectors.some((c) => c.id === connectorId);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// src/hooks/useWalletAdapter.ts
|
|
908
|
+
var useWalletAdapter = (options) => {
|
|
909
|
+
const { factory } = useWalletContext();
|
|
910
|
+
const { chainId, depositAddress } = typeof options === "object" && options !== null ? options : { chainId: options, depositAddress: void 0 };
|
|
911
|
+
const [activeAdapter, setActiveAdapter] = (0, import_react5.useState)(
|
|
912
|
+
null
|
|
913
|
+
);
|
|
914
|
+
const [connectionInfo, setConnectionInfo] = (0, import_react5.useState)(null);
|
|
915
|
+
const [balance, setBalance] = (0, import_react5.useState)(null);
|
|
916
|
+
const [isSending, setIsSending] = (0, import_react5.useState)(false);
|
|
917
|
+
const [txHash, setTxHash] = (0, import_react5.useState)(null);
|
|
918
|
+
(0, import_react5.useEffect)(() => {
|
|
919
|
+
if (depositAddress) {
|
|
920
|
+
const adapter = factory.getAdapterByAddress(depositAddress);
|
|
921
|
+
setActiveAdapter(adapter);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (chainId) {
|
|
925
|
+
const adapter = factory.getAdapterForChain(chainId);
|
|
926
|
+
setActiveAdapter(adapter);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const connected = factory.getConnectedAdapter();
|
|
930
|
+
setActiveAdapter(connected);
|
|
931
|
+
}, [chainId, depositAddress, factory]);
|
|
932
|
+
(0, import_react5.useEffect)(() => {
|
|
933
|
+
if (!activeAdapter) return;
|
|
934
|
+
const updateConnectionInfo = () => {
|
|
935
|
+
const info = activeAdapter.getConnectionInfo();
|
|
936
|
+
setConnectionInfo(info);
|
|
937
|
+
if (info?.isConnected) {
|
|
938
|
+
activeAdapter.getBalance().then(setBalance);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
updateConnectionInfo();
|
|
942
|
+
if (activeAdapter.type === "evm" && activeAdapter instanceof EvmWalletAdapter) {
|
|
943
|
+
const unwatch = (0, import_core2.watchChainId)(activeAdapter.config, {
|
|
944
|
+
onChange: (chainId2) => {
|
|
945
|
+
console.log("[useWalletAdapter] Chain changed to:", chainId2);
|
|
946
|
+
updateConnectionInfo();
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
return unwatch;
|
|
950
|
+
}
|
|
951
|
+
}, [activeAdapter]);
|
|
952
|
+
const connect2 = async (walletType, connectorId) => {
|
|
953
|
+
try {
|
|
954
|
+
const adapter = factory.getAdapter(walletType);
|
|
955
|
+
if (!adapter) {
|
|
956
|
+
throw new Error(`Adapter for ${walletType} not found`);
|
|
957
|
+
}
|
|
958
|
+
await adapter.connect(connectorId);
|
|
959
|
+
setActiveAdapter(adapter);
|
|
960
|
+
const info = adapter.getConnectionInfo();
|
|
961
|
+
setConnectionInfo(info);
|
|
962
|
+
if (info?.isConnected) {
|
|
963
|
+
const bal = await adapter.getBalance();
|
|
964
|
+
setBalance(bal);
|
|
965
|
+
}
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.error("[useWalletAdapter] Connection failed:", error);
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
const disconnect2 = async () => {
|
|
972
|
+
if (!activeAdapter) return;
|
|
973
|
+
try {
|
|
974
|
+
await activeAdapter.disconnect();
|
|
975
|
+
setConnectionInfo(null);
|
|
976
|
+
setBalance(null);
|
|
977
|
+
if (!depositAddress && !chainId) {
|
|
978
|
+
setActiveAdapter(null);
|
|
979
|
+
}
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error("[useWalletAdapter] Disconnect failed:", error);
|
|
982
|
+
throw error;
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
const switchChain2 = async (targetChainId) => {
|
|
986
|
+
if (!activeAdapter) {
|
|
987
|
+
throw new Error("No active adapter");
|
|
988
|
+
}
|
|
989
|
+
try {
|
|
990
|
+
await activeAdapter.switchChain(targetChainId);
|
|
991
|
+
const info = activeAdapter.getConnectionInfo();
|
|
992
|
+
setConnectionInfo(info);
|
|
993
|
+
} catch (error) {
|
|
994
|
+
console.error("[useWalletAdapter] Switch chain failed:", error);
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
const sendPayment = async (tx) => {
|
|
999
|
+
if (!activeAdapter) {
|
|
1000
|
+
throw new Error("No active adapter");
|
|
1001
|
+
}
|
|
1002
|
+
setIsSending(true);
|
|
1003
|
+
setTxHash(null);
|
|
1004
|
+
try {
|
|
1005
|
+
const result = await activeAdapter.sendPayment(tx);
|
|
1006
|
+
if (result.success) {
|
|
1007
|
+
setTxHash(result.hash);
|
|
1008
|
+
}
|
|
1009
|
+
return result;
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
console.error("[useWalletAdapter] Payment failed:", error);
|
|
1012
|
+
return {
|
|
1013
|
+
success: false,
|
|
1014
|
+
hash: "",
|
|
1015
|
+
error: error?.message || "Payment failed"
|
|
1016
|
+
};
|
|
1017
|
+
} finally {
|
|
1018
|
+
setIsSending(false);
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
const getAllConnectors = () => {
|
|
1022
|
+
const adapters = factory.getAllAdapters();
|
|
1023
|
+
const connectors = [];
|
|
1024
|
+
adapters.forEach((adapter) => {
|
|
1025
|
+
const adapterConnectors = adapter.getConnectors();
|
|
1026
|
+
adapterConnectors.forEach((conn) => {
|
|
1027
|
+
connectors.push({
|
|
1028
|
+
...conn,
|
|
1029
|
+
walletType: adapter.type
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
return connectors;
|
|
1034
|
+
};
|
|
1035
|
+
const getConnectors2 = (walletType) => {
|
|
1036
|
+
if (walletType) {
|
|
1037
|
+
const adapter = factory.getAdapter(walletType);
|
|
1038
|
+
return adapter?.getConnectors() || [];
|
|
1039
|
+
}
|
|
1040
|
+
if (activeAdapter) {
|
|
1041
|
+
return activeAdapter.getConnectors();
|
|
1042
|
+
}
|
|
1043
|
+
return [];
|
|
1044
|
+
};
|
|
1045
|
+
const checkWalletInstalled = (walletType, connectorId) => {
|
|
1046
|
+
const adapter = factory.getAdapter(walletType);
|
|
1047
|
+
return adapter?.checkWalletInstalled(connectorId) || false;
|
|
1048
|
+
};
|
|
1049
|
+
const getChainType = (chainId2) => {
|
|
1050
|
+
try {
|
|
1051
|
+
return factory.getChainType(chainId2);
|
|
1052
|
+
} catch {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
const getWallet = () => {
|
|
1057
|
+
if (!activeAdapter) return null;
|
|
1058
|
+
return activeAdapter.getConnectionInfo();
|
|
1059
|
+
};
|
|
1060
|
+
return {
|
|
1061
|
+
// 연결 정보
|
|
1062
|
+
wallet: connectionInfo,
|
|
1063
|
+
address: connectionInfo?.address || null,
|
|
1064
|
+
chainId: connectionInfo?.chainId || null,
|
|
1065
|
+
chainType: connectionInfo?.chainType || null,
|
|
1066
|
+
isConnected: connectionInfo?.isConnected || false,
|
|
1067
|
+
balance,
|
|
1068
|
+
// 어댑터 정보
|
|
1069
|
+
activeAdapter,
|
|
1070
|
+
adapterType: activeAdapter?.type || null,
|
|
1071
|
+
// 연결 관리
|
|
1072
|
+
connect: connect2,
|
|
1073
|
+
disconnect: disconnect2,
|
|
1074
|
+
switchChain: switchChain2,
|
|
1075
|
+
// 커넥터
|
|
1076
|
+
connectors: getConnectors2(),
|
|
1077
|
+
allConnectors: getAllConnectors(),
|
|
1078
|
+
checkWalletInstalled,
|
|
1079
|
+
// 트랜잭션
|
|
1080
|
+
sendPayment,
|
|
1081
|
+
isSending,
|
|
1082
|
+
txHash,
|
|
1083
|
+
// 유틸리티
|
|
1084
|
+
getChainType,
|
|
1085
|
+
getWallet,
|
|
1086
|
+
factory
|
|
1087
|
+
};
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// src/utils/format.ts
|
|
1091
|
+
var truncateAddress = (address, startChars = 6, endChars = 6) => {
|
|
1092
|
+
if (!address) return "";
|
|
1093
|
+
if (address.length <= startChars + endChars) return address;
|
|
1094
|
+
return `${address.slice(0, startChars)}...${address.slice(-endChars)}`;
|
|
1095
|
+
};
|
|
1096
|
+
var truncateHash = (hash) => {
|
|
1097
|
+
return truncateAddress(hash, 10, 8);
|
|
1098
|
+
};
|
|
1099
|
+
var truncateOrderId = (orderId) => {
|
|
1100
|
+
return truncateAddress(orderId, 6, 6);
|
|
1101
|
+
};
|
|
1102
|
+
var shortenErrorMessage = (error) => {
|
|
1103
|
+
const message = typeof error === "string" ? error : error.message;
|
|
1104
|
+
const patterns = [
|
|
1105
|
+
{
|
|
1106
|
+
pattern: /User rejected the request|User denied transaction signature/i,
|
|
1107
|
+
replacement: "Transaction cancelled"
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
pattern: /insufficient funds|Insufficient balance/i,
|
|
1111
|
+
replacement: "Insufficient balance"
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
pattern: /Failed to fetch|Network request failed/i,
|
|
1115
|
+
replacement: "Network error"
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
pattern: /Cannot read properties of null/i,
|
|
1119
|
+
replacement: "Data not ready"
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
pattern: /Order data is incomplete - missing required fields/i,
|
|
1123
|
+
replacement: "Payment details loading..."
|
|
1124
|
+
}
|
|
1125
|
+
];
|
|
1126
|
+
for (const { pattern, replacement } of patterns) {
|
|
1127
|
+
if (pattern.test(message)) {
|
|
1128
|
+
return replacement;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (message.length > 100) {
|
|
1132
|
+
return message.slice(0, 97) + "...";
|
|
1133
|
+
}
|
|
1134
|
+
return message;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/components/PayWithWallet.tsx
|
|
1138
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1139
|
+
var PayWithWallet = ({
|
|
1140
|
+
toAddress,
|
|
1141
|
+
amount,
|
|
1142
|
+
coinId,
|
|
1143
|
+
chainId,
|
|
1144
|
+
chainInfo,
|
|
1145
|
+
tokenAddress,
|
|
1146
|
+
decimals,
|
|
1147
|
+
publicOrderId,
|
|
1148
|
+
onSuccess,
|
|
1149
|
+
onError,
|
|
1150
|
+
className = "",
|
|
1151
|
+
children
|
|
1152
|
+
}) => {
|
|
1153
|
+
const adapter = useWalletAdapter({ depositAddress: toAddress });
|
|
1154
|
+
const {
|
|
1155
|
+
wallet,
|
|
1156
|
+
connect: connect2,
|
|
1157
|
+
disconnect: disconnect2,
|
|
1158
|
+
connectors,
|
|
1159
|
+
sendPayment,
|
|
1160
|
+
txHash,
|
|
1161
|
+
checkWalletInstalled,
|
|
1162
|
+
switchChain: switchChain2,
|
|
1163
|
+
adapterType,
|
|
1164
|
+
getWallet
|
|
1165
|
+
} = adapter;
|
|
1166
|
+
const [showWalletSelector, setShowWalletSelector] = (0, import_react6.useState)(false);
|
|
1167
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1168
|
+
const [hasNotified] = (0, import_react6.useState)(false);
|
|
1169
|
+
const [isProcessing, setIsProcessing] = (0, import_react6.useState)(false);
|
|
1170
|
+
const [isConnecting, setIsConnecting] = (0, import_react6.useState)(false);
|
|
1171
|
+
const installedWallets = (0, import_react6.useMemo)(() => {
|
|
1172
|
+
if (!adapterType) return [];
|
|
1173
|
+
console.log("[PayWithWallet] Checking installed wallets...", {
|
|
1174
|
+
adapterType,
|
|
1175
|
+
connectors: connectors.map((c) => c.id)
|
|
1176
|
+
});
|
|
1177
|
+
const installed = connectors.filter((c) => {
|
|
1178
|
+
const isInstalled = checkWalletInstalled(adapterType, c.id);
|
|
1179
|
+
console.log(
|
|
1180
|
+
`[PayWithWallet] Connector ${c.id}: ${isInstalled ? "INSTALLED" : "NOT INSTALLED"}`
|
|
1181
|
+
);
|
|
1182
|
+
return isInstalled;
|
|
1183
|
+
});
|
|
1184
|
+
console.log(
|
|
1185
|
+
"[PayWithWallet] Installed wallets:",
|
|
1186
|
+
installed.map((w) => w.id)
|
|
1187
|
+
);
|
|
1188
|
+
return installed;
|
|
1189
|
+
}, [adapterType, connectors, checkWalletInstalled]);
|
|
1190
|
+
console.log("[PayWithWallet] Render state:", {
|
|
1191
|
+
depositAddress: toAddress,
|
|
1192
|
+
adapterType,
|
|
1193
|
+
connectors: connectors.map((c) => c.id),
|
|
1194
|
+
installedWallets: installedWallets.map((w) => w.id),
|
|
1195
|
+
hasInstalledWallets: installedWallets.length > 0
|
|
1196
|
+
});
|
|
1197
|
+
(0, import_react6.useEffect)(() => {
|
|
1198
|
+
if (txHash) {
|
|
1199
|
+
setIsProcessing(false);
|
|
1200
|
+
}
|
|
1201
|
+
}, [txHash]);
|
|
1202
|
+
(0, import_react6.useEffect)(() => {
|
|
1203
|
+
}, [txHash, publicOrderId, hasNotified, onSuccess]);
|
|
1204
|
+
const handleConnect = async (connectorId) => {
|
|
1205
|
+
if (!adapterType) {
|
|
1206
|
+
setError("Wallet adapter not initialized");
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
setError(null);
|
|
1211
|
+
setIsConnecting(true);
|
|
1212
|
+
await connect2(adapterType, connectorId);
|
|
1213
|
+
setShowWalletSelector(false);
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
const errorMsg = shortenErrorMessage(
|
|
1216
|
+
err instanceof Error ? err : "Failed to connect wallet"
|
|
1217
|
+
);
|
|
1218
|
+
setError(errorMsg);
|
|
1219
|
+
} finally {
|
|
1220
|
+
setIsConnecting(false);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
const handlePay = async () => {
|
|
1224
|
+
if (!wallet) {
|
|
1225
|
+
setError("Please connect your wallet first");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
setError(null);
|
|
1230
|
+
setIsProcessing(true);
|
|
1231
|
+
const validation = validateWithdrawalAddress(wallet.address, toAddress);
|
|
1232
|
+
if (!validation.valid) {
|
|
1233
|
+
setError("Invalid wallet address");
|
|
1234
|
+
setIsProcessing(false);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (wallet.chainId !== chainId) {
|
|
1238
|
+
if (adapterType === "tron") {
|
|
1239
|
+
const networkNames = {
|
|
1240
|
+
728126428: "Mainnet",
|
|
1241
|
+
2494104990: "Shasta",
|
|
1242
|
+
3448148188: "Nile"
|
|
1243
|
+
};
|
|
1244
|
+
const requiredNetwork = networkNames[chainId] || chainId;
|
|
1245
|
+
setError(`Switch to ${requiredNetwork} network`);
|
|
1246
|
+
setIsProcessing(false);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
console.log(`[PayWithWallet] Switching chain from ${wallet.chainId} to ${chainId}`);
|
|
1250
|
+
await switchChain2(chainId);
|
|
1251
|
+
let attempts = 0;
|
|
1252
|
+
let switched = false;
|
|
1253
|
+
while (attempts < 30) {
|
|
1254
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1255
|
+
const currentWallet = getWallet();
|
|
1256
|
+
console.log(`[PayWithWallet] Attempt ${attempts}: current chainId = ${currentWallet?.chainId}, target = ${chainId}`);
|
|
1257
|
+
if (currentWallet && currentWallet.chainId === chainId) {
|
|
1258
|
+
console.log(`[PayWithWallet] Chain switched successfully to ${chainId}`);
|
|
1259
|
+
switched = true;
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
attempts++;
|
|
1263
|
+
}
|
|
1264
|
+
if (!switched) {
|
|
1265
|
+
setError(`Please switch to ${chainInfo.name} network in your wallet`);
|
|
1266
|
+
setIsProcessing(false);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const finalCheck = getWallet();
|
|
1271
|
+
if (!finalCheck || finalCheck.chainId !== chainId) {
|
|
1272
|
+
console.error(`[PayWithWallet] Chain mismatch before sending! Current: ${finalCheck?.chainId}, Required: ${chainId}`);
|
|
1273
|
+
setError(`Wrong network. Please switch to ${chainInfo.name} in MetaMask`);
|
|
1274
|
+
setIsProcessing(false);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
console.log(`[PayWithWallet] Chain verified: ${chainId}, proceeding with payment`);
|
|
1278
|
+
let value;
|
|
1279
|
+
if (tokenAddress && decimals !== void 0) {
|
|
1280
|
+
value = (0, import_viem.parseUnits)(amount, decimals);
|
|
1281
|
+
} else {
|
|
1282
|
+
value = (0, import_viem.parseUnits)(amount, chainInfo.coinDigit);
|
|
1283
|
+
}
|
|
1284
|
+
const result = await sendPayment({
|
|
1285
|
+
to: toAddress,
|
|
1286
|
+
value,
|
|
1287
|
+
chainId,
|
|
1288
|
+
coinId,
|
|
1289
|
+
tokenAddress,
|
|
1290
|
+
decimals
|
|
1291
|
+
});
|
|
1292
|
+
if (!result.success) {
|
|
1293
|
+
const errorMsg = shortenErrorMessage(
|
|
1294
|
+
result.error || "Transaction failed"
|
|
1295
|
+
);
|
|
1296
|
+
setError(errorMsg);
|
|
1297
|
+
onError?.(errorMsg);
|
|
1298
|
+
setIsProcessing(false);
|
|
1299
|
+
}
|
|
1300
|
+
} catch (err) {
|
|
1301
|
+
const errorMsg = shortenErrorMessage(
|
|
1302
|
+
err instanceof Error ? err : "Payment failed"
|
|
1303
|
+
);
|
|
1304
|
+
setError(errorMsg);
|
|
1305
|
+
onError?.(errorMsg);
|
|
1306
|
+
setIsProcessing(false);
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `payment-with-wallet ${className}`, children: [
|
|
1310
|
+
children ? children : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "payment-container", children: [
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "Payment Details" }),
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "payment-info", children: [
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "info-row", children: [
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "label", children: "Amount" }),
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "value amount-value", children: [
|
|
1316
|
+
amount,
|
|
1317
|
+
" ",
|
|
1318
|
+
coinId
|
|
1319
|
+
] })
|
|
1320
|
+
] }),
|
|
1321
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "info-row", children: [
|
|
1322
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "label", children: "Network" }),
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "value", children: chainInfo.name })
|
|
1324
|
+
] }),
|
|
1325
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "info-row", children: [
|
|
1326
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "label", children: "To" }),
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "value address", children: truncateAddress(toAddress) })
|
|
1328
|
+
] })
|
|
1329
|
+
] }),
|
|
1330
|
+
isProcessing && !txHash ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "waiting-verification", children: [
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "waiting-icon", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "waiting-spinner-large" }) }),
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "Waiting for Approval" }),
|
|
1333
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "waiting-message", children: [
|
|
1334
|
+
"Please approve the transaction in your wallet",
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}),
|
|
1336
|
+
"Check your wallet extension to continue"
|
|
1337
|
+
] }),
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "verification-notice", children: [
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1340
|
+
"svg",
|
|
1341
|
+
{
|
|
1342
|
+
width: "16",
|
|
1343
|
+
height: "16",
|
|
1344
|
+
viewBox: "0 0 16 16",
|
|
1345
|
+
fill: "currentColor",
|
|
1346
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 1a7 7 0 100 14A7 7 0 008 1zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zM8 11a.75.75 0 01-.75-.75v-4.5a.75.75 0 011.5 0v4.5A.75.75 0 018 11z" })
|
|
1347
|
+
}
|
|
1348
|
+
),
|
|
1349
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Do not close this window" })
|
|
1350
|
+
] })
|
|
1351
|
+
] }) : txHash ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "waiting-verification", children: [
|
|
1352
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "waiting-icon", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "waiting-spinner-large" }) }),
|
|
1353
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "Processing Payment" }),
|
|
1354
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "waiting-message", children: [
|
|
1355
|
+
"Your transaction is being processed",
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}),
|
|
1357
|
+
"Please wait for confirmation"
|
|
1358
|
+
] }),
|
|
1359
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tx-info", children: [
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tx-label", children: "Transaction Hash" }),
|
|
1361
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "tx-hash-code", children: truncateHash(txHash) })
|
|
1362
|
+
] }),
|
|
1363
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "verification-notice", children: [
|
|
1364
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1365
|
+
"svg",
|
|
1366
|
+
{
|
|
1367
|
+
width: "16",
|
|
1368
|
+
height: "16",
|
|
1369
|
+
viewBox: "0 0 16 16",
|
|
1370
|
+
fill: "currentColor",
|
|
1371
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 1a7 7 0 100 14A7 7 0 008 1zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zM8 11a.75.75 0 01-.75-.75v-4.5a.75.75 0 011.5 0v4.5A.75.75 0 018 11z" })
|
|
1372
|
+
}
|
|
1373
|
+
),
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "This may take a few moments" })
|
|
1375
|
+
] })
|
|
1376
|
+
] }) : !wallet?.isConnected ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wallet-connection", children: [
|
|
1377
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "Connect your wallet to continue" }),
|
|
1378
|
+
installedWallets.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1379
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1380
|
+
"button",
|
|
1381
|
+
{
|
|
1382
|
+
onClick: () => setShowWalletSelector(!showWalletSelector),
|
|
1383
|
+
className: "btn-primary",
|
|
1384
|
+
disabled: isConnecting,
|
|
1385
|
+
children: isConnecting ? "Connecting..." : "Select Wallet"
|
|
1386
|
+
}
|
|
1387
|
+
),
|
|
1388
|
+
showWalletSelector && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "wallet-selector", children: installedWallets.map((connector) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1389
|
+
"button",
|
|
1390
|
+
{
|
|
1391
|
+
onClick: () => handleConnect(connector.id),
|
|
1392
|
+
className: "wallet-option",
|
|
1393
|
+
children: [
|
|
1394
|
+
connector.icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: connector.icon, alt: connector.name }),
|
|
1395
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: connector.name })
|
|
1396
|
+
]
|
|
1397
|
+
},
|
|
1398
|
+
connector.id
|
|
1399
|
+
)) })
|
|
1400
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "no-wallets", children: [
|
|
1401
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "No compatible wallet detected" }),
|
|
1402
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "wallet-hint", children: adapterType === "tron" ? "Please install TronLink extension to continue" : "Please install MetaMask, Trust Wallet, or WalletConnect to continue" })
|
|
1403
|
+
] })
|
|
1404
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wallet-connected", children: [
|
|
1405
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "connected-info", children: [
|
|
1406
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "connected-address", children: truncateAddress(wallet.address, 6, 4) }),
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => disconnect2(), className: "btn-disconnect", children: "Disconnect" })
|
|
1408
|
+
] }),
|
|
1409
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1410
|
+
"button",
|
|
1411
|
+
{
|
|
1412
|
+
onClick: handlePay,
|
|
1413
|
+
className: "btn-primary",
|
|
1414
|
+
disabled: isProcessing,
|
|
1415
|
+
children: isProcessing ? "Sending..." : `Pay ${amount} ${coinId}`
|
|
1416
|
+
}
|
|
1417
|
+
)
|
|
1418
|
+
] }),
|
|
1419
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "error-message", children: error })
|
|
1420
|
+
] }),
|
|
1421
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
|
|
1422
|
+
.payment-with-wallet {
|
|
1423
|
+
padding: 1.5rem;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
.payment-container {
|
|
1427
|
+
max-width: 500px;
|
|
1428
|
+
margin: 0 auto;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
h3 {
|
|
1432
|
+
font-size: 1.25rem;
|
|
1433
|
+
font-weight: 600;
|
|
1434
|
+
margin-bottom: 1.5rem;
|
|
1435
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
.payment-info {
|
|
1439
|
+
background: hsl(0 0% 100%);
|
|
1440
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1441
|
+
padding: 1.25rem;
|
|
1442
|
+
border-radius: 0.75rem;
|
|
1443
|
+
margin-bottom: 1.5rem;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
.info-row {
|
|
1447
|
+
display: flex;
|
|
1448
|
+
justify-content: space-between;
|
|
1449
|
+
align-items: center;
|
|
1450
|
+
padding: 0.625rem 0;
|
|
1451
|
+
border-bottom: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
.info-row:last-child {
|
|
1455
|
+
border-bottom: none;
|
|
1456
|
+
padding-bottom: 0;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
.info-row:first-child {
|
|
1460
|
+
padding-top: 0;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.label {
|
|
1464
|
+
font-size: 0.875rem;
|
|
1465
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1466
|
+
font-weight: 500;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.value {
|
|
1470
|
+
font-size: 0.875rem;
|
|
1471
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1472
|
+
font-weight: 500;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.amount-value {
|
|
1476
|
+
font-size: 1.125rem;
|
|
1477
|
+
font-weight: 600;
|
|
1478
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.address {
|
|
1482
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
1483
|
+
font-size: 0.8125rem;
|
|
1484
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.waiting-verification {
|
|
1488
|
+
text-align: center;
|
|
1489
|
+
padding: 2.5rem 1.5rem;
|
|
1490
|
+
background: hsl(0 0% 100%);
|
|
1491
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1492
|
+
border-radius: 0.75rem;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.waiting-icon {
|
|
1496
|
+
margin: 0 auto 1.5rem;
|
|
1497
|
+
display: flex;
|
|
1498
|
+
justify-content: center;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
.waiting-spinner-large {
|
|
1502
|
+
width: 48px;
|
|
1503
|
+
height: 48px;
|
|
1504
|
+
border: 3px solid hsl(214.3 31.8% 91.4%);
|
|
1505
|
+
border-top-color: hsl(222.2 47.4% 11.2%);
|
|
1506
|
+
border-radius: 50%;
|
|
1507
|
+
animation: spin 0.8s linear infinite;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
.waiting-verification h3 {
|
|
1511
|
+
font-size: 1.125rem;
|
|
1512
|
+
font-weight: 600;
|
|
1513
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1514
|
+
margin-bottom: 0.75rem;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.waiting-message {
|
|
1518
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1519
|
+
line-height: 1.6;
|
|
1520
|
+
margin-bottom: 1.5rem;
|
|
1521
|
+
font-size: 0.875rem;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
.tx-info {
|
|
1525
|
+
background: hsl(210 40% 98%);
|
|
1526
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1527
|
+
padding: 1rem;
|
|
1528
|
+
border-radius: 0.5rem;
|
|
1529
|
+
margin-bottom: 1.5rem;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.tx-label {
|
|
1533
|
+
display: block;
|
|
1534
|
+
font-size: 0.8125rem;
|
|
1535
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1536
|
+
margin-bottom: 0.5rem;
|
|
1537
|
+
font-weight: 500;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
.tx-hash-code {
|
|
1541
|
+
display: block;
|
|
1542
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
1543
|
+
font-size: 0.8125rem;
|
|
1544
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1545
|
+
word-break: break-all;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
.verification-notice {
|
|
1549
|
+
display: flex;
|
|
1550
|
+
align-items: flex-start;
|
|
1551
|
+
gap: 0.75rem;
|
|
1552
|
+
padding: 0.875rem 1rem;
|
|
1553
|
+
background: hsl(210 40% 98%);
|
|
1554
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1555
|
+
border-radius: 0.5rem;
|
|
1556
|
+
text-align: left;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
.verification-notice svg {
|
|
1560
|
+
flex-shrink: 0;
|
|
1561
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1562
|
+
margin-top: 1px;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.verification-notice span {
|
|
1566
|
+
font-size: 0.8125rem;
|
|
1567
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1568
|
+
line-height: 1.5;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.wallet-connection,
|
|
1572
|
+
.wallet-connected {
|
|
1573
|
+
margin-top: 1.5rem;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.wallet-selector {
|
|
1577
|
+
margin-top: 1rem;
|
|
1578
|
+
display: flex;
|
|
1579
|
+
flex-direction: column;
|
|
1580
|
+
gap: 0.625rem;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.wallet-option {
|
|
1584
|
+
display: flex;
|
|
1585
|
+
align-items: center;
|
|
1586
|
+
gap: 0.75rem;
|
|
1587
|
+
padding: 0.875rem 1rem;
|
|
1588
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1589
|
+
border-radius: 0.5rem;
|
|
1590
|
+
background: hsl(0 0% 100%);
|
|
1591
|
+
cursor: pointer;
|
|
1592
|
+
transition: all 0.15s;
|
|
1593
|
+
font-weight: 500;
|
|
1594
|
+
font-size: 0.875rem;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.wallet-option:hover {
|
|
1598
|
+
background: hsl(210 40% 98%);
|
|
1599
|
+
border-color: hsl(217.2 32.6% 17.5%);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
.wallet-option img {
|
|
1603
|
+
width: 20px;
|
|
1604
|
+
height: 20px;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.connected-info {
|
|
1608
|
+
display: flex;
|
|
1609
|
+
justify-content: space-between;
|
|
1610
|
+
align-items: center;
|
|
1611
|
+
margin-bottom: 1rem;
|
|
1612
|
+
padding: 0.875rem 1rem;
|
|
1613
|
+
background: hsl(142.1 76.2% 97.3%);
|
|
1614
|
+
border: 1px solid hsl(142.1 76.2% 90%);
|
|
1615
|
+
border-radius: 0.5rem;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
.connected-address {
|
|
1619
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
1620
|
+
font-size: 0.8125rem;
|
|
1621
|
+
color: hsl(142.1 70.6% 45.3%);
|
|
1622
|
+
font-weight: 500;
|
|
1623
|
+
margin: 0;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.btn-primary,
|
|
1627
|
+
.btn-disconnect {
|
|
1628
|
+
border: none;
|
|
1629
|
+
border-radius: 0.5rem;
|
|
1630
|
+
font-weight: 600;
|
|
1631
|
+
cursor: pointer;
|
|
1632
|
+
transition: all 0.15s;
|
|
1633
|
+
font-size: 0.875rem;
|
|
1634
|
+
display: inline-flex;
|
|
1635
|
+
align-items: center;
|
|
1636
|
+
justify-content: center;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
.btn-primary {
|
|
1640
|
+
padding: 0.625rem 1.25rem;
|
|
1641
|
+
background: hsl(222.2 47.4% 11.2%);
|
|
1642
|
+
color: hsl(210 40% 98%);
|
|
1643
|
+
width: 100%;
|
|
1644
|
+
height: 2.5rem;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
.btn-primary:hover:not(:disabled) {
|
|
1648
|
+
background: hsl(222.2 47.4% 8%);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
.btn-primary:disabled {
|
|
1652
|
+
background: hsl(215.4 16.3% 46.9%);
|
|
1653
|
+
cursor: not-allowed;
|
|
1654
|
+
opacity: 0.5;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.btn-disconnect {
|
|
1658
|
+
background: transparent;
|
|
1659
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1660
|
+
width: auto;
|
|
1661
|
+
padding: 0.375rem 0.875rem;
|
|
1662
|
+
font-size: 0.8125rem;
|
|
1663
|
+
font-weight: 500;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
.btn-disconnect:hover {
|
|
1667
|
+
background: hsl(210 40% 96.1%);
|
|
1668
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.no-wallets {
|
|
1672
|
+
padding: 1.25rem;
|
|
1673
|
+
background: hsl(47.9 95.8% 96.1%);
|
|
1674
|
+
border: 1px solid hsl(47.9 95.8% 82%);
|
|
1675
|
+
border-radius: 0.5rem;
|
|
1676
|
+
text-align: center;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.no-wallets p {
|
|
1680
|
+
color: hsl(25 95.8% 25%);
|
|
1681
|
+
font-size: 0.875rem;
|
|
1682
|
+
margin: 0;
|
|
1683
|
+
font-weight: 600;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.wallet-hint {
|
|
1687
|
+
margin-top: 0.5rem !important;
|
|
1688
|
+
font-weight: 400 !important;
|
|
1689
|
+
color: hsl(25 95.8% 35%) !important;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
@keyframes spin {
|
|
1693
|
+
to { transform: rotate(360deg); }
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.error-message {
|
|
1697
|
+
margin-top: 1rem;
|
|
1698
|
+
padding: 0.875rem 1rem;
|
|
1699
|
+
background: hsl(0 84.2% 97.3%);
|
|
1700
|
+
border: 1px solid hsl(0 84.2% 92%);
|
|
1701
|
+
border-radius: 0.5rem;
|
|
1702
|
+
color: hsl(0 74.3% 42%);
|
|
1703
|
+
font-size: 0.875rem;
|
|
1704
|
+
line-height: 1.5;
|
|
1705
|
+
}
|
|
1706
|
+
` })
|
|
1707
|
+
] });
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
// src/components/PayWithAddress.tsx
|
|
1711
|
+
var import_react7 = require("react");
|
|
1712
|
+
var import_qrcode = require("qrcode.react");
|
|
1713
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1714
|
+
var PayWithAddress = ({
|
|
1715
|
+
walletAddress,
|
|
1716
|
+
amount,
|
|
1717
|
+
coinId,
|
|
1718
|
+
chainId: _chainId,
|
|
1719
|
+
chainInfo,
|
|
1720
|
+
publicOrderId,
|
|
1721
|
+
expiresAt,
|
|
1722
|
+
onCopy,
|
|
1723
|
+
className = "",
|
|
1724
|
+
children
|
|
1725
|
+
}) => {
|
|
1726
|
+
const [copied, setCopied] = (0, import_react7.useState)(false);
|
|
1727
|
+
const [showQR, setShowQR] = (0, import_react7.useState)(true);
|
|
1728
|
+
const [currentTime, setCurrentTime] = (0, import_react7.useState)(Date.now());
|
|
1729
|
+
(0, import_react7.useEffect)(() => {
|
|
1730
|
+
if (!expiresAt) return;
|
|
1731
|
+
const interval = setInterval(() => {
|
|
1732
|
+
setCurrentTime(Date.now());
|
|
1733
|
+
}, 1e3);
|
|
1734
|
+
return () => clearInterval(interval);
|
|
1735
|
+
}, [expiresAt]);
|
|
1736
|
+
const handleCopy = async () => {
|
|
1737
|
+
try {
|
|
1738
|
+
await navigator.clipboard.writeText(walletAddress);
|
|
1739
|
+
setCopied(true);
|
|
1740
|
+
onCopy?.();
|
|
1741
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
console.error("Failed to copy:", err);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
const formatExpiryTime = (expiryString) => {
|
|
1747
|
+
if (!expiryString) return null;
|
|
1748
|
+
const expiry = new Date(expiryString);
|
|
1749
|
+
const diff = expiry.getTime() - currentTime;
|
|
1750
|
+
if (diff <= 0) return "Expired";
|
|
1751
|
+
const minutes = Math.floor(diff / 1e3 / 60);
|
|
1752
|
+
const seconds = Math.floor(diff / 1e3 % 60);
|
|
1753
|
+
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
1754
|
+
};
|
|
1755
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `payment-with-address ${className}`, children: [
|
|
1756
|
+
children ? children : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "payment-container", children: [
|
|
1757
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Payment Address" }),
|
|
1758
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "payment-info", children: [
|
|
1759
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "info-row", children: [
|
|
1760
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Amount" }),
|
|
1761
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "value amount-value", children: [
|
|
1762
|
+
amount,
|
|
1763
|
+
" ",
|
|
1764
|
+
coinId
|
|
1765
|
+
] })
|
|
1766
|
+
] }),
|
|
1767
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "info-row", children: [
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Network" }),
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "value", children: chainInfo.name })
|
|
1770
|
+
] }),
|
|
1771
|
+
publicOrderId && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "info-row", children: [
|
|
1772
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Order ID" }),
|
|
1773
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "value order-id", children: truncateOrderId(publicOrderId) })
|
|
1774
|
+
] }),
|
|
1775
|
+
expiresAt && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "info-row expiry", children: [
|
|
1776
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Time Remaining" }),
|
|
1777
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "value time", children: formatExpiryTime(expiresAt) })
|
|
1778
|
+
] })
|
|
1779
|
+
] }),
|
|
1780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "payment-methods", children: [
|
|
1781
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "method-toggle", children: [
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1783
|
+
"button",
|
|
1784
|
+
{
|
|
1785
|
+
onClick: () => setShowQR(true),
|
|
1786
|
+
className: showQR ? "active" : "",
|
|
1787
|
+
children: "QR Code"
|
|
1788
|
+
}
|
|
1789
|
+
),
|
|
1790
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1791
|
+
"button",
|
|
1792
|
+
{
|
|
1793
|
+
onClick: () => setShowQR(false),
|
|
1794
|
+
className: !showQR ? "active" : "",
|
|
1795
|
+
children: "Address"
|
|
1796
|
+
}
|
|
1797
|
+
)
|
|
1798
|
+
] }),
|
|
1799
|
+
showQR ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "qr-section", children: [
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "qr-code", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1801
|
+
import_qrcode.QRCodeSVG,
|
|
1802
|
+
{
|
|
1803
|
+
value: walletAddress,
|
|
1804
|
+
size: 200,
|
|
1805
|
+
level: "H",
|
|
1806
|
+
includeMargin: true
|
|
1807
|
+
}
|
|
1808
|
+
) }),
|
|
1809
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "qr-instruction", children: "Scan QR code with your wallet app" })
|
|
1810
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "address-section", children: [
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "address-display", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: walletAddress }) }),
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1813
|
+
"button",
|
|
1814
|
+
{
|
|
1815
|
+
onClick: handleCopy,
|
|
1816
|
+
className: "copy-button",
|
|
1817
|
+
children: copied ? "Copied!" : "Copy Address"
|
|
1818
|
+
}
|
|
1819
|
+
)
|
|
1820
|
+
] })
|
|
1821
|
+
] }),
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "payment-status", children: [
|
|
1823
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "status-badge waiting", children: [
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "status-spinner" }),
|
|
1825
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Waiting for Payment" })
|
|
1826
|
+
] }),
|
|
1827
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "status-text", children: "We're checking for your payment on the blockchain" })
|
|
1828
|
+
] }),
|
|
1829
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "warning", children: [
|
|
1830
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 1a7 7 0 100 14A7 7 0 008 1zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zM8 11a.75.75 0 01-.75-.75v-4.5a.75.75 0 011.5 0v4.5A.75.75 0 018 11z" }) }),
|
|
1831
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Send only to this address. Sending to wrong address may result in loss of funds." })
|
|
1832
|
+
] })
|
|
1833
|
+
] }),
|
|
1834
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
|
|
1835
|
+
.payment-with-address {
|
|
1836
|
+
padding: 1.5rem;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
.payment-container {
|
|
1840
|
+
max-width: 500px;
|
|
1841
|
+
margin: 0 auto;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
h3 {
|
|
1845
|
+
font-size: 1.25rem;
|
|
1846
|
+
font-weight: 600;
|
|
1847
|
+
margin-bottom: 1.5rem;
|
|
1848
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
.payment-info {
|
|
1852
|
+
background: hsl(0 0% 100%);
|
|
1853
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1854
|
+
padding: 1.25rem;
|
|
1855
|
+
border-radius: 0.75rem;
|
|
1856
|
+
margin-bottom: 1.5rem;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
.info-row {
|
|
1860
|
+
display: flex;
|
|
1861
|
+
justify-content: space-between;
|
|
1862
|
+
align-items: center;
|
|
1863
|
+
padding: 0.625rem 0;
|
|
1864
|
+
border-bottom: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.info-row:last-child {
|
|
1868
|
+
border-bottom: none;
|
|
1869
|
+
padding-bottom: 0;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
.info-row:first-child {
|
|
1873
|
+
padding-top: 0;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
.label {
|
|
1877
|
+
font-size: 0.875rem;
|
|
1878
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1879
|
+
font-weight: 500;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.value {
|
|
1883
|
+
font-size: 0.875rem;
|
|
1884
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1885
|
+
font-weight: 500;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.amount-value {
|
|
1889
|
+
font-size: 1.125rem;
|
|
1890
|
+
font-weight: 600;
|
|
1891
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.order-id {
|
|
1895
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
1896
|
+
font-size: 0.8125rem;
|
|
1897
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.expiry {
|
|
1901
|
+
border-top: 2px solid hsl(214.3 31.8% 91.4%);
|
|
1902
|
+
padding-top: 0.75rem;
|
|
1903
|
+
margin-top: 0.5rem;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.time {
|
|
1907
|
+
font-weight: 600;
|
|
1908
|
+
font-size: 0.9375rem;
|
|
1909
|
+
color: hsl(0 74.3% 42%);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
.payment-methods {
|
|
1913
|
+
margin: 1.5rem 0;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.method-toggle {
|
|
1917
|
+
display: flex;
|
|
1918
|
+
gap: 0.5rem;
|
|
1919
|
+
padding: 0.25rem;
|
|
1920
|
+
background: hsl(210 40% 98%);
|
|
1921
|
+
border-radius: 0.5rem;
|
|
1922
|
+
margin-bottom: 1.5rem;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
.method-toggle button {
|
|
1926
|
+
flex: 1;
|
|
1927
|
+
padding: 0.5rem 1rem;
|
|
1928
|
+
border: none;
|
|
1929
|
+
border-radius: 0.375rem;
|
|
1930
|
+
background: transparent;
|
|
1931
|
+
cursor: pointer;
|
|
1932
|
+
transition: all 0.15s;
|
|
1933
|
+
font-weight: 500;
|
|
1934
|
+
font-size: 0.875rem;
|
|
1935
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
.method-toggle button.active {
|
|
1939
|
+
background: hsl(0 0% 100%);
|
|
1940
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1941
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
.method-toggle button:hover:not(.active) {
|
|
1945
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
.qr-section {
|
|
1949
|
+
display: flex;
|
|
1950
|
+
flex-direction: column;
|
|
1951
|
+
align-items: center;
|
|
1952
|
+
padding: 1.5rem;
|
|
1953
|
+
background: hsl(0 0% 100%);
|
|
1954
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1955
|
+
border-radius: 0.75rem;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.qr-code {
|
|
1959
|
+
padding: 1rem;
|
|
1960
|
+
background: hsl(0 0% 100%);
|
|
1961
|
+
border-radius: 0.5rem;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
.qr-instruction {
|
|
1965
|
+
margin-top: 1rem;
|
|
1966
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
1967
|
+
text-align: center;
|
|
1968
|
+
font-size: 0.875rem;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
.address-section {
|
|
1972
|
+
display: flex;
|
|
1973
|
+
flex-direction: column;
|
|
1974
|
+
gap: 1rem;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
.address-display {
|
|
1978
|
+
padding: 1rem;
|
|
1979
|
+
background: hsl(210 40% 98%);
|
|
1980
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
1981
|
+
border-radius: 0.5rem;
|
|
1982
|
+
overflow: auto;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
.address-display code {
|
|
1986
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
1987
|
+
font-size: 0.8125rem;
|
|
1988
|
+
word-break: break-all;
|
|
1989
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
.copy-button {
|
|
1993
|
+
padding: 0.625rem 1.25rem;
|
|
1994
|
+
background: hsl(222.2 47.4% 11.2%);
|
|
1995
|
+
color: hsl(210 40% 98%);
|
|
1996
|
+
border: none;
|
|
1997
|
+
border-radius: 0.5rem;
|
|
1998
|
+
font-weight: 600;
|
|
1999
|
+
cursor: pointer;
|
|
2000
|
+
transition: all 0.15s;
|
|
2001
|
+
font-size: 0.875rem;
|
|
2002
|
+
height: 2.5rem;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
.copy-button:hover {
|
|
2006
|
+
background: hsl(222.2 47.4% 8%);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
.payment-status {
|
|
2010
|
+
margin: 1.5rem 0;
|
|
2011
|
+
padding: 1rem 1.25rem;
|
|
2012
|
+
background: hsl(47.9 95.8% 96.1%);
|
|
2013
|
+
border-radius: 0.5rem;
|
|
2014
|
+
border: 1px solid hsl(47.9 95.8% 82%);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
.status-badge {
|
|
2018
|
+
display: flex;
|
|
2019
|
+
align-items: center;
|
|
2020
|
+
gap: 0.625rem;
|
|
2021
|
+
margin-bottom: 0.5rem;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
.status-badge.waiting {
|
|
2025
|
+
color: hsl(25 95.8% 25%);
|
|
2026
|
+
font-weight: 600;
|
|
2027
|
+
font-size: 0.875rem;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
.status-spinner {
|
|
2031
|
+
width: 14px;
|
|
2032
|
+
height: 14px;
|
|
2033
|
+
border: 2px solid hsl(25 95.8% 55%);
|
|
2034
|
+
border-top-color: transparent;
|
|
2035
|
+
border-radius: 50%;
|
|
2036
|
+
animation: spin 0.8s linear infinite;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
@keyframes spin {
|
|
2040
|
+
to { transform: rotate(360deg); }
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
.status-text {
|
|
2044
|
+
color: hsl(25 95.8% 35%);
|
|
2045
|
+
font-size: 0.8125rem;
|
|
2046
|
+
margin: 0;
|
|
2047
|
+
line-height: 1.5;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.warning {
|
|
2051
|
+
display: flex;
|
|
2052
|
+
gap: 0.75rem;
|
|
2053
|
+
padding: 0.875rem 1rem;
|
|
2054
|
+
background: hsl(47.9 95.8% 96.1%);
|
|
2055
|
+
border-radius: 0.5rem;
|
|
2056
|
+
border: 1px solid hsl(47.9 95.8% 82%);
|
|
2057
|
+
color: hsl(25 95.8% 25%);
|
|
2058
|
+
font-size: 0.8125rem;
|
|
2059
|
+
line-height: 1.5;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
.warning svg {
|
|
2063
|
+
flex-shrink: 0;
|
|
2064
|
+
margin-top: 1px;
|
|
2065
|
+
color: hsl(25 95.8% 45%);
|
|
2066
|
+
}
|
|
2067
|
+
` })
|
|
2068
|
+
] });
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
// src/api/coins.ts
|
|
2072
|
+
var coins_exports = {};
|
|
2073
|
+
__export(coins_exports, {
|
|
2074
|
+
findChainByCoinAndChainId: () => findChainByCoinAndChainId,
|
|
2075
|
+
getCoinChains: () => getCoinChains
|
|
2076
|
+
});
|
|
2077
|
+
async function getCoinChains(coinId) {
|
|
2078
|
+
try {
|
|
2079
|
+
const response = await apiClient.getClient().get(
|
|
2080
|
+
`/coins/${coinId}`
|
|
2081
|
+
);
|
|
2082
|
+
if (!response.data.isSuccess) {
|
|
2083
|
+
throw new Error(response.data.message || "Failed to fetch coin chains");
|
|
2084
|
+
}
|
|
2085
|
+
return response.data.output.chains;
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
if (error.response?.data?.message) {
|
|
2088
|
+
throw new Error(error.response.data.message);
|
|
2089
|
+
}
|
|
2090
|
+
throw new Error("Failed to fetch coin chains");
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
async function findChainByCoinAndChainId(coinId, chainId) {
|
|
2094
|
+
const chains = await getCoinChains(coinId);
|
|
2095
|
+
return chains.find((chain) => chain.chainId === chainId) || null;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// src/components/PaymentFlow.tsx
|
|
2099
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2100
|
+
var PaymentFlow = ({
|
|
2101
|
+
toAddress,
|
|
2102
|
+
amount,
|
|
2103
|
+
coinId,
|
|
2104
|
+
chainId,
|
|
2105
|
+
tokenAddress,
|
|
2106
|
+
decimals,
|
|
2107
|
+
publicOrderId,
|
|
2108
|
+
expiresAt,
|
|
2109
|
+
defaultMethod = "wallet",
|
|
2110
|
+
allowMethodSwitch = true,
|
|
2111
|
+
onSuccess,
|
|
2112
|
+
onError,
|
|
2113
|
+
onMethodChange,
|
|
2114
|
+
className = "",
|
|
2115
|
+
children
|
|
2116
|
+
}) => {
|
|
2117
|
+
const [selectedMethod, setSelectedMethod] = (0, import_react8.useState)(defaultMethod);
|
|
2118
|
+
const [chainInfo, setChainInfo] = (0, import_react8.useState)(null);
|
|
2119
|
+
const [isLoadingChain, setIsLoadingChain] = (0, import_react8.useState)(true);
|
|
2120
|
+
const [chainError, setChainError] = (0, import_react8.useState)(null);
|
|
2121
|
+
(0, import_react8.useEffect)(() => {
|
|
2122
|
+
const fetchChainInfo = async () => {
|
|
2123
|
+
try {
|
|
2124
|
+
setIsLoadingChain(true);
|
|
2125
|
+
setChainError(null);
|
|
2126
|
+
console.log("[PaymentFlow] Fetching chain info:", {
|
|
2127
|
+
coinId,
|
|
2128
|
+
requestedChainId: chainId,
|
|
2129
|
+
requestedChainIdType: typeof chainId
|
|
2130
|
+
});
|
|
2131
|
+
const chains = await getCoinChains(coinId);
|
|
2132
|
+
console.log("[PaymentFlow] Available chains:", chains.map((c) => ({
|
|
2133
|
+
chainId: c.chainId,
|
|
2134
|
+
name: c.name
|
|
2135
|
+
})));
|
|
2136
|
+
const matchingChain = chains.find(
|
|
2137
|
+
(chain) => chain.chainId === chainId.toString()
|
|
2138
|
+
);
|
|
2139
|
+
if (!matchingChain) {
|
|
2140
|
+
console.error("[PaymentFlow] No matching chain found!", {
|
|
2141
|
+
requestedChainId: chainId.toString(),
|
|
2142
|
+
availableChainIds: chains.map((c) => c.chainId)
|
|
2143
|
+
});
|
|
2144
|
+
setChainError(`Chain ${chainId} not supported for ${coinId}`);
|
|
2145
|
+
} else {
|
|
2146
|
+
console.log("[PaymentFlow] Matching chain found:", {
|
|
2147
|
+
chainId: matchingChain.chainId,
|
|
2148
|
+
name: matchingChain.name
|
|
2149
|
+
});
|
|
2150
|
+
setChainInfo(matchingChain);
|
|
2151
|
+
}
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
console.error("[PaymentFlow] Failed to fetch chain info:", error);
|
|
2154
|
+
setChainError(error.message || "Failed to load chain information");
|
|
2155
|
+
} finally {
|
|
2156
|
+
setIsLoadingChain(false);
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
fetchChainInfo();
|
|
2160
|
+
}, [coinId, chainId]);
|
|
2161
|
+
const handleMethodChange = (method) => {
|
|
2162
|
+
setSelectedMethod(method);
|
|
2163
|
+
onMethodChange?.(method);
|
|
2164
|
+
};
|
|
2165
|
+
const handleWalletSuccess = (txHash) => {
|
|
2166
|
+
onSuccess?.({ txHash, publicOrderId });
|
|
2167
|
+
};
|
|
2168
|
+
if (children) {
|
|
2169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children });
|
|
2170
|
+
}
|
|
2171
|
+
if (isLoadingChain) {
|
|
2172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `payment-flow loading ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "loading-spinner", children: [
|
|
2173
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "spinner" }),
|
|
2174
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Loading chain information..." })
|
|
2175
|
+
] }) });
|
|
2176
|
+
}
|
|
2177
|
+
if (chainError || !chainInfo) {
|
|
2178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `payment-flow error ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "error-message", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
|
|
2179
|
+
"\u26A0\uFE0F ",
|
|
2180
|
+
chainError || "Chain information not available"
|
|
2181
|
+
] }) }) });
|
|
2182
|
+
}
|
|
2183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `payment-flow ${className}`, children: [
|
|
2184
|
+
allowMethodSwitch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "payment-method-selector", children: [
|
|
2185
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2186
|
+
"button",
|
|
2187
|
+
{
|
|
2188
|
+
onClick: () => handleMethodChange("wallet"),
|
|
2189
|
+
className: `method-button ${selectedMethod === "wallet" ? "active" : ""}`,
|
|
2190
|
+
children: [
|
|
2191
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: [
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z", strokeWidth: "2", strokeLinecap: "round" }),
|
|
2193
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M9 12l2 2 4-4", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
2194
|
+
] }),
|
|
2195
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Connect Wallet" })
|
|
2196
|
+
]
|
|
2197
|
+
}
|
|
2198
|
+
),
|
|
2199
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2200
|
+
"button",
|
|
2201
|
+
{
|
|
2202
|
+
onClick: () => handleMethodChange("address"),
|
|
2203
|
+
className: `method-button ${selectedMethod === "address" ? "active" : ""}`,
|
|
2204
|
+
children: [
|
|
2205
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: [
|
|
2206
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "3", y: "11", width: "18", height: "10", rx: "2", strokeWidth: "2" }),
|
|
2207
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M7 11V7a5 5 0 0110 0v4", strokeWidth: "2", strokeLinecap: "round" })
|
|
2208
|
+
] }),
|
|
2209
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "QR / Address" })
|
|
2210
|
+
]
|
|
2211
|
+
}
|
|
2212
|
+
)
|
|
2213
|
+
] }),
|
|
2214
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "payment-content", children: selectedMethod === "wallet" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2215
|
+
PayWithWallet,
|
|
2216
|
+
{
|
|
2217
|
+
toAddress,
|
|
2218
|
+
amount,
|
|
2219
|
+
coinId,
|
|
2220
|
+
chainId: parseInt(chainInfo.chainId),
|
|
2221
|
+
chainInfo,
|
|
2222
|
+
tokenAddress,
|
|
2223
|
+
decimals,
|
|
2224
|
+
publicOrderId,
|
|
2225
|
+
onSuccess: handleWalletSuccess,
|
|
2226
|
+
onError
|
|
2227
|
+
}
|
|
2228
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2229
|
+
PayWithAddress,
|
|
2230
|
+
{
|
|
2231
|
+
walletAddress: toAddress,
|
|
2232
|
+
amount,
|
|
2233
|
+
coinId,
|
|
2234
|
+
chainId: parseInt(chainInfo.chainId),
|
|
2235
|
+
chainInfo,
|
|
2236
|
+
publicOrderId,
|
|
2237
|
+
expiresAt,
|
|
2238
|
+
onCopy: () => console.log("Address copied")
|
|
2239
|
+
}
|
|
2240
|
+
) }),
|
|
2241
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
|
|
2242
|
+
.payment-flow {
|
|
2243
|
+
width: 100%;
|
|
2244
|
+
max-width: 600px;
|
|
2245
|
+
margin: 0 auto;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
.payment-method-selector {
|
|
2249
|
+
display: flex;
|
|
2250
|
+
gap: 0.5rem;
|
|
2251
|
+
margin-bottom: 1.5rem;
|
|
2252
|
+
padding: 0.25rem;
|
|
2253
|
+
background: hsl(210 40% 98%);
|
|
2254
|
+
border-radius: 0.5rem;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.method-button {
|
|
2258
|
+
flex: 1;
|
|
2259
|
+
display: flex;
|
|
2260
|
+
align-items: center;
|
|
2261
|
+
justify-content: center;
|
|
2262
|
+
gap: 0.5rem;
|
|
2263
|
+
padding: 0.625rem 1rem;
|
|
2264
|
+
border: none;
|
|
2265
|
+
border-radius: 0.375rem;
|
|
2266
|
+
background: transparent;
|
|
2267
|
+
cursor: pointer;
|
|
2268
|
+
transition: all 0.15s;
|
|
2269
|
+
font-weight: 500;
|
|
2270
|
+
font-size: 0.875rem;
|
|
2271
|
+
color: hsl(215.4 16.3% 46.9%);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
.method-button:hover:not(.active) {
|
|
2275
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
.method-button.active {
|
|
2279
|
+
background: hsl(0 0% 100%);
|
|
2280
|
+
color: hsl(222.2 47.4% 11.2%);
|
|
2281
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
.method-button svg {
|
|
2285
|
+
width: 18px;
|
|
2286
|
+
height: 18px;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
.payment-content {
|
|
2290
|
+
background: hsl(0 0% 100%);
|
|
2291
|
+
border: 1px solid hsl(214.3 31.8% 91.4%);
|
|
2292
|
+
border-radius: 0.75rem;
|
|
2293
|
+
padding: 0;
|
|
2294
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
2295
|
+
}
|
|
2296
|
+
` })
|
|
2297
|
+
] });
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
// src/components/OrderStatusScreen.tsx
|
|
2301
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2302
|
+
var OrderStatusScreen = ({
|
|
2303
|
+
state,
|
|
2304
|
+
publicOrderId,
|
|
2305
|
+
amount,
|
|
2306
|
+
coinId,
|
|
2307
|
+
onRetry
|
|
2308
|
+
}) => {
|
|
2309
|
+
const getContent = () => {
|
|
2310
|
+
switch (state) {
|
|
2311
|
+
case "success" /* SUCCESS */:
|
|
2312
|
+
return {
|
|
2313
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", children: [
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10", fill: "#10b981" }),
|
|
2315
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2316
|
+
"path",
|
|
2317
|
+
{
|
|
2318
|
+
d: "M8 12l2 2 6-6",
|
|
2319
|
+
stroke: "white",
|
|
2320
|
+
strokeWidth: "2",
|
|
2321
|
+
strokeLinecap: "round",
|
|
2322
|
+
strokeLinejoin: "round"
|
|
2323
|
+
}
|
|
2324
|
+
)
|
|
2325
|
+
] }),
|
|
2326
|
+
title: "Payment Successful!",
|
|
2327
|
+
message: "Your payment has been processed successfully.",
|
|
2328
|
+
color: "#10b981"
|
|
2329
|
+
};
|
|
2330
|
+
case "failed" /* FAILED */:
|
|
2331
|
+
return {
|
|
2332
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", children: [
|
|
2333
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10", fill: "#ef4444" }),
|
|
2334
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2335
|
+
"path",
|
|
2336
|
+
{
|
|
2337
|
+
d: "M8 8l8 8M16 8l-8 8",
|
|
2338
|
+
stroke: "white",
|
|
2339
|
+
strokeWidth: "2",
|
|
2340
|
+
strokeLinecap: "round"
|
|
2341
|
+
}
|
|
2342
|
+
)
|
|
2343
|
+
] }),
|
|
2344
|
+
title: "Payment Failed",
|
|
2345
|
+
message: "The payment transaction could not be completed.",
|
|
2346
|
+
color: "#ef4444",
|
|
2347
|
+
showRetry: true
|
|
2348
|
+
};
|
|
2349
|
+
case "refund_insufficient" /* REFUND_INSUFFICIENT */:
|
|
2350
|
+
return {
|
|
2351
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", children: [
|
|
2352
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10", fill: "#f59e0b" }),
|
|
2353
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2354
|
+
"path",
|
|
2355
|
+
{
|
|
2356
|
+
d: "M12 8v4M12 16h.01",
|
|
2357
|
+
stroke: "white",
|
|
2358
|
+
strokeWidth: "2",
|
|
2359
|
+
strokeLinecap: "round"
|
|
2360
|
+
}
|
|
2361
|
+
)
|
|
2362
|
+
] }),
|
|
2363
|
+
title: "Insufficient Amount - Refund Issued",
|
|
2364
|
+
message: "The payment amount was less than required. A refund has been initiated.",
|
|
2365
|
+
color: "#f59e0b",
|
|
2366
|
+
showRetry: true
|
|
2367
|
+
};
|
|
2368
|
+
case "refund_excess" /* REFUND_EXCESS */:
|
|
2369
|
+
return {
|
|
2370
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", children: [
|
|
2371
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10", fill: "#f59e0b" }),
|
|
2372
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2373
|
+
"path",
|
|
2374
|
+
{
|
|
2375
|
+
d: "M12 8v4M12 16h.01",
|
|
2376
|
+
stroke: "white",
|
|
2377
|
+
strokeWidth: "2",
|
|
2378
|
+
strokeLinecap: "round"
|
|
2379
|
+
}
|
|
2380
|
+
)
|
|
2381
|
+
] }),
|
|
2382
|
+
title: "Excess Amount - Partial Refund Issued",
|
|
2383
|
+
message: "The payment amount exceeded the required amount. The excess has been refunded.",
|
|
2384
|
+
color: "#f59e0b",
|
|
2385
|
+
showRetry: true
|
|
2386
|
+
};
|
|
2387
|
+
default:
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
const content = getContent();
|
|
2392
|
+
if (!content) return null;
|
|
2393
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "order-status-screen", children: [
|
|
2394
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "status-container", children: [
|
|
2395
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "status-icon", children: content.icon }),
|
|
2396
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { style: { color: content.color }, children: content.title }),
|
|
2397
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: content.message }),
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "order-details", children: [
|
|
2399
|
+
amount && coinId && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "detail-row", children: [
|
|
2400
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Amount:" }),
|
|
2401
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "detail-value", children: [
|
|
2402
|
+
amount,
|
|
2403
|
+
" ",
|
|
2404
|
+
coinId
|
|
2405
|
+
] })
|
|
2406
|
+
] }),
|
|
2407
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "detail-row", children: [
|
|
2408
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Order ID:" }),
|
|
2409
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "detail-value", children: [
|
|
2410
|
+
publicOrderId.slice(0, 8),
|
|
2411
|
+
"..."
|
|
2412
|
+
] })
|
|
2413
|
+
] })
|
|
2414
|
+
] }),
|
|
2415
|
+
content.showRetry && onRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: onRetry, className: "retry-button", children: "Try Again" })
|
|
2416
|
+
] }),
|
|
2417
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `
|
|
2418
|
+
.order-status-screen {
|
|
2419
|
+
display: flex;
|
|
2420
|
+
align-items: center;
|
|
2421
|
+
justify-content: center;
|
|
2422
|
+
min-height: 400px;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
.status-container {
|
|
2426
|
+
text-align: center;
|
|
2427
|
+
max-width: 400px;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
.status-icon {
|
|
2431
|
+
margin: 0 auto 1.5rem;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
.status-container h3 {
|
|
2435
|
+
font-size: 1.5rem;
|
|
2436
|
+
font-weight: 600;
|
|
2437
|
+
margin-bottom: 0.5rem;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
.status-container p {
|
|
2441
|
+
color: #6b7280;
|
|
2442
|
+
margin-bottom: 1.5rem;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
.order-details {
|
|
2446
|
+
background: #f9fafb;
|
|
2447
|
+
padding: 1rem;
|
|
2448
|
+
border-radius: 8px;
|
|
2449
|
+
margin-bottom: 1.5rem;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
.detail-row {
|
|
2453
|
+
display: flex;
|
|
2454
|
+
justify-content: space-between;
|
|
2455
|
+
margin-bottom: 0.5rem;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
.detail-row:last-child {
|
|
2459
|
+
margin-bottom: 0;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
.detail-value {
|
|
2463
|
+
font-weight: 600;
|
|
2464
|
+
color: #2563eb;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
.retry-button {
|
|
2468
|
+
padding: 0.75rem 1.5rem;
|
|
2469
|
+
background: #2563eb;
|
|
2470
|
+
color: white;
|
|
2471
|
+
border: none;
|
|
2472
|
+
border-radius: 8px;
|
|
2473
|
+
font-weight: 600;
|
|
2474
|
+
cursor: pointer;
|
|
2475
|
+
transition: background 0.2s;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
.retry-button:hover {
|
|
2479
|
+
background: #1d4ed8;
|
|
2480
|
+
}
|
|
2481
|
+
` })
|
|
2482
|
+
] });
|
|
2483
|
+
};
|
|
2484
|
+
|
|
2485
|
+
// src/components/OrderPayment.tsx
|
|
2486
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2487
|
+
var OrderPayment = ({
|
|
2488
|
+
publicOrderId,
|
|
2489
|
+
onSuccess,
|
|
2490
|
+
onError,
|
|
2491
|
+
className = ""
|
|
2492
|
+
}) => {
|
|
2493
|
+
const { order, isLoading, error } = useOrder(publicOrderId, {
|
|
2494
|
+
pollingInterval: 5e3,
|
|
2495
|
+
enablePolling: true
|
|
2496
|
+
});
|
|
2497
|
+
const [hasNotifiedSuccess, setHasNotifiedSuccess] = (0, import_react9.useState)(false);
|
|
2498
|
+
(0, import_react9.useEffect)(() => {
|
|
2499
|
+
if (error) {
|
|
2500
|
+
onError?.(error.message);
|
|
2501
|
+
}
|
|
2502
|
+
}, [error, onError]);
|
|
2503
|
+
(0, import_react9.useEffect)(() => {
|
|
2504
|
+
if (!order || !order.orderStat) return;
|
|
2505
|
+
const state = getOrderState(order.orderStat);
|
|
2506
|
+
if (state === "success" /* SUCCESS */ && !hasNotifiedSuccess) {
|
|
2507
|
+
setHasNotifiedSuccess(true);
|
|
2508
|
+
onSuccess?.({ publicOrderId });
|
|
2509
|
+
}
|
|
2510
|
+
if (state === "failed" /* FAILED */ || state === "refund_insufficient" /* REFUND_INSUFFICIENT */ || state === "refund_excess" /* REFUND_EXCESS */) {
|
|
2511
|
+
const errorMessage = getOrderStatusMessage(order.orderStat);
|
|
2512
|
+
onError?.(errorMessage);
|
|
2513
|
+
}
|
|
2514
|
+
}, [order, hasNotifiedSuccess, onSuccess, onError, publicOrderId]);
|
|
2515
|
+
const handleWalletPaymentSuccess = (result) => {
|
|
2516
|
+
console.log("[OrderPayment] Wallet payment success, txHash:", result.txHash);
|
|
2517
|
+
};
|
|
2518
|
+
const handleRetry = () => {
|
|
2519
|
+
window.location.reload();
|
|
2520
|
+
};
|
|
2521
|
+
if (isLoading) {
|
|
2522
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `order-payment loading ${className}`, children: [
|
|
2523
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "loading-spinner", children: [
|
|
2524
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "spinner" }),
|
|
2525
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: "Loading payment details..." })
|
|
2526
|
+
] }),
|
|
2527
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
|
|
2528
|
+
.order-payment.loading {
|
|
2529
|
+
display: flex;
|
|
2530
|
+
align-items: center;
|
|
2531
|
+
justify-content: center;
|
|
2532
|
+
min-height: 400px;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
.loading-spinner {
|
|
2536
|
+
text-align: center;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
.spinner {
|
|
2540
|
+
width: 48px;
|
|
2541
|
+
height: 48px;
|
|
2542
|
+
border: 4px solid #e5e7eb;
|
|
2543
|
+
border-top-color: #2563eb;
|
|
2544
|
+
border-radius: 50%;
|
|
2545
|
+
animation: spin 0.8s linear infinite;
|
|
2546
|
+
margin: 0 auto 1rem;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
@keyframes spin {
|
|
2550
|
+
to { transform: rotate(360deg); }
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
.loading-spinner p {
|
|
2554
|
+
color: #6b7280;
|
|
2555
|
+
font-size: 0.875rem;
|
|
2556
|
+
}
|
|
2557
|
+
` })
|
|
2558
|
+
] });
|
|
2559
|
+
}
|
|
2560
|
+
if (error) {
|
|
2561
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `order-payment error ${className}`, children: [
|
|
2562
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "error-container", children: [
|
|
2563
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "#ef4444", children: [
|
|
2564
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "10", strokeWidth: "2" }),
|
|
2565
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 8v4M12 16h.01", strokeWidth: "2", strokeLinecap: "round" })
|
|
2566
|
+
] }),
|
|
2567
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "Payment Error" }),
|
|
2568
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: error.message }),
|
|
2569
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: () => window.location.reload(), className: "retry-button", children: "Try Again" })
|
|
2570
|
+
] }),
|
|
2571
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
|
|
2572
|
+
.order-payment.error {
|
|
2573
|
+
display: flex;
|
|
2574
|
+
align-items: center;
|
|
2575
|
+
justify-content: center;
|
|
2576
|
+
min-height: 400px;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
.error-container {
|
|
2580
|
+
text-align: center;
|
|
2581
|
+
max-width: 400px;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
.error-container svg {
|
|
2585
|
+
margin: 0 auto 1rem;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
.error-container h3 {
|
|
2589
|
+
font-size: 1.25rem;
|
|
2590
|
+
font-weight: 600;
|
|
2591
|
+
color: #ef4444;
|
|
2592
|
+
margin-bottom: 0.5rem;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
.error-container p {
|
|
2596
|
+
color: #6b7280;
|
|
2597
|
+
margin-bottom: 1.5rem;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
.retry-button {
|
|
2601
|
+
padding: 0.75rem 1.5rem;
|
|
2602
|
+
background: #2563eb;
|
|
2603
|
+
color: white;
|
|
2604
|
+
border: none;
|
|
2605
|
+
border-radius: 8px;
|
|
2606
|
+
font-weight: 600;
|
|
2607
|
+
cursor: pointer;
|
|
2608
|
+
transition: background 0.2s;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
.retry-button:hover {
|
|
2612
|
+
background: #1d4ed8;
|
|
2613
|
+
}
|
|
2614
|
+
` })
|
|
2615
|
+
] });
|
|
2616
|
+
}
|
|
2617
|
+
if (!order || !order.orderStat) {
|
|
2618
|
+
return null;
|
|
2619
|
+
}
|
|
2620
|
+
const orderState = getOrderState(order.orderStat);
|
|
2621
|
+
const isExpired = new Date(order.expireDt) < /* @__PURE__ */ new Date();
|
|
2622
|
+
if (orderState !== "waiting" /* WAITING */ || isExpired) {
|
|
2623
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2624
|
+
OrderStatusScreen,
|
|
2625
|
+
{
|
|
2626
|
+
state: isExpired ? "failed" /* FAILED */ : orderState,
|
|
2627
|
+
publicOrderId,
|
|
2628
|
+
amount: order.price,
|
|
2629
|
+
coinId: order.coin,
|
|
2630
|
+
onRetry: handleRetry
|
|
2631
|
+
}
|
|
2632
|
+
) });
|
|
2633
|
+
}
|
|
2634
|
+
const walletInfo = order.walletInfo?.[0];
|
|
2635
|
+
if (!walletInfo?.address) {
|
|
2636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2637
|
+
OrderStatusScreen,
|
|
2638
|
+
{
|
|
2639
|
+
state: "failed" /* FAILED */,
|
|
2640
|
+
publicOrderId,
|
|
2641
|
+
onRetry: handleRetry
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2646
|
+
PaymentFlow,
|
|
2647
|
+
{
|
|
2648
|
+
toAddress: walletInfo.address,
|
|
2649
|
+
amount: order.price.toString(),
|
|
2650
|
+
coinId: order.coin,
|
|
2651
|
+
chainId: parseInt(order.chain),
|
|
2652
|
+
tokenAddress: walletInfo.contractAddr,
|
|
2653
|
+
decimals: walletInfo.digit,
|
|
2654
|
+
publicOrderId,
|
|
2655
|
+
expiresAt: order.expireDt,
|
|
2656
|
+
onSuccess: handleWalletPaymentSuccess,
|
|
2657
|
+
onError,
|
|
2658
|
+
allowMethodSwitch: true,
|
|
2659
|
+
defaultMethod: "wallet"
|
|
2660
|
+
}
|
|
2661
|
+
) });
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
// src/components/PaymentModal.tsx
|
|
2665
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2666
|
+
var PaymentModal = ({
|
|
2667
|
+
publicOrderId,
|
|
2668
|
+
isOpen,
|
|
2669
|
+
onClose,
|
|
2670
|
+
onSuccess,
|
|
2671
|
+
onError
|
|
2672
|
+
}) => {
|
|
2673
|
+
(0, import_react10.useEffect)(() => {
|
|
2674
|
+
const handleEscape = (e) => {
|
|
2675
|
+
if (e.key === "Escape" && isOpen) {
|
|
2676
|
+
onClose();
|
|
2677
|
+
}
|
|
2678
|
+
};
|
|
2679
|
+
document.addEventListener("keydown", handleEscape);
|
|
2680
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
2681
|
+
}, [isOpen, onClose]);
|
|
2682
|
+
(0, import_react10.useEffect)(() => {
|
|
2683
|
+
if (isOpen) {
|
|
2684
|
+
document.body.style.overflow = "hidden";
|
|
2685
|
+
} else {
|
|
2686
|
+
document.body.style.overflow = "unset";
|
|
2687
|
+
}
|
|
2688
|
+
return () => {
|
|
2689
|
+
document.body.style.overflow = "unset";
|
|
2690
|
+
};
|
|
2691
|
+
}, [isOpen]);
|
|
2692
|
+
if (!isOpen) return null;
|
|
2693
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "payment-modal-overlay", onClick: onClose, children: [
|
|
2694
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "payment-modal-content", onClick: (e) => e.stopPropagation(), children: [
|
|
2695
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { className: "payment-modal-close", onClick: onClose, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M18 6L6 18M6 6l12 12", strokeWidth: "2", strokeLinecap: "round" }) }) }),
|
|
2696
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2697
|
+
OrderPayment,
|
|
2698
|
+
{
|
|
2699
|
+
publicOrderId,
|
|
2700
|
+
onSuccess: (result) => {
|
|
2701
|
+
onSuccess?.(result);
|
|
2702
|
+
onClose();
|
|
2703
|
+
},
|
|
2704
|
+
onError
|
|
2705
|
+
}
|
|
2706
|
+
)
|
|
2707
|
+
] }),
|
|
2708
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `
|
|
2709
|
+
.payment-modal-overlay {
|
|
2710
|
+
position: fixed;
|
|
2711
|
+
top: 0;
|
|
2712
|
+
left: 0;
|
|
2713
|
+
right: 0;
|
|
2714
|
+
bottom: 0;
|
|
2715
|
+
background: rgba(0, 0, 0, 0.5);
|
|
2716
|
+
display: flex;
|
|
2717
|
+
align-items: center;
|
|
2718
|
+
justify-content: center;
|
|
2719
|
+
z-index: 9999;
|
|
2720
|
+
padding: 1rem;
|
|
2721
|
+
animation: fadeIn 0.2s ease-out;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
@keyframes fadeIn {
|
|
2725
|
+
from {
|
|
2726
|
+
opacity: 0;
|
|
2727
|
+
}
|
|
2728
|
+
to {
|
|
2729
|
+
opacity: 1;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
.payment-modal-content {
|
|
2734
|
+
position: relative;
|
|
2735
|
+
background: white;
|
|
2736
|
+
border-radius: 16px;
|
|
2737
|
+
max-width: 640px;
|
|
2738
|
+
width: 100%;
|
|
2739
|
+
max-height: 90vh;
|
|
2740
|
+
overflow-y: auto;
|
|
2741
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
2742
|
+
animation: slideUp 0.3s ease-out;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
@keyframes slideUp {
|
|
2746
|
+
from {
|
|
2747
|
+
transform: translateY(20px);
|
|
2748
|
+
opacity: 0;
|
|
2749
|
+
}
|
|
2750
|
+
to {
|
|
2751
|
+
transform: translateY(0);
|
|
2752
|
+
opacity: 1;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
.payment-modal-close {
|
|
2757
|
+
position: absolute;
|
|
2758
|
+
top: 1rem;
|
|
2759
|
+
right: 1rem;
|
|
2760
|
+
width: 40px;
|
|
2761
|
+
height: 40px;
|
|
2762
|
+
border: none;
|
|
2763
|
+
background: rgba(0, 0, 0, 0.05);
|
|
2764
|
+
border-radius: 50%;
|
|
2765
|
+
cursor: pointer;
|
|
2766
|
+
display: flex;
|
|
2767
|
+
align-items: center;
|
|
2768
|
+
justify-content: center;
|
|
2769
|
+
transition: all 0.2s;
|
|
2770
|
+
z-index: 1;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
.payment-modal-close:hover {
|
|
2774
|
+
background: rgba(0, 0, 0, 0.1);
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
.payment-modal-close svg {
|
|
2778
|
+
width: 20px;
|
|
2779
|
+
height: 20px;
|
|
2780
|
+
color: #6b7280;
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
/* \uBAA8\uBC14\uC77C \uB300\uC751 */
|
|
2784
|
+
@media (max-width: 640px) {
|
|
2785
|
+
.payment-modal-overlay {
|
|
2786
|
+
padding: 0;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
.payment-modal-content {
|
|
2790
|
+
max-width: 100%;
|
|
2791
|
+
max-height: 100vh;
|
|
2792
|
+
border-radius: 0;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
` })
|
|
2796
|
+
] });
|
|
2797
|
+
};
|
|
2798
|
+
|
|
2799
|
+
// src/components/PaymentButton.tsx
|
|
2800
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2801
|
+
var PaymentButton = ({
|
|
2802
|
+
productId,
|
|
2803
|
+
metadata,
|
|
2804
|
+
onSuccess,
|
|
2805
|
+
onError,
|
|
2806
|
+
className = "",
|
|
2807
|
+
disabled = false,
|
|
2808
|
+
children
|
|
2809
|
+
}) => {
|
|
2810
|
+
const context = (0, import_react11.useContext)(PaymentContext);
|
|
2811
|
+
const { createOrder: createOrder2, isLoading } = usePayment();
|
|
2812
|
+
const [isModalOpen, setIsModalOpen] = (0, import_react11.useState)(false);
|
|
2813
|
+
const [publicOrderId, setPublicOrderId] = (0, import_react11.useState)(null);
|
|
2814
|
+
const [isCompleted, setIsCompleted] = (0, import_react11.useState)(false);
|
|
2815
|
+
const [createdOrder, setCreatedOrder] = (0, import_react11.useState)(null);
|
|
2816
|
+
const isOurDomain = (url) => {
|
|
2817
|
+
const environment = context?.config?.environment || "sandbox";
|
|
2818
|
+
const ourDomain = PAYMENT_DOMAINS[environment];
|
|
2819
|
+
return url.startsWith(ourDomain);
|
|
2820
|
+
};
|
|
2821
|
+
const handleClick = async () => {
|
|
2822
|
+
if (publicOrderId) {
|
|
2823
|
+
setIsModalOpen(true);
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
try {
|
|
2827
|
+
const params = {
|
|
2828
|
+
productId,
|
|
2829
|
+
metadata
|
|
2830
|
+
};
|
|
2831
|
+
const order = await createOrder2(params);
|
|
2832
|
+
if (order.redirectUrl && order.redirectUrl.trim() !== "") {
|
|
2833
|
+
if (!isOurDomain(order.redirectUrl)) {
|
|
2834
|
+
throw new Error("Invalid redirect URL. Only our domain is allowed.");
|
|
2835
|
+
}
|
|
2836
|
+
const separator = order.redirectUrl.endsWith("/") ? "" : "/";
|
|
2837
|
+
window.location.href = `${order.redirectUrl}${separator}order/${order.publicOrderId}`;
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
setPublicOrderId(order.publicOrderId);
|
|
2841
|
+
setCreatedOrder(order);
|
|
2842
|
+
setIsModalOpen(true);
|
|
2843
|
+
} catch (err) {
|
|
2844
|
+
if (onError && err instanceof Error) {
|
|
2845
|
+
onError(err);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
const handleModalSuccess = async (result) => {
|
|
2850
|
+
setIsCompleted(true);
|
|
2851
|
+
setIsModalOpen(false);
|
|
2852
|
+
if (!onSuccess) return;
|
|
2853
|
+
try {
|
|
2854
|
+
let orderDetail = null;
|
|
2855
|
+
let retryCount = 0;
|
|
2856
|
+
const maxRetries = 5;
|
|
2857
|
+
while (retryCount < maxRetries) {
|
|
2858
|
+
try {
|
|
2859
|
+
const detail = await getOrder(result.publicOrderId);
|
|
2860
|
+
if (detail && detail.orderId && detail.orderStat !== void 0) {
|
|
2861
|
+
orderDetail = detail;
|
|
2862
|
+
break;
|
|
2863
|
+
}
|
|
2864
|
+
throw new Error("Incomplete order data");
|
|
2865
|
+
} catch (err) {
|
|
2866
|
+
retryCount++;
|
|
2867
|
+
if (retryCount >= maxRetries) {
|
|
2868
|
+
console.error(`[PaymentButton] Failed after ${maxRetries} retries:`, err);
|
|
2869
|
+
throw err;
|
|
2870
|
+
}
|
|
2871
|
+
console.log(`[PaymentButton] Retry ${retryCount}/${maxRetries}...`);
|
|
2872
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (orderDetail) {
|
|
2876
|
+
onSuccess({
|
|
2877
|
+
orderId: orderDetail.orderId,
|
|
2878
|
+
publicOrderId: result.publicOrderId,
|
|
2879
|
+
paymentUrl: "",
|
|
2880
|
+
amount: orderDetail.price || 0,
|
|
2881
|
+
coinId: orderDetail.coin || "",
|
|
2882
|
+
chainId: orderDetail.chain || "",
|
|
2883
|
+
status: orderDetail.orderStat,
|
|
2884
|
+
expiresAt: orderDetail.expireDt || "",
|
|
2885
|
+
redirectUrl: orderDetail.redirectUrl || null
|
|
2886
|
+
});
|
|
2887
|
+
} else {
|
|
2888
|
+
throw new Error("Order detail is null after retries");
|
|
2889
|
+
}
|
|
2890
|
+
} catch (err) {
|
|
2891
|
+
console.error("[PaymentButton] Failed to get valid order details:", err);
|
|
2892
|
+
if (createdOrder) {
|
|
2893
|
+
console.log("[PaymentButton] Using created order info as fallback");
|
|
2894
|
+
onSuccess({
|
|
2895
|
+
...createdOrder,
|
|
2896
|
+
status: "completed"
|
|
2897
|
+
// 결제 완료 상태로 업데이트
|
|
2898
|
+
});
|
|
2899
|
+
} else {
|
|
2900
|
+
console.warn("[PaymentButton] No order info available, using minimal data");
|
|
2901
|
+
onSuccess({
|
|
2902
|
+
orderId: result.publicOrderId,
|
|
2903
|
+
publicOrderId: result.publicOrderId,
|
|
2904
|
+
paymentUrl: "",
|
|
2905
|
+
amount: 0,
|
|
2906
|
+
coinId: "",
|
|
2907
|
+
chainId: "",
|
|
2908
|
+
status: "completed",
|
|
2909
|
+
expiresAt: "",
|
|
2910
|
+
redirectUrl: null
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
const handleModalError = (error) => {
|
|
2916
|
+
if (onError) {
|
|
2917
|
+
onError(new Error(error));
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
2921
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2922
|
+
"button",
|
|
2923
|
+
{
|
|
2924
|
+
onClick: handleClick,
|
|
2925
|
+
disabled: disabled || isLoading || isCompleted,
|
|
2926
|
+
className,
|
|
2927
|
+
type: "button",
|
|
2928
|
+
children: isLoading ? "Processing..." : isCompleted ? "Completed" : children || "Pay Now"
|
|
2929
|
+
}
|
|
2930
|
+
),
|
|
2931
|
+
publicOrderId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2932
|
+
PaymentModal,
|
|
2933
|
+
{
|
|
2934
|
+
publicOrderId,
|
|
2935
|
+
isOpen: isModalOpen,
|
|
2936
|
+
onClose: () => setIsModalOpen(false),
|
|
2937
|
+
onSuccess: handleModalSuccess,
|
|
2938
|
+
onError: handleModalError
|
|
2939
|
+
}
|
|
2940
|
+
)
|
|
2941
|
+
] });
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2944
|
+
// src/components/WalletProvider.tsx
|
|
2945
|
+
var import_react12 = require("react");
|
|
2946
|
+
var import_wagmi2 = require("wagmi");
|
|
2947
|
+
var import_react_query = require("@tanstack/react-query");
|
|
2948
|
+
|
|
2949
|
+
// src/adapters/types.ts
|
|
2950
|
+
var WalletType = /* @__PURE__ */ ((WalletType2) => {
|
|
2951
|
+
WalletType2["EVM"] = "evm";
|
|
2952
|
+
WalletType2["TRON"] = "tron";
|
|
2953
|
+
return WalletType2;
|
|
2954
|
+
})(WalletType || {});
|
|
2955
|
+
var ChainType = /* @__PURE__ */ ((ChainType2) => {
|
|
2956
|
+
ChainType2["EVM"] = "evm";
|
|
2957
|
+
ChainType2["TRON"] = "tron";
|
|
2958
|
+
return ChainType2;
|
|
2959
|
+
})(ChainType || {});
|
|
2960
|
+
|
|
2961
|
+
// src/adapters/TronWalletAdapter.ts
|
|
2962
|
+
var TRON_NETWORKS = {
|
|
2963
|
+
mainnet: 728126428,
|
|
2964
|
+
shasta: 2494104990,
|
|
2965
|
+
// Testnet
|
|
2966
|
+
nile: 3448148188
|
|
2967
|
+
// Testnet
|
|
2968
|
+
};
|
|
2969
|
+
var TRON_NETWORK_NAMES = {
|
|
2970
|
+
728126428: "mainnet",
|
|
2971
|
+
2494104990: "shasta",
|
|
2972
|
+
3448148188: "nile"
|
|
2973
|
+
};
|
|
2974
|
+
var TRC20_TRANSFER_ABI = {
|
|
2975
|
+
type: "Function",
|
|
2976
|
+
stateMutability: "Nonpayable",
|
|
2977
|
+
name: "transfer",
|
|
2978
|
+
inputs: [
|
|
2979
|
+
{ type: "address", name: "_to" },
|
|
2980
|
+
{ type: "uint256", name: "_value" }
|
|
2981
|
+
],
|
|
2982
|
+
outputs: [{ type: "bool" }]
|
|
2983
|
+
};
|
|
2984
|
+
var TronWalletAdapter = class {
|
|
2985
|
+
constructor() {
|
|
2986
|
+
this.type = "tron";
|
|
2987
|
+
this.chainType = "tron";
|
|
2988
|
+
this.tronWeb = null;
|
|
2989
|
+
this.currentAddress = null;
|
|
2990
|
+
this.currentNetwork = null;
|
|
2991
|
+
this.initialized = false;
|
|
2992
|
+
this.eventListenerRegistered = false;
|
|
2993
|
+
this.handleTronLinkMessage = (event) => {
|
|
2994
|
+
if (event.data?.message && event.data.message.action === "setNode") {
|
|
2995
|
+
if (this.isTronWalletAvailable()) {
|
|
2996
|
+
this.initializeTronWeb();
|
|
2997
|
+
if (this.initialized) {
|
|
2998
|
+
this.removeEventListener();
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Trust Wallet 설치 확인
|
|
3006
|
+
*/
|
|
3007
|
+
hasTrustWallet() {
|
|
3008
|
+
if (typeof window === "undefined") return false;
|
|
3009
|
+
const windowAny = window;
|
|
3010
|
+
return !!(windowAny.trustwallet || windowAny.trustWallet || windowAny.ethereum?.isTrust || windowAny.ethereum?.isTrustWallet);
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* TronLink 전용 감지 (Trust Wallet보다 우선)
|
|
3014
|
+
* ⚠️ CRITICAL: 이 메서드는 connect() 시점에만 호출되어야 함
|
|
3015
|
+
*/
|
|
3016
|
+
isTronLinkInstalled() {
|
|
3017
|
+
if (typeof window === "undefined") return false;
|
|
3018
|
+
return true;
|
|
3019
|
+
}
|
|
3020
|
+
/**
|
|
3021
|
+
* Tron 지갑 사용 가능 여부 확인
|
|
3022
|
+
* ⚠️ CRITICAL: 이 메서드는 connect() 시점에만 호출되어야 함
|
|
3023
|
+
*/
|
|
3024
|
+
isTronWalletAvailable() {
|
|
3025
|
+
if (typeof window === "undefined") return false;
|
|
3026
|
+
return true;
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* 현재 사용 중인 wallet 타입 반환
|
|
3030
|
+
*/
|
|
3031
|
+
getWalletType() {
|
|
3032
|
+
if (this.isTronLinkInstalled()) return "TronLink";
|
|
3033
|
+
if (this.hasTrustWallet()) return "Trust Wallet";
|
|
3034
|
+
return "Unknown";
|
|
3035
|
+
}
|
|
3036
|
+
/**
|
|
3037
|
+
* 이벤트 리스너 등록 (connect 호출 시에만)
|
|
3038
|
+
*/
|
|
3039
|
+
registerEventListener() {
|
|
3040
|
+
if (typeof window !== "undefined" && !this.eventListenerRegistered && !this.initialized) {
|
|
3041
|
+
window.addEventListener("message", this.handleTronLinkMessage);
|
|
3042
|
+
this.eventListenerRegistered = true;
|
|
3043
|
+
console.log("[TronWalletAdapter] Message event listener registered");
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
/**
|
|
3047
|
+
* 이벤트 리스너 제거
|
|
3048
|
+
*/
|
|
3049
|
+
removeEventListener() {
|
|
3050
|
+
if (typeof window !== "undefined" && this.eventListenerRegistered) {
|
|
3051
|
+
window.removeEventListener("message", this.handleTronLinkMessage);
|
|
3052
|
+
this.eventListenerRegistered = false;
|
|
3053
|
+
console.log("[TronWalletAdapter] Message event listener removed");
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
// TronWeb에서 현재 네트워크 감지
|
|
3057
|
+
detectNetwork(tronWeb) {
|
|
3058
|
+
try {
|
|
3059
|
+
let fullNodeHost = "";
|
|
3060
|
+
if (tronWeb.fullNode?.host) {
|
|
3061
|
+
fullNodeHost = tronWeb.fullNode.host;
|
|
3062
|
+
} else if (tronWeb.fullHost) {
|
|
3063
|
+
fullNodeHost = tronWeb.fullHost;
|
|
3064
|
+
} else if (tronWeb.solidityNode?.host) {
|
|
3065
|
+
fullNodeHost = tronWeb.solidityNode.host;
|
|
3066
|
+
}
|
|
3067
|
+
console.log("[TronWalletAdapter] Detecting network from host:", fullNodeHost);
|
|
3068
|
+
const hostLower = fullNodeHost.toLowerCase();
|
|
3069
|
+
if (hostLower.includes("shasta")) {
|
|
3070
|
+
console.log("[TronWalletAdapter] Detected: Shasta Testnet");
|
|
3071
|
+
return TRON_NETWORKS.shasta;
|
|
3072
|
+
} else if (hostLower.includes("nile")) {
|
|
3073
|
+
console.log("[TronWalletAdapter] Detected: Nile Testnet");
|
|
3074
|
+
return TRON_NETWORKS.nile;
|
|
3075
|
+
} else {
|
|
3076
|
+
console.log("[TronWalletAdapter] Detected: Mainnet (default)");
|
|
3077
|
+
return TRON_NETWORKS.mainnet;
|
|
3078
|
+
}
|
|
3079
|
+
} catch (error) {
|
|
3080
|
+
console.warn("[TronWalletAdapter] Failed to detect network:", error);
|
|
3081
|
+
return TRON_NETWORKS.mainnet;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
initializeTronWeb() {
|
|
3085
|
+
if (this.initialized) return;
|
|
3086
|
+
if (typeof window === "undefined") return;
|
|
3087
|
+
if (!this.isTronWalletAvailable()) {
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const windowAny = window;
|
|
3091
|
+
if (windowAny.tronLink?.tronWeb) {
|
|
3092
|
+
this.tronWeb = windowAny.tronLink.tronWeb;
|
|
3093
|
+
this.initialized = true;
|
|
3094
|
+
console.log(`[TronWalletAdapter] ${this.getWalletType()} initialized`);
|
|
3095
|
+
} else if (windowAny.tronWeb) {
|
|
3096
|
+
this.tronWeb = windowAny.tronWeb;
|
|
3097
|
+
this.initialized = true;
|
|
3098
|
+
console.log("[TronWalletAdapter] Using window.tronWeb");
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
// 연결 상태
|
|
3102
|
+
isConnected() {
|
|
3103
|
+
return !!this.currentAddress && !!this.tronWeb;
|
|
3104
|
+
}
|
|
3105
|
+
getAddress() {
|
|
3106
|
+
return this.currentAddress;
|
|
3107
|
+
}
|
|
3108
|
+
getChainId() {
|
|
3109
|
+
if (this.tronWeb) {
|
|
3110
|
+
this.currentNetwork = this.detectNetwork(this.tronWeb);
|
|
3111
|
+
}
|
|
3112
|
+
return this.currentNetwork || TRON_NETWORKS.mainnet;
|
|
3113
|
+
}
|
|
3114
|
+
async getBalance() {
|
|
3115
|
+
if (!this.tronWeb || !this.currentAddress) return null;
|
|
3116
|
+
try {
|
|
3117
|
+
const balance = await this.tronWeb.trx.getBalance(this.currentAddress);
|
|
3118
|
+
return {
|
|
3119
|
+
value: BigInt(balance),
|
|
3120
|
+
decimals: 6,
|
|
3121
|
+
symbol: "TRX",
|
|
3122
|
+
formatted: (Number(balance) / 1e6).toFixed(6)
|
|
3123
|
+
};
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
console.error("[TronWalletAdapter] Failed to get balance:", error);
|
|
3126
|
+
return null;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
getConnectionInfo() {
|
|
3130
|
+
if (!this.isConnected() || !this.currentAddress) {
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
return {
|
|
3134
|
+
address: this.currentAddress,
|
|
3135
|
+
chainId: this.getChainId() || TRON_NETWORKS.mainnet,
|
|
3136
|
+
chainType: this.chainType,
|
|
3137
|
+
isConnected: true,
|
|
3138
|
+
connector: {
|
|
3139
|
+
id: "tronlink",
|
|
3140
|
+
name: "TronLink",
|
|
3141
|
+
icon: "https://www.tronlink.org/favicon.ico"
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
// 연결 관리
|
|
3146
|
+
async connect(_connectorId) {
|
|
3147
|
+
if (typeof window === "undefined") {
|
|
3148
|
+
throw new Error("Window not available");
|
|
3149
|
+
}
|
|
3150
|
+
const windowAny = window;
|
|
3151
|
+
if (!windowAny.tronLink || typeof windowAny.tronLink.request !== "function") {
|
|
3152
|
+
throw new Error(
|
|
3153
|
+
"Tron wallet not installed. Please install TronLink extension."
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
const isTronLinkProp = windowAny.tronLink?.isTronLink;
|
|
3157
|
+
const hasTrustWallet = this.hasTrustWallet();
|
|
3158
|
+
console.log("[TronWalletAdapter] Connecting with wallet:", {
|
|
3159
|
+
hasTronLink: !!windowAny.tronLink,
|
|
3160
|
+
isTronLinkProp,
|
|
3161
|
+
hasTrustWallet,
|
|
3162
|
+
walletType: isTronLinkProp === true ? "TronLink" : hasTrustWallet ? "Trust Wallet (Tron support)" : "Unknown"
|
|
3163
|
+
});
|
|
3164
|
+
if (!this.initialized) {
|
|
3165
|
+
this.registerEventListener();
|
|
3166
|
+
}
|
|
3167
|
+
try {
|
|
3168
|
+
console.log(`[TronWalletAdapter] Connecting to ${this.getWalletType()}...`);
|
|
3169
|
+
const result = await windowAny.tronLink.request({
|
|
3170
|
+
method: "tron_requestAccounts"
|
|
3171
|
+
});
|
|
3172
|
+
if (result.code === 200) {
|
|
3173
|
+
this.initializeTronWeb();
|
|
3174
|
+
if (!this.tronWeb) {
|
|
3175
|
+
throw new Error("TronWeb not initialized after connection");
|
|
3176
|
+
}
|
|
3177
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3178
|
+
if (windowAny.tronLink?.tronWeb) {
|
|
3179
|
+
this.tronWeb = windowAny.tronLink.tronWeb;
|
|
3180
|
+
this.currentAddress = this.tronWeb.defaultAddress?.base58 || null;
|
|
3181
|
+
this.currentNetwork = this.detectNetwork(this.tronWeb);
|
|
3182
|
+
} else if (windowAny.tronWeb) {
|
|
3183
|
+
this.tronWeb = windowAny.tronWeb;
|
|
3184
|
+
this.currentAddress = this.tronWeb.defaultAddress?.base58 || null;
|
|
3185
|
+
this.currentNetwork = this.detectNetwork(this.tronWeb);
|
|
3186
|
+
}
|
|
3187
|
+
if (!this.currentAddress) {
|
|
3188
|
+
throw new Error("Failed to get wallet address after connection");
|
|
3189
|
+
}
|
|
3190
|
+
console.log(
|
|
3191
|
+
"[TronWalletAdapter] Connected:",
|
|
3192
|
+
this.currentAddress,
|
|
3193
|
+
"Network:",
|
|
3194
|
+
this.currentNetwork ? TRON_NETWORK_NAMES[this.currentNetwork] : "unknown"
|
|
3195
|
+
);
|
|
3196
|
+
} else {
|
|
3197
|
+
throw new Error(result.message || "Connection rejected");
|
|
3198
|
+
}
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
console.error("[TronWalletAdapter] Connection failed:", error);
|
|
3201
|
+
throw error;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
async disconnect() {
|
|
3205
|
+
try {
|
|
3206
|
+
if (typeof window !== "undefined") {
|
|
3207
|
+
try {
|
|
3208
|
+
const windowAny = window;
|
|
3209
|
+
if (windowAny.tronLink?.request) {
|
|
3210
|
+
await windowAny.tronLink.request({
|
|
3211
|
+
method: "wallet_disconnect"
|
|
3212
|
+
});
|
|
3213
|
+
console.log("[TronWalletAdapter] TronLink disconnected via wallet_disconnect");
|
|
3214
|
+
}
|
|
3215
|
+
} catch (error) {
|
|
3216
|
+
console.log("[TronWalletAdapter] wallet_disconnect not supported, clearing state only");
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
console.warn("[TronWalletAdapter] Disconnect request failed:", error);
|
|
3221
|
+
}
|
|
3222
|
+
this.removeEventListener();
|
|
3223
|
+
this.currentAddress = null;
|
|
3224
|
+
this.currentNetwork = null;
|
|
3225
|
+
this.tronWeb = null;
|
|
3226
|
+
this.initialized = false;
|
|
3227
|
+
console.log("[TronWalletAdapter] Internal state cleared");
|
|
3228
|
+
}
|
|
3229
|
+
async switchChain(chainId) {
|
|
3230
|
+
const networkId = typeof chainId === "number" ? chainId : parseInt(chainId);
|
|
3231
|
+
const validNetworks = [TRON_NETWORKS.mainnet, TRON_NETWORKS.shasta, TRON_NETWORKS.nile];
|
|
3232
|
+
if (!validNetworks.includes(networkId)) {
|
|
3233
|
+
throw new Error(`Unsupported Tron network: ${chainId}`);
|
|
3234
|
+
}
|
|
3235
|
+
console.warn(
|
|
3236
|
+
"[TronWalletAdapter] Network switching must be done manually in TronLink"
|
|
3237
|
+
);
|
|
3238
|
+
this.currentNetwork = networkId;
|
|
3239
|
+
}
|
|
3240
|
+
// 트랜잭션
|
|
3241
|
+
async sendPayment(tx) {
|
|
3242
|
+
try {
|
|
3243
|
+
if (!this.tronWeb || !this.currentAddress) {
|
|
3244
|
+
throw new Error("Wallet not connected");
|
|
3245
|
+
}
|
|
3246
|
+
let hash;
|
|
3247
|
+
if (tx.tokenAddress) {
|
|
3248
|
+
const contract = await this.tronWeb.contract(
|
|
3249
|
+
[TRC20_TRANSFER_ABI],
|
|
3250
|
+
tx.tokenAddress
|
|
3251
|
+
);
|
|
3252
|
+
const result = await contract.transfer(tx.to, tx.value.toString()).send({
|
|
3253
|
+
from: this.currentAddress
|
|
3254
|
+
});
|
|
3255
|
+
hash = result;
|
|
3256
|
+
} else {
|
|
3257
|
+
const amountInSun = Number(tx.value);
|
|
3258
|
+
const transaction = await this.tronWeb.transactionBuilder.sendTrx(
|
|
3259
|
+
tx.to,
|
|
3260
|
+
amountInSun,
|
|
3261
|
+
this.currentAddress
|
|
3262
|
+
);
|
|
3263
|
+
const signedTx = await this.tronWeb.trx.sign(transaction);
|
|
3264
|
+
const broadcast = await this.tronWeb.trx.sendRawTransaction(signedTx);
|
|
3265
|
+
if (!broadcast.result) {
|
|
3266
|
+
throw new Error(
|
|
3267
|
+
broadcast.message || "Transaction broadcast failed"
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
hash = broadcast.txid || signedTx.txID;
|
|
3271
|
+
}
|
|
3272
|
+
return { success: true, hash };
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
console.error("[TronWalletAdapter] Payment failed:", error);
|
|
3275
|
+
return {
|
|
3276
|
+
success: false,
|
|
3277
|
+
hash: "",
|
|
3278
|
+
error: error?.message ?? "Transaction failed"
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
// 지원 여부 확인
|
|
3283
|
+
isChainSupported(chainId) {
|
|
3284
|
+
const networkId = typeof chainId === "number" ? chainId : parseInt(chainId);
|
|
3285
|
+
const validNetworks = [TRON_NETWORKS.mainnet, TRON_NETWORKS.shasta, TRON_NETWORKS.nile];
|
|
3286
|
+
return validNetworks.includes(networkId);
|
|
3287
|
+
}
|
|
3288
|
+
getChainType(_chainId) {
|
|
3289
|
+
return this.chainType;
|
|
3290
|
+
}
|
|
3291
|
+
// 커넥터 정보
|
|
3292
|
+
getConnectors() {
|
|
3293
|
+
return [
|
|
3294
|
+
{
|
|
3295
|
+
id: "tronlink",
|
|
3296
|
+
name: "TronLink",
|
|
3297
|
+
icon: "https://www.tronlink.org/favicon.ico"
|
|
3298
|
+
}
|
|
3299
|
+
];
|
|
3300
|
+
}
|
|
3301
|
+
checkWalletInstalled(connectorId) {
|
|
3302
|
+
if (typeof window === "undefined") return false;
|
|
3303
|
+
if (connectorId === "tronlink") {
|
|
3304
|
+
const hasTronLinkAPI = !!window.tronLink;
|
|
3305
|
+
const hasTronWeb = !!window.tronWeb;
|
|
3306
|
+
const isTronLinkProp = window.tronLink?.isTronLink;
|
|
3307
|
+
const hasTronLink = hasTronLinkAPI || hasTronWeb;
|
|
3308
|
+
console.log("[TronWalletAdapter] TronLink detection:", {
|
|
3309
|
+
hasTronLinkAPI,
|
|
3310
|
+
hasTronWeb,
|
|
3311
|
+
isTronLinkProp,
|
|
3312
|
+
hasTrustWallet: this.hasTrustWallet(),
|
|
3313
|
+
result: hasTronLink
|
|
3314
|
+
});
|
|
3315
|
+
return hasTronLink;
|
|
3316
|
+
}
|
|
3317
|
+
return false;
|
|
3318
|
+
}
|
|
3319
|
+
};
|
|
3320
|
+
|
|
3321
|
+
// src/adapters/WalletAdapterFactory.ts
|
|
3322
|
+
var WalletAdapterFactory = class {
|
|
3323
|
+
constructor() {
|
|
3324
|
+
this.evmAdapter = null;
|
|
3325
|
+
this.tronAdapter = null;
|
|
3326
|
+
}
|
|
3327
|
+
// EVM 체인 ID 확인 (숫자 또는 0x로 시작하는 문자열)
|
|
3328
|
+
isEvmChainId(chainId) {
|
|
3329
|
+
if (chainId === "evm") return true;
|
|
3330
|
+
if (typeof chainId === "number") return true;
|
|
3331
|
+
if (typeof chainId === "string") {
|
|
3332
|
+
if (chainId === "mainnet" || chainId === "shasta" || chainId === "nile" || chainId === "tron") {
|
|
3333
|
+
return false;
|
|
3334
|
+
}
|
|
3335
|
+
const numericChainId = Number(chainId);
|
|
3336
|
+
if (!isNaN(numericChainId) && numericChainId > 0) {
|
|
3337
|
+
return true;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return false;
|
|
3341
|
+
}
|
|
3342
|
+
// Tron 네트워크 확인
|
|
3343
|
+
isTronNetwork(chainId) {
|
|
3344
|
+
if (chainId === "tron") return true;
|
|
3345
|
+
const tronNetworkIds = [728126428, 2494104990, 3448148188];
|
|
3346
|
+
const networkId = typeof chainId === "number" ? chainId : parseInt(chainId);
|
|
3347
|
+
if (tronNetworkIds.includes(networkId)) {
|
|
3348
|
+
return true;
|
|
3349
|
+
}
|
|
3350
|
+
return false;
|
|
3351
|
+
}
|
|
3352
|
+
// EVM 주소 형식 확인 (0x로 시작하는 42자)
|
|
3353
|
+
isEvmAddress(address) {
|
|
3354
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
3355
|
+
}
|
|
3356
|
+
// Tron 주소 형식 확인 (T로 시작하는 34자 Base58)
|
|
3357
|
+
isTronAddress(address) {
|
|
3358
|
+
return /^T[A-Za-z0-9]{33}$/.test(address);
|
|
3359
|
+
}
|
|
3360
|
+
/**
|
|
3361
|
+
* EVM 어댑터 초기화
|
|
3362
|
+
*/
|
|
3363
|
+
initializeEvmAdapter(wagmiConfig) {
|
|
3364
|
+
this.evmAdapter = new EvmWalletAdapter(wagmiConfig);
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Tron 어댑터 초기화
|
|
3368
|
+
*/
|
|
3369
|
+
initializeTronAdapter() {
|
|
3370
|
+
this.tronAdapter = new TronWalletAdapter();
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* 어댑터 타입으로 어댑터 가져오기
|
|
3374
|
+
*/
|
|
3375
|
+
getAdapter(type) {
|
|
3376
|
+
switch (type) {
|
|
3377
|
+
case "evm":
|
|
3378
|
+
return this.evmAdapter;
|
|
3379
|
+
case "tron":
|
|
3380
|
+
return this.tronAdapter;
|
|
3381
|
+
default:
|
|
3382
|
+
return null;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* 체인 ID로 적절한 어댑터 가져오기
|
|
3387
|
+
*/
|
|
3388
|
+
getAdapterForChain(chainId) {
|
|
3389
|
+
console.log("testtest get chainId", chainId);
|
|
3390
|
+
if (this.isEvmChainId(chainId)) {
|
|
3391
|
+
return this.evmAdapter;
|
|
3392
|
+
}
|
|
3393
|
+
if (this.isTronNetwork(chainId)) {
|
|
3394
|
+
return this.tronAdapter;
|
|
3395
|
+
}
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* 입금 주소 형식으로 적절한 어댑터 가져오기
|
|
3400
|
+
* - 0x로 시작 → EVM adapter
|
|
3401
|
+
* - T로 시작 → Tron adapter
|
|
3402
|
+
*/
|
|
3403
|
+
getAdapterByAddress(address) {
|
|
3404
|
+
if (this.isEvmAddress(address)) {
|
|
3405
|
+
return this.evmAdapter;
|
|
3406
|
+
}
|
|
3407
|
+
if (this.isTronAddress(address)) {
|
|
3408
|
+
return this.tronAdapter;
|
|
3409
|
+
}
|
|
3410
|
+
console.warn(`[WalletAdapterFactory] Unknown address format: ${address}`);
|
|
3411
|
+
return null;
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* 주소로부터 체인 타입 결정
|
|
3415
|
+
*/
|
|
3416
|
+
getChainTypeByAddress(address) {
|
|
3417
|
+
if (this.isEvmAddress(address)) {
|
|
3418
|
+
return "evm";
|
|
3419
|
+
}
|
|
3420
|
+
if (this.isTronAddress(address)) {
|
|
3421
|
+
return "tron";
|
|
3422
|
+
}
|
|
3423
|
+
return null;
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* 체인 타입 결정
|
|
3427
|
+
*/
|
|
3428
|
+
getChainType(chainId) {
|
|
3429
|
+
if (this.isEvmChainId(chainId)) {
|
|
3430
|
+
return "evm";
|
|
3431
|
+
}
|
|
3432
|
+
if (this.isTronNetwork(chainId)) {
|
|
3433
|
+
return "tron";
|
|
3434
|
+
}
|
|
3435
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* 모든 어댑터 가져오기
|
|
3439
|
+
*/
|
|
3440
|
+
getAllAdapters() {
|
|
3441
|
+
const adapters = [];
|
|
3442
|
+
if (this.evmAdapter) adapters.push(this.evmAdapter);
|
|
3443
|
+
if (this.tronAdapter) adapters.push(this.tronAdapter);
|
|
3444
|
+
return adapters;
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* 연결된 어댑터 가져오기
|
|
3448
|
+
*/
|
|
3449
|
+
getConnectedAdapter() {
|
|
3450
|
+
if (this.evmAdapter?.isConnected()) {
|
|
3451
|
+
return this.evmAdapter;
|
|
3452
|
+
}
|
|
3453
|
+
if (this.tronAdapter?.isConnected()) {
|
|
3454
|
+
return this.tronAdapter;
|
|
3455
|
+
}
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3459
|
+
|
|
3460
|
+
// src/components/WalletProvider.tsx
|
|
3461
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
3462
|
+
var defaultQueryClient = new import_react_query.QueryClient({
|
|
3463
|
+
defaultOptions: {
|
|
3464
|
+
queries: {
|
|
3465
|
+
refetchOnWindowFocus: false,
|
|
3466
|
+
retry: 3,
|
|
3467
|
+
staleTime: 1e3 * 60 * 5
|
|
3468
|
+
// 5 minutes
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
var WalletProvider = ({
|
|
3473
|
+
children,
|
|
3474
|
+
projectId,
|
|
3475
|
+
queryClient = defaultQueryClient
|
|
3476
|
+
}) => {
|
|
3477
|
+
const wagmiConfig = (0, import_react12.useMemo)(
|
|
3478
|
+
() => createWagmiConfig(projectId),
|
|
3479
|
+
[projectId]
|
|
3480
|
+
);
|
|
3481
|
+
const adapterFactory = (0, import_react12.useMemo)(() => {
|
|
3482
|
+
const factory = new WalletAdapterFactory();
|
|
3483
|
+
factory.initializeEvmAdapter(wagmiConfig);
|
|
3484
|
+
factory.initializeTronAdapter();
|
|
3485
|
+
return factory;
|
|
3486
|
+
}, [wagmiConfig]);
|
|
3487
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_wagmi2.WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_query.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WalletContextProvider, { factory: adapterFactory, children }) }) });
|
|
3488
|
+
};
|
|
3489
|
+
|
|
3490
|
+
// src/components/PaymentQRCode.tsx
|
|
3491
|
+
var import_react13 = require("react");
|
|
3492
|
+
var import_qrcode2 = require("qrcode.react");
|
|
3493
|
+
|
|
3494
|
+
// src/utils/qrcode.ts
|
|
3495
|
+
function generatePaymentQRUrl(publicOrderId) {
|
|
3496
|
+
const sdkConfig = config.getConfig();
|
|
3497
|
+
const environment = sdkConfig.environment || "sandbox";
|
|
3498
|
+
const paymentDomain = PAYMENT_DOMAINS[environment];
|
|
3499
|
+
const domain = paymentDomain.endsWith("/") ? paymentDomain.slice(0, -1) : paymentDomain;
|
|
3500
|
+
return `${domain}/order/${publicOrderId}`;
|
|
3501
|
+
}
|
|
3502
|
+
function getPaymentDomain() {
|
|
3503
|
+
const sdkConfig = config.getConfig();
|
|
3504
|
+
const environment = sdkConfig.environment || "sandbox";
|
|
3505
|
+
return PAYMENT_DOMAINS[environment];
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
// src/components/PaymentQRCode.tsx
|
|
3509
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
3510
|
+
function PaymentQRCode({
|
|
3511
|
+
publicOrderId,
|
|
3512
|
+
size = 200,
|
|
3513
|
+
level = "H",
|
|
3514
|
+
includeMargin = true,
|
|
3515
|
+
className = "",
|
|
3516
|
+
showUrl = false,
|
|
3517
|
+
enableCopy = false,
|
|
3518
|
+
onCopy
|
|
3519
|
+
}) {
|
|
3520
|
+
const [copied, setCopied] = (0, import_react13.useState)(false);
|
|
3521
|
+
const paymentUrl = generatePaymentQRUrl(publicOrderId);
|
|
3522
|
+
const handleCopy = async () => {
|
|
3523
|
+
try {
|
|
3524
|
+
await navigator.clipboard.writeText(paymentUrl);
|
|
3525
|
+
setCopied(true);
|
|
3526
|
+
onCopy?.();
|
|
3527
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3528
|
+
} catch (error) {
|
|
3529
|
+
console.error("Failed to copy to clipboard:", error);
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `payment-qr-code ${className}`, children: [
|
|
3533
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3534
|
+
"div",
|
|
3535
|
+
{
|
|
3536
|
+
style: {
|
|
3537
|
+
padding: "16px",
|
|
3538
|
+
backgroundColor: "white",
|
|
3539
|
+
borderRadius: "8px",
|
|
3540
|
+
display: "inline-block"
|
|
3541
|
+
},
|
|
3542
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3543
|
+
import_qrcode2.QRCodeSVG,
|
|
3544
|
+
{
|
|
3545
|
+
value: paymentUrl,
|
|
3546
|
+
size,
|
|
3547
|
+
level,
|
|
3548
|
+
includeMargin
|
|
3549
|
+
}
|
|
3550
|
+
)
|
|
3551
|
+
}
|
|
3552
|
+
),
|
|
3553
|
+
showUrl && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3554
|
+
"div",
|
|
3555
|
+
{
|
|
3556
|
+
style: {
|
|
3557
|
+
marginTop: "12px",
|
|
3558
|
+
fontSize: "12px",
|
|
3559
|
+
color: "#666",
|
|
3560
|
+
wordBreak: "break-all"
|
|
3561
|
+
},
|
|
3562
|
+
children: paymentUrl
|
|
3563
|
+
}
|
|
3564
|
+
),
|
|
3565
|
+
enableCopy && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3566
|
+
"button",
|
|
3567
|
+
{
|
|
3568
|
+
onClick: handleCopy,
|
|
3569
|
+
style: {
|
|
3570
|
+
marginTop: "12px",
|
|
3571
|
+
padding: "8px 16px",
|
|
3572
|
+
backgroundColor: copied ? "#22c55e" : "#3b82f6",
|
|
3573
|
+
color: "white",
|
|
3574
|
+
border: "none",
|
|
3575
|
+
borderRadius: "6px",
|
|
3576
|
+
cursor: "pointer",
|
|
3577
|
+
fontSize: "14px",
|
|
3578
|
+
fontWeight: "500"
|
|
3579
|
+
},
|
|
3580
|
+
children: copied ? "Copied!" : "Copy Link"
|
|
3581
|
+
}
|
|
3582
|
+
)
|
|
3583
|
+
] });
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
// src/components/PaymentQRModal.tsx
|
|
3587
|
+
var import_react14 = require("react");
|
|
3588
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
3589
|
+
function PaymentQRModal({
|
|
3590
|
+
isOpen,
|
|
3591
|
+
onClose,
|
|
3592
|
+
productId,
|
|
3593
|
+
productName,
|
|
3594
|
+
qrSize = 200,
|
|
3595
|
+
className = ""
|
|
3596
|
+
}) {
|
|
3597
|
+
const { createOrder: createOrder2 } = usePayment();
|
|
3598
|
+
const [publicOrderId, setPublicOrderId] = (0, import_react14.useState)(null);
|
|
3599
|
+
const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
|
|
3600
|
+
const [error, setError] = (0, import_react14.useState)(null);
|
|
3601
|
+
(0, import_react14.useEffect)(() => {
|
|
3602
|
+
if (isOpen && !publicOrderId) {
|
|
3603
|
+
const generateOrder = async () => {
|
|
3604
|
+
try {
|
|
3605
|
+
setIsLoading(true);
|
|
3606
|
+
setError(null);
|
|
3607
|
+
const order = await createOrder2({ productId });
|
|
3608
|
+
setPublicOrderId(order.publicOrderId);
|
|
3609
|
+
} catch (err) {
|
|
3610
|
+
setError(err.message || "Failed to create order");
|
|
3611
|
+
} finally {
|
|
3612
|
+
setIsLoading(false);
|
|
3613
|
+
}
|
|
3614
|
+
};
|
|
3615
|
+
generateOrder();
|
|
3616
|
+
}
|
|
3617
|
+
}, [isOpen, productId, publicOrderId, createOrder2]);
|
|
3618
|
+
const handleClose = () => {
|
|
3619
|
+
setPublicOrderId(null);
|
|
3620
|
+
setError(null);
|
|
3621
|
+
onClose();
|
|
3622
|
+
};
|
|
3623
|
+
if (!isOpen) return null;
|
|
3624
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3625
|
+
"div",
|
|
3626
|
+
{
|
|
3627
|
+
style: {
|
|
3628
|
+
position: "fixed",
|
|
3629
|
+
inset: 0,
|
|
3630
|
+
zIndex: 9999,
|
|
3631
|
+
display: "flex",
|
|
3632
|
+
alignItems: "center",
|
|
3633
|
+
justifyContent: "center",
|
|
3634
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
3635
|
+
padding: "16px"
|
|
3636
|
+
},
|
|
3637
|
+
onClick: onClose,
|
|
3638
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
3639
|
+
"div",
|
|
3640
|
+
{
|
|
3641
|
+
className: `payment-qr-modal ${className}`,
|
|
3642
|
+
style: {
|
|
3643
|
+
position: "relative",
|
|
3644
|
+
backgroundColor: "white",
|
|
3645
|
+
borderRadius: "12px",
|
|
3646
|
+
padding: "24px",
|
|
3647
|
+
maxWidth: "400px",
|
|
3648
|
+
width: "100%",
|
|
3649
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)"
|
|
3650
|
+
},
|
|
3651
|
+
onClick: (e) => e.stopPropagation(),
|
|
3652
|
+
children: [
|
|
3653
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3654
|
+
"button",
|
|
3655
|
+
{
|
|
3656
|
+
onClick: handleClose,
|
|
3657
|
+
style: {
|
|
3658
|
+
position: "absolute",
|
|
3659
|
+
right: "16px",
|
|
3660
|
+
top: "16px",
|
|
3661
|
+
background: "none",
|
|
3662
|
+
border: "none",
|
|
3663
|
+
fontSize: "24px",
|
|
3664
|
+
cursor: "pointer",
|
|
3665
|
+
color: "#666",
|
|
3666
|
+
lineHeight: 1,
|
|
3667
|
+
padding: 0
|
|
3668
|
+
},
|
|
3669
|
+
children: "\u2715"
|
|
3670
|
+
}
|
|
3671
|
+
),
|
|
3672
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3673
|
+
"h2",
|
|
3674
|
+
{
|
|
3675
|
+
style: {
|
|
3676
|
+
fontSize: "20px",
|
|
3677
|
+
fontWeight: "bold",
|
|
3678
|
+
marginBottom: "8px",
|
|
3679
|
+
marginTop: 0
|
|
3680
|
+
},
|
|
3681
|
+
children: "Payment QR Code"
|
|
3682
|
+
}
|
|
3683
|
+
),
|
|
3684
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3685
|
+
"p",
|
|
3686
|
+
{
|
|
3687
|
+
style: {
|
|
3688
|
+
fontSize: "14px",
|
|
3689
|
+
color: "#666",
|
|
3690
|
+
marginBottom: "24px",
|
|
3691
|
+
marginTop: 0
|
|
3692
|
+
},
|
|
3693
|
+
children: productName ? `Scan this QR code to pay for ${productName}` : "Scan this QR code to complete your payment"
|
|
3694
|
+
}
|
|
3695
|
+
),
|
|
3696
|
+
isLoading && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3697
|
+
"div",
|
|
3698
|
+
{
|
|
3699
|
+
style: {
|
|
3700
|
+
display: "flex",
|
|
3701
|
+
justifyContent: "center",
|
|
3702
|
+
padding: "40px"
|
|
3703
|
+
},
|
|
3704
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { color: "#666" }, children: "Creating order..." })
|
|
3705
|
+
}
|
|
3706
|
+
),
|
|
3707
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3708
|
+
"div",
|
|
3709
|
+
{
|
|
3710
|
+
style: {
|
|
3711
|
+
backgroundColor: "#fee",
|
|
3712
|
+
color: "#c33",
|
|
3713
|
+
padding: "12px",
|
|
3714
|
+
borderRadius: "6px",
|
|
3715
|
+
marginBottom: "16px"
|
|
3716
|
+
},
|
|
3717
|
+
children: error
|
|
3718
|
+
}
|
|
3719
|
+
),
|
|
3720
|
+
publicOrderId && !isLoading && !error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3721
|
+
PaymentQRCode,
|
|
3722
|
+
{
|
|
3723
|
+
publicOrderId,
|
|
3724
|
+
size: qrSize,
|
|
3725
|
+
level: "H",
|
|
3726
|
+
includeMargin: true,
|
|
3727
|
+
showUrl: true,
|
|
3728
|
+
enableCopy: true
|
|
3729
|
+
}
|
|
3730
|
+
) })
|
|
3731
|
+
]
|
|
3732
|
+
}
|
|
3733
|
+
)
|
|
3734
|
+
}
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
// src/hooks/useWallet.ts
|
|
3739
|
+
var useWallet = () => {
|
|
3740
|
+
const adapter = useWalletAdapter();
|
|
3741
|
+
const walletInfo = adapter.isConnected && adapter.address && typeof adapter.chainId === "number" ? {
|
|
3742
|
+
address: adapter.address,
|
|
3743
|
+
chainId: adapter.chainId,
|
|
3744
|
+
isConnected: adapter.isConnected,
|
|
3745
|
+
connector: adapter.wallet?.connector
|
|
3746
|
+
} : null;
|
|
3747
|
+
const sendPayment = async (tx) => {
|
|
3748
|
+
const result = await adapter.sendPayment({
|
|
3749
|
+
to: tx.to,
|
|
3750
|
+
value: tx.value,
|
|
3751
|
+
chainId: tx.chainId,
|
|
3752
|
+
coinId: tx.coinId,
|
|
3753
|
+
tokenAddress: tx.tokenAddress,
|
|
3754
|
+
decimals: tx.decimals
|
|
3755
|
+
});
|
|
3756
|
+
return {
|
|
3757
|
+
hash: result.hash,
|
|
3758
|
+
success: result.success,
|
|
3759
|
+
error: result.error
|
|
3760
|
+
};
|
|
3761
|
+
};
|
|
3762
|
+
const connectWallet = async (connectorId) => {
|
|
3763
|
+
if (adapter.isConnected) {
|
|
3764
|
+
await adapter.disconnect();
|
|
3765
|
+
}
|
|
3766
|
+
await adapter.connect("evm" /* EVM */, connectorId);
|
|
3767
|
+
};
|
|
3768
|
+
const evmAdapter = adapter.factory.getAdapter("evm" /* EVM */);
|
|
3769
|
+
const evmConnectors = evmAdapter?.getConnectors() || [];
|
|
3770
|
+
return {
|
|
3771
|
+
// Wallet info
|
|
3772
|
+
wallet: walletInfo,
|
|
3773
|
+
address: adapter.address,
|
|
3774
|
+
chainId: typeof adapter.chainId === "number" ? adapter.chainId : null,
|
|
3775
|
+
isConnected: adapter.isConnected,
|
|
3776
|
+
balance: adapter.balance,
|
|
3777
|
+
// Connection actions
|
|
3778
|
+
connect: connectWallet,
|
|
3779
|
+
disconnect: adapter.disconnect,
|
|
3780
|
+
connectors: evmConnectors,
|
|
3781
|
+
// EVM 커넥터만
|
|
3782
|
+
isConnecting: false,
|
|
3783
|
+
// TODO: track loading state
|
|
3784
|
+
// Chain switching
|
|
3785
|
+
switchChain: (chainId) => adapter.switchChain(chainId),
|
|
3786
|
+
// Transaction
|
|
3787
|
+
sendPayment,
|
|
3788
|
+
isSending: adapter.isSending,
|
|
3789
|
+
isConfirming: false,
|
|
3790
|
+
// TODO: track confirmation
|
|
3791
|
+
isConfirmed: false,
|
|
3792
|
+
// TODO: track confirmation
|
|
3793
|
+
txHash: adapter.txHash,
|
|
3794
|
+
// Utilities
|
|
3795
|
+
checkWalletInstalled: (connectorId) => adapter.checkWalletInstalled("evm" /* EVM */, connectorId)
|
|
3796
|
+
// EVM만 체크
|
|
3797
|
+
};
|
|
3798
|
+
};
|
|
3799
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3800
|
+
0 && (module.exports = {
|
|
3801
|
+
API_ENDPOINTS,
|
|
3802
|
+
ChainTypes,
|
|
3803
|
+
DEFAULT_TIMEOUT,
|
|
3804
|
+
ENVIRONMENT_URLS,
|
|
3805
|
+
ERROR_CODES,
|
|
3806
|
+
EvmWalletAdapter,
|
|
3807
|
+
MIN_WITHDRAWAL_AMOUNT,
|
|
3808
|
+
ORDER_EXPIRY_MINUTES,
|
|
3809
|
+
OrderPayment,
|
|
3810
|
+
PAYMENT_DOMAINS,
|
|
3811
|
+
PayWithAddress,
|
|
3812
|
+
PayWithWallet,
|
|
3813
|
+
PaymentButton,
|
|
3814
|
+
PaymentContext,
|
|
3815
|
+
PaymentFlow,
|
|
3816
|
+
PaymentModal,
|
|
3817
|
+
PaymentProvider,
|
|
3818
|
+
PaymentQRCode,
|
|
3819
|
+
PaymentQRModal,
|
|
3820
|
+
SDK_VERSION,
|
|
3821
|
+
SUPPORTED_CHAINS,
|
|
3822
|
+
TRON_NETWORKS,
|
|
3823
|
+
TronWalletAdapter,
|
|
3824
|
+
WalletAdapterFactory,
|
|
3825
|
+
WalletProvider,
|
|
3826
|
+
WalletTypes,
|
|
3827
|
+
coinsAPI,
|
|
3828
|
+
config,
|
|
3829
|
+
createWagmiConfig,
|
|
3830
|
+
detectChainType,
|
|
3831
|
+
generatePaymentQRUrl,
|
|
3832
|
+
getChainById,
|
|
3833
|
+
getErrorMessage,
|
|
3834
|
+
getEvmChainById,
|
|
3835
|
+
getPaymentDomain,
|
|
3836
|
+
handleAPIError,
|
|
3837
|
+
isEvmAddress,
|
|
3838
|
+
isTronAddress,
|
|
3839
|
+
isValidAddress,
|
|
3840
|
+
ordersAPI,
|
|
3841
|
+
supportedChains,
|
|
3842
|
+
supportedEvmChains,
|
|
3843
|
+
useOrder,
|
|
3844
|
+
usePayment,
|
|
3845
|
+
useWallet,
|
|
3846
|
+
useWalletAdapter,
|
|
3847
|
+
useWalletContext,
|
|
3848
|
+
validation
|
|
3849
|
+
});
|