taiwan-payment-skill 1.0.2 → 1.0.3

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.
@@ -1,997 +1,997 @@
1
- # PayUni Payment API Reference
2
-
3
- 統一金流 (PAYUNi) 金流 API 完整參考文件。
4
-
5
- ---
6
-
7
- ## 目錄
8
-
9
- 1. [API 端點總覽](#api-端點總覽)
10
- 2. [測試環境](#測試環境)
11
- 3. [加密機制](#加密機制)
12
- 4. [通用參數](#通用參數)
13
- 5. [整合式支付頁 (UPP)](#整合式支付頁-upp)
14
- 6. [信用卡幕後](#信用卡幕後)
15
- 7. [ATM 虛擬帳號](#atm-虛擬帳號)
16
- 8. [超商代碼](#超商代碼)
17
- 9. [LINE Pay](#line-pay)
18
- 10. [AFTEE 先享後付](#aftee-先享後付)
19
- 11. [Apple Pay / Google Pay / Samsung Pay](#apple-pay--google-pay--samsung-pay)
20
- 12. [愛金卡 iCash](#愛金卡-icash)
21
- 13. [交易查詢](#交易查詢)
22
- 14. [交易請退款](#交易請退款)
23
- 15. [交易取消授權](#交易取消授權)
24
- 16. [信用卡約定 (Token)](#信用卡約定-token)
25
- 17. [付款結果通知](#付款結果通知)
26
- 18. [錯誤碼對照表](#錯誤碼對照表)
27
- 19. [常見問題排解](#常見問題排解)
28
-
29
- ---
30
-
31
- ## API 端點總覽
32
-
33
- ### 基礎 API 路徑
34
-
35
- | 環境 | 基礎路徑 |
36
- |------|----------|
37
- | **測試環境** | `https://sandbox-api.payuni.com.tw/api/` |
38
- | **正式環境** | `https://api.payuni.com.tw/api/` |
39
-
40
- ### 支付相關端點
41
-
42
- | 功能 | 端點路徑 | 說明 |
43
- |------|----------|------|
44
- | 整合式支付頁 | `/upp` | 導向 PayUni 支付頁面 |
45
- | ATM 虛擬帳號 | `/atm` | 幕後取得虛擬帳號 |
46
- | 超商代碼 | `/cvs` | 幕後取得繳費代碼 |
47
- | 信用卡幕後 | `/credit` | 信用卡直接扣款 |
48
- | LINE Pay | `/linepay` | LINE Pay 支付 |
49
- | AFTEE 先享後付 | `/aftee_direct` | AFTEE 支付 |
50
-
51
- ### 交易管理端點
52
-
53
- | 功能 | 端點路徑 | 說明 |
54
- |------|----------|------|
55
- | 交易查詢 | `/trade_query` | 查詢交易狀態 |
56
- | 請退款 | `/trade_close` | 信用卡請款/退款 |
57
- | 取消授權 | `/trade_cancel` | 取消信用卡授權 |
58
- | CVS 取消 | `/cancel_cvs` | 取消超商代碼 |
59
-
60
- ### 信用卡約定 (Token) 端點
61
-
62
- | 功能 | 端點路徑 | 說明 |
63
- |------|----------|------|
64
- | Token 查詢 | `/credit_bind_query` | 查詢約定信用卡 |
65
- | Token 取消 | `/credit_bind_cancel` | 取消約定信用卡 |
66
-
67
- ### 特殊退款端點
68
-
69
- | 功能 | 端點路徑 | 說明 |
70
- |------|----------|------|
71
- | 愛金卡退款 | `/trade_refund_icash` | iCash 退款 |
72
- | AFTEE 退款 | `/trade_refund_aftee` | AFTEE 退款 |
73
- | AFTEE 確認 | `/trade_confirm_aftee` | AFTEE 確認交易 |
74
- | LINE Pay 退款 | `/trade_refund_linepay` | LINE Pay 退款 |
75
-
76
- ---
77
-
78
- ## 測試環境
79
-
80
- ### 測試帳號
81
-
82
- 測試帳號請至 PayUni 後台申請:
83
-
84
- ```
85
- 後台網址: https://www.payuni.com.tw/
86
- 路徑: 會員 > 商店清單 > 指定商店名稱 > 串接設定
87
- ```
88
-
89
- 取得以下資訊:
90
- - **商店代號 (MerID)**
91
- - **Hash Key**
92
- - **Hash IV**
93
-
94
- ### 測試信用卡
95
-
96
- | 卡號 | 說明 |
97
- |------|------|
98
- | `4000-2211-1111-1111` | 測試用信用卡 |
99
-
100
- - **有效期限**: 任意未過期日期
101
- - **CVV/CVC**: 任意 3 碼
102
-
103
- ### 測試環境端點
104
-
105
- ```
106
- https://sandbox-api.payuni.com.tw/api/{endpoint}
107
- ```
108
-
109
- ---
110
-
111
- ## 加密機制
112
-
113
- PayUni 採用 **AES-256-GCM** 加密與 **SHA256 HMAC** 驗證。
114
-
115
- ### 加密流程
116
-
117
- 1. **準備參數** - 組合所有請求參數
118
- 2. **URL Encode** - 將參數轉為 Query String
119
- 3. **AES-256-GCM 加密** - 使用 Hash Key 和 Hash IV 加密
120
- 4. **產生 HashInfo** - 使用 SHA256 計算驗證碼
121
- 5. **發送請求** - 將加密資料 POST 至 API
122
-
123
- ### PHP 加密範例
124
-
125
- ```php
126
- <?php
127
-
128
- class PayuniEncryption
129
- {
130
- private string $merKey;
131
- private string $merIV;
132
-
133
- public function __construct(string $merKey, string $merIV)
134
- {
135
- $this->merKey = $merKey;
136
- $this->merIV = $merIV;
137
- }
138
-
139
- /**
140
- * AES-256-GCM 加密
141
- */
142
- public function encrypt(array $params): string
143
- {
144
- // 1. 組合 Query String
145
- $queryString = http_build_query($params);
146
-
147
- // 2. AES-256-GCM 加密
148
- $tag = '';
149
- $encrypted = openssl_encrypt(
150
- $queryString,
151
- 'aes-256-gcm',
152
- $this->merKey,
153
- OPENSSL_RAW_DATA,
154
- $this->merIV,
155
- $tag,
156
- '',
157
- 16
158
- );
159
-
160
- // 3. 組合加密結果 (加密資料 + tag)
161
- $encryptInfo = bin2hex($encrypted . $tag);
162
-
163
- return $encryptInfo;
164
- }
165
-
166
- /**
167
- * AES-256-GCM 解密
168
- */
169
- public function decrypt(string $encryptInfo): array
170
- {
171
- // 1. Hex 轉 Binary
172
- $data = hex2bin($encryptInfo);
173
-
174
- // 2. 分離加密資料和 tag
175
- $encrypted = substr($data, 0, -16);
176
- $tag = substr($data, -16);
177
-
178
- // 3. AES-256-GCM 解密
179
- $decrypted = openssl_decrypt(
180
- $encrypted,
181
- 'aes-256-gcm',
182
- $this->merKey,
183
- OPENSSL_RAW_DATA,
184
- $this->merIV,
185
- $tag
186
- );
187
-
188
- // 4. 解析 Query String
189
- parse_str($decrypted, $result);
190
-
191
- return $result;
192
- }
193
-
194
- /**
195
- * 產生 HashInfo (SHA256)
196
- */
197
- public function hashInfo(string $encryptInfo): string
198
- {
199
- $raw = $encryptInfo . $this->merKey . $this->merIV;
200
- return strtoupper(hash('sha256', $raw));
201
- }
202
- }
203
- ```
204
-
205
- ### Python 加密範例
206
-
207
- ```python
208
- """PayUni AES-256-GCM 加密"""
209
-
210
- from Crypto.Cipher import AES
211
- from urllib.parse import urlencode, parse_qs
212
- import hashlib
213
-
214
-
215
- class PayuniEncryption:
216
- def __init__(self, mer_key: str, mer_iv: str):
217
- self.mer_key = mer_key.encode('utf-8')
218
- self.mer_iv = mer_iv.encode('utf-8')
219
-
220
- def encrypt(self, params: dict) -> str:
221
- """AES-256-GCM 加密"""
222
- # 1. 組合 Query String
223
- query_string = urlencode(params)
224
-
225
- # 2. AES-256-GCM 加密
226
- cipher = AES.new(self.mer_key, AES.MODE_GCM, nonce=self.mer_iv)
227
- encrypted, tag = cipher.encrypt_and_digest(query_string.encode('utf-8'))
228
-
229
- # 3. 組合加密結果
230
- encrypt_info = (encrypted + tag).hex()
231
-
232
- return encrypt_info
233
-
234
- def decrypt(self, encrypt_info: str) -> dict:
235
- """AES-256-GCM 解密"""
236
- # 1. Hex 轉 Binary
237
- data = bytes.fromhex(encrypt_info)
238
-
239
- # 2. 分離加密資料和 tag
240
- encrypted = data[:-16]
241
- tag = data[-16:]
242
-
243
- # 3. AES-256-GCM 解密
244
- cipher = AES.new(self.mer_key, AES.MODE_GCM, nonce=self.mer_iv)
245
- decrypted = cipher.decrypt_and_verify(encrypted, tag)
246
-
247
- # 4. 解析 Query String
248
- result = dict(parse_qs(decrypted.decode('utf-8')))
249
- return {k: v[0] for k, v in result.items()}
250
-
251
- def hash_info(self, encrypt_info: str) -> str:
252
- """產生 HashInfo (SHA256)"""
253
- raw = encrypt_info + self.mer_key.decode() + self.mer_iv.decode()
254
- return hashlib.sha256(raw.encode('utf-8')).hexdigest().upper()
255
- ```
256
-
257
- ---
258
-
259
- ## 通用參數
260
-
261
- ### 請求參數
262
-
263
- 所有 API 請求都需要以下參數:
264
-
265
- | 參數 | 類型 | 必填 | 說明 |
266
- |------|------|------|------|
267
- | `MerID` | String | | 商店代號 |
268
- | `Version` | String | 否 | API 版本,預設 `1.0` (LINE Pay 預設 `1.1`) |
269
- | `EncryptInfo` | String | | AES-256-GCM 加密後的參數 |
270
- | `HashInfo` | String | | SHA256 驗證碼 |
271
-
272
- ### EncryptInfo 內容參數
273
-
274
- 以下參數需加密放入 `EncryptInfo`:
275
-
276
- | 參數 | 類型 | 長度 | 必填 | 說明 |
277
- |------|------|------|------|------|
278
- | `MerID` | String | 20 | | 商店代號 |
279
- | `MerTradeNo` | String | 50 | | 商店訂單編號,需唯一 |
280
- | `TradeAmt` | Integer | - | | 交易金額 (整數) |
281
- | `Timestamp` | Integer | - | | Unix 時間戳 |
282
- | `ProdDesc` | String | 100 | 否 | 商品描述 |
283
- | `UsrMail` | String | 100 | 否 | 消費者 Email |
284
- | `ReturnURL` | String | 500 | 否 | 前台返回網址 |
285
- | `NotifyURL` | String | 500 | 否 | 背景通知網址 |
286
- | `Lang` | String | 5 | 否 | 語系 `zh-tw` / `en` |
287
-
288
- ### API 回應格式
289
-
290
- ```json
291
- {
292
- "Status": "SUCCESS",
293
- "Message": "成功",
294
- "EncryptInfo": "加密後的回應資料",
295
- "HashInfo": "SHA256 驗證碼"
296
- }
297
- ```
298
-
299
- ### Status 狀態碼
300
-
301
- | 狀態 | 說明 |
302
- |------|------|
303
- | `SUCCESS` | 成功 |
304
- | `ERROR` | 失敗 |
305
-
306
- ---
307
-
308
- ## 整合式支付頁 (UPP)
309
-
310
- 導向 PayUni 整合式支付頁面,消費者可選擇付款方式。
311
-
312
- ### 端點
313
-
314
- ```
315
- POST /api/upp
316
- ```
317
-
318
- ### EncryptInfo 參數
319
-
320
- | 參數 | 類型 | 必填 | 說明 |
321
- |------|------|------|------|
322
- | `MerID` | String | | 商店代號 |
323
- | `MerTradeNo` | String | | 訂單編號 |
324
- | `TradeAmt` | Integer | | 交易金額 |
325
- | `Timestamp` | Integer | | Unix 時間戳 |
326
- | `ExpireDate` | Integer | 否 | 繳費期限 (天),`1`~`180`,預設 `7` |
327
- | `ProdDesc` | String | 否 | 商品描述 |
328
- | `UsrMail` | String | 否 | 消費者 Email |
329
- | `ReturnURL` | String | 否 | 前台返回網址 |
330
- | `NotifyURL` | String | 否 | 背景通知網址 |
331
- | `Lang` | String | 否 | 語系 `zh-tw` / `en` |
332
-
333
- ### 支援的付款方式
334
-
335
- 整合式支付頁可顯示以下付款方式 (依商店設定):
336
-
337
- - 信用卡 (一次付清、分期、紅利折抵)
338
- - ATM 虛擬帳號
339
- - 超商代碼
340
- - Apple Pay
341
- - Google Pay
342
- - Samsung Pay
343
- - LINE Pay
344
- - AFTEE 先享後付
345
- - 愛金卡 iCash
346
-
347
- ### PHP 範例
348
-
349
- ```php
350
- <?php
351
-
352
- $encryption = new PayuniEncryption($merKey, $merIV);
353
-
354
- $params = [
355
- 'MerID' => 'YOUR_MER_ID',
356
- 'MerTradeNo' => 'ORDER' . time(),
357
- 'TradeAmt' => 1000,
358
- 'Timestamp' => time(),
359
- 'ProdDesc' => '測試商品',
360
- 'ReturnURL' => 'https://your-site.com/return',
361
- 'NotifyURL' => 'https://your-site.com/notify',
362
- ];
363
-
364
- $encryptInfo = $encryption->encrypt($params);
365
- $hashInfo = $encryption->hashInfo($encryptInfo);
366
-
367
- // 產生表單
368
- $html = <<<HTML
369
- <form method="post" action="https://api.payuni.com.tw/api/upp">
370
- <input type="hidden" name="MerID" value="{$params['MerID']}">
371
- <input type="hidden" name="Version" value="1.0">
372
- <input type="hidden" name="EncryptInfo" value="{$encryptInfo}">
373
- <input type="hidden" name="HashInfo" value="{$hashInfo}">
374
- <button type="submit">前往付款</button>
375
- </form>
376
- HTML;
377
-
378
- echo $html;
379
- ```
380
-
381
- ---
382
-
383
- ## 信用卡幕後
384
-
385
- 直接在商店頁面完成信用卡扣款,不需導向 PayUni。
386
-
387
- ### 端點
388
-
389
- ```
390
- POST /api/credit
391
- ```
392
-
393
- ### EncryptInfo 參數
394
-
395
- | 參數 | 類型 | 必填 | 說明 |
396
- |------|------|------|------|
397
- | `MerID` | String | | 商店代號 |
398
- | `MerTradeNo` | String | | 訂單編號 |
399
- | `TradeAmt` | Integer | | 交易金額 |
400
- | `Timestamp` | Integer | | Unix 時間戳 |
401
- | `CardNo` | String | | 信用卡號 (16 碼) |
402
- | `CardExpiry` | String | | 有效期限 `MMYY` |
403
- | `CardCVC` | String | | 安全碼 (3 碼) |
404
- | `ProdDesc` | String | 否 | 商品描述 |
405
- | `UsrMail` | String | 否 | 消費者 Email |
406
- | `NotifyURL` | String | 否 | 背景通知網址 |
407
-
408
- ### 分期付款參數
409
-
410
- | 參數 | 類型 | 說明 |
411
- |------|------|------|
412
- | `Inst` | Integer | 分期期數 `3`, `6`, `12`, `18`, `24`, `30` |
413
-
414
- ### 紅利折抵參數
415
-
416
- | 參數 | 類型 | 說明 |
417
- |------|------|------|
418
- | `Red` | String | 啟用紅利折抵 `Y` |
419
-
420
- ### 銀聯卡參數
421
-
422
- | 參數 | 類型 | 說明 |
423
- |------|------|------|
424
- | `UnionPay` | Integer | `1`:使用銀聯 |
425
-
426
- ### 信用卡約定 (Token) 參數
427
-
428
- | 參數 | 類型 | 說明 |
429
- |------|------|------|
430
- | `UseTokenType` | Integer | `1`:建立 Token `2`:使用既有 Token |
431
- | `BindVal` | String | Token 代碼 (UseTokenType=2 時必填) |
432
-
433
- ---
434
-
435
- ## ATM 虛擬帳號
436
-
437
- 取得 ATM 繳費虛擬帳號。
438
-
439
- ### 端點
440
-
441
- ```
442
- POST /api/atm
443
- ```
444
-
445
- ### EncryptInfo 參數
446
-
447
- | 參數 | 類型 | 必填 | 說明 |
448
- |------|------|------|------|
449
- | `MerID` | String | | 商店代號 |
450
- | `MerTradeNo` | String | | 訂單編號 |
451
- | `TradeAmt` | Integer | | 交易金額 |
452
- | `Timestamp` | Integer | | Unix 時間戳 |
453
- | `ExpireDate` | Integer | 否 | 繳費期限 (天),`1`~`60`,預設 `3` |
454
- | `BankType` | String | 否 | 指定銀行 (參見下表) |
455
- | `NotifyURL` | String | 否 | 背景通知網址 |
456
-
457
- ### BankType 銀行代碼
458
-
459
- | 代碼 | 銀行 |
460
- |------|------|
461
- | `FIRST` | 第一銀行 |
462
- | `ESUN` | 玉山銀行 |
463
- | `TAISHIN` | 台新銀行 |
464
- | `CATHAY` | 國泰世華 |
465
- | `CHINATRUST` | 中國信託 |
466
-
467
- ### 回應參數 (解密後)
468
-
469
- | 參數 | 說明 |
470
- |------|------|
471
- | `TradeNo` | PayUni 交易編號 |
472
- | `BankCode` | 銀行代碼 (3 碼) |
473
- | `vAccount` | 虛擬帳號 (14~16 碼) |
474
- | `ExpireDate` | 繳費期限 `yyyy/MM/dd` |
475
-
476
- ---
477
-
478
- ## 超商代碼
479
-
480
- 取得超商繳費代碼。
481
-
482
- ### 端點
483
-
484
- ```
485
- POST /api/cvs
486
- ```
487
-
488
- ### EncryptInfo 參數
489
-
490
- | 參數 | 類型 | 必填 | 說明 |
491
- |------|------|------|------|
492
- | `MerID` | String | | 商店代號 |
493
- | `MerTradeNo` | String | | 訂單編號 |
494
- | `TradeAmt` | Integer | | 交易金額 (`30`~`20000`) |
495
- | `Timestamp` | Integer | | Unix 時間戳 |
496
- | `ExpireDate` | Integer | 否 | 繳費期限 (天),預設 `7` |
497
- | `NotifyURL` | String | 否 | 背景通知網址 |
498
-
499
- ### 金額限制
500
-
501
- | 項目 | 限制 |
502
- |------|------|
503
- | 最低金額 | 30 元 |
504
- | 最高金額 | 20,000 元 |
505
-
506
- ### 回應參數 (解密後)
507
-
508
- | 參數 | 說明 |
509
- |------|------|
510
- | `TradeNo` | PayUni 交易編號 |
511
- | `PayNo` | 繳費代碼 |
512
- | `ExpireDate` | 繳費期限 |
513
-
514
- ---
515
-
516
- ## LINE Pay
517
-
518
- LINE Pay 支付整合。
519
-
520
- ### 端點
521
-
522
- ```
523
- POST /api/linepay
524
- ```
525
-
526
- ### 版本
527
-
528
- ```
529
- Version: 1.1
530
- ```
531
-
532
- ### EncryptInfo 參數
533
-
534
- | 參數 | 類型 | 必填 | 說明 |
535
- |------|------|------|------|
536
- | `MerID` | String | | 商店代號 |
537
- | `MerTradeNo` | String | | 訂單編號 |
538
- | `TradeAmt` | Integer | | 交易金額 |
539
- | `Timestamp` | Integer | | Unix 時間戳 |
540
- | `ProdDesc` | String | | 商品描述 |
541
- | `ReturnURL` | String | | 付款完成返回網址 |
542
- | `NotifyURL` | String | 否 | 背景通知網址 |
543
-
544
- ### LINE Pay 退款
545
-
546
- #### 端點
547
-
548
- ```
549
- POST /api/trade_refund_linepay
550
- ```
551
-
552
- #### EncryptInfo 參數
553
-
554
- | 參數 | 類型 | 必填 | 說明 |
555
- |------|------|------|------|
556
- | `MerID` | String | | 商店代號 |
557
- | `TradeNo` | String | | PayUni 交易編號 |
558
- | `TradeAmt` | Integer | | 退款金額 |
559
- | `Timestamp` | Integer | | Unix 時間戳 |
560
-
561
- ---
562
-
563
- ## AFTEE 先享後付
564
-
565
- AFTEE 後支付整合。
566
-
567
- ### 端點
568
-
569
- ```
570
- POST /api/aftee_direct
571
- ```
572
-
573
- ### EncryptInfo 參數
574
-
575
- | 參數 | 類型 | 必填 | 說明 |
576
- |------|------|------|------|
577
- | `MerID` | String | | 商店代號 |
578
- | `MerTradeNo` | String | | 訂單編號 |
579
- | `TradeAmt` | Integer | | 交易金額 |
580
- | `Timestamp` | Integer | | Unix 時間戳 |
581
- | `ProdDesc` | String | | 商品描述 |
582
- | `UsrMail` | String | | 消費者 Email |
583
- | `UsrName` | String | | 消費者姓名 |
584
- | `UsrPhone` | String | | 消費者手機 |
585
- | `ReturnURL` | String | | 付款完成返回網址 |
586
- | `NotifyURL` | String | 否 | 背景通知網址 |
587
-
588
- ### AFTEE 確認交易
589
-
590
- 當 AFTEE 交易需要確認時:
591
-
592
- ```
593
- POST /api/trade_confirm_aftee
594
- ```
595
-
596
- ### AFTEE 退款
597
-
598
- ```
599
- POST /api/trade_refund_aftee
600
- ```
601
-
602
- ---
603
-
604
- ## Apple Pay / Google Pay / Samsung Pay
605
-
606
- 行動支付整合 (需透過整合式支付頁或前端 SDK)。
607
-
608
- ### 支援說明
609
-
610
- | 支付方式 | 說明 |
611
- |----------|------|
612
- | Apple Pay | Safari 瀏覽器、iOS/macOS 裝置 |
613
- | Google Pay | Chrome 瀏覽器、Android 裝置 |
614
- | Samsung Pay | Samsung 裝置 |
615
-
616
- ### 使用方式
617
-
618
- 透過整合式支付頁 (UPP) 使用,商店需向 PayUni 申請開通。
619
-
620
- ---
621
-
622
- ## 愛金卡 iCash
623
-
624
- 愛金卡 (iCash) 支付。
625
-
626
- ### 退款端點
627
-
628
- ```
629
- POST /api/trade_refund_icash
630
- ```
631
-
632
- ### EncryptInfo 參數
633
-
634
- | 參數 | 類型 | 必填 | 說明 |
635
- |------|------|------|------|
636
- | `MerID` | String | | 商店代號 |
637
- | `TradeNo` | String | | PayUni 交易編號 |
638
- | `TradeAmt` | Integer | | 退款金額 |
639
- | `Timestamp` | Integer | | Unix 時間戳 |
640
-
641
- ---
642
-
643
- ## 交易查詢
644
-
645
- 查詢交易狀態。
646
-
647
- ### 端點
648
-
649
- ```
650
- POST /api/trade_query
651
- ```
652
-
653
- ### EncryptInfo 參數
654
-
655
- | 參數 | 類型 | 必填 | 說明 |
656
- |------|------|------|------|
657
- | `MerID` | String | | 商店代號 |
658
- | `MerTradeNo` | String | | 商店訂單編號 |
659
- | `Timestamp` | Integer | | Unix 時間戳 |
660
-
661
- ### 回應參數 (解密後)
662
-
663
- | 參數 | 說明 |
664
- |------|------|
665
- | `TradeNo` | PayUni 交易編號 |
666
- | `MerTradeNo` | 商店訂單編號 |
667
- | `TradeAmt` | 交易金額 |
668
- | `TradeStatus` | 交易狀態 |
669
- | `PaymentType` | 付款方式 |
670
- | `CreateTime` | 訂單建立時間 |
671
- | `PayTime` | 付款時間 |
672
-
673
- ### TradeStatus 交易狀態
674
-
675
- | 狀態 | 說明 |
676
- |------|------|
677
- | `0` | 未付款 / 處理中 |
678
- | `1` | 已付款 |
679
- | `2` | 付款失敗 |
680
- | `3` | 已退款 |
681
-
682
- ### PHP 範例
683
-
684
- ```php
685
- <?php
686
-
687
- $encryption = new PayuniEncryption($merKey, $merIV);
688
-
689
- $params = [
690
- 'MerID' => 'YOUR_MER_ID',
691
- 'MerTradeNo' => 'ORDER1234567890',
692
- 'Timestamp' => time(),
693
- ];
694
-
695
- $encryptInfo = $encryption->encrypt($params);
696
- $hashInfo = $encryption->hashInfo($encryptInfo);
697
-
698
- $response = file_get_contents('https://api.payuni.com.tw/api/trade_query', false, stream_context_create([
699
- 'http' => [
700
- 'method' => 'POST',
701
- 'header' => 'Content-Type: application/x-www-form-urlencoded',
702
- 'content' => http_build_query([
703
- 'MerID' => $params['MerID'],
704
- 'Version' => '1.0',
705
- 'EncryptInfo' => $encryptInfo,
706
- 'HashInfo' => $hashInfo,
707
- ]),
708
- ],
709
- ]));
710
-
711
- $result = json_decode($response, true);
712
-
713
- if ($result['Status'] === 'SUCCESS') {
714
- $data = $encryption->decrypt($result['EncryptInfo']);
715
- print_r($data);
716
- }
717
- ```
718
-
719
- ---
720
-
721
- ## 交易請退款
722
-
723
- 信用卡請款或退款。
724
-
725
- ### 端點
726
-
727
- ```
728
- POST /api/trade_close
729
- ```
730
-
731
- ### EncryptInfo 參數
732
-
733
- | 參數 | 類型 | 必填 | 說明 |
734
- |------|------|------|------|
735
- | `MerID` | String | | 商店代號 |
736
- | `TradeNo` | String | | PayUni 交易編號 |
737
- | `CloseType` | Integer | | 操作類型 |
738
- | `CloseAmt` | Integer | 否 | 請退款金額 (部分退款時使用) |
739
- | `Timestamp` | Integer | | Unix 時間戳 |
740
-
741
- ### CloseType 操作類型
742
-
743
- | 代碼 | 說明 |
744
- |------|------|
745
- | `1` | 請款 (Capture) |
746
- | `2` | 退款 (Refund) |
747
-
748
- ### 退款限制
749
-
750
- - 支援部分退款
751
- - 退款金額需小於等於原交易金額
752
- - 信用卡退款期限:請款後 1 年內
753
-
754
- ---
755
-
756
- ## 交易取消授權
757
-
758
- 取消信用卡授權 (尚未請款)。
759
-
760
- ### 端點
761
-
762
- ```
763
- POST /api/trade_cancel
764
- ```
765
-
766
- ### EncryptInfo 參數
767
-
768
- | 參數 | 類型 | 必填 | 說明 |
769
- |------|------|------|------|
770
- | `MerID` | String | | 商店代號 |
771
- | `TradeNo` | String | | PayUni 交易編號 |
772
- | `Timestamp` | Integer | | Unix 時間戳 |
773
-
774
- ---
775
-
776
- ## 信用卡約定 (Token)
777
-
778
- ### 查詢約定信用卡
779
-
780
- #### 端點
781
-
782
- ```
783
- POST /api/credit_bind_query
784
- ```
785
-
786
- #### EncryptInfo 參數
787
-
788
- | 參數 | 類型 | 必填 | 說明 |
789
- |------|------|------|------|
790
- | `MerID` | String | | 商店代號 |
791
- | `BindVal` | String | | Token 代碼 |
792
- | `Timestamp` | Integer | | Unix 時間戳 |
793
-
794
- ### 取消約定信用卡
795
-
796
- #### 端點
797
-
798
- ```
799
- POST /api/credit_bind_cancel
800
- ```
801
-
802
- #### EncryptInfo 參數
803
-
804
- | 參數 | 類型 | 必填 | 說明 |
805
- |------|------|------|------|
806
- | `MerID` | String | | 商店代號 |
807
- | `BindVal` | String | | Token 代碼 |
808
- | `Timestamp` | Integer | | Unix 時間戳 |
809
-
810
- ---
811
-
812
- ## 付款結果通知
813
-
814
- ### 通知流程
815
-
816
- ```
817
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
818
- │ 消費者 │────▶│ PayUni │────▶│ 商店 │
819
- │ 付款 │ │ 處理 │ │ NotifyURL │
820
- └─────────────┘ └─────────────┘ └─────────────┘
821
-
822
- │ POST (加密資料)
823
-
824
- ┌─────────────┐
825
- │ 商店 │
826
- │ 解密處理 │
827
- └─────────────┘
828
-
829
- │ 回應 "SUCCESS"
830
-
831
- ┌─────────────┐
832
- │ PayUni │
833
- │ 確認收到 │
834
- └─────────────┘
835
- ```
836
-
837
- ### 通知參數
838
-
839
- PayUni 會 POST 加密資料到 `NotifyURL`:
840
-
841
- | 參數 | 說明 |
842
- |------|------|
843
- | `MerID` | 商店代號 |
844
- | `EncryptInfo` | 加密的交易結果 |
845
- | `HashInfo` | SHA256 驗證碼 |
846
-
847
- ### 解密後的通知內容
848
-
849
- | 參數 | 說明 |
850
- |------|------|
851
- | `MerID` | 商店代號 |
852
- | `MerTradeNo` | 商店訂單編號 |
853
- | `TradeNo` | PayUni 交易編號 |
854
- | `TradeAmt` | 交易金額 |
855
- | `TradeStatus` | 交易狀態 (`0`:處理中 `1`:成功) |
856
- | `PaymentType` | 付款方式 |
857
- | `CreateTime` | 訂單建立時間 |
858
- | `PayTime` | 付款時間 |
859
- | `Message` | 交易訊息 |
860
- | `Card4No` | 信用卡末四碼 (信用卡交易) |
861
- | `AuthCode` | 銀行授權碼 (信用卡交易) |
862
-
863
- ### 處理範例
864
-
865
- ```php
866
- <?php
867
-
868
- // 接收通知
869
- $encryptInfo = $_POST['EncryptInfo'] ?? '';
870
- $hashInfo = $_POST['HashInfo'] ?? '';
871
- $merID = $_POST['MerID'] ?? '';
872
-
873
- // 驗證 HashInfo
874
- $encryption = new PayuniEncryption($merKey, $merIV);
875
- $calculatedHash = $encryption->hashInfo($encryptInfo);
876
-
877
- if ($hashInfo !== $calculatedHash) {
878
- echo 'HashInfo Error';
879
- exit;
880
- }
881
-
882
- // 解密
883
- $data = $encryption->decrypt($encryptInfo);
884
-
885
- // 檢查交易狀態
886
- if ($data['TradeStatus'] === '1') {
887
- // 交易成功,更新訂單狀態
888
- updateOrderStatus($data['MerTradeNo'], 'paid', $data);
889
- }
890
-
891
- // 回應 SUCCESS
892
- echo 'SUCCESS';
893
- ```
894
-
895
- ---
896
-
897
- ## 錯誤碼對照表
898
-
899
- ### 交易狀態 (TradeStatus)
900
-
901
- | 狀態 | 說明 |
902
- |------|------|
903
- | `0` | 未付款 / 處理中 |
904
- | `1` | 已付款 / 成功 |
905
- | `2` | 付款失敗 |
906
- | `3` | 已退款 |
907
-
908
- ### 常見錯誤訊息
909
-
910
- | 錯誤碼 | 說明 | 處理方式 |
911
- |--------|------|----------|
912
- | `參數錯誤` | 必填參數缺失或格式錯誤 | 檢查參數格式 |
913
- | `商店代號錯誤` | MerID 不存在 | 確認商店代號 |
914
- | `訂單編號重複` | MerTradeNo 已使用 | 使用新的訂單編號 |
915
- | `HashInfo 驗證失敗` | 加密資料不正確 | 重新計算 HashInfo |
916
- | `交易金額錯誤` | 金額超出範圍 | 確認金額限制 |
917
- | `卡片授權失敗` | 信用卡交易被拒 | 請客戶聯繫發卡銀行 |
918
- | `餘額不足` | 信用卡額度不足 | 請客戶確認額度 |
919
- | `卡片過期` | 信用卡已過期 | 請客戶使用有效卡片 |
920
-
921
- ---
922
-
923
- ## 常見問題排解
924
-
925
- ### HashInfo 驗證失敗
926
-
927
- **問題**: 收到 `HashInfo 驗證失敗`
928
-
929
- **檢查項目**:
930
- 1. Hash Key 和 Hash IV 是否正確
931
- 2. AES-256-GCM 加密是否正確實作
932
- 3. SHA256 計算是否正確
933
- 4. 測試/正式環境金鑰是否混用
934
-
935
- ### 訂單編號重複
936
-
937
- **問題**: 收到 `訂單編號重複`
938
-
939
- **解決**:
940
- ```python
941
- import time
942
- import random
943
- order_id = f"ORD{int(time.time())}{random.randint(100, 999)}"
944
- ```
945
-
946
- ### 付款通知未收到
947
-
948
- **問題**: 付款成功但沒收到 NotifyURL 通知
949
-
950
- **檢查項目**:
951
- 1. NotifyURL 是否為 HTTPS
952
- 2. 伺服器是否能被外網存取
953
- 3. 是否正確回應 `SUCCESS`
954
- 4. 防火牆是否阻擋 PayUni IP
955
-
956
- ### 加密/解密問題
957
-
958
- **問題**: 加密資料無法解密
959
-
960
- **檢查項目**:
961
- 1. AES-256-GCM 參數是否正確 (Key 32 bytes, IV 16 bytes)
962
- 2. Tag 長度是否為 16 bytes
963
- 3. Hex 編碼/解碼是否正確
964
-
965
- ---
966
-
967
- ## SDK 資源
968
-
969
- ### 官方 SDK
970
-
971
- | 語言 | GitHub |
972
- |------|--------|
973
- | PHP | https://github.com/payuni/PHP_SDK |
974
- | .NET | https://github.com/payuni/NET_SDK |
975
-
976
- ### 安裝方式
977
-
978
- #### PHP (Composer)
979
-
980
- ```bash
981
- composer require payuni/sdk
982
- ```
983
-
984
- #### .NET (NuGet)
985
-
986
- 需要安裝以下套件:
987
- - Newtonsoft.Json (13.0.1+)
988
- - Portable.BouncyCastle (1.9.0+)
989
-
990
- ---
991
-
992
- ## 官方資源
993
-
994
- - **官方網站**: https://www.payuni.com.tw/
995
- - **API 文件**: https://www.payuni.com.tw/docs/web/
996
- - **GitHub**: https://github.com/payuni
997
- - **WooCommerce 外掛**: https://github.com/payuni/PAYUNi_for_WooCommerce
1
+ # PayUni Payment API Reference
2
+
3
+ 統一金流 (PAYUNi) 金流 API 完整參考文件。
4
+
5
+ ---
6
+
7
+ ## 目錄
8
+
9
+ 1. [API 端點總覽](#api-端點總覽)
10
+ 2. [測試環境](#測試環境)
11
+ 3. [加密機制](#加密機制)
12
+ 4. [通用參數](#通用參數)
13
+ 5. [整合式支付頁 (UPP)](#整合式支付頁-upp)
14
+ 6. [信用卡幕後](#信用卡幕後)
15
+ 7. [ATM 虛擬帳號](#atm-虛擬帳號)
16
+ 8. [超商代碼](#超商代碼)
17
+ 9. [LINE Pay](#line-pay)
18
+ 10. [AFTEE 先享後付](#aftee-先享後付)
19
+ 11. [Apple Pay / Google Pay / Samsung Pay](#apple-pay--google-pay--samsung-pay)
20
+ 12. [愛金卡 iCash](#愛金卡-icash)
21
+ 13. [交易查詢](#交易查詢)
22
+ 14. [交易請退款](#交易請退款)
23
+ 15. [交易取消授權](#交易取消授權)
24
+ 16. [信用卡約定 (Token)](#信用卡約定-token)
25
+ 17. [付款結果通知](#付款結果通知)
26
+ 18. [錯誤碼對照表](#錯誤碼對照表)
27
+ 19. [常見問題排解](#常見問題排解)
28
+
29
+ ---
30
+
31
+ ## API 端點總覽
32
+
33
+ ### 基礎 API 路徑
34
+
35
+ | 環境 | 基礎路徑 |
36
+ |------|----------|
37
+ | **測試環境** | `https://sandbox-api.payuni.com.tw/api/` |
38
+ | **正式環境** | `https://api.payuni.com.tw/api/` |
39
+
40
+ ### 支付相關端點
41
+
42
+ | 功能 | 端點路徑 | 說明 |
43
+ |------|----------|------|
44
+ | 整合式支付頁 | `/upp` | 導向 PayUni 支付頁面 |
45
+ | ATM 虛擬帳號 | `/atm` | 幕後取得虛擬帳號 |
46
+ | 超商代碼 | `/cvs` | 幕後取得繳費代碼 |
47
+ | 信用卡幕後 | `/credit` | 信用卡直接扣款 |
48
+ | LINE Pay | `/linepay` | LINE Pay 支付 |
49
+ | AFTEE 先享後付 | `/aftee_direct` | AFTEE 支付 |
50
+
51
+ ### 交易管理端點
52
+
53
+ | 功能 | 端點路徑 | 說明 |
54
+ |------|----------|------|
55
+ | 交易查詢 | `/trade_query` | 查詢交易狀態 |
56
+ | 請退款 | `/trade_close` | 信用卡請款/退款 |
57
+ | 取消授權 | `/trade_cancel` | 取消信用卡授權 |
58
+ | CVS 取消 | `/cancel_cvs` | 取消超商代碼 |
59
+
60
+ ### 信用卡約定 (Token) 端點
61
+
62
+ | 功能 | 端點路徑 | 說明 |
63
+ |------|----------|------|
64
+ | Token 查詢 | `/credit_bind_query` | 查詢約定信用卡 |
65
+ | Token 取消 | `/credit_bind_cancel` | 取消約定信用卡 |
66
+
67
+ ### 特殊退款端點
68
+
69
+ | 功能 | 端點路徑 | 說明 |
70
+ |------|----------|------|
71
+ | 愛金卡退款 | `/trade_refund_icash` | iCash 退款 |
72
+ | AFTEE 退款 | `/trade_refund_aftee` | AFTEE 退款 |
73
+ | AFTEE 確認 | `/trade_confirm_aftee` | AFTEE 確認交易 |
74
+ | LINE Pay 退款 | `/trade_refund_linepay` | LINE Pay 退款 |
75
+
76
+ ---
77
+
78
+ ## 測試環境
79
+
80
+ ### 測試帳號
81
+
82
+ 測試帳號請至 PayUni 後台申請:
83
+
84
+ ```
85
+ 後台網址: https://www.payuni.com.tw/
86
+ 路徑: 會員 > 商店清單 > 指定商店名稱 > 串接設定
87
+ ```
88
+
89
+ 取得以下資訊:
90
+ - **商店代號 (MerID)**
91
+ - **Hash Key**
92
+ - **Hash IV**
93
+
94
+ ### 測試信用卡
95
+
96
+ | 卡號 | 說明 |
97
+ |------|------|
98
+ | `4000-2211-1111-1111` | 測試用信用卡 |
99
+
100
+ - **有效期限**: 任意未過期日期
101
+ - **CVV/CVC**: 任意 3 碼
102
+
103
+ ### 測試環境端點
104
+
105
+ ```
106
+ https://sandbox-api.payuni.com.tw/api/{endpoint}
107
+ ```
108
+
109
+ ---
110
+
111
+ ## 加密機制
112
+
113
+ PayUni 採用 **AES-256-GCM** 加密與 **SHA256 HMAC** 驗證。
114
+
115
+ ### 加密流程
116
+
117
+ 1. **準備參數** - 組合所有請求參數
118
+ 2. **URL Encode** - 將參數轉為 Query String
119
+ 3. **AES-256-GCM 加密** - 使用 Hash Key 和 Hash IV 加密
120
+ 4. **產生 HashInfo** - 使用 SHA256 計算驗證碼
121
+ 5. **發送請求** - 將加密資料 POST 至 API
122
+
123
+ ### PHP 加密範例
124
+
125
+ ```php
126
+ <?php
127
+
128
+ class PayuniEncryption
129
+ {
130
+ private string $merKey;
131
+ private string $merIV;
132
+
133
+ public function __construct(string $merKey, string $merIV)
134
+ {
135
+ $this->merKey = $merKey;
136
+ $this->merIV = $merIV;
137
+ }
138
+
139
+ /**
140
+ * AES-256-GCM 加密
141
+ */
142
+ public function encrypt(array $params): string
143
+ {
144
+ // 1. 組合 Query String
145
+ $queryString = http_build_query($params);
146
+
147
+ // 2. AES-256-GCM 加密
148
+ $tag = '';
149
+ $encrypted = openssl_encrypt(
150
+ $queryString,
151
+ 'aes-256-gcm',
152
+ $this->merKey,
153
+ OPENSSL_RAW_DATA,
154
+ $this->merIV,
155
+ $tag,
156
+ '',
157
+ 16
158
+ );
159
+
160
+ // 3. 組合加密結果 (加密資料 + tag)
161
+ $encryptInfo = bin2hex($encrypted . $tag);
162
+
163
+ return $encryptInfo;
164
+ }
165
+
166
+ /**
167
+ * AES-256-GCM 解密
168
+ */
169
+ public function decrypt(string $encryptInfo): array
170
+ {
171
+ // 1. Hex 轉 Binary
172
+ $data = hex2bin($encryptInfo);
173
+
174
+ // 2. 分離加密資料和 tag
175
+ $encrypted = substr($data, 0, -16);
176
+ $tag = substr($data, -16);
177
+
178
+ // 3. AES-256-GCM 解密
179
+ $decrypted = openssl_decrypt(
180
+ $encrypted,
181
+ 'aes-256-gcm',
182
+ $this->merKey,
183
+ OPENSSL_RAW_DATA,
184
+ $this->merIV,
185
+ $tag
186
+ );
187
+
188
+ // 4. 解析 Query String
189
+ parse_str($decrypted, $result);
190
+
191
+ return $result;
192
+ }
193
+
194
+ /**
195
+ * 產生 HashInfo (SHA256)
196
+ */
197
+ public function hashInfo(string $encryptInfo): string
198
+ {
199
+ $raw = $encryptInfo . $this->merKey . $this->merIV;
200
+ return strtoupper(hash('sha256', $raw));
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Python 加密範例
206
+
207
+ ```python
208
+ """PayUni AES-256-GCM 加密"""
209
+
210
+ from Crypto.Cipher import AES
211
+ from urllib.parse import urlencode, parse_qs
212
+ import hashlib
213
+
214
+
215
+ class PayuniEncryption:
216
+ def __init__(self, mer_key: str, mer_iv: str):
217
+ self.mer_key = mer_key.encode('utf-8')
218
+ self.mer_iv = mer_iv.encode('utf-8')
219
+
220
+ def encrypt(self, params: dict) -> str:
221
+ """AES-256-GCM 加密"""
222
+ # 1. 組合 Query String
223
+ query_string = urlencode(params)
224
+
225
+ # 2. AES-256-GCM 加密
226
+ cipher = AES.new(self.mer_key, AES.MODE_GCM, nonce=self.mer_iv)
227
+ encrypted, tag = cipher.encrypt_and_digest(query_string.encode('utf-8'))
228
+
229
+ # 3. 組合加密結果
230
+ encrypt_info = (encrypted + tag).hex()
231
+
232
+ return encrypt_info
233
+
234
+ def decrypt(self, encrypt_info: str) -> dict:
235
+ """AES-256-GCM 解密"""
236
+ # 1. Hex 轉 Binary
237
+ data = bytes.fromhex(encrypt_info)
238
+
239
+ # 2. 分離加密資料和 tag
240
+ encrypted = data[:-16]
241
+ tag = data[-16:]
242
+
243
+ # 3. AES-256-GCM 解密
244
+ cipher = AES.new(self.mer_key, AES.MODE_GCM, nonce=self.mer_iv)
245
+ decrypted = cipher.decrypt_and_verify(encrypted, tag)
246
+
247
+ # 4. 解析 Query String
248
+ result = dict(parse_qs(decrypted.decode('utf-8')))
249
+ return {k: v[0] for k, v in result.items()}
250
+
251
+ def hash_info(self, encrypt_info: str) -> str:
252
+ """產生 HashInfo (SHA256)"""
253
+ raw = encrypt_info + self.mer_key.decode() + self.mer_iv.decode()
254
+ return hashlib.sha256(raw.encode('utf-8')).hexdigest().upper()
255
+ ```
256
+
257
+ ---
258
+
259
+ ## 通用參數
260
+
261
+ ### 請求參數
262
+
263
+ 所有 API 請求都需要以下參數:
264
+
265
+ | 參數 | 類型 | 必填 | 說明 |
266
+ |------|------|------|------|
267
+ | `MerID` | String | | 商店代號 |
268
+ | `Version` | String | 否 | API 版本,預設 `1.0` (LINE Pay 預設 `1.1`) |
269
+ | `EncryptInfo` | String | | AES-256-GCM 加密後的參數 |
270
+ | `HashInfo` | String | | SHA256 驗證碼 |
271
+
272
+ ### EncryptInfo 內容參數
273
+
274
+ 以下參數需加密放入 `EncryptInfo`:
275
+
276
+ | 參數 | 類型 | 長度 | 必填 | 說明 |
277
+ |------|------|------|------|------|
278
+ | `MerID` | String | 20 | | 商店代號 |
279
+ | `MerTradeNo` | String | 50 | | 商店訂單編號,需唯一 |
280
+ | `TradeAmt` | Integer | - | | 交易金額 (整數) |
281
+ | `Timestamp` | Integer | - | | Unix 時間戳 |
282
+ | `ProdDesc` | String | 100 | 否 | 商品描述 |
283
+ | `UsrMail` | String | 100 | 否 | 消費者 Email |
284
+ | `ReturnURL` | String | 500 | 否 | 前台返回網址 |
285
+ | `NotifyURL` | String | 500 | 否 | 背景通知網址 |
286
+ | `Lang` | String | 5 | 否 | 語系 `zh-tw` / `en` |
287
+
288
+ ### API 回應格式
289
+
290
+ ```json
291
+ {
292
+ "Status": "SUCCESS",
293
+ "Message": "成功",
294
+ "EncryptInfo": "加密後的回應資料",
295
+ "HashInfo": "SHA256 驗證碼"
296
+ }
297
+ ```
298
+
299
+ ### Status 狀態碼
300
+
301
+ | 狀態 | 說明 |
302
+ |------|------|
303
+ | `SUCCESS` | 成功 |
304
+ | `ERROR` | 失敗 |
305
+
306
+ ---
307
+
308
+ ## 整合式支付頁 (UPP)
309
+
310
+ 導向 PayUni 整合式支付頁面,消費者可選擇付款方式。
311
+
312
+ ### 端點
313
+
314
+ ```
315
+ POST /api/upp
316
+ ```
317
+
318
+ ### EncryptInfo 參數
319
+
320
+ | 參數 | 類型 | 必填 | 說明 |
321
+ |------|------|------|------|
322
+ | `MerID` | String | | 商店代號 |
323
+ | `MerTradeNo` | String | | 訂單編號 |
324
+ | `TradeAmt` | Integer | | 交易金額 |
325
+ | `Timestamp` | Integer | | Unix 時間戳 |
326
+ | `ExpireDate` | Integer | 否 | 繳費期限 (天),`1`~`180`,預設 `7` |
327
+ | `ProdDesc` | String | 否 | 商品描述 |
328
+ | `UsrMail` | String | 否 | 消費者 Email |
329
+ | `ReturnURL` | String | 否 | 前台返回網址 |
330
+ | `NotifyURL` | String | 否 | 背景通知網址 |
331
+ | `Lang` | String | 否 | 語系 `zh-tw` / `en` |
332
+
333
+ ### 支援的付款方式
334
+
335
+ 整合式支付頁可顯示以下付款方式 (依商店設定):
336
+
337
+ - 信用卡 (一次付清、分期、紅利折抵)
338
+ - ATM 虛擬帳號
339
+ - 超商代碼
340
+ - Apple Pay
341
+ - Google Pay
342
+ - Samsung Pay
343
+ - LINE Pay
344
+ - AFTEE 先享後付
345
+ - 愛金卡 iCash
346
+
347
+ ### PHP 範例
348
+
349
+ ```php
350
+ <?php
351
+
352
+ $encryption = new PayuniEncryption($merKey, $merIV);
353
+
354
+ $params = [
355
+ 'MerID' => 'YOUR_MER_ID',
356
+ 'MerTradeNo' => 'ORDER' . time(),
357
+ 'TradeAmt' => 1000,
358
+ 'Timestamp' => time(),
359
+ 'ProdDesc' => '測試商品',
360
+ 'ReturnURL' => 'https://your-site.com/return',
361
+ 'NotifyURL' => 'https://your-site.com/notify',
362
+ ];
363
+
364
+ $encryptInfo = $encryption->encrypt($params);
365
+ $hashInfo = $encryption->hashInfo($encryptInfo);
366
+
367
+ // 產生表單
368
+ $html = <<<HTML
369
+ <form method="post" action="https://api.payuni.com.tw/api/upp">
370
+ <input type="hidden" name="MerID" value="{$params['MerID']}">
371
+ <input type="hidden" name="Version" value="1.0">
372
+ <input type="hidden" name="EncryptInfo" value="{$encryptInfo}">
373
+ <input type="hidden" name="HashInfo" value="{$hashInfo}">
374
+ <button type="submit">前往付款</button>
375
+ </form>
376
+ HTML;
377
+
378
+ echo $html;
379
+ ```
380
+
381
+ ---
382
+
383
+ ## 信用卡幕後
384
+
385
+ 直接在商店頁面完成信用卡扣款,不需導向 PayUni。
386
+
387
+ ### 端點
388
+
389
+ ```
390
+ POST /api/credit
391
+ ```
392
+
393
+ ### EncryptInfo 參數
394
+
395
+ | 參數 | 類型 | 必填 | 說明 |
396
+ |------|------|------|------|
397
+ | `MerID` | String | | 商店代號 |
398
+ | `MerTradeNo` | String | | 訂單編號 |
399
+ | `TradeAmt` | Integer | | 交易金額 |
400
+ | `Timestamp` | Integer | | Unix 時間戳 |
401
+ | `CardNo` | String | | 信用卡號 (16 碼) |
402
+ | `CardExpiry` | String | | 有效期限 `MMYY` |
403
+ | `CardCVC` | String | | 安全碼 (3 碼) |
404
+ | `ProdDesc` | String | 否 | 商品描述 |
405
+ | `UsrMail` | String | 否 | 消費者 Email |
406
+ | `NotifyURL` | String | 否 | 背景通知網址 |
407
+
408
+ ### 分期付款參數
409
+
410
+ | 參數 | 類型 | 說明 |
411
+ |------|------|------|
412
+ | `Inst` | Integer | 分期期數 `3`, `6`, `12`, `18`, `24`, `30` |
413
+
414
+ ### 紅利折抵參數
415
+
416
+ | 參數 | 類型 | 說明 |
417
+ |------|------|------|
418
+ | `Red` | String | 啟用紅利折抵 `Y` |
419
+
420
+ ### 銀聯卡參數
421
+
422
+ | 參數 | 類型 | 說明 |
423
+ |------|------|------|
424
+ | `UnionPay` | Integer | `1`:使用銀聯 |
425
+
426
+ ### 信用卡約定 (Token) 參數
427
+
428
+ | 參數 | 類型 | 說明 |
429
+ |------|------|------|
430
+ | `UseTokenType` | Integer | `1`:建立 Token `2`:使用既有 Token |
431
+ | `BindVal` | String | Token 代碼 (UseTokenType=2 時必填) |
432
+
433
+ ---
434
+
435
+ ## ATM 虛擬帳號
436
+
437
+ 取得 ATM 繳費虛擬帳號。
438
+
439
+ ### 端點
440
+
441
+ ```
442
+ POST /api/atm
443
+ ```
444
+
445
+ ### EncryptInfo 參數
446
+
447
+ | 參數 | 類型 | 必填 | 說明 |
448
+ |------|------|------|------|
449
+ | `MerID` | String | | 商店代號 |
450
+ | `MerTradeNo` | String | | 訂單編號 |
451
+ | `TradeAmt` | Integer | | 交易金額 |
452
+ | `Timestamp` | Integer | | Unix 時間戳 |
453
+ | `ExpireDate` | Integer | 否 | 繳費期限 (天),`1`~`60`,預設 `3` |
454
+ | `BankType` | String | 否 | 指定銀行 (參見下表) |
455
+ | `NotifyURL` | String | 否 | 背景通知網址 |
456
+
457
+ ### BankType 銀行代碼
458
+
459
+ | 代碼 | 銀行 |
460
+ |------|------|
461
+ | `FIRST` | 第一銀行 |
462
+ | `ESUN` | 玉山銀行 |
463
+ | `TAISHIN` | 台新銀行 |
464
+ | `CATHAY` | 國泰世華 |
465
+ | `CHINATRUST` | 中國信託 |
466
+
467
+ ### 回應參數 (解密後)
468
+
469
+ | 參數 | 說明 |
470
+ |------|------|
471
+ | `TradeNo` | PayUni 交易編號 |
472
+ | `BankCode` | 銀行代碼 (3 碼) |
473
+ | `vAccount` | 虛擬帳號 (14~16 碼) |
474
+ | `ExpireDate` | 繳費期限 `yyyy/MM/dd` |
475
+
476
+ ---
477
+
478
+ ## 超商代碼
479
+
480
+ 取得超商繳費代碼。
481
+
482
+ ### 端點
483
+
484
+ ```
485
+ POST /api/cvs
486
+ ```
487
+
488
+ ### EncryptInfo 參數
489
+
490
+ | 參數 | 類型 | 必填 | 說明 |
491
+ |------|------|------|------|
492
+ | `MerID` | String | | 商店代號 |
493
+ | `MerTradeNo` | String | | 訂單編號 |
494
+ | `TradeAmt` | Integer | | 交易金額 (`30`~`20000`) |
495
+ | `Timestamp` | Integer | | Unix 時間戳 |
496
+ | `ExpireDate` | Integer | 否 | 繳費期限 (天),預設 `7` |
497
+ | `NotifyURL` | String | 否 | 背景通知網址 |
498
+
499
+ ### 金額限制
500
+
501
+ | 項目 | 限制 |
502
+ |------|------|
503
+ | 最低金額 | 30 元 |
504
+ | 最高金額 | 20,000 元 |
505
+
506
+ ### 回應參數 (解密後)
507
+
508
+ | 參數 | 說明 |
509
+ |------|------|
510
+ | `TradeNo` | PayUni 交易編號 |
511
+ | `PayNo` | 繳費代碼 |
512
+ | `ExpireDate` | 繳費期限 |
513
+
514
+ ---
515
+
516
+ ## LINE Pay
517
+
518
+ LINE Pay 支付整合。
519
+
520
+ ### 端點
521
+
522
+ ```
523
+ POST /api/linepay
524
+ ```
525
+
526
+ ### 版本
527
+
528
+ ```
529
+ Version: 1.1
530
+ ```
531
+
532
+ ### EncryptInfo 參數
533
+
534
+ | 參數 | 類型 | 必填 | 說明 |
535
+ |------|------|------|------|
536
+ | `MerID` | String | | 商店代號 |
537
+ | `MerTradeNo` | String | | 訂單編號 |
538
+ | `TradeAmt` | Integer | | 交易金額 |
539
+ | `Timestamp` | Integer | | Unix 時間戳 |
540
+ | `ProdDesc` | String | | 商品描述 |
541
+ | `ReturnURL` | String | | 付款完成返回網址 |
542
+ | `NotifyURL` | String | 否 | 背景通知網址 |
543
+
544
+ ### LINE Pay 退款
545
+
546
+ #### 端點
547
+
548
+ ```
549
+ POST /api/trade_refund_linepay
550
+ ```
551
+
552
+ #### EncryptInfo 參數
553
+
554
+ | 參數 | 類型 | 必填 | 說明 |
555
+ |------|------|------|------|
556
+ | `MerID` | String | | 商店代號 |
557
+ | `TradeNo` | String | | PayUni 交易編號 |
558
+ | `TradeAmt` | Integer | | 退款金額 |
559
+ | `Timestamp` | Integer | | Unix 時間戳 |
560
+
561
+ ---
562
+
563
+ ## AFTEE 先享後付
564
+
565
+ AFTEE 後支付整合。
566
+
567
+ ### 端點
568
+
569
+ ```
570
+ POST /api/aftee_direct
571
+ ```
572
+
573
+ ### EncryptInfo 參數
574
+
575
+ | 參數 | 類型 | 必填 | 說明 |
576
+ |------|------|------|------|
577
+ | `MerID` | String | | 商店代號 |
578
+ | `MerTradeNo` | String | | 訂單編號 |
579
+ | `TradeAmt` | Integer | | 交易金額 |
580
+ | `Timestamp` | Integer | | Unix 時間戳 |
581
+ | `ProdDesc` | String | | 商品描述 |
582
+ | `UsrMail` | String | | 消費者 Email |
583
+ | `UsrName` | String | | 消費者姓名 |
584
+ | `UsrPhone` | String | | 消費者手機 |
585
+ | `ReturnURL` | String | | 付款完成返回網址 |
586
+ | `NotifyURL` | String | 否 | 背景通知網址 |
587
+
588
+ ### AFTEE 確認交易
589
+
590
+ 當 AFTEE 交易需要確認時:
591
+
592
+ ```
593
+ POST /api/trade_confirm_aftee
594
+ ```
595
+
596
+ ### AFTEE 退款
597
+
598
+ ```
599
+ POST /api/trade_refund_aftee
600
+ ```
601
+
602
+ ---
603
+
604
+ ## Apple Pay / Google Pay / Samsung Pay
605
+
606
+ 行動支付整合 (需透過整合式支付頁或前端 SDK)。
607
+
608
+ ### 支援說明
609
+
610
+ | 支付方式 | 說明 |
611
+ |----------|------|
612
+ | Apple Pay | Safari 瀏覽器、iOS/macOS 裝置 |
613
+ | Google Pay | Chrome 瀏覽器、Android 裝置 |
614
+ | Samsung Pay | Samsung 裝置 |
615
+
616
+ ### 使用方式
617
+
618
+ 透過整合式支付頁 (UPP) 使用,商店需向 PayUni 申請開通。
619
+
620
+ ---
621
+
622
+ ## 愛金卡 iCash
623
+
624
+ 愛金卡 (iCash) 支付。
625
+
626
+ ### 退款端點
627
+
628
+ ```
629
+ POST /api/trade_refund_icash
630
+ ```
631
+
632
+ ### EncryptInfo 參數
633
+
634
+ | 參數 | 類型 | 必填 | 說明 |
635
+ |------|------|------|------|
636
+ | `MerID` | String | | 商店代號 |
637
+ | `TradeNo` | String | | PayUni 交易編號 |
638
+ | `TradeAmt` | Integer | | 退款金額 |
639
+ | `Timestamp` | Integer | | Unix 時間戳 |
640
+
641
+ ---
642
+
643
+ ## 交易查詢
644
+
645
+ 查詢交易狀態。
646
+
647
+ ### 端點
648
+
649
+ ```
650
+ POST /api/trade_query
651
+ ```
652
+
653
+ ### EncryptInfo 參數
654
+
655
+ | 參數 | 類型 | 必填 | 說明 |
656
+ |------|------|------|------|
657
+ | `MerID` | String | | 商店代號 |
658
+ | `MerTradeNo` | String | | 商店訂單編號 |
659
+ | `Timestamp` | Integer | | Unix 時間戳 |
660
+
661
+ ### 回應參數 (解密後)
662
+
663
+ | 參數 | 說明 |
664
+ |------|------|
665
+ | `TradeNo` | PayUni 交易編號 |
666
+ | `MerTradeNo` | 商店訂單編號 |
667
+ | `TradeAmt` | 交易金額 |
668
+ | `TradeStatus` | 交易狀態 |
669
+ | `PaymentType` | 付款方式 |
670
+ | `CreateTime` | 訂單建立時間 |
671
+ | `PayTime` | 付款時間 |
672
+
673
+ ### TradeStatus 交易狀態
674
+
675
+ | 狀態 | 說明 |
676
+ |------|------|
677
+ | `0` | 未付款 / 處理中 |
678
+ | `1` | 已付款 |
679
+ | `2` | 付款失敗 |
680
+ | `3` | 已退款 |
681
+
682
+ ### PHP 範例
683
+
684
+ ```php
685
+ <?php
686
+
687
+ $encryption = new PayuniEncryption($merKey, $merIV);
688
+
689
+ $params = [
690
+ 'MerID' => 'YOUR_MER_ID',
691
+ 'MerTradeNo' => 'ORDER1234567890',
692
+ 'Timestamp' => time(),
693
+ ];
694
+
695
+ $encryptInfo = $encryption->encrypt($params);
696
+ $hashInfo = $encryption->hashInfo($encryptInfo);
697
+
698
+ $response = file_get_contents('https://api.payuni.com.tw/api/trade_query', false, stream_context_create([
699
+ 'http' => [
700
+ 'method' => 'POST',
701
+ 'header' => 'Content-Type: application/x-www-form-urlencoded',
702
+ 'content' => http_build_query([
703
+ 'MerID' => $params['MerID'],
704
+ 'Version' => '1.0',
705
+ 'EncryptInfo' => $encryptInfo,
706
+ 'HashInfo' => $hashInfo,
707
+ ]),
708
+ ],
709
+ ]));
710
+
711
+ $result = json_decode($response, true);
712
+
713
+ if ($result['Status'] === 'SUCCESS') {
714
+ $data = $encryption->decrypt($result['EncryptInfo']);
715
+ print_r($data);
716
+ }
717
+ ```
718
+
719
+ ---
720
+
721
+ ## 交易請退款
722
+
723
+ 信用卡請款或退款。
724
+
725
+ ### 端點
726
+
727
+ ```
728
+ POST /api/trade_close
729
+ ```
730
+
731
+ ### EncryptInfo 參數
732
+
733
+ | 參數 | 類型 | 必填 | 說明 |
734
+ |------|------|------|------|
735
+ | `MerID` | String | | 商店代號 |
736
+ | `TradeNo` | String | | PayUni 交易編號 |
737
+ | `CloseType` | Integer | | 操作類型 |
738
+ | `CloseAmt` | Integer | 否 | 請退款金額 (部分退款時使用) |
739
+ | `Timestamp` | Integer | | Unix 時間戳 |
740
+
741
+ ### CloseType 操作類型
742
+
743
+ | 代碼 | 說明 |
744
+ |------|------|
745
+ | `1` | 請款 (Capture) |
746
+ | `2` | 退款 (Refund) |
747
+
748
+ ### 退款限制
749
+
750
+ - 支援部分退款
751
+ - 退款金額需小於等於原交易金額
752
+ - 信用卡退款期限:請款後 1 年內
753
+
754
+ ---
755
+
756
+ ## 交易取消授權
757
+
758
+ 取消信用卡授權 (尚未請款)。
759
+
760
+ ### 端點
761
+
762
+ ```
763
+ POST /api/trade_cancel
764
+ ```
765
+
766
+ ### EncryptInfo 參數
767
+
768
+ | 參數 | 類型 | 必填 | 說明 |
769
+ |------|------|------|------|
770
+ | `MerID` | String | | 商店代號 |
771
+ | `TradeNo` | String | | PayUni 交易編號 |
772
+ | `Timestamp` | Integer | | Unix 時間戳 |
773
+
774
+ ---
775
+
776
+ ## 信用卡約定 (Token)
777
+
778
+ ### 查詢約定信用卡
779
+
780
+ #### 端點
781
+
782
+ ```
783
+ POST /api/credit_bind_query
784
+ ```
785
+
786
+ #### EncryptInfo 參數
787
+
788
+ | 參數 | 類型 | 必填 | 說明 |
789
+ |------|------|------|------|
790
+ | `MerID` | String | | 商店代號 |
791
+ | `BindVal` | String | | Token 代碼 |
792
+ | `Timestamp` | Integer | | Unix 時間戳 |
793
+
794
+ ### 取消約定信用卡
795
+
796
+ #### 端點
797
+
798
+ ```
799
+ POST /api/credit_bind_cancel
800
+ ```
801
+
802
+ #### EncryptInfo 參數
803
+
804
+ | 參數 | 類型 | 必填 | 說明 |
805
+ |------|------|------|------|
806
+ | `MerID` | String | | 商店代號 |
807
+ | `BindVal` | String | | Token 代碼 |
808
+ | `Timestamp` | Integer | | Unix 時間戳 |
809
+
810
+ ---
811
+
812
+ ## 付款結果通知
813
+
814
+ ### 通知流程
815
+
816
+ ```
817
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
818
+ │ 消費者 │────▶│ PayUni │────▶│ 商店 │
819
+ │ 付款 │ │ 處理 │ │ NotifyURL │
820
+ └─────────────┘ └─────────────┘ └─────────────┘
821
+
822
+ │ POST (加密資料)
823
+
824
+ ┌─────────────┐
825
+ │ 商店 │
826
+ │ 解密處理 │
827
+ └─────────────┘
828
+
829
+ │ 回應 "SUCCESS"
830
+
831
+ ┌─────────────┐
832
+ │ PayUni │
833
+ │ 確認收到 │
834
+ └─────────────┘
835
+ ```
836
+
837
+ ### 通知參數
838
+
839
+ PayUni 會 POST 加密資料到 `NotifyURL`:
840
+
841
+ | 參數 | 說明 |
842
+ |------|------|
843
+ | `MerID` | 商店代號 |
844
+ | `EncryptInfo` | 加密的交易結果 |
845
+ | `HashInfo` | SHA256 驗證碼 |
846
+
847
+ ### 解密後的通知內容
848
+
849
+ | 參數 | 說明 |
850
+ |------|------|
851
+ | `MerID` | 商店代號 |
852
+ | `MerTradeNo` | 商店訂單編號 |
853
+ | `TradeNo` | PayUni 交易編號 |
854
+ | `TradeAmt` | 交易金額 |
855
+ | `TradeStatus` | 交易狀態 (`0`:處理中 `1`:成功) |
856
+ | `PaymentType` | 付款方式 |
857
+ | `CreateTime` | 訂單建立時間 |
858
+ | `PayTime` | 付款時間 |
859
+ | `Message` | 交易訊息 |
860
+ | `Card4No` | 信用卡末四碼 (信用卡交易) |
861
+ | `AuthCode` | 銀行授權碼 (信用卡交易) |
862
+
863
+ ### 處理範例
864
+
865
+ ```php
866
+ <?php
867
+
868
+ // 接收通知
869
+ $encryptInfo = $_POST['EncryptInfo'] ?? '';
870
+ $hashInfo = $_POST['HashInfo'] ?? '';
871
+ $merID = $_POST['MerID'] ?? '';
872
+
873
+ // 驗證 HashInfo
874
+ $encryption = new PayuniEncryption($merKey, $merIV);
875
+ $calculatedHash = $encryption->hashInfo($encryptInfo);
876
+
877
+ if ($hashInfo !== $calculatedHash) {
878
+ echo 'HashInfo Error';
879
+ exit;
880
+ }
881
+
882
+ // 解密
883
+ $data = $encryption->decrypt($encryptInfo);
884
+
885
+ // 檢查交易狀態
886
+ if ($data['TradeStatus'] === '1') {
887
+ // 交易成功,更新訂單狀態
888
+ updateOrderStatus($data['MerTradeNo'], 'paid', $data);
889
+ }
890
+
891
+ // 回應 SUCCESS
892
+ echo 'SUCCESS';
893
+ ```
894
+
895
+ ---
896
+
897
+ ## 錯誤碼對照表
898
+
899
+ ### 交易狀態 (TradeStatus)
900
+
901
+ | 狀態 | 說明 |
902
+ |------|------|
903
+ | `0` | 未付款 / 處理中 |
904
+ | `1` | 已付款 / 成功 |
905
+ | `2` | 付款失敗 |
906
+ | `3` | 已退款 |
907
+
908
+ ### 常見錯誤訊息
909
+
910
+ | 錯誤碼 | 說明 | 處理方式 |
911
+ |--------|------|----------|
912
+ | `參數錯誤` | 必填參數缺失或格式錯誤 | 檢查參數格式 |
913
+ | `商店代號錯誤` | MerID 不存在 | 確認商店代號 |
914
+ | `訂單編號重複` | MerTradeNo 已使用 | 使用新的訂單編號 |
915
+ | `HashInfo 驗證失敗` | 加密資料不正確 | 重新計算 HashInfo |
916
+ | `交易金額錯誤` | 金額超出範圍 | 確認金額限制 |
917
+ | `卡片授權失敗` | 信用卡交易被拒 | 請客戶聯繫發卡銀行 |
918
+ | `餘額不足` | 信用卡額度不足 | 請客戶確認額度 |
919
+ | `卡片過期` | 信用卡已過期 | 請客戶使用有效卡片 |
920
+
921
+ ---
922
+
923
+ ## 常見問題排解
924
+
925
+ ### HashInfo 驗證失敗
926
+
927
+ **問題**: 收到 `HashInfo 驗證失敗`
928
+
929
+ **檢查項目**:
930
+ 1. Hash Key 和 Hash IV 是否正確
931
+ 2. AES-256-GCM 加密是否正確實作
932
+ 3. SHA256 計算是否正確
933
+ 4. 測試/正式環境金鑰是否混用
934
+
935
+ ### 訂單編號重複
936
+
937
+ **問題**: 收到 `訂單編號重複`
938
+
939
+ **解決**:
940
+ ```python
941
+ import time
942
+ import random
943
+ order_id = f"ORD{int(time.time())}{random.randint(100, 999)}"
944
+ ```
945
+
946
+ ### 付款通知未收到
947
+
948
+ **問題**: 付款成功但沒收到 NotifyURL 通知
949
+
950
+ **檢查項目**:
951
+ 1. NotifyURL 是否為 HTTPS
952
+ 2. 伺服器是否能被外網存取
953
+ 3. 是否正確回應 `SUCCESS`
954
+ 4. 防火牆是否阻擋 PayUni IP
955
+
956
+ ### 加密/解密問題
957
+
958
+ **問題**: 加密資料無法解密
959
+
960
+ **檢查項目**:
961
+ 1. AES-256-GCM 參數是否正確 (Key 32 bytes, IV 16 bytes)
962
+ 2. Tag 長度是否為 16 bytes
963
+ 3. Hex 編碼/解碼是否正確
964
+
965
+ ---
966
+
967
+ ## SDK 資源
968
+
969
+ ### 官方 SDK
970
+
971
+ | 語言 | GitHub |
972
+ |------|--------|
973
+ | PHP | https://github.com/payuni/PHP_SDK |
974
+ | .NET | https://github.com/payuni/NET_SDK |
975
+
976
+ ### 安裝方式
977
+
978
+ #### PHP (Composer)
979
+
980
+ ```bash
981
+ composer require payuni/sdk
982
+ ```
983
+
984
+ #### .NET (NuGet)
985
+
986
+ 需要安裝以下套件:
987
+ - Newtonsoft.Json (13.0.1+)
988
+ - Portable.BouncyCastle (1.9.0+)
989
+
990
+ ---
991
+
992
+ ## 官方資源
993
+
994
+ - **官方網站**: https://www.payuni.com.tw/
995
+ - **API 文件**: https://www.payuni.com.tw/docs/web/
996
+ - **GitHub**: https://github.com/payuni
997
+ - **WooCommerce 外掛**: https://github.com/payuni/PAYUNi_for_WooCommerce