Metadata-Version: 2.4
Name: pay-py-kit
Version: 0.2.0
Summary: 统一支付基础设施 SDK，支持微信支付和支付宝
License-Expression: MIT
Requires-Python: >=3.11
Requires-Dist: cryptography>=43.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown

# pay-kit

统一支付基础设施 SDK，支持微信支付和支付宝商户。一个 SDK 即可处理支付、转账、余额查询。

## 特性

- **双平台支持** — 微信支付 V3 + 支付宝 V3
- **多种支付方式** — 扫码、JSAPI、H5、App、电脑网站
- **统一接口** — PayProvider / WithdrawProvider / WalletProvider 三个 ABC
- **类型安全** — 全面类型注解，mypy strict 通过
- **纯异步** — 基于 httpx，async/await 原生支持
- **安全** — RSA-SHA256 签名/验签、AES-GCM 回调解密、SecretStr 敏感字段保护

## 安装

```bash
pip install pay-kit
```

## 快速开始

### 微信支付 — Native 扫码支付

```python
from pay_kit.providers.wechat import WechatPay, WechatPayConfig
from pay_kit.models import PaymentRequest
from pay_kit.types import Fen

config = WechatPayConfig(
    appid="wx_your_appid",
    mch_id="your_mch_id",
    private_key=open("apiclient_key.pem").read(),      # 商户 API 私钥
    cert_serial_no="YOUR_CERT_SERIAL_NO",               # 商户证书序列号
    wechat_public_key=open("wechat_public_key.pem").read(),  # 微信支付公钥
    wechat_public_key_id="YOUR_PUB_KEY_ID",
    apiv3_key="your_apiv3_key",
    notify_url="https://your-domain.com/callback/wechat",
)

async with WechatPay(config) as pay:
    # 创建支付订单
    result = await pay.create_payment(
        PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
    )
    print(result.credential["code_url"])  # 用此 URL 生成二维码
```

### 支付宝 — 当面付扫码支付

```python
from pay_kit.providers.alipay import Alipay, AlipayConfig
from pay_kit.models import PaymentRequest
from pay_kit.types import Fen

config = AlipayConfig(
    app_id="your_app_id",
    private_key=open("app_private_key.pem").read(),     # 应用私钥
    alipay_public_key=open("alipay_public_key.pem").read(),  # 支付宝公钥
    notify_url="https://your-domain.com/callback/alipay",
)

async with Alipay(config) as pay:
    result = await pay.create_payment(
        PaymentRequest(out_trade_no="ORDER_001", total_amount=Fen(100), subject="商品名称")
    )
    print(result.credential["qr_code"])  # 二维码 URL
```

## 支付方式

### 微信支付

```python
async with WechatPay(config) as pay:
    # Native 扫码支付（通用接口）
    result = await pay.create_payment(request)
    # credential: {"code_url": "weixin://wxpay/..."}

    # JSAPI 公众号/小程序支付
    result = await pay.create_jsapi_payment(request, openid="user_openid")
    # credential: {"appId", "timeStamp", "nonceStr", "package", "signType", "paySign"}

    # H5 手机浏览器支付
    result = await pay.create_h5_payment(request, payer_client_ip="1.2.3.4")
    # credential: {"h5_url": "https://wx.tenpay.com/..."}

    # App 支付
    result = await pay.create_app_payment(request)
    # credential: {"appId", "partnerId", "prepayId", "package", "nonceStr", "timeStamp", "sign"}
```

### 支付宝

```python
async with Alipay(config) as pay:
    # 当面付扫码支付（通用接口）
    result = await pay.create_payment(request)
    # credential: {"qr_code": "https://qr.alipay.com/..."}

    # 电脑网站支付
    result = await pay.create_page_payment(request, return_url="https://your-site.com/return")
    # credential: {"pay_url": "https://openapi.alipay.com/..."}

    # 手机网站支付
    result = await pay.create_wap_payment(request, return_url="https://your-site.com/return")
    # credential: {"pay_url": "https://openapi.alipay.com/..."}

    # App 支付
    result = await pay.create_app_payment(request)
    # credential: {"order_str": "alipay_sdk=..."}
```

## 通用操作

查询、关单、退款等操作两个平台用法一致：

```python
# 查询订单
result = await pay.query_payment("ORDER_001")
print(result.status)  # PaymentStatus.PAID

# 关闭订单
await pay.close_payment("ORDER_001")

# 发起退款
from pay_kit.models import RefundRequest
refund = await pay.refund(
    RefundRequest(
        out_trade_no="ORDER_001",
        out_refund_no="REFUND_001",
        total_amount=Fen(100),
        refund_amount=Fen(50),
    )
)

# 查询退款
refund = await pay.query_refund("REFUND_001")
```

## 转账

```python
from pay_kit.models import WithdrawRequest

async with WechatPay(config) as pay:
    # 商家转账到零钱
    result = await pay.create_withdrawal(
        WithdrawRequest(
            out_withdrawal_no="TRANSFER_001",
            amount=Fen(1000),
            recipient="user_openid",         # 微信: openid / 支付宝: user_id
            description="佣金提现",
        )
    )
    print(result.status)  # WithdrawStatus.PENDING

    # 查询转账状态
    result = await pay.query_withdrawal("TRANSFER_001")
```

## 余额查询

```python
async with WechatPay(config) as pay:
    balance = await pay.query_balance()
    print(f"可用余额: {balance.available} 分")
    print(f"待结算: {balance.pending} 分")
```

## 回调验签

```python
# FastAPI 示例
from fastapi import Request

@app.post("/callback/wechat")
async def wechat_callback(request: Request):
    headers = dict(request.headers)
    body = await request.body()

    async with WechatPay(config) as pay:
        event = await pay.verify_callback(headers, body)

    print(event.event_type)     # "TRANSACTION.SUCCESS"
    print(event.out_trade_no)   # "ORDER_001"
    print(event.trade_no)       # 微信交易号
    print(event.raw)            # 完整回调数据

    return {"code": "SUCCESS"}
```

## 异常处理

```python
from pay_kit.exceptions import (
    PayKitError,       # 所有异常基类
    ConfigError,       # 配置错误
    NetworkError,      # 网络超时/连接失败
    SignatureError,    # 签名验证失败
    PaymentError,      # 支付业务错误
    WithdrawError,     # 转账业务错误
    WalletError,       # 余额查询错误
)

try:
    result = await pay.create_payment(request)
except NetworkError:
    # 网络问题，可重试
    pass
except SignatureError:
    # 签名验证失败，响应不可信
    pass
except PaymentError as e:
    # 业务错误
    print(e.error_code)      # 支付商错误码，如 "ORDER_CLOSED"
    print(e.provider_name)   # "wechat" 或 "alipay"
```

## 金额类型

pay-kit 使用 `Fen`（分）作为金额单位，避免浮点精度问题：

```python
from pay_kit.types import Fen, Yuan, fen_to_yuan, yuan_to_fen

amount = Fen(100)                    # 100 分 = 1 元
yuan = fen_to_yuan(Fen(100))         # Yuan("1.00")
fen = yuan_to_fen(Yuan("1.00"))      # Fen(100)
```

## 开发

```bash
git clone https://github.com/Xinzz995/pay-kit.git
cd pay-kit
uv sync

uv run pytest                        # 运行测试
uv run pytest --cov=pay_kit          # 覆盖率
uv run ruff check .                  # Lint
uv run mypy src/                     # 类型检查
```

## 许可证

MIT
