ts-glitter 21.0.4 → 21.0.5

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.
Files changed (159) hide show
  1. package/lowcode/Entry.js +1 -1
  2. package/lowcode/Entry.ts +1 -1
  3. package/lowcode/backend-manager/bg-blog.js +22 -37
  4. package/lowcode/backend-manager/bg-blog.ts +45 -34
  5. package/lowcode/backend-manager/bg-list-component.js +2 -2
  6. package/lowcode/backend-manager/bg-list-component.ts +2 -2
  7. package/lowcode/backend-manager/bg-widget.js +21 -0
  8. package/lowcode/backend-manager/bg-widget.ts +21 -0
  9. package/lowcode/cms-plugin/filter-options.js +3 -3
  10. package/lowcode/cms-plugin/filter-options.ts +1 -1
  11. package/lowcode/cms-plugin/shopping-finance-setting.js +251 -6
  12. package/lowcode/cms-plugin/shopping-finance-setting.ts +291 -6
  13. package/lowcode/cms-plugin/shopping-order-manager.js +56 -46
  14. package/lowcode/cms-plugin/shopping-order-manager.ts +59 -47
  15. package/lowcode/cms-plugin/shopping-product-setting.js +10 -5
  16. package/lowcode/cms-plugin/shopping-product-setting.ts +14 -6
  17. package/lowcode/cms-plugin/shopping-setting-advance.js +21 -72
  18. package/lowcode/cms-plugin/shopping-setting-advance.ts +31 -98
  19. package/lowcode/css/editor.css +48 -16
  20. package/lowcode/css/front-end.css +16 -17
  21. package/lowcode/glitter-base/global/language.js +2 -0
  22. package/lowcode/glitter-base/global/language.ts +3 -0
  23. package/lowcode/glitter-base/global/shipment-config.js +4 -4
  24. package/lowcode/glitter-base/global/shipment-config.ts +1 -1
  25. package/lowcode/glitter-base/route/shopping.js +11 -0
  26. package/lowcode/glitter-base/route/shopping.ts +12 -0
  27. package/lowcode/official_view_component/form-widget/input-custom.js +0 -1
  28. package/lowcode/official_view_component/form-widget/input-custom.ts +0 -1
  29. package/lowcode/public-components/checkout/index.js +578 -780
  30. package/lowcode/public-components/checkout/index.ts +2457 -2652
  31. package/lowcode/public-components/headers/header-class.js +252 -324
  32. package/lowcode/public-components/headers/header-class.ts +389 -480
  33. package/lowcode/public-components/modules/cart-module.js +433 -0
  34. package/lowcode/public-components/modules/cart-module.ts +491 -0
  35. package/lowcode/public-components/modules/product-module.js +45 -0
  36. package/lowcode/public-components/modules/product-module.ts +58 -0
  37. package/lowcode/public-components/product/product-detail.js +178 -184
  38. package/lowcode/public-components/product/product-detail.ts +564 -579
  39. package/lowcode/public-components/user-manager/um-order.js +162 -32
  40. package/lowcode/public-components/user-manager/um-order.ts +181 -46
  41. package/lowcode/public-models/product.ts +1 -0
  42. package/package.json +1 -1
  43. package/src/Language.js +1 -0
  44. package/src/Language.js.map +1 -1
  45. package/src/Language.ts +3 -0
  46. package/src/api-public/controllers/article.js +33 -29
  47. package/src/api-public/controllers/article.js.map +1 -1
  48. package/src/api-public/controllers/article.ts +152 -146
  49. package/src/api-public/controllers/index.js +2 -0
  50. package/src/api-public/controllers/index.js.map +1 -1
  51. package/src/api-public/controllers/index.ts +2 -0
  52. package/src/api-public/controllers/shop.js +125 -47
  53. package/src/api-public/controllers/shop.js.map +1 -1
  54. package/src/api-public/controllers/shop.ts +148 -61
  55. package/src/api-public/controllers/user.js +51 -52
  56. package/src/api-public/controllers/user.js.map +1 -1
  57. package/src/api-public/controllers/user.ts +63 -52
  58. package/src/api-public/models/glitter-finance.js +2 -1
  59. package/src/api-public/models/glitter-finance.js.map +5 -1
  60. package/src/api-public/services/checkout-event.d.ts +1 -0
  61. package/src/api-public/services/checkout-event.js +18 -10
  62. package/src/api-public/services/checkout-event.js.map +1 -1
  63. package/src/api-public/services/checkout-event.ts +18 -7
  64. package/src/api-public/services/factories/payment-strategy-factory.d.ts +6 -0
  65. package/src/api-public/services/factories/payment-strategy-factory.js +49 -0
  66. package/src/api-public/services/factories/payment-strategy-factory.js.map +1 -0
  67. package/src/api-public/services/factories/payment-strategy-factory.ts +71 -0
  68. package/src/api-public/services/fb-service.js +4 -4
  69. package/src/api-public/services/fb-service.js.map +1 -1
  70. package/src/api-public/services/financial-service.d.ts +2 -1
  71. package/src/api-public/services/financial-service.js +48 -53
  72. package/src/api-public/services/financial-service.js.map +1 -1
  73. package/src/api-public/services/financial-service.ts +29 -38
  74. package/src/api-public/services/financial-serviceV2.d.ts +298 -0
  75. package/src/api-public/services/financial-serviceV2.js +1158 -0
  76. package/src/api-public/services/financial-serviceV2.js.map +1 -0
  77. package/src/api-public/services/financial-serviceV2.ts +1518 -0
  78. package/src/api-public/services/interface/payment-keys-interface.d.ts +37 -0
  79. package/src/api-public/services/interface/payment-keys-interface.js +3 -0
  80. package/src/api-public/services/interface/payment-keys-interface.js.map +1 -0
  81. package/src/api-public/services/interface/payment-keys-interface.ts +46 -0
  82. package/src/api-public/services/interface/payment-strategy-interface.d.ts +17 -0
  83. package/src/api-public/services/interface/payment-strategy-interface.js +3 -0
  84. package/src/api-public/services/interface/payment-strategy-interface.js.map +1 -0
  85. package/src/api-public/services/interface/payment-strategy-interface.ts +48 -0
  86. package/src/api-public/services/migrate-event/public-user-config.d.ts +5 -0
  87. package/src/api-public/services/migrate-event/public-user-config.js +81 -0
  88. package/src/api-public/services/migrate-event/public-user-config.js.map +1 -0
  89. package/src/api-public/services/migrate-event/public-user-config.ts +106 -0
  90. package/src/api-public/services/model/handlePaymentTransaction.js +0 -68
  91. package/src/api-public/services/model/handlePaymentTransaction.js.map +1 -1
  92. package/src/api-public/services/model/handlePaymentTransaction.ts +62 -54
  93. package/src/api-public/services/monitor.d.ts +1 -0
  94. package/src/api-public/services/payment-service.d.ts +12 -0
  95. package/src/api-public/services/payment-service.js +83 -0
  96. package/src/api-public/services/payment-service.js.map +1 -0
  97. package/src/api-public/services/payment-service.ts +112 -0
  98. package/src/api-public/services/shopping.d.ts +2 -1
  99. package/src/api-public/services/shopping.js +60 -23
  100. package/src/api-public/services/shopping.js.map +1 -1
  101. package/src/api-public/services/shopping.ts +88 -16
  102. package/src/api-public/services/strategies/ecpay-strategy.d.ts +8 -0
  103. package/src/api-public/services/strategies/ecpay-strategy.js +26 -0
  104. package/src/api-public/services/strategies/ecpay-strategy.js.map +1 -0
  105. package/src/api-public/services/strategies/ecpay-strategy.ts +30 -0
  106. package/src/api-public/services/strategies/ezpay-strategy.d.ts +8 -0
  107. package/src/api-public/services/strategies/ezpay-strategy.js +26 -0
  108. package/src/api-public/services/strategies/ezpay-strategy.js.map +1 -0
  109. package/src/api-public/services/strategies/ezpay-strategy.ts +31 -0
  110. package/src/api-public/services/strategies/jkopay-strategy.d.ts +8 -0
  111. package/src/api-public/services/strategies/jkopay-strategy.js +32 -0
  112. package/src/api-public/services/strategies/jkopay-strategy.js.map +1 -0
  113. package/src/api-public/services/strategies/jkopay-strategy.ts +36 -0
  114. package/src/api-public/services/strategies/linepay-strategy.d.ts +8 -0
  115. package/src/api-public/services/strategies/linepay-strategy.js +32 -0
  116. package/src/api-public/services/strategies/linepay-strategy.js.map +1 -0
  117. package/src/api-public/services/strategies/linepay-strategy.ts +35 -0
  118. package/src/api-public/services/strategies/paynow-strategy.d.ts +8 -0
  119. package/src/api-public/services/strategies/paynow-strategy.js +26 -0
  120. package/src/api-public/services/strategies/paynow-strategy.js.map +1 -0
  121. package/src/api-public/services/strategies/paynow-strategy.ts +29 -0
  122. package/src/api-public/services/strategies/paypal-strategy.d.ts +8 -0
  123. package/src/api-public/services/strategies/paypal-strategy.js +28 -0
  124. package/src/api-public/services/strategies/paypal-strategy.js.map +1 -0
  125. package/src/api-public/services/strategies/paypal-strategy.ts +31 -0
  126. package/src/api-public/services/updated-table-checked.d.ts +1 -0
  127. package/src/api-public/services/updated-table-checked.js +37 -15
  128. package/src/api-public/services/updated-table-checked.js.map +1 -1
  129. package/src/api-public/services/updated-table-checked.ts +56 -36
  130. package/src/api-public/utils/ut-permission.d.ts +1 -0
  131. package/src/app-project/serverless/src/index.js +17 -7
  132. package/src/app-project/serverless/src/index.js.map +1 -1
  133. package/src/app-project/serverless/src/modules/CryptoJS.js +17 -7
  134. package/src/app-project/serverless/src/modules/CryptoJS.js.map +1 -1
  135. package/src/app-project/serverless/src/modules/ssh.js +17 -7
  136. package/src/app-project/serverless/src/modules/ssh.js.map +1 -1
  137. package/src/config.d.ts +1 -1
  138. package/src/modules/AWSLib.js +3 -2
  139. package/src/modules/AWSLib.js.map +1 -1
  140. package/src/modules/CryptoJS.js +17 -7
  141. package/src/modules/CryptoJS.js.map +1 -1
  142. package/src/modules/database.d.ts +1 -1
  143. package/src/modules/redis.d.ts +1 -1
  144. package/src/modules/ssh.js +17 -7
  145. package/src/modules/ssh.js.map +1 -1
  146. package/src/modules/tool.d.ts +4 -4
  147. package/src/modules/tool.js +2 -1
  148. package/src/modules/tool.js.map +1 -1
  149. package/src/run.js +2 -1
  150. package/src/run.js.map +1 -1
  151. package/src/services/create-instance.js +4 -3
  152. package/src/services/create-instance.js.map +1 -1
  153. package/src/services/saas-table-check.js +2 -2
  154. package/src/services/saas-table-check.js.map +5 -1
  155. package/src/services/tool.js +3 -2
  156. package/src/services/tool.js.map +1 -1
  157. package/vp00rqhw1r.json +1 -0
  158. package/src/api-public/services/product-migrate.d.ts +0 -8
  159. package/src/api-public/services/product-migrate.js.map +0 -1
@@ -0,0 +1,1518 @@
1
+ import crypto, { Encoding } from 'crypto';
2
+ import db from '../../modules/database.js';
3
+ import moment from 'moment-timezone';
4
+ import axios, { AxiosRequestConfig } from 'axios';
5
+ import redis from '../../modules/redis';
6
+ import process from 'process';
7
+ import CryptoJS from 'crypto-js';
8
+ import { createCipheriv, randomBytes, createHash } from 'crypto';
9
+ import Tool from './ezpay/tool.js';
10
+ import tool from '../../modules/tool.js';
11
+ import { OrderEvent } from './order-event.js';
12
+ import { Private_config } from '../../services/private_config.js';
13
+ import exception from '../../modules/exception.js';
14
+ import e from 'express';
15
+ import * as console from 'node:console';
16
+ import FinancialService, { EzPay } from './financial-service';
17
+ import qs from 'qs';
18
+
19
+
20
+
21
+ export interface KeyData {
22
+ SECRET?: string;
23
+ CLIENT_ID?: string;
24
+ PAYPAL_SECRET?: string;
25
+ PAYPAL_CLIENT_ID?: string;
26
+ MERCHANT_ID?: string ;
27
+ HASH_KEY?: string;
28
+ HASH_IV?: string;
29
+ NotifyURL: string;
30
+ ReturnURL: string;
31
+ ActionURL?: string;
32
+ TYPE: 'newWebPay' | 'ecPay' | 'PayPal' | 'LinePay' | 'jkopay';
33
+ CreditCheckCode?: string;
34
+ BETA?: boolean;
35
+ }
36
+
37
+
38
+ const html = String.raw;
39
+
40
+ export default class FinancialServiceV2 {
41
+ keyData: KeyData;
42
+
43
+ appName: string;
44
+
45
+ constructor(appName: string, keyData: KeyData) {
46
+ this.keyData = keyData;
47
+ this.appName = appName;
48
+ }
49
+
50
+ static aesEncrypt(
51
+ data: string,
52
+ key: string,
53
+ iv: string,
54
+ input: Encoding = 'utf-8',
55
+ output: Encoding = 'hex',
56
+ method = 'aes-256-cbc'
57
+ ): string {
58
+ while (key.length % 32 !== 0) {
59
+ key += '\0';
60
+ }
61
+ while (iv.length % 16 !== 0) {
62
+ iv += '\0';
63
+ }
64
+ const cipher = crypto.createCipheriv(method, key, iv);
65
+ let encrypted = cipher.update(data, input, output);
66
+ encrypted += cipher.final(output);
67
+ return encrypted;
68
+ }
69
+
70
+ static JsonToQueryString(data: { [key: string]: string | string[] | number }): string {
71
+ const queryString = Object.keys(data)
72
+ .map(key => {
73
+ const value = data[key];
74
+ if (Array.isArray(value)) {
75
+ return value.map(v => `${encodeURIComponent(key)}[]=${encodeURIComponent(v)}`).join('&');
76
+ }
77
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
78
+ })
79
+ .join('&');
80
+ return queryString;
81
+ }
82
+
83
+ async createOrderPage(orderData: {
84
+ lineItems: {
85
+ id: string;
86
+ spec: string[];
87
+ count: number;
88
+ sale_price: number;
89
+ title: string;
90
+ }[];
91
+ total: number;
92
+ email: string;
93
+ shipment_fee: number;
94
+ orderID: string;
95
+ use_wallet: number;
96
+ user_email: string;
97
+ method: string;
98
+ }) {
99
+ orderData.method = orderData.method || 'ALL';
100
+ await OrderEvent.insertOrder({
101
+ cartData: orderData,
102
+ status: 0,
103
+ app: this.appName,
104
+ });
105
+ if (this.keyData.TYPE === 'newWebPay') {
106
+ return await new EzPayV2(this.appName, this.keyData).executePayment(orderData);
107
+ } else if (this.keyData.TYPE === 'ecPay') {
108
+ return await new EcPayV2(this.appName, this.keyData).executePayment(orderData);
109
+ }
110
+ }
111
+
112
+
113
+ async saveWallet(orderData: {
114
+ total: number;
115
+ userID: number;
116
+ note: any;
117
+ method: string;
118
+ table: string;
119
+ title: string;
120
+ ratio: number;
121
+ }): Promise<string> {
122
+ if (this.keyData.TYPE === 'newWebPay') {
123
+ return await new EzPayV2(this.appName, this.keyData).saveMoney(orderData);
124
+ } else if (this.keyData.TYPE === 'ecPay') {
125
+ return await new EcPayV2(this.appName).saveMoney(orderData);
126
+ }
127
+ return '';
128
+ }
129
+ }
130
+
131
+ // 藍新金流
132
+ export class EzPayV2 {
133
+ keyData: KeyData;
134
+ appName: string;
135
+
136
+ constructor(appName: string, keyData: KeyData) {
137
+ this.keyData = keyData;
138
+ this.appName = appName;
139
+ }
140
+
141
+ static aesDecrypt = (
142
+ data: string,
143
+ key: string,
144
+ iv: string,
145
+ input: Encoding = 'hex',
146
+ output: Encoding = 'utf-8',
147
+ method = 'aes-256-cbc'
148
+ ) => {
149
+ while (key.length % 32 !== 0) {
150
+ key += '\0';
151
+ }
152
+ while (iv.length % 16 !== 0) {
153
+ iv += '\0';
154
+ }
155
+ const decipher = crypto.createDecipheriv(method, key, iv);
156
+ let decrypted = decipher.update(data, input, output);
157
+ try {
158
+ decrypted += decipher.final(output);
159
+ } catch (e) {
160
+ e instanceof Error && console.error(e.message);
161
+ }
162
+ return decrypted;
163
+ };
164
+
165
+ decode(data: string) {
166
+ return EzPayV2.aesDecrypt(data, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
167
+ }
168
+
169
+ async executePayment(orderData: {
170
+ lineItems: {
171
+ id: string;
172
+ spec: string[];
173
+ count: number;
174
+ sale_price: number;
175
+ }[];
176
+ total: number;
177
+ email: string;
178
+ shipment_fee: number;
179
+ orderID: string;
180
+ use_wallet: number;
181
+ user_email: string;
182
+ method?: string;
183
+ }) {
184
+ // await OrderEvent.insertOrder({
185
+ // cartData: orderData,
186
+ // status: 0,
187
+ // app: this.appName,
188
+ // });
189
+ // 1. 建立請求的參數
190
+ const params = {
191
+ MerchantID: this.keyData.MERCHANT_ID??'',
192
+ RespondType: 'JSON',
193
+ TimeStamp: Math.floor(Date.now() / 1000),
194
+ Version: '2.0',
195
+ MerchantOrderNo: orderData.orderID,
196
+ Amt: orderData.total - orderData.use_wallet,
197
+ ItemDesc: '商品資訊',
198
+ NotifyURL: this.keyData.NotifyURL??'',
199
+ ReturnURL: this.keyData.ReturnURL??'',
200
+ TradeLimit: 600,
201
+ };
202
+ if (orderData.method && orderData.method !== 'ALL') {
203
+ [
204
+ {
205
+ value: 'credit',
206
+ title: '信用卡',
207
+ realKey: 'CREDIT',
208
+ },
209
+ {
210
+ value: 'atm',
211
+ title: 'ATM',
212
+ realKey: 'VACC',
213
+ },
214
+ {
215
+ value: 'web_atm',
216
+ title: '網路ATM',
217
+ realKey: 'WEBATM',
218
+ },
219
+ {
220
+ value: 'c_code',
221
+ title: '超商代碼',
222
+ realKey: 'CVS',
223
+ },
224
+ {
225
+ value: 'c_bar_code',
226
+ title: '超商條碼',
227
+ realKey: 'BARCODE',
228
+ },
229
+ ].map(dd => {
230
+ if (dd.value === orderData.method) {
231
+ (params as any)[dd.realKey] = 1;
232
+ } else {
233
+ (params as any)[dd.realKey] = 0;
234
+ }
235
+ });
236
+ }
237
+ // 2. 產生 Query String
238
+ const qs = FinancialService.JsonToQueryString(params);
239
+ // 3. 開始加密
240
+ // { method: 'aes-256-cbc', inputEndcoding: 'utf-8', outputEndcoding: 'hex' };
241
+ // createCipheriv 方法中,key 要滿 32 字元、iv 要滿 16 字元,請之後測試多注意這點
242
+ const tradeInfo = FinancialService.aesEncrypt(qs, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
243
+ // 4. 產生檢查碼
244
+ const tradeSha = crypto
245
+ .createHash('sha256')
246
+ .update(`HashKey=${this.keyData.HASH_KEY}&${tradeInfo}&HashIV=${this.keyData.HASH_IV}`)
247
+ .digest('hex')
248
+ .toUpperCase();
249
+ // 5. 回傳物件
250
+ return html` <form name="Newebpay" action="${this.keyData.ActionURL}" method="POST" class="payment">
251
+ <input type="hidden" name="MerchantID" value="${this.keyData.MERCHANT_ID}" />
252
+ <input type="hidden" name="TradeInfo" value="${tradeInfo}" />
253
+ <input type="hidden" name="TradeSha" value="${tradeSha}" />
254
+ <input type="hidden" name="Version" value="${params.Version}" />
255
+ <input type="hidden" name="MerchantOrderNo" value="${params.MerchantOrderNo}" />
256
+ <button type="submit" class="btn btn-secondary custom-btn beside-btn" id="submit" hidden></button>
257
+ </form>`;
258
+ }
259
+
260
+ async saveMoney(orderData: {
261
+ total: number;
262
+ userID: number;
263
+ note: string;
264
+ table: string;
265
+ title: string;
266
+ ratio: number;
267
+ }) {
268
+ // 1. 建立請求的參數
269
+ const params = {
270
+ MerchantID: this.keyData.MERCHANT_ID??'',
271
+ RespondType: 'JSON',
272
+ TimeStamp: Math.floor(Date.now() / 1000),
273
+ Version: '2.0',
274
+ MerchantOrderNo: new Date().getTime(),
275
+ Amt: orderData.total,
276
+ ItemDesc: orderData.title,
277
+ NotifyURL: this.keyData.NotifyURL??'',
278
+ ReturnURL: this.keyData.ReturnURL??'',
279
+ };
280
+
281
+ const appName = this.appName;
282
+ await db.execute(
283
+ `INSERT INTO \`${appName}\`.${orderData.table} (orderID, userID, money, status, note)
284
+ VALUES (?, ?, ?, ?, ?)
285
+ `,
286
+ [params.MerchantOrderNo, orderData.userID, orderData.total * orderData.ratio, 0, orderData.note]
287
+ );
288
+
289
+ // 2. 產生 Query String
290
+ const qs = FinancialService.JsonToQueryString(params);
291
+ // 3. 開始加密
292
+ // { method: 'aes-256-cbc', inputEndcoding: 'utf-8', outputEndcoding: 'hex' };
293
+ // createCipheriv 方法中,key 要滿 32 字元、iv 要滿 16 字元,請之後測試多注意這點
294
+ const tradeInfo = FinancialService.aesEncrypt(qs, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
295
+
296
+ // 4. 產生檢查碼
297
+ const tradeSha = crypto
298
+ .createHash('sha256')
299
+ .update(`HashKey=${this.keyData.HASH_KEY}&${tradeInfo}&HashIV=${this.keyData.HASH_IV??''}`)
300
+ .digest('hex')
301
+ .toUpperCase();
302
+ const subMitData = {
303
+ actionURL: this.keyData.ActionURL,
304
+ MerchantOrderNo: params.MerchantOrderNo,
305
+ MerchantID: this.keyData.MERCHANT_ID,
306
+ TradeInfo: tradeInfo,
307
+ TradeSha: tradeSha,
308
+ Version: params.Version,
309
+ };
310
+
311
+ // 5. 回傳物件
312
+ return html` <form name="Newebpay" action="${subMitData.actionURL}" method="POST" class="payment">
313
+ <input type="hidden" name="MerchantID" value="${subMitData.MerchantID}" />
314
+ <input type="hidden" name="TradeInfo" value="${subMitData.TradeInfo}" />
315
+ <input type="hidden" name="TradeSha" value="${subMitData.TradeSha}" />
316
+ <input type="hidden" name="Version" value="${subMitData.Version}" />
317
+ <input type="hidden" name="MerchantOrderNo" value="${subMitData.MerchantOrderNo}" />
318
+ <button type="submit" class="btn btn-secondary custom-btn beside-btn" id="submit" hidden></button>
319
+ </form>`;
320
+ }
321
+ }
322
+
323
+ // 綠界金流
324
+ export class EcPayV2 {
325
+ // beta版控制寫在金流設定的前端 透過ActionURL去做設定 同時MERCHANT_ID
326
+ keyData!: KeyData;
327
+
328
+ appName: string;
329
+
330
+ static beta = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5';
331
+
332
+ constructor(appName: string, keyData?: KeyData) {
333
+ this.appName = appName;
334
+ this.keyData = keyData!;
335
+ }
336
+
337
+ async key_initial() {
338
+ const keyData = (
339
+ await Private_config.getConfig({
340
+ appName: this.appName,
341
+ key: 'glitter_finance',
342
+ })
343
+ )[0].value;
344
+
345
+ let kd = keyData['ecPay'] ?? {
346
+ ReturnURL: '',
347
+ NotifyURL: '',
348
+ };
349
+ this.keyData = kd;
350
+ }
351
+
352
+ static generateCheckMacValue(params: Record<string, any>, HashKey: string, HashIV: string): string {
353
+ // 步驟 1:依參數名稱排序並串接
354
+ const sortedQueryString = Object.keys(params)
355
+ .sort() // 按英文字母順序排序
356
+ .map(key => `${key}=${params[key]}`) // 串接成 key=value 形式
357
+ .join('&');
358
+
359
+ // 步驟 2:加上 HashKey 和 HashIV
360
+ const rawString = `HashKey=${HashKey}&${sortedQueryString}&HashIV=${HashIV}`;
361
+
362
+ // 步驟 3:URL Encode (RFC 1866)
363
+ const encodedString = encodeURIComponent(rawString)
364
+ .replace(/%2d/g, '-')
365
+ .replace(/%5f/g, '_')
366
+ .replace(/%2e/g, '.')
367
+ .replace(/%21/g, '!')
368
+ .replace(/%2a/g, '*')
369
+ .replace(/%28/g, '(')
370
+ .replace(/%29/g, ')')
371
+ .replace(/%20/g, '+');
372
+
373
+ // 步驟 4:轉為小寫
374
+ const lowerCaseString = encodedString.toLowerCase();
375
+
376
+ // 步驟 5:使用 SHA256 進行雜湊
377
+ const sha256Hash = crypto.createHash('sha256').update(lowerCaseString).digest('hex');
378
+
379
+ // 步驟 6:轉大寫產生 CheckMacValue
380
+ return sha256Hash.toUpperCase();
381
+ }
382
+
383
+ async executePayment(orderData: {
384
+ lineItems: {
385
+ id: string;
386
+ spec: string[];
387
+ count: number;
388
+ sale_price: number;
389
+ title: string;
390
+ }[];
391
+ total: number;
392
+ email: string;
393
+ shipment_fee: number;
394
+ orderID: string;
395
+ user_email: string;
396
+ use_wallet: number;
397
+ method: string;
398
+ CheckMacValue?: string;
399
+ }) {
400
+
401
+ const params = {
402
+ MerchantTradeNo: orderData.orderID,
403
+ MerchantTradeDate: moment().tz('Asia/Taipei').format('YYYY/MM/DD HH:mm:ss'),
404
+ TotalAmount: orderData.total - orderData.use_wallet,
405
+ TradeDesc: '商品資訊',
406
+ ItemName: orderData.lineItems
407
+ .map(dd => {
408
+ return dd.title + (dd.spec.join('-') && '-' + dd.spec.join('-'));
409
+ })
410
+ .join('#'),
411
+ ReturnURL: this.keyData.NotifyURL,
412
+ ChoosePayment:
413
+ orderData.method && orderData.method !== 'ALL'
414
+ ? (() => {
415
+ const find = [
416
+ {
417
+ value: 'credit',
418
+ title: '信用卡',
419
+ realKey: 'Credit',
420
+ },
421
+ {
422
+ value: 'atm',
423
+ title: 'ATM',
424
+ realKey: 'ATM',
425
+ },
426
+ {
427
+ value: 'web_atm',
428
+ title: '網路ATM',
429
+ realKey: 'WebATM',
430
+ },
431
+ {
432
+ value: 'c_code',
433
+ title: '超商代碼',
434
+ realKey: 'CVS',
435
+ },
436
+ {
437
+ value: 'c_bar_code',
438
+ title: '超商條碼',
439
+ realKey: 'BARCODE',
440
+ },
441
+ ].find(dd => {
442
+ return dd.value === orderData.method;
443
+ });
444
+ return find && find!.realKey;
445
+ })()
446
+ : 'ALL',
447
+ PlatformID: '',
448
+ MerchantID: this.keyData.MERCHANT_ID,
449
+ InvoiceMark: 'N',
450
+ IgnorePayment: '',
451
+ DeviceSource: '',
452
+ EncryptType: '1',
453
+ PaymentType: 'aio',
454
+ OrderResultURL: this.keyData.ReturnURL,
455
+ NeedExtraPaidInfo: 'Y',
456
+ };
457
+ const chkSum = EcPayV2.generateCheckMacValue(params, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
458
+ orderData.CheckMacValue = chkSum;
459
+
460
+ // 5. 回傳物件
461
+ return html`
462
+ <form id="_form_aiochk" action="${this.keyData.ActionURL}" method="post">
463
+ <input type="hidden" name="MerchantTradeNo" id="MerchantTradeNo" value="${params.MerchantTradeNo}" />
464
+ <input type="hidden" name="MerchantTradeDate" id="MerchantTradeDate" value="${params.MerchantTradeDate}" />
465
+ <input type="hidden" name="TotalAmount" id="TotalAmount" value="${params.TotalAmount}" />
466
+ <input type="hidden" name="TradeDesc" id="TradeDesc" value="${params.TradeDesc}" />
467
+ <input type="hidden" name="ItemName" id="ItemName" value="${params.ItemName}" />
468
+ <input type="hidden" name="ReturnURL" id="ReturnURL" value="${params.ReturnURL}" />
469
+ <input type="hidden" name="ChoosePayment" id="ChoosePayment" value="${params.ChoosePayment}" />
470
+ <input type="hidden" name="PlatformID" id="PlatformID" value="${params.PlatformID}" />
471
+ <input type="hidden" name="MerchantID" id="MerchantID" value="${params.MerchantID}" />
472
+ <input type="hidden" name="InvoiceMark" id="InvoiceMark" value="${params.InvoiceMark}" />
473
+ <input type="hidden" name="IgnorePayment" id="IgnorePayment" value="${params.IgnorePayment}" />
474
+ <input type="hidden" name="DeviceSource" id="DeviceSource" value="${params.DeviceSource}" />
475
+ <input type="hidden" name="EncryptType" id="EncryptType" value="${params.EncryptType}" />
476
+ <input type="hidden" name="PaymentType" id="PaymentType" value="${params.PaymentType}" />
477
+ <input type="hidden" name="OrderResultURL" id="OrderResultURL" value="${params.OrderResultURL}" />
478
+ <input type="hidden" name="NeedExtraPaidInfo" id="NeedExtraPaidInfo" value="${params.NeedExtraPaidInfo}" />
479
+ <input type="hidden" name="CheckMacValue" id="CheckMacValue" value="${chkSum}" />
480
+ <button type="submit" class="btn btn-secondary custom-btn beside-btn d-none" id="submit" hidden></button>
481
+ </form>
482
+ `;
483
+ }
484
+
485
+ async checkCreditInfo(CreditRefundId: string, CreditAmount: string) {
486
+ await this.key_initial();
487
+ const params: any = {
488
+ CreditRefundId: `${CreditRefundId}`,
489
+ CreditAmount: CreditAmount,
490
+ MerchantID: this.keyData.MERCHANT_ID,
491
+ CreditCheckCode: this.keyData.CreditCheckCode,
492
+ };
493
+
494
+ const chkSum = EcPayV2.generateCheckMacValue(params, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
495
+ params.CheckMacValue = chkSum;
496
+
497
+ let config = {
498
+ method: 'post',
499
+ maxBodyLength: Infinity,
500
+ url: `https://payment.ecpay.com.tw/CreditDetail/QueryTrade/V2`,
501
+ headers: {},
502
+ 'Content-Type': 'application/x-www-form-urlencoded',
503
+ data: new URLSearchParams(params).toString(),
504
+ };
505
+
506
+ //發送通知
507
+ //PlatformID
508
+ return await new Promise<any>((resolve, reject) => {
509
+ axios
510
+ .request(config)
511
+ .then((response: any) => {
512
+ resolve(response.data.RtnValue);
513
+ })
514
+ .catch((error: any) => {
515
+ console.log(error);
516
+ resolve({});
517
+ });
518
+ });
519
+ }
520
+
521
+ async checkPaymentStatus(orderID: string) {
522
+ await this.key_initial();
523
+ const params: any = {
524
+ MerchantTradeNo: `${orderID}`,
525
+ TimeStamp: Math.floor(Date.now() / 1000),
526
+ MerchantID: this.keyData.MERCHANT_ID,
527
+ };
528
+
529
+ const chkSum = EcPayV2.generateCheckMacValue(params, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
530
+ params.CheckMacValue = chkSum;
531
+
532
+ let config = {
533
+ method: 'post',
534
+ maxBodyLength: Infinity,
535
+ url:
536
+ EcPayV2.beta === this.keyData.ActionURL
537
+ ? 'https://payment-stage.ecpay.com.tw/Cashier/QueryTradeInfo/V5'
538
+ : 'https://payment.ecpay.com.tw/Cashier/QueryTradeInfo/V5',
539
+ headers: {},
540
+ 'Content-Type': 'application/x-www-form-urlencoded',
541
+ data: new URLSearchParams(params).toString(),
542
+ };
543
+ //發送通知
544
+ //PlatformID
545
+ return await new Promise<any>((resolve, reject) => {
546
+ axios
547
+ .request(config)
548
+ .then(async (response: any) => {
549
+ const params = new URLSearchParams(response.data);
550
+ // 將 URLSearchParams 轉換成對象
551
+ const paramsObject: any = {};
552
+ params.forEach((value, key) => {
553
+ // 將每組 key 和 value 加入對象中
554
+ paramsObject[key] = value;
555
+ });
556
+ // 將對象轉換為 JSON
557
+ if (paramsObject.gwsr && this.keyData.CreditCheckCode && EcPayV2.beta !== this.keyData.ActionURL) {
558
+ paramsObject.credit_receipt = await this.checkCreditInfo(paramsObject.gwsr, paramsObject.TradeAmt);
559
+ if (paramsObject.credit_receipt.status !== '已授權') {
560
+ paramsObject.TradeStatus = '10200095';
561
+ }
562
+ }
563
+ resolve(paramsObject);
564
+ })
565
+ .catch((error: any) => {
566
+ console.log(error);
567
+ resolve({
568
+ TradeStatus: '10200095',
569
+ });
570
+ });
571
+ });
572
+ }
573
+
574
+ async brushBack(orderID: string, tradNo: string, total: string) {
575
+ await this.key_initial();
576
+ const params: any = {
577
+ MerchantTradeNo: `${orderID}`,
578
+ TradeNo: tradNo,
579
+ Action: 'N',
580
+ TotalAmount: parseInt(total, 10),
581
+ MerchantID: this.keyData.MERCHANT_ID,
582
+ };
583
+
584
+ const chkSum = EcPayV2.generateCheckMacValue(params, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
585
+ params.CheckMacValue = chkSum;
586
+
587
+ let config = {
588
+ method: 'post',
589
+ maxBodyLength: Infinity,
590
+ url: `https://payment.ecpay.com.tw/CreditDetail/DoAction`,
591
+ headers: {},
592
+ 'Content-Type': 'application/x-www-form-urlencoded',
593
+ data: new URLSearchParams(params).toString(),
594
+ };
595
+ //發送通知
596
+ //PlatformID
597
+ return await new Promise<any>((resolve, reject) => {
598
+ axios
599
+ .request(config)
600
+ .then((response: any) => {
601
+ const params = new URLSearchParams(response.data);
602
+ // 將 URLSearchParams 轉換成對象
603
+ const paramsObject: any = {};
604
+ params.forEach((value, key) => {
605
+ // 將每組 key 和 value 加入對象中
606
+ paramsObject[key] = value;
607
+ });
608
+ // 將對象轉換為 JSON
609
+ resolve(paramsObject);
610
+ })
611
+ .catch((error: any) => {
612
+ console.log(error);
613
+ resolve({
614
+ RtnCode: `-1`,
615
+ });
616
+ });
617
+ });
618
+ }
619
+
620
+ async saveMoney(orderData: {
621
+ total: number;
622
+ userID: number;
623
+ note: string;
624
+ method: string;
625
+ CheckMacValue?: string;
626
+ table: string;
627
+ title: string;
628
+ ratio: number;
629
+ }) {
630
+ await this.key_initial();
631
+ // 1. 建立請求的參數
632
+ const params = {
633
+ MerchantTradeNo: new Date().getTime(),
634
+ MerchantTradeDate: moment().tz('Asia/Taipei').format('YYYY/MM/DD HH:mm:ss'),
635
+ TotalAmount: orderData.total,
636
+ TradeDesc: '商品資訊',
637
+ ItemName: orderData.title,
638
+ ReturnURL: this.keyData.NotifyURL,
639
+ ChoosePayment:
640
+ orderData.method && orderData.method !== 'ALL'
641
+ ? (() => {
642
+ const find = [
643
+ {
644
+ value: 'credit',
645
+ title: '信用卡',
646
+ realKey: 'Credit',
647
+ },
648
+ {
649
+ value: 'atm',
650
+ title: 'ATM',
651
+ realKey: 'ATM',
652
+ },
653
+ {
654
+ value: 'web_atm',
655
+ title: '網路ATM',
656
+ realKey: 'WebATM',
657
+ },
658
+ {
659
+ value: 'c_code',
660
+ title: '超商代碼',
661
+ realKey: 'CVS',
662
+ },
663
+ {
664
+ value: 'c_bar_code',
665
+ title: '超商條碼',
666
+ realKey: 'BARCODE',
667
+ },
668
+ ].find(dd => {
669
+ return dd.value === orderData.method;
670
+ });
671
+ return find && find!.realKey;
672
+ })()
673
+ : 'ALL',
674
+ PlatformID: '',
675
+ MerchantID: this.keyData.MERCHANT_ID,
676
+ InvoiceMark: 'N',
677
+ IgnorePayment: '',
678
+ DeviceSource: '',
679
+ EncryptType: '1',
680
+ PaymentType: 'aio',
681
+ OrderResultURL: this.keyData.ReturnURL,
682
+ };
683
+
684
+ const chkSum = EcPayV2.generateCheckMacValue(params, this.keyData.HASH_KEY??'', this.keyData.HASH_IV??'');
685
+ orderData.CheckMacValue = chkSum;
686
+ await db.execute(
687
+ `INSERT INTO \`${this.appName}\`.${orderData.table} (orderID, userID, money, status, note)
688
+ VALUES (?, ?, ?, ?, ?)
689
+ `,
690
+ [params.MerchantTradeNo, orderData.userID, orderData.total * orderData.ratio, 0, orderData.note]
691
+ );
692
+
693
+ // 5. 回傳物件
694
+ return html`
695
+ <form id="_form_aiochk" action="${this.keyData.ActionURL}" method="post">
696
+ <input type="hidden" name="MerchantTradeNo" id="MerchantTradeNo" value="${params.MerchantTradeNo}" />
697
+ <input type="hidden" name="MerchantTradeDate" id="MerchantTradeDate" value="${params.MerchantTradeDate}" />
698
+ <input type="hidden" name="TotalAmount" id="TotalAmount" value="${params.TotalAmount}" />
699
+ <input type="hidden" name="TradeDesc" id="TradeDesc" value="${params.TradeDesc}" />
700
+ <input type="hidden" name="ItemName" id="ItemName" value="${params.ItemName}" />
701
+ <input type="hidden" name="ReturnURL" id="ReturnURL" value="${params.ReturnURL}" />
702
+ <input type="hidden" name="ChoosePayment" id="ChoosePayment" value="${params.ChoosePayment}" />
703
+ <input type="hidden" name="PlatformID" id="PlatformID" value="${params.PlatformID}" />
704
+ <input type="hidden" name="MerchantID" id="MerchantID" value="${params.MerchantID}" />
705
+ <input type="hidden" name="InvoiceMark" id="InvoiceMark" value="${params.InvoiceMark}" />
706
+ <input type="hidden" name="IgnorePayment" id="IgnorePayment" value="${params.IgnorePayment}" />
707
+ <input type="hidden" name="DeviceSource" id="DeviceSource" value="${params.DeviceSource}" />
708
+ <input type="hidden" name="EncryptType" id="EncryptType" value="${params.EncryptType}" />
709
+ <input type="hidden" name="PaymentType" id="PaymentType" value="${params.PaymentType}" />
710
+ <input type="hidden" name="OrderResultURL" id="OrderResultURL" value="${params.OrderResultURL}" />
711
+ <input type="hidden" name="CheckMacValue" id="CheckMacValue" value="${chkSum}" />
712
+ <button type="submit" class="btn btn-secondary custom-btn beside-btn d-none" id="submit" hidden></button>
713
+ </form>
714
+ `;
715
+ }
716
+ }
717
+
718
+ // PayPal金流
719
+ export class PayPalV2 {
720
+ keyData: {
721
+ ReturnURL?: string;
722
+ NotifyURL?: string;
723
+ PAYPAL_CLIENT_ID: string;
724
+ PAYPAL_SECRET: string;
725
+ BETA: string;
726
+ };
727
+ appName: string;
728
+ PAYPAL_CLIENT_ID: string;
729
+ PAYPAL_SECRET: string;
730
+ PAYPAL_BASE_URL: string;
731
+
732
+ constructor(
733
+ appName: string,
734
+ keyData: {
735
+ ReturnURL?: string;
736
+ NotifyURL?: string;
737
+ PAYPAL_CLIENT_ID: string;
738
+ PAYPAL_SECRET: string;
739
+ BETA: string;
740
+ }
741
+ ) {
742
+ this.keyData = keyData;
743
+ this.appName = appName;
744
+ this.PAYPAL_CLIENT_ID = keyData.BETA == 'true' ?"ATz7uJryxmGA2SmR5PxQ_IFXFYKeWd_R1SIzsr_bDrJQMYgRR5z_TXEnjcBh2P4DQDDYnLdHu0aNfugX":keyData.PAYPAL_CLIENT_ID; // 替換為您的 Client ID
745
+ this.PAYPAL_SECRET = keyData.BETA == 'true' ?"ENb25ujfYB0GBzv6GvzDW2a7gx-KgsVZwxOBqF0WSH3Zr7SU1BBdI8KQ_XRpcgcjj8VWTOWwo83NxK5d" : keyData.PAYPAL_SECRET; // 替換為您的 Secret Key
746
+ this.PAYPAL_BASE_URL = keyData.BETA == 'true' ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; // 沙箱環境
747
+ // const PAYPAL_BASE_URL = "https://api-m.paypal.com"; // 正式環境
748
+ }
749
+
750
+ async getAccessToken(): Promise<string> {
751
+ try {
752
+ const tokenUrl = `${this.PAYPAL_BASE_URL}/v1/oauth2/token`;
753
+
754
+ const config: AxiosRequestConfig = {
755
+ method: 'POST',
756
+ url: tokenUrl,
757
+ headers: {
758
+ 'Content-Type': 'application/x-www-form-urlencoded',
759
+ },
760
+ auth: {
761
+ username: this.PAYPAL_CLIENT_ID,
762
+ password: this.PAYPAL_SECRET,
763
+ },
764
+ data: new URLSearchParams({
765
+ grant_type: 'client_credentials',
766
+ }).toString(),
767
+ };
768
+ console.log("this.PAYPAL_CLIENT_ID -- " , this.PAYPAL_CLIENT_ID);
769
+ console.log("this.PAYPAL_SECRE -- " , this.PAYPAL_SECRET);
770
+ const response = await axios.request(config);
771
+ return response.data.access_token;
772
+ } catch (error: any) {
773
+ console.error('Error fetching access token:', error.response?.data || error.message);
774
+ throw new Error('Failed to retrieve PayPal access token.');
775
+ }
776
+ }
777
+
778
+ async checkout(orderData: any) {
779
+ const accessToken = await this.getAccessToken();
780
+ const order = await this.executePayment(accessToken, orderData);
781
+ return {
782
+ orderId: order.id,
783
+ approveLink: order.links.find((link: any) => link.rel === 'approve')?.href,
784
+ };
785
+ }
786
+
787
+ async executePayment(
788
+ accessToken: string,
789
+ orderData: {
790
+ lineItems: {
791
+ id: string;
792
+ spec: string[];
793
+ count: number;
794
+ sale_price: number;
795
+ title: string;
796
+ }[];
797
+ total: number;
798
+ email: string;
799
+ shipment_fee: number;
800
+ orderID: string;
801
+ use_wallet: number;
802
+ user_email: string;
803
+ method?: string;
804
+ }
805
+ ) {
806
+ try {
807
+ const createOrderUrl = `${this.PAYPAL_BASE_URL}/v2/checkout/orders`;
808
+ let itemPrice = 0;
809
+ orderData.lineItems.forEach(item => {
810
+ itemPrice += item.sale_price;
811
+ });
812
+ const config: AxiosRequestConfig = {
813
+ method: 'POST',
814
+ url: createOrderUrl,
815
+ headers: {
816
+ 'Content-Type': 'application/json',
817
+ Authorization: `Bearer ${accessToken}`,
818
+ },
819
+ data: {
820
+ intent: 'CAPTURE', // 訂單目標: CAPTURE (立即支付) 或 AUTHORIZE (授權後支付)
821
+ purchase_units: [
822
+ {
823
+ reference_id: orderData.orderID, // 訂單參考 ID,可自定義
824
+ amount: {
825
+ currency_code: 'TWD', // 貨幣
826
+ value: itemPrice, // 總金額
827
+ breakdown: {
828
+ item_total: {
829
+ currency_code: 'TWD',
830
+ value: itemPrice,
831
+ },
832
+ },
833
+ },
834
+ items: orderData.lineItems.map(item => {
835
+ return {
836
+ name: item.title, // 商品名稱
837
+ unit_amount: {
838
+ currency_code: 'TWD',
839
+ value: item.sale_price,
840
+ },
841
+ quantity: item.count, // 商品數量
842
+ description: item.spec.join(',') ?? '',
843
+ };
844
+ }),
845
+ },
846
+ ],
847
+
848
+ application_context: {
849
+ brand_name: this.appName, // 商店名稱
850
+ landing_page: 'NO_PREFERENCE', // 登陸頁面類型
851
+ user_action: 'PAY_NOW', // 用戶操作: PAY_NOW (立即支付)
852
+ return_url: `${this.keyData.ReturnURL}&payment=true&appName=${this.appName}&orderID=${orderData.orderID}`, // 成功返回 URL
853
+ cancel_url: `${this.keyData.ReturnURL}&payment=false`, // 取消返回 URL
854
+ },
855
+ },
856
+ };
857
+
858
+ const response = await axios.request(config);
859
+ await redis.setValue('paypal' + orderData.orderID, response.data.id);
860
+ return response.data;
861
+ } catch (error: any) {
862
+ console.error('Error creating order:', error.response?.data || error.message);
863
+ throw new Error('Failed to create PayPal order.');
864
+ }
865
+ }
866
+
867
+ async getOrderDetails(orderId: string, accessToken: string) {
868
+ const url = `/v2/checkout/orders/${orderId}`;
869
+ const axiosInstance = axios.create({
870
+ baseURL: this.PAYPAL_BASE_URL,
871
+ headers: {
872
+ 'Content-Type': 'application/json',
873
+ },
874
+ });
875
+ const config: AxiosRequestConfig = {
876
+ method: 'GET',
877
+ url: url,
878
+ headers: {
879
+ Authorization: `Bearer ${accessToken}`,
880
+ },
881
+ };
882
+
883
+ try {
884
+ const response = await axiosInstance.request(config);
885
+ const order = response.data;
886
+
887
+ // 檢查訂單狀態是否為 APPROVED
888
+ if (order.status === 'APPROVED') {
889
+ return order;
890
+ } else {
891
+ throw new Error(`Order status is not APPROVED. Current status: ${order.status}`);
892
+ }
893
+ } catch (error: any) {
894
+ console.error('Error fetching order details:', error.response?.data || error.message);
895
+ throw error;
896
+ }
897
+ }
898
+
899
+ async capturePayment(orderId: string, accessToken: string) {
900
+ const url = `/v2/checkout/orders/${orderId}/capture`;
901
+ const axiosInstance = axios.create({
902
+ baseURL: this.PAYPAL_BASE_URL,
903
+ headers: {
904
+ 'Content-Type': 'application/json',
905
+ },
906
+ });
907
+ const config: AxiosRequestConfig = {
908
+ method: 'POST',
909
+ url: url,
910
+ headers: {
911
+ Authorization: `Bearer ${accessToken}`,
912
+ },
913
+ };
914
+
915
+ try {
916
+ const response = await axiosInstance.request(config);
917
+ return response.data;
918
+ } catch (error: any) {
919
+ console.error('Error capturing payment:', error.response?.data || error.message);
920
+ throw error;
921
+ }
922
+ }
923
+
924
+ async confirmAndCaptureOrder(orderId: string) {
925
+ try {
926
+ const accessToken = await this.getAccessToken();
927
+ // 確認訂單狀態
928
+ const order = await this.getOrderDetails(orderId, accessToken);
929
+ // 捕獲付款
930
+ const captureResult = await this.capturePayment(order.id, accessToken);
931
+
932
+ console.log('Payment process completed successfully.');
933
+ return captureResult;
934
+ } catch (error: any) {
935
+ console.error('Error during order confirmation or payment capture:', error.message);
936
+ throw error;
937
+ }
938
+ }
939
+ }
940
+
941
+ // LinePay金流
942
+ export class LinePayV2 {
943
+ keyData: {
944
+ ReturnURL: string;
945
+ LinePay_CLIENT_ID: string;
946
+ LinePay_SECRET: string;
947
+ BETA: string;
948
+ };
949
+ appName: string;
950
+ LinePay_CLIENT_ID: string;
951
+ LinePay_SECRET: string;
952
+ LinePay_BASE_URL: string;
953
+
954
+ constructor(appName: string, keyData: {ReturnURL:string, LinePay_CLIENT_ID:string , LinePay_SECRET:string, BETA:string}) {
955
+ this.keyData = keyData;
956
+ this.appName = appName;
957
+ this.LinePay_CLIENT_ID = keyData.LinePay_CLIENT_ID; // 替換為您的 Client ID
958
+ this.LinePay_SECRET = keyData.LinePay_SECRET; // 替換為您的 Secret Key
959
+ this.LinePay_BASE_URL = keyData.BETA == 'true' ? 'https://sandbox-api-pay.line.me' : 'https://api-pay.line.me'; // 沙箱環境
960
+ // this.LinePay_RETURN_HOST = '';
961
+ // this.LinePay_CLIENT_ID = "2006615995"; // 替換為您的 Client ID
962
+ // this.LinePay_CLIENT_ID = this.keyData.LinePay_CLIENT_ID;
963
+ // this.LinePay_SECRET = "05231f46428525ee68c2816f16635145"; // 替換為您的 Secret Key
964
+ // this.LinePay_SECRET = keyData.LinePay_SECRET;
965
+ // this.LinePay_BASE_URL = "https://sandbox-api-pay.line.me"; // 沙箱環境
966
+ // const PAYPAL_BASE_URL = "https://api-pay.line.me"; // 正式環境
967
+ }
968
+
969
+ async confirmAndCaptureOrder(transactionId: string, total: number) {
970
+ const body: any = {
971
+ amount: parseInt(`${total}`, 10),
972
+ currency: 'TWD',
973
+ };
974
+ const uri = `/payments/requests/${transactionId}/check`;
975
+ const nonce = new Date().getTime().toString();
976
+ const url = `${this.LinePay_BASE_URL}/v3${uri}`;
977
+ const head = [this.LinePay_SECRET, `/v3${uri}`, JSON.stringify(body), nonce].join('');
978
+ const signature = crypto.createHmac('sha256', this.LinePay_SECRET).update(head).digest('base64');
979
+
980
+ const config: AxiosRequestConfig = {
981
+ method: 'POST',
982
+ url: url,
983
+ headers: {
984
+ 'Content-Type': 'application/json',
985
+ 'X-LINE-ChannelId': this.LinePay_CLIENT_ID,
986
+ 'X-LINE-Authorization-Nonce': nonce,
987
+ 'X-LINE-Authorization': signature,
988
+ },
989
+ data: body,
990
+ };
991
+ console.log(`line-conform->
992
+ URL:${url}
993
+ X-LINE-ChannelId:${this.LinePay_CLIENT_ID}
994
+ LinePay_SECRET:${this.LinePay_SECRET}
995
+ `);
996
+ try {
997
+ const response = await axios.request(config);
998
+ return response;
999
+ } catch (error: any) {
1000
+ console.error('Error linePay:', error.response?.data.data || error.message);
1001
+ throw error;
1002
+ }
1003
+ }
1004
+
1005
+ async executePayment(orderData: {
1006
+ lineItems: {
1007
+ id: string;
1008
+ spec: string[];
1009
+ count: number;
1010
+ sale_price: number;
1011
+ title: string;
1012
+ }[];
1013
+ total: number;
1014
+ email: string;
1015
+ shipment_fee: number;
1016
+ orderID: string;
1017
+ use_wallet: number;
1018
+ user_email: string;
1019
+ method: string;
1020
+ discount?: any;
1021
+ }) {
1022
+ const confirm_url = `${this.keyData.ReturnURL}&LinePay=true&appName=${this.appName}&orderID=${orderData.orderID}`;
1023
+ const cancel_url = `${this.keyData.ReturnURL}&payment=false`;
1024
+
1025
+ orderData.discount = parseInt(orderData.discount ?? 0, 10);
1026
+
1027
+ const body = {
1028
+ amount: orderData.total,
1029
+ currency: 'TWD',
1030
+ orderId: orderData.orderID,
1031
+ shippingFee: orderData.shipment_fee,
1032
+ packages: [
1033
+ {
1034
+ id: 'product_list',
1035
+ amount:
1036
+ orderData.lineItems
1037
+ .map(data => {
1038
+ return data.count * data.sale_price;
1039
+ })
1040
+ .reduce((a, b) => a + b, 0) - orderData.discount,
1041
+ products: orderData.lineItems
1042
+ .map(data => {
1043
+ return {
1044
+ id: data.spec.join(','),
1045
+ name: data.title,
1046
+ imageUrl: '',
1047
+ quantity: data.count,
1048
+ price: data.sale_price,
1049
+ };
1050
+ })
1051
+ .concat([
1052
+ {
1053
+ id: 'discount',
1054
+ name: '折扣',
1055
+ imageUrl: '',
1056
+ quantity: 1,
1057
+ price: orderData.discount * -1,
1058
+ },
1059
+ ]),
1060
+ },
1061
+ ],
1062
+ redirectUrls: {
1063
+ confirmUrl: confirm_url,
1064
+ cancelUrl: cancel_url,
1065
+ },
1066
+ };
1067
+
1068
+ body.packages.push({
1069
+ id: 'shipping',
1070
+ amount: orderData.shipment_fee,
1071
+ products: [
1072
+ {
1073
+ id: 'shipping',
1074
+ name: 'shipping',
1075
+ imageUrl: '',
1076
+ quantity: 1,
1077
+ price: orderData.shipment_fee,
1078
+ },
1079
+ ],
1080
+ });
1081
+
1082
+ const uri = '/payments/request';
1083
+ const nonce = new Date().getTime().toString();
1084
+ const url = `${this.LinePay_BASE_URL}/v3${uri}`;
1085
+ const head = [this.LinePay_SECRET, `/v3${uri}`, JSON.stringify(body), nonce].join('');
1086
+
1087
+ //sha256加密
1088
+ const signature = crypto.createHmac('sha256', this.LinePay_SECRET).update(head).digest('base64');
1089
+ const config: AxiosRequestConfig = {
1090
+ method: 'POST',
1091
+ url: url,
1092
+ headers: {
1093
+ 'Content-Type': 'application/json',
1094
+ 'X-LINE-ChannelId': this.LinePay_CLIENT_ID,
1095
+ 'X-LINE-Authorization-Nonce': nonce,
1096
+ 'X-LINE-Authorization': signature,
1097
+ },
1098
+ data: body,
1099
+ };
1100
+ try {
1101
+ const response = await axios.request(config);
1102
+ await redis.setValue('linepay' + orderData.orderID, response.data.info.transactionId);
1103
+ if (response.data.returnCode === '0000') {
1104
+ return response.data;
1105
+ }else {
1106
+ console.log(" Line Pay Error: ", response.data.returnCode, response.data.returnMessage);
1107
+ return response.data;
1108
+ }
1109
+
1110
+ } catch (error: any) {
1111
+ console.error('Error linePay:', error.response?.data || error.message);
1112
+ throw error;
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ // paynow金流
1118
+ export class PayNowV2 {
1119
+ keyData: {
1120
+ ReturnURL?: string;
1121
+ NotifyURL?: string;
1122
+ public_key: string;
1123
+ private_key: string;
1124
+ BETA: string;
1125
+ };
1126
+ appName: string;
1127
+ PublicKey: string;
1128
+ PrivateKey: string;
1129
+ BASE_URL: string;
1130
+
1131
+ constructor(appName: string, keyData: any) {
1132
+ this.keyData = keyData;
1133
+ this.appName = appName;
1134
+ this.PublicKey = keyData.public_key ?? '';
1135
+ this.PrivateKey = keyData.private_key ?? '';
1136
+ this.BASE_URL = keyData.BETA == 'true' ? 'https://sandboxapi.paynow.com.tw' : 'https://api.paynow.com.tw'; // 沙箱環境
1137
+ }
1138
+
1139
+ async executePaymentIntent(transactionId: string, secret: string, paymentNo: string) {
1140
+ let config = {
1141
+ method: 'POST',
1142
+ maxBodyLength: Infinity,
1143
+ url: `${this.BASE_URL}/api/v1/payment-intents/${transactionId}/checkout`,
1144
+ headers: {
1145
+ Accept: 'application/json',
1146
+ Authorization: `Bearer ` + this.PrivateKey,
1147
+ },
1148
+ data: JSON.stringify({
1149
+ paymentNo: paymentNo,
1150
+ usePayNowSdk: true,
1151
+ key: this.PublicKey,
1152
+ secret: secret,
1153
+ paymentMethodType: 'CreditCard',
1154
+ paymentMethodData: {},
1155
+ otpFlag: false,
1156
+ meta: {
1157
+ client: {
1158
+ height: 0,
1159
+ width: 0,
1160
+ },
1161
+ iframe: {
1162
+ height: 0,
1163
+ width: 0,
1164
+ },
1165
+ },
1166
+ }),
1167
+ };
1168
+
1169
+ try {
1170
+ const response = await axios.request(config);
1171
+ return response.data;
1172
+ } catch (error: any) {
1173
+ console.error('Error paynow:', error.response?.data.data || error.message);
1174
+ throw error;
1175
+ }
1176
+ }
1177
+
1178
+ //取得並綁定商戶金鑰匙
1179
+ async bindKey() {
1180
+ if (this.keyData.BETA == 'true'){
1181
+ return {
1182
+ public_key:"sm22610RIIwOTz4STCFf0dF22G067lnd",
1183
+ private_key:"bES1o13CUQJhZzcOkkq2BRoSa8a4f0Kv"
1184
+ }
1185
+ }
1186
+ const keyData = (
1187
+ await Private_config.getConfig({
1188
+ appName: this.appName,
1189
+ key: 'glitter_finance',
1190
+ })
1191
+ )[0].value;
1192
+ let kd = keyData['paynow'] ?? {
1193
+ ReturnURL: '',
1194
+ NotifyURL: '',
1195
+ };
1196
+ let config = {
1197
+ method: 'post',
1198
+ maxBodyLength: Infinity,
1199
+ url: 'https://api.paynow.com.tw/api/v1/partner/merchants/binding',
1200
+ headers: {
1201
+ 'Content-Type': 'application/json',
1202
+ Authorization: `Bearer ` + process.env.paynow_partner,
1203
+ },
1204
+ data: {
1205
+ merchant_no: kd.account,
1206
+ api_key: kd.pwd,
1207
+ },
1208
+ };
1209
+ return await new Promise<{
1210
+ public_key: string;
1211
+ private_key: string;
1212
+ }>((resolve, reject) => {
1213
+ axios
1214
+ .request(config)
1215
+ .then(async (response: any) => {
1216
+ if (response.data.result.length) {
1217
+ keyData.public_key = response.data.result[0].public_key;
1218
+ keyData.private_key = response.data.result[0].private_key;
1219
+ }
1220
+ resolve({
1221
+ public_key: keyData.public_key,
1222
+ private_key: keyData.private_key,
1223
+ });
1224
+ })
1225
+ .catch((error: any) => {
1226
+ resolve({
1227
+ public_key: '',
1228
+ private_key: '',
1229
+ });
1230
+ });
1231
+ });
1232
+ }
1233
+
1234
+ //
1235
+ //確認付款資訊
1236
+ async confirmAndCaptureOrder(transactionId?: string) {
1237
+ let config = {
1238
+ method: 'get',
1239
+ maxBodyLength: Infinity,
1240
+ url: `${this.BASE_URL}/api/v1/payment-intents/${transactionId}`,
1241
+ headers: {
1242
+ Accept: 'application/json',
1243
+ Authorization: `Bearer ` + (await this.bindKey()).private_key,
1244
+ },
1245
+ };
1246
+ try {
1247
+ const response = await axios.request(config);
1248
+ return response.data;
1249
+ } catch (error: any) {
1250
+ console.error('Error paynow:', error.response?.data.data || error.message);
1251
+ throw error;
1252
+ }
1253
+ }
1254
+
1255
+ async createOrder(orderData: {
1256
+ lineItems: {
1257
+ id: string;
1258
+ spec: string[];
1259
+ count: number;
1260
+ sale_price: number;
1261
+ title: string;
1262
+ }[];
1263
+ total: number;
1264
+ email: string;
1265
+ shipment_fee: number;
1266
+ orderID: string;
1267
+ use_wallet: number;
1268
+ user_email: string;
1269
+ method: string;
1270
+ }) {
1271
+ const data = JSON.stringify({
1272
+ amount: orderData.total,
1273
+ currency: 'TWD',
1274
+ description: orderData.orderID,
1275
+ resultUrl: this.keyData.ReturnURL + `&orderID=${orderData.orderID}`,
1276
+ webhookUrl: this.keyData.NotifyURL + `&orderID=${orderData.orderID}`,
1277
+ allowedPaymentMethods: ['CreditCard'],
1278
+ expireDays: 3,
1279
+ });
1280
+ const url = `${this.BASE_URL}/api/v1/payment-intents`
1281
+ const key_ = (this.keyData.BETA)?{private_key:"bES1o13CUQJhZzcOkkq2BRoSa8a4f0Kv",public_key:"sm22610RIIwOTz4STCFf0dF22G067lnd"}:await this.bindKey();
1282
+ const config = {
1283
+ method: 'post',
1284
+ maxBodyLength: Infinity,
1285
+ url: url,
1286
+ headers: {
1287
+ 'Content-Type': 'application/json',
1288
+ Authorization: `Bearer ` + key_.private_key,
1289
+ },
1290
+ data: data,
1291
+ };
1292
+ try {
1293
+ const response = await axios.request(config);
1294
+ (orderData as any).paynow_id = response.data.result.id;
1295
+ await OrderEvent.insertOrder({
1296
+ cartData: orderData,
1297
+ status: 0,
1298
+ app: this.appName,
1299
+ });
1300
+ await redis.setValue('paynow' + orderData.orderID, response.data.result.id);
1301
+ return {
1302
+ data: response.data,
1303
+ publicKey: key_.public_key,
1304
+ BETA: this.keyData.BETA,
1305
+ };
1306
+ } catch (error: any) {
1307
+ console.error('Error payNow:', error.response?.data || error.message);
1308
+ throw error;
1309
+ }
1310
+ }
1311
+
1312
+ async executePayment(orderData: {
1313
+ lineItems: {
1314
+ id: string;
1315
+ spec: string[];
1316
+ count: number;
1317
+ sale_price: number;
1318
+ title: string;
1319
+ }[];
1320
+ total: number;
1321
+ email: string;
1322
+ shipment_fee: number;
1323
+ orderID: string;
1324
+ use_wallet: number;
1325
+ user_email: string;
1326
+ method: string;
1327
+ }) {
1328
+ const data = JSON.stringify({
1329
+ amount: orderData.total,
1330
+ currency: 'TWD',
1331
+ description: orderData.orderID,
1332
+ resultUrl: this.keyData.ReturnURL + `&orderID=${orderData.orderID}`,
1333
+ webhookUrl: this.keyData.NotifyURL + `&orderID=${orderData.orderID}`,
1334
+ allowedPaymentMethods: ['CreditCard'],
1335
+ expireDays: 3,
1336
+ });
1337
+ const url = `${this.BASE_URL}/api/v1/payment-intents`
1338
+ const key_ = (this.keyData.BETA)?{private_key:"bES1o13CUQJhZzcOkkq2BRoSa8a4f0Kv",public_key:"sm22610RIIwOTz4STCFf0dF22G067lnd"}:await this.bindKey();
1339
+ const config = {
1340
+ method: 'post',
1341
+ maxBodyLength: Infinity,
1342
+ url: url,
1343
+ headers: {
1344
+ 'Content-Type': 'application/json',
1345
+ Authorization: `Bearer ` + key_.private_key,
1346
+ },
1347
+ data: data,
1348
+ };
1349
+ try {
1350
+ const response = await axios.request(config);
1351
+ (orderData as any).paynow_id = response.data.result.id;
1352
+ await redis.setValue('paynow' + orderData.orderID, response.data.result.id);
1353
+ return {
1354
+ returnUrl:this.keyData.ReturnURL,
1355
+ data: response.data,
1356
+ publicKey: key_.public_key,
1357
+ BETA: this.keyData.BETA,
1358
+ };
1359
+ } catch (error: any) {
1360
+ console.error('Error payNow:', error.response?.data || error.message);
1361
+ throw error;
1362
+ }
1363
+ }
1364
+ }
1365
+
1366
+ // 街口
1367
+ export class JKOV2 {
1368
+ keyData: {
1369
+ ReturnURL?: string;
1370
+ NotifyURL?: string;
1371
+ API_KEY: string;
1372
+ STORE_ID: string;
1373
+ SECRET_KEY: string;
1374
+ };
1375
+ appName: string;
1376
+ BASE_URL: string;
1377
+
1378
+ constructor(appName: string, keyData: {
1379
+ ReturnURL?: string;
1380
+ NotifyURL?: string;
1381
+ API_KEY: string;
1382
+ STORE_ID: string;
1383
+ SECRET_KEY: string;
1384
+ }) {
1385
+ this.keyData = keyData;
1386
+ this.appName = appName;
1387
+ this.BASE_URL = 'https://onlinepay.jkopay.com/';
1388
+ }
1389
+
1390
+ async confirmAndCaptureOrder(transactionId?: string) {
1391
+ //平台 API KEY
1392
+ const apiKey: string = process.env.jko_api_key || '';
1393
+ //平台 Secret Key
1394
+ const secretKey: string = process.env.jko_api_secret || '';
1395
+
1396
+ const digest = this.generateDigest(`platform_order_ids=${transactionId}`, secretKey);
1397
+
1398
+ let config = {
1399
+ method: 'get',
1400
+ url: `${this.BASE_URL}platform/inquiry?platform_order_ids=${transactionId}`,
1401
+ headers: {
1402
+ 'api-key': apiKey,
1403
+ digest: digest,
1404
+ 'Content-Type': 'application/json',
1405
+ },
1406
+ };
1407
+ try {
1408
+ const response = await axios.request(config);
1409
+ return response.data.result_object;
1410
+ } catch (error: any) {
1411
+ console.error('Error paynow:', error.response?.data.data || error.message);
1412
+ throw error;
1413
+ }
1414
+ }
1415
+
1416
+ async executePayment(orderData: {
1417
+ lineItems: {
1418
+ id: string;
1419
+ spec: string[];
1420
+ count: number;
1421
+ sale_price: number;
1422
+ preview_image: string;
1423
+ title: string;
1424
+ }[];
1425
+ total: number;
1426
+ email: string;
1427
+ shipment_fee: number;
1428
+ orderID: string;
1429
+ use_wallet: number;
1430
+ user_email: string;
1431
+ method: string;
1432
+ }) {
1433
+ const payload = {
1434
+ currency: 'TWD',
1435
+ total_price: orderData.total,
1436
+ final_price: orderData.total,
1437
+ platform_order_id:orderData.orderID,
1438
+ store_id: this.keyData.STORE_ID,
1439
+ result_url: this.keyData.NotifyURL + `&orderID=${orderData.orderID}`,
1440
+ result_display_url:this.keyData.ReturnURL + `&orderID=${orderData.orderID}`,
1441
+ unredeem:0
1442
+ };
1443
+ //平台 API KEY
1444
+ const apiKey: string = process.env.jko_api_key || '';
1445
+ //平台 Secret Key
1446
+ const secretKey: string = process.env.jko_api_secret || '';
1447
+ // 使用 HMAC-SHA256 生成 `digest`
1448
+ const digest = crypto.createHmac('sha256', secretKey).update(JSON.stringify(payload), 'utf8').digest('hex');
1449
+
1450
+ // 設定 Headers
1451
+ const headers = {
1452
+ 'api-key': apiKey,
1453
+ digest: digest,
1454
+ 'Content-Type': 'application/json',
1455
+ };
1456
+
1457
+ const url = `${this.BASE_URL}platform/entry`;
1458
+ const config = {
1459
+ method: 'post',
1460
+ url: url,
1461
+ headers: headers,
1462
+ data: payload,
1463
+ };
1464
+ try {
1465
+ const response = await axios.request(config);
1466
+ await OrderEvent.insertOrder({
1467
+ cartData: orderData,
1468
+ status: 0,
1469
+ app: this.appName,
1470
+ });
1471
+ return response.data;
1472
+
1473
+ } catch (error: any) {
1474
+ console.error('Error jkoPay:', error.response?.data || error.message);
1475
+ throw error;
1476
+ }
1477
+ }
1478
+
1479
+ async refundOrder(platform_order_id: string, refund_amount: number) {
1480
+ const payload = JSON.stringify({
1481
+ platform_order_id: '1740299355493',
1482
+ refund_amount: 10000,
1483
+ });
1484
+ const apiKey = '689c57cd9d5b5ec80f5d5451d18fe24cfe855d21b25c7ff30bcd07829a902f7a';
1485
+
1486
+ // 設定 Secret Key
1487
+ const secretKey = '8ec78345a13e3d376452d9c89c66b543ef1516c0ef1a05f0adf654c37ac8edac';
1488
+
1489
+ console.log('payload -- ', payload);
1490
+
1491
+ // 使用 HMAC-SHA256 生成 `digest`
1492
+ const digest = crypto.createHmac('sha256', secretKey).update(payload, 'utf8').digest('hex');
1493
+
1494
+ // 設定 Headers
1495
+ const headers = {
1496
+ 'api-key': apiKey,
1497
+ digest: digest,
1498
+ 'Content-Type': 'application/json',
1499
+ };
1500
+
1501
+ // 顯示結果
1502
+ console.log('API Key:', apiKey);
1503
+ console.log('Digest:', digest);
1504
+ console.log('Headers:', headers);
1505
+ }
1506
+
1507
+ private generateDigest(data: string, apiSecret: string): string {
1508
+ // 轉換 data 和 apiSecret 為 UTF-8 Byte Array
1509
+ console.log('data --', data);
1510
+ console.log('apiSecret -- ', apiSecret);
1511
+ // 使用 HMAC-SHA256 進行雜湊
1512
+ const hmac = crypto.createHmac('sha256', apiSecret);
1513
+ hmac.update(data);
1514
+
1515
+ // 轉換為 16 進位字串 (與 C# 的 Convert.ToHexString(hash).ToLower() 等效)
1516
+ return hmac.digest('hex');
1517
+ }
1518
+ }