> ## 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.

# 非同步通知

> 說明如何處理 QFPay 的支付與退款非同步通知。

QFPay 目前支援以下兩種非同步通知：

* 付款成功：`"notify_type": "payment"`
* 退款成功：`"notify_type": "refund"`

<Note>
  未來版本的通知參數可能新增欄位。建議你的接收端採用「可忽略未知欄位」的處理方式，並定期查看最新文件更新。
</Note>

***

## 概述

當支付或退款成功後，QFPay 會以 **HTTP POST** 方式向商戶已設定的 callback URL 發送一筆 JSON 通知，用於即時更新交易結果。

<Tip>
  即使已收到通知，也建議使用 [交易查詢 API](/zh-hant/integration/common-api/transaction-enquiry) 二次驗證交易狀態，避免因網路重試或重放造成誤判。
</Tip>

<Info>
  出於安全考量，callback URL 僅支援 **80 / 443** 端口，且 `Content-Type` 固定為 `application/json`。
</Info>

***

## 非同步通知規則

1. 僅在交易成功（付款或退款）後才會發送通知。
2. 請透過電郵提交通知端點 URL 至 `technical.support@qfpay.com`，由技術支援協助設定。
3. 商戶收到通知後必須進行簽名驗證。驗證成功後回覆：
   * HTTP Status Code：`200 OK`
   * Response Body：`SUCCESS`
4. 若未收到預期回應，系統會依以下時間間隔重試：
   * 2 分鐘 → 10 分鐘 → 10 分鐘 → 60 分鐘 → 2 小時 → 6 小時 → 15 小時
5. 同一組 `app_code` + `client_key` 僅能綁定 **一個** 通知 URL。代理商應為子商戶共用同一接收端點。
6. **HTTP 方法**：`POST`\
   **Content-Type**：`application/json`

***

## 簽名驗證

非同步通知的簽名驗證方式與一般 API 請求不同：必須使用「**原始 request body（未重排 / 未格式化的 JSON 字串）**」來驗證。

### 驗證步驟

1. 從 HTTP header 取得 `X-QF-SIGN`。
2. 取得原始 request body（JSON 字串），直接在尾端串接 `client_key`。
3. 對字串做 **MD5** 雜湊，並轉成 **大寫**。
4. 若雜湊結果等於 `X-QF-SIGN`，視為驗證成功，回覆 `200 OK` 與 `SUCCESS`。

<Warning>
  請使用「原始 body」做驗證。若你把 JSON parse 後再 `json.dumps()` 重新序列化，欄位順序與空白可能不同，會導致簽名不一致。
</Warning>

***

## 簽名範例

<CodeGroup>
  ```python Python theme={null}
  import hashlib

  client_key = "3ABB1BFFE2E0497BB9270978B0BXXXXX"

  # 假設你取得的 raw_body 是 HTTP request 的原始 body（bytes 或 str）
  raw_body = b'{"status":"1","notify_type":"payment"}'

  combine = raw_body + client_key.encode("utf-8")
  signature = hashlib.md5(combine).hexdigest().upper()

  print(signature)
  ```
</CodeGroup>

<Info>
  實作時請以你實際收到的完整 raw body 進行計算；上例僅示意流程。
</Info>

***

## 通知內容範例

```json JSON theme={null}
{
  "status": "1",
  "pay_type": "800101",
  "sysdtm": "2020-05-14 12:32:56",
  "paydtm": "2020-05-14 12:33:56",
  "goods_name": "",
  "txcurrcd": "HKD",
  "txdtm": "2020-05-14 12:32:56",
  "mchid": "lkbqahlRYj",
  "txamt": "10",
  "exchange_rate": "",
  "chnlsn2": "",
  "out_trade_no": "YEPE7WTW46NVU30JW5N90H7DHD94N56B",
  "syssn": "20200514000300020093755455",
  "cash_fee_type": "",
  "cancel": "0",
  "respcd": "0000",
  "goods_info": "",
  "cash_fee": "0",
  "notify_type": "payment",
  "chnlsn": "2020051422001453561444935817",
  "cardcd": "2088032341453564"
}
```

***

## 回傳欄位說明

| 欄位名稱                   | 必填 | 類型     | 說明                                                                 |
| ---------------------- | -- | ------ | ------------------------------------------------------------------ |
| `status`               | 是  | String | 交易成功狀態，固定為 `1`                                                     |
| `notify_type`          | 是  | String | 通知類型：`payment` 或 `refund`                                          |
| `pay_type`             | 是  | String | QFPay 支付代碼，請參考 [支付方式](/zh-hant/integration/api-reference/paytypes) |
| `syssn`                | 是  | String | QFPay 交易號（可能是「付款交易號」或「退款交易號」，取決於 `notify_type`）                    |
| `out_trade_no`         | 是  | String | 商戶訂單號                                                              |
| `txamt`                | 是  | String | 金額（單位：分）。建議金額 > 200 以避免風控                                          |
| `txcurrcd`             | 是  | String | 幣別，請參考 [交易貨幣](/zh-hant/integration/api-reference/currencies)       |
| `txdtm`                | 是  | String | 商戶請求時間                                                             |
| `sysdtm`               | 是  | String | 系統交易時間（常用作結算日切點）                                                   |
| `paydtm`               | 是  | String | 用戶實際支付時間                                                           |
| `cancel`               | 是  | String | 交易取消狀態：`0`=未取消；其他值請見下方「Cancel 定義」                                  |
| `respcd`               | 是  | String | 固定為 `0000`（表示成功通知）                                                 |
| `mchid`                | 否  | String | 商戶號（多見於代理商場景）                                                      |
| `goods_name`           | 否  | String | 商品名稱（中文請使用 UTF-8）                                                  |
| `goods_info`           | 否  | String | 商品描述                                                               |
| `exchange_rate`        | 否  | String | 匯率（若有跨幣種轉換）                                                        |
| `chnlsn`               | 否  | String | 支付渠道交易號                                                            |
| `chnlsn2`              | 否  | String | 附加渠道交易參考                                                           |
| `cardcd`               | 否  | String | 卡號                                                                 |
| `cash_fee`             | 否  | String | 實際用戶支付金額（扣除優惠後）                                                    |
| `cash_fee_type`        | 否  | String | `cash_fee` 的幣別                                                     |
| `cash_refund_fee`      | 否  | String | 實際退款金額                                                             |
| `cash_refund_fee_type` | 否  | String | `cash_refund_fee` 的幣別                                              |

<Note>
  `syssn` 的語境容易混淆：在非同步通知中，`syssn` 代表「本次通知所對應的交易號」。若 `notify_type=refund`，它通常是退款交易號，而不是原始付款交易號。
</Note>

***

## Cancel 定義

| 值   | 意義               |
| --- | ---------------- |
| `0` | 未取消              |
| `1` | CPM：已撤銷或已退款      |
| `2` | MPM：已取消          |
| `3` | 已退款              |
| `4` | 預授權訂單已完成（Alipay） |
| `5` | 部分退款             |

***

## 通知來源 IP

請確保你的伺服器允許以下來源 IP 發送的 POST 請求：

* `13.228.112.115`
* `18.138.115.47`
* `18.166.202.92`
