Metadata-Version: 2.4
Name: ipaynow-python-sdk
Version: 1.0.0
Summary: 现在支付（iPayNow）Python SDK：统一下单、订单查询、退款、撤销、回调验签。
Author-email: iPayNow <jishuzhichi@ipaynow.cn>
License: Proprietary
Project-URL: Homepage, https://www.ipaynow.cn
Project-URL: Repository, https://github.com/ipaynow/ipaynow-sdk
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: cryptography<46,>=45

# iPayNow Python SDK

现在支付 Python SDK，封装统一下单、订单查询、退款、撤销、回调验签，零运行时依赖（仅使用标准库）。

## 环境要求

- Python 3.9+

## 安装方式

```bash
pip install ipaynow-python-sdk
```

包名为 `ipaynow-python-sdk`，导入名为 `ipaynow_sdk`。

## 快速开始

```python
from ipaynow_sdk import (
    IpaynowClient,
    IpaynowConfig,
    H5UnifiedOrderParams,
    H5UnifiedOrderRequest,
)

client = IpaynowClient(IpaynowConfig("appId", "appKey", "https://pay.ipaynow.cn"))

params = H5UnifiedOrderParams(
    mhtOrderNo="ORDER202607030001",
    mhtOrderName="测试订单",
    mhtOrderDetail="测试订单",
    mhtOrderAmt=1,
    mhtOrderTimeOut=120,
    mhtOrderStartTime="20260703120000",
    notifyUrl="https://example.com/notify",
    frontNotifyUrl="https://example.com/front",
    payChannelType="12",
    outputType="1",
    consumerCreateIp="127.0.0.1",
)

response = client.execute(H5UnifiedOrderRequest(params))
if response.is_success():
    tn = response.tn()
```

## 前端 Form 表单提交

需要让浏览器直接向现在支付网关提交表单时，使用 `build_form_fields` 生成完整请求字段。入参与 `execute` 一致，SDK 不发起 HTTP 请求。

```python
fields = client.build_form_fields(H5UnifiedOrderRequest(params))
```

返回字段包含业务参数、公共参数和签名字段，例如：

- `funcode`
- `version`
- `appId`
- `mhtCharset`
- `mhtSignType` 或 `signType`
- `mhtSignature`

表单提交地址由请求路径决定：

```python
action = client.config.endpoint(H5UnifiedOrderRequest(params).path)
```

## 支持接口

- `WP001`：统一下单（JSAPI / 聚合码 / 小程序 / H5 / APP）
- `MQ002`：订单查询
- `N001`：支付结果回调
- `R001`：退款
- `Q001`：退款查询
- `RN001`：退款结果回调
- `R002`：撤销
- `Q002`：撤销查询

对应的 Params/Request/Response 类型：

| 场景 | Params | Request | Response |
| --- | --- | --- | --- |
| JSAPI 下单 | `JsapiUnifiedOrderParams` | `JsapiUnifiedOrderRequest` | `JsapiUnifiedOrderResponse` |
| 聚合码下单 | `AggregateCodeUnifiedOrderParams` | `AggregateCodeUnifiedOrderRequest` | `AggregateCodeUnifiedOrderResponse` |
| 小程序下单 | `MiniProgramUnifiedOrderParams` | `MiniProgramUnifiedOrderRequest` | `MiniProgramUnifiedOrderResponse` |
| H5 下单 | `H5UnifiedOrderParams` | `H5UnifiedOrderRequest` | `H5UnifiedOrderResponse` |
| APP 下单 | `AppUnifiedOrderParams` | `AppUnifiedOrderRequest` | `AppUnifiedOrderResponse` |
| 订单查询 | `OrderQueryParams` | `OrderQueryRequest` | `OrderQueryResponse` |
| 退款 | `RefundParams` | `RefundRequest` | `RefundResponse` |
| 退款查询 | `RefundQueryParams` | `RefundQueryRequest` | `RefundQueryResponse` |
| 撤销 | `ReverseParams` | `ReverseRequest` | `ReverseResponse` |
| 撤销查询 | `ReverseQueryParams` | `ReverseQueryRequest` | `ReverseQueryResponse` |
| 支付回调 | - | - | `PaymentNotifyResponse` |
| 退款回调 | - | - | `RefundNotifyResponse` |

## 响应判断

- 正向同步接口（下单、订单查询）默认使用 `responseCode == "A001"` 判断成功（`IpaynowResponse.is_success()`）。
- 退款、撤销、退款查询、撤销查询使用 `responseCode == "R000"` 判断成功（`_RefundFamilyResponse.is_success()`）。
- 支付回调用 `transStatus == "A001"` 判断成功（`PaymentNotifyResponse.is_success()`）。
- 退款回调用 `tradeStatus == "A001"` 判断成功（`RefundNotifyResponse.is_success()`）。

也可以直接读取原始字段：

```python
code = response.code()
message = response.message()
raw_body = response.raw_body()
fields = response.fields()
value = response.get("someField")
```

统一下单响应还提供 `tn()` / `now_pay_order_no()`；订单查询响应提供 `trans_status()` / `now_pay_order_no()` / `pay_time()` / `channel_order_no()`。

## 扩展字段

每个 Params 类型都支持 `extra_params`（字典），会合并进 `to_map()` 的结果。SDK 只禁止覆盖公共签名字段，业务字段不做强校验，避免网关协议扩展时 SDK 阻塞接入。

```python
params = H5UnifiedOrderParams(
    mhtOrderNo="ORDER202607030001",
    extra_params={"someNewField": "value"},
)
```

保留字段（`extra_params` 中若出现会抛出 `ValueError`）：

- `funcode`
- `version`
- `appId`
- `mhtCharset`
- `mhtSignType`
- `signType`
- `mhtSignature`
- `signature`

## 回调验签

```python
from ipaynow_sdk import NotifyAck, NotifyParser

notify = NotifyParser.parse_payment(body, app_key)
if notify.is_success():
    ack = NotifyAck.success()
else:
    ack = NotifyAck.fail()
```

退款回调：

```python
notify = NotifyParser.parse_refund(body, app_key)
```

验签失败时 `NotifyParser` 会抛出 `IpaynowError`（`code == IpaynowErrorCode.SIGN_ERROR`），而不是返回一个“失败”的响应对象，调用方需要用 `try/except` 包裹。

## 错误处理

SDK 只抛出一种异常 `IpaynowError`，通过 `.code`（`IpaynowErrorCode` 枚举）判断具体失败类型：

```python
from ipaynow_sdk import IpaynowError, IpaynowErrorCode

try:
    response = client.execute(request)
except IpaynowError as e:
    if e.code is IpaynowErrorCode.SIGN_ERROR:
        ...
```

错误码表：

| 错误码 | 枚举 | 说明 |
| --- | --- | --- |
| `E0001` | `IpaynowErrorCode.SYSTEM_ERROR` | 组件内部异常 |
| `E0002` | `IpaynowErrorCode.HTTP_EXCEPTION` | HTTP 异常 |
| `E0003` | `IpaynowErrorCode.CONNECT_TIMEOUT` | 建连超时 |
| `E0004` | `IpaynowErrorCode.SOCKET_TIMEOUT` | 读超时 |
| `E0012` | `IpaynowErrorCode.SIGN_ERROR` | 渠道报文验签失败 |

业务成功/失败（如余额不足、订单不存在）请从 `response.is_success()` / `response.code()` / `response.message()` 读取，不会以异常形式抛出。
