> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.qfapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 沖正 API 指南

> 使用沖正 API 撤回尚未完成的反掃交易，或撤銷已完成的信用卡/Apple Pay 交易。若反掃交易已成功，請改用退款 API。

本頁說明如何使用「沖正（reversal）API」撤銷交易。請注意，沖正的行為依\*\*支付場景（反掃 vs 信用卡）\*\*而有本質上的不同：

1. 反掃**場景**：僅能撤銷**尚未完成**的交易。若原始交易已成功（`respcd=0000`），請改用 [退款 API](/zh-hant/integration/common-api/refund)。
2. **信用卡 / Apple Pay 場景**：用於撤銷**已經完成**的卡交易。執行後會直接取消該筆交易。

<Warning>
  對於支付寶、微信支付等錢包，沖正（reversal）不是退款。只有當原始交易尚未成功完成時，才能發起沖正。
</Warning>

## 支援場景

<Note>
  目前以下支付場景支援 `/trade/v1/reversal`。請根據你的接入場景確認對應的交易行為。
</Note>

### 1. 反掃支付場景

通常用於商戶已建立訂單，但消費者尚未完成付款等狀態。

* **支付寶**
  * `800108`：支付寶反掃支付
* **微信支付**
  * `800208`：微信反掃支付
* **銀聯雲閃付**
  * `800708`：銀聯雲閃付反掃支付

### 2. 信用卡 / Apple Pay 場景（適用於「已完成」交易）

* **線上信用卡支付**
  * `802801`: Visa/Mastercard
* **Apple Pay 交易**
  * `806527`: Apple Pay
* **行為說明**：在此場景下，沖正的效果為\*\*撤銷（Void）\*\*該筆已扣款或已授權的卡交易。

<Note>
  如需對其他錢包或模式進行交易取消，請參考本文下方的「Reversal vs Close」或聯絡 QFPay 技術支援取得整合建議。
</Note>

## API 端點

* **Endpoint**：`/trade/v1/reversal`
* **方法**：`POST`

<Note>
  沖正成功通常回應 `respcd=0000`。若二維碼交易付款回應已為 `respcd=0000`，則無法發起沖正，請改用退款。
</Note>

## 請求參數

| 參數名稱           | 類型          | 必填  | 說明                                   |
| -------------- | ----------- | --- | ------------------------------------ |
| `mchid`        | String(16)  | 否   | QFPay 商戶號（僅部分代理商／特定配置需要）             |
| `syssn`        | String(40)  | 是\* | QFPay 交易號（付款或退款皆可能存在；此處指「要被沖正的那筆交易」） |
| `out_trade_no` | String(128) | 是\* | 商戶端交易單號（要被沖正的那筆交易）                   |
| `txamt`        | Int(11)     | 是   | 交易金額（單位：分）。建議金額 > 200 以避免風控          |
| `txdtm`        | String(20)  | 否   | 原交易時間，格式：`YYYY-MM-DD HH:mm:ss`       |
| `udid`         | String(40)  | 否   | 裝置唯一 ID（用於追蹤交易設備）                    |

<Info>
  `syssn` 與 `out_trade_no` 至少提供其中一個。
</Info>

## 回應參數

| 參數名稱         | 類型        | 說明                                    |
| ------------ | --------- | ------------------------------------- |
| `syssn`      | String    | 此次沖正動作的 QFPay 交易號                     |
| `orig_syssn` | String    | 原始（被沖正）交易的 QFPay 交易號                  |
| `txamt`      | Int       | 沖正金額（單位：分）                            |
| `txcurrcd`   | String(3) | 幣別代碼（例如 HKD）                          |
| `txdtm`      | String    | 原始交易時間                                |
| `sysdtm`     | String    | QFPay 系統處理時間                          |
| `chnlsn`     | String    | 錢包端交易號（若未產生則可能為空）                     |
| `respcd`     | String(4) | 回應代碼（`0000`=成功；`1143/1145`=處理中；其他=失敗） |
| `resperr`    | String    | 結果訊息                                  |
| `respmsg`    | String    | 補充說明（如有）                              |

## 程式碼範例

```python Python theme={null}
import hashlib
import requests

environment = "https://test-openapi-hk.qfapi.com"
app_code = "YOUR_APP_CODE"
client_key = "YOUR_CLIENT_KEY"

def make_sign(data: dict, key: str) -> str:
    ordered = sorted(data.items())
    sign_str = "&".join(f"{k}={v}" for k, v in ordered) + key
    return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()

payload = {
    "mchid": "MNxMp11FV35qQN",
    "txamt": "2500",
    "out_trade_no": "4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ",
    "txdtm": "2020-03-05 16:50:30"
    # "syssn": "20200305066100020000977812"  # syssn / out_trade_no 擇一即可
}

headers = {
    "X-QF-APPCODE": app_code,
    "X-QF-SIGN": make_sign(payload, client_key)
}

r = requests.post(environment + "/trade/v1/reversal", data=payload, headers=headers)
print(r.json())
```

```java Java theme={null}
import java.util.HashMap;
import java.util.Map;

public class ReversalExample {
    public static void main(String[] args) {
        String url = "https://test-openapi-hk.qfapi.com/trade/v1/reversal";
        String appcode = "YOUR_APP_CODE";
        String clientKey = "YOUR_CLIENT_KEY";

        Map<String, String> payload = new HashMap<>();
        payload.put("mchid", "MNxMp11FV35qQN");
        payload.put("txamt", "2500");
        payload.put("out_trade_no", "4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ");
        payload.put("txdtm", "2020-03-05 16:50:30");
        // payload.put("syssn", "20200305066100020000977812"); // syssn / out_trade_no 擇一即可

        // 產生簽名、送出請求的方式請沿用你們既有的 QFPayUtils / Requests 實作：
        // 1) key=value&...（依參數名排序）+ clientKey
        // 2) MD5 -> 大寫
        // 3) 以 X-QF-APPCODE / X-QF-SIGN header 發送 x-www-form-urlencoded
    }
}
```

```javascript JavaScript theme={null}
const crypto = require("crypto");
const request = require("request");

const environment = "https://test-openapi-hk.qfapi.com";
const app_code = "YOUR_APP_CODE";
const client_key = "YOUR_CLIENT_KEY";

const payload = {
  mchid: "MNxMp11FV35qQN",
  txamt: "2500",
  out_trade_no: "4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ",
  txdtm: "2020-03-05 16:50:30"
  // syssn: "20200305066100020000977812" // syssn / out_trade_no 擇一即可
};

const sorted = Object.keys(payload)
  .sort()
  .map(k => `${k}=${payload[k]}`)
  .join("&");

const sign = crypto
  .createHash("md5")
  .update(sorted + client_key)
  .digest("hex")
  .toUpperCase();

request({
  uri: environment + "/trade/v1/reversal",
  method: "POST",
  headers: {
    "X-QF-APPCODE": app_code,
    "X-QF-SIGN": sign
  },
  form: payload
}, function (error, response, body) {
  console.log(body);
});
```

```php PHP theme={null}
<?php
function make_sign($fields, $key) {
  ksort($fields);
  $pairs = [];
  foreach ($fields as $k => $v) {
    $pairs[] = $k . "=" . $v;
  }
  $sign_str = implode("&", $pairs) . $key;
  return strtoupper(md5($sign_str));
}

$url = "https://test-openapi-hk.qfapi.com/trade/v1/reversal";
$app_code = "YOUR_APP_CODE";
$client_key = "YOUR_CLIENT_KEY";

$fields = [
  "mchid" => "MNxMp11FV35qQN",
  "txamt" => "2500",
  "out_trade_no" => "4MDGEJ7L496LAAU1V1HBY9HMOGWZWLXQ",
  "txdtm" => "2020-03-05 16:50:30"
  // "syssn" => "20200305066100020000977812" // syssn / out_trade_no 擇一即可
];

$sign = make_sign($fields, $client_key);

$headers = [
  "X-QF-APPCODE: " . $app_code,
  "X-QF-SIGN: " . $sign
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
$response = curl_exec($ch);
curl_close($ch);

echo $response;
?>
```

## 回應範例

```json JSON theme={null}
{
  "surcharge_fee": "0",
  "resperr": "success",
  "txdtm": "2020-03-05 16:50:30",
  "syssn": "20200305066100020000977814",
  "sysdtm": "2020-03-05 16:54:38",
  "txcurrcd": "EUR",
  "respmsg": "",
  "chnlsn2": "",
  "cardcd": "",
  "udid": "qiantai2",
  "txamt": "2500",
  "orig_syssn": "20200305066100020000977813",
  "surcharge_rate": "0",
  "respcd": "0000",
  "chnlsn": ""
}
```

## 補充說明

* 沖正不代表一定「不會扣款」。請務必搭配 [交易查詢 API](/zh-hant/integration/common-api/transaction-enquiry) 驗證最終結果。
* 若回應為 `respcd=1143` 或 `1145`，代表處理中，請延遲後重試查詢直到取得最終狀態。

## Reversal（沖正）vs Close（關閉訂單）

部分錢包在「取消未完成交易」時會使用 `/trade/v1/close`（關閉訂單）取代 `/trade/v1/reversal`。

### 典型支援範圍（示例）

* `800201`：微信正掃
* `800212`：微信H5支付
* `800701`：銀聯雲閃付正掃
* `801514`：支付宝PC正掃

**Endpoint**：`/trade/v1/close`\
**方法**：`GET`

<Note>
  實際支援範圍可能因商戶配置與渠道而異。如需確認你方可用的取消方式，請聯絡技術支援。
</Note>

### Close 請求參數（摘要）

| 參數名稱           | 必填  | 類型          | 說明                    |
| -------------- | --- | ----------- | --------------------- |
| `mchid`        | 否   | String(16)  | 商戶號（視配置）              |
| `syssn`        | 是\* | String(40)  | QFPay 交易號             |
| `out_trade_no` | 是\* | String(128) | 商戶訂單號                 |
| `txamt`        | 是   | Int(11)     | 金額（分）                 |
| `txdtm`        | 是   | String(20)  | `YYYY-MM-DD HH:mm:ss` |
| `udid`         | 否   | String(40)  | 裝置識別碼                 |

<Info>
  `syssn` 與 `out_trade_no` 至少提供其中一個。
</Info>

### Close 回應參數（摘要）

| 參數名稱           | 類型          | 說明                              |
| -------------- | ----------- | ------------------------------- |
| `orig_syssn`   | String(40)  | 原始交易號                           |
| `syssn`        | String(40)  | 關單交易號                           |
| `out_trade_no` | String(128) | 商戶訂單號                           |
| `txamt`        | Int(11)     | 金額（分）                           |
| `txcurrcd`     | String(3)   | 幣別                              |
| `txdtm`        | String(20)  | 原交易時間                           |
| `sysdtm`       | String(20)  | 系統處理時間                          |
| `chnlsn`       | String      | 通道交易號                           |
| `respcd`       | String(4)   | `0000`=成功；`1143/1145`=處理中；其他=失敗 |
| `resperr`      | String(128) | 結果描述                            |
| `respmsg`      | String(128) | 補充資訊                            |
