三行python代碼搞定微信支付V3版接口對接


微信支付 API v3 Python SDK

介紹

微信支付接口V3版python庫。

歡迎微信支付開發者掃碼進QQ群(群號:973102221)討論:

適用對象

wechatpayv3支持微信支付直連商戶,接口說明詳見 官網

特性

  1. 平台證書自動更新,無需開發者關注平台證書有效性,無需手動下載更新;
  2. 支持本地緩存平台證書,初始化時指定平台證書保存目錄即可;
  3. 敏感信息直接傳入明文參數,SDK內部自動加密,無需手動處理;
  4. 回調通知自動驗證回調消息,自動解密resource對象,並返回解密后的數據。

適配進度

微信支付V3版API接口

其中:

基礎支付

JSAPI支付 已適配
APP支付 已適配
H5支付 已適配
Native支付 已適配
小程序支付 已適配
合單支付 已適配

經營能力

支付即服務 已適配

行業方案

智慧商圈 已適配
微信支付分停車服務 已適配

營銷工具

圖片上傳(營銷專用) 已適配

資金應用

分賬 已適配

其他能力

圖片上傳 已適配
視頻上傳 已適配

需要的接口還沒有適配怎么辦?

由於wechatpayv3包內核心的core.py已經封裝了請求簽名和消息驗證過程,開發者無需關心web請求細節,直接根據官方文檔參考以下基礎支付的申請退款接口代碼自行適配,測試OK的話,歡迎提交代碼。
必填的參數建議加上空值檢查,可選的參數默認傳入None。參數類型對照參考下表:

文檔聲明 wechatpayv3
string string
int int
object dict: {}
array list: []
boolean bool: True, False
def refund(self,
           out_refund_no,
           amount,
           transaction_id=None,
           out_trade_no=None,
           reason=None,
           funds_account=None,
           goods_detail=None,
           notify_url=None):
    """申請退款
    :param out_refund_no: 商戶退款單號,示例值:'1217752501201407033233368018'
    :param amount: 金額信息,示例值:{'refund':888, 'total':888, 'currency':'CNY'}
    :param transaction_id: 微信支付訂單號,示例值:'1217752501201407033233368018'
    :param out_trade_no: 商戶訂單號,示例值:'1217752501201407033233368018'
    :param reason: 退款原因,示例值:'商品已售完'
    :param funds_account: 退款資金來源,示例值:'AVAILABLE'
    :param goods_detail: 退款商品,示例值:{'merchant_goods_id':'1217752501201407033233368018', 'wechatpay_goods_id':'1001', 'goods_name':'iPhone6s 16G', 'unit_price':528800, 'refund_amount':528800, 'refund_quantity':1}
    :param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php'
    """
    params = {}
    params['notify_url'] = notify_url or self._notify_url
    # 
    if out_refund_no:
        params.update({'out_refund_no': out_refund_no})
    else:
        raise Exception('out_refund_no is not assigned.')
    if amount:
        params.update({'amount': amount})
    else:
        raise Exception('amount is not assigned.')
    if transaction_id:
        params.update({'transaction_id': transaction_id})
    if out_trade_no:
        params.update({'out_trade_no': out_trade_no})
    if reason:
        params.update({'reason': reason})
    if funds_account:
        params.update({'funds_account': funds_account})
    if goods_detail:
        params.update({'goods_detail': goods_detail})
    path = '/v3/refund/domestic/refunds'
    return self._core.request(path, method=RequestType.POST, data=params)

源碼

github

gitee

安裝

$ pip install wechatpayv3

使用方法

准備

參考微信官方文檔准備好密鑰, 證書文件和配置(證書/密鑰/簽名介紹)

  • 商戶 API 證書私鑰:PRIVATE_KEY。商戶申請商戶 API 證書時,會生成商戶私鑰,並保存在本地證書文件夾的文件 apiclient_key.pem 中。

⚠️ 不要把私鑰文件暴露在公共場合,如上傳到 Github,寫在客戶端代碼等。

  • 商戶API證書序列號:CERT_SERIAL_NO。每個證書都有一個由 CA 頒發的唯一編號,即證書序列號。擴展閱讀 如何查看證書序列號
  • 微信支付 APIv3 密鑰:APIV3_KEY,是在回調通知和微信支付平台證書下載接口中,為加強數據安全,對關鍵信息 AES-256-GCM 加密時使用的對稱加密密鑰。

初始化

from wechatpayv3 import WeChatPay, WeChatPayType

# 微信支付商戶號
MCHID = '1230000109'
# 商戶證書私鑰
with open('path_to_key/apiclient_key.pem') as f:
    PRIVATE_KEY = f.read()
# 商戶證書序列號
CERT_SERIAL_NO = '444F4864EA9B34415...'
# API v3密鑰, https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
APIV3_KEY = 'MIIEvwIBADANBgkqhkiG9w0BAQE...'
# APPID
APPID = 'wxd678efh567hg6787'
# 回調地址,也可以在調用接口的時候覆蓋
NOTIFY_URL = 'https://www.weixin.qq.com/wxpay/pay.php'
# 微信支付平台證書緩存目錄
CERT_DIR = './cert'

wxpay = WeChatPay(
    wechatpay_type=WeChatPayType.MINIPROG,
    mchid=MCHID,
    private_key=PRIVATE_KEY,
    cert_serial_no=CERT_SERIAL_NO,
    apiv3_key=APIV3_KEY,
    appid=APPID,
    notify_url=NOTIFY_URL,
    cert_dir=CERT_DIR)

接口


# 統一下單
def pay():
    code, message = wxpay.pay(
        description='demo-description',
        out_trade_no='demo-trade-no',
        amount={'total': 100},
        payer={'openid': 'demo-openid'}
    )
    print('code: %s, message: %s' % (code, message))

# 訂單查詢
def query():
    code, message = wxpay.query(
        transaction_id='demo-transation-id'
    )
    print('code: %s, message: %s' % (code, message))

# 關閉訂單
def close():
    code, message = wxpay.close(
        out_trade_no='demo-out-trade-no'
    )
    print('code: %s, message: %s' % (code, message))

# 申請退款
def refund():
    code, message=wxpay.refund(
        out_refund_no='demo-out-refund-no',
        amount={'refund': 100, 'total': 100, 'currency': 'CNY'},
        transaction_id='1217752501201407033233368018'
    )
    print('code: %s, message: %s' % (code, message))

# 退款查詢
def query_refund():
    code, message = wxpay.query_refund(
        out_refund_no='demo-out-refund-no'
    )
    print('code: %s, message: %s' % (code, message))

# 申請交易賬單
def trade_bill():
    code, message = wxpay.trade_bill(
        bill_date='2021-04-01'
    )
    print('code: %s, message: %s' % (code, message))

# 申請資金流水賬單
def fundflow_bill():
    code, message = wxpay.fundflow_bill(
        bill_date='2021-04-01'
    )
    print('code: %s, message: %s' % (code, message))

# 下載賬單
def download_bill():
    code, message = wxpay.download_bill(
        url='https://api.mch.weixin.qq.com/v3/billdownload/file?token=demo-token'
    )
    print('code: %s, message: %s' % (code, message))

# 合單支付下單
def combine_pay():
    code, message = wxpay.combine_pay(
        combine_out_trade_no='demo_out_trade_no',
        sub_orders=[{'mchid':'1900000109',
                     'attach':'深圳分店',
                     'amount':{'total_amount':100,'currency':'CNY'},
                     'out_trade_no':'20150806125346',
                     'description':'騰訊充值中心-QQ會員充值',
                     'settle_info':{'profit_sharing':False, 'subsidy_amount':10}}]
    )
    print('code: %s, message: %s' % (code, message))

# 合單訂單查詢
def combine_query():
    code, message = wxpay.combine_query(
        combine_out_trade_no='demo_out_trade_no'
    )
    print('code: %s, message: %s' % (code, message))

# 合單訂單關閉
def combine_close():
    code, message = wxpay.combine_close(
        combine_out_trade_no='demo_out_trade_no', 
        sub_orders=[{'mchid': '1900000109', 'out_trade_no': '20150806125346'}]
    )
    print('code: %s, message: %s' % (code, message))

# 計算簽名供調起支付時拼湊參數使用
# 注意事項:注意參數順序,某個參數為空時不能省略,以空字符串''占位
def sign():
    print(wxpay.sign(['wx888','1414561699','5K8264ILTKCH16CQ2502S....','prepay_id=wx201410272009395522657....']))

# 驗證並解密回調消息,把回調接口收到的headers和body傳入
# 這里以flask框架為例,其他web框架如果遇到InvalidSignature,請確認傳入的body和收到的一致,沒有做額外的預處理
def decrypt_callback(headers=request.headers, body=request.data):
    print(wxpay.decrypt_callback(headers, body))

# 智慧商圈積分通知
def points_notify():
    code, message = wxpay.points_notify(
        transaction_id='4200000533202000000000000000',
        openid='otPAN5xxxxxxxxrOEG6lUv_pzacc',
        earn_points=True,
        increased_points=100,
        points_update_time='2020-05-20T13:29:35.120+08:00'
    )
    print('code: %s, message: %s' % (code, message))

# 智慧商圈積分授權查詢
def user_authorization():
    code, message = wxpay.user_authorizations(
        openid='otPAN5xxxxxxxxrOEG6lUv_pzacc'
    )
    print('code: %s, message: %s' % (code, message))

# 支付即服務人員注冊
def guides_register():
    code, message = wxpay.guides_register(
        corpid='1234567890',
        store_id=1234,
        userid='rebert',
        name='robert',
        mobile='13900000000',
        qr_code='https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx',
        avatar='http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0',
        group_qrcode='http://p.qpic.cn/wwhead/nMl9ssowtibVGyrmvBiaibzDtp/0'
    )
    print('code: %s, message: %s' % (code, message))

# 支付即服務人員分配
def guides_assign():
    code, message = wxpay.guides_assign(
        guide_id='LLA3WJ6DSZUfiaZDS79FH5Wm5m4X69TBic',
        out_trade_no='20150806125346'
    )
    print('code: %s, message: %s' % (code, message))

# 支付即服務人員查詢
def guides_query():
    code, message = wxpay.guides_query(
        store_id=1234,
        userid='robert',
        mobile='13900000000',
        work_id='robert',
        limit=5,
        offset=0
    )
    print('code: %s, message: %s' % (code, message))

# 支付即服務人員信息更新
def guides_update():
    code, message = wxpay.guides_update(
        guide_id='LLA3WJ6DSZUfiaZDS79FH5Wm5m4X69TBic',
        name='robert',
        mobile='13900000000',
        qr_code='https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx',
        avatar='http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0',
        group_qrcode='http://p.qpic.cn/wwhead/nMl9ssowtibVGyrmvBiaibzDtp/0'
    )
    print('code: %s, message: %s' % (code, message))

# 圖片上傳
def image_upload():
    code, message = wxpay.image_upload(
        filepath='./media/demo.png'
    )
    print('code: %s, message: %s' % (code, message))

# 視頻上傳
def video_upload():
    code, message = wxpay.video_upload(
        filepath='./media/demo.mp4'
    )
    print('code: %s, message: %s' % (code, message))

# 查詢車牌服務開通信息
def parking_service_find():
    code, message = wxpay.parking_service_find(
        plate_number='粵B888888',
        plate_color='BLUE',
        openid='oUpF8uMuAJOM2pxb1Q'
    )
    print('code: %s, message: %s' % (code, message))

# 創建停車入場
def parking_enter():
    code, message = wxpay.parking_enter(
        out_parking_no='1231243',
        plate_number='粵B888888',
        plate_color='BLUE',
        start_time='2017-08-26T10:43:39+08:00',
        parking_name='歡樂海岸停車場',
        free_duration=3600
    )
    print('code: %s, message: %s' % (code, message))

# 停車扣費受理
def parking_order():
    code, message = wxpay.parking_order(
        description='停車場扣費',
        out_trade_no='20150806125346',
        total=888,
        parking_id='5K8264ILTKCH16CQ250',
        plate_number='粵B888888',
        plate_color='BLUE',
        start_time='2017-08-26T10:43:39+08:00',
        end_time='2017-08-26T10:43:39+08:00',
        parking_name='歡樂海岸停車場',
        charging_duration=3600,
        device_id='12313'
    )
    print('code: %s, message: %s' % (code, message))

# 查詢停車扣費訂單
def parking_order_query():
    code, message = wxpay.parking_order_query(
        out_trade_no='20150806125346'
    )
    print('code: %s, message: %s' % (code, message))

# 圖片上傳(營銷專用)
def marking_image_upload():
    code, message = wxpay.marketing_image_upload(
        filepath='./media/demo.png'
    )
    print('code: %s, message: %s' % (code, message))

# 請求分賬
def profitsharing_order():
    code, message = wxpay.profitsharing_order(
        transaction_id='4208450740201411110007820472',
        out_order_no='P20150806125346',
        receivers={{'type': 'MERCHANT_ID', 'account': '86693852', 'amount': 888, 'description': '分給商戶A'}},
        unfreeze_unsplit=True
    )
    print('code: %s, message: %s' % (code, message))

# 查詢分賬結果
def profitsharing_order_query():
    code, message = wxpay.profitsharing_order_query(
        transaction_id='4208450740201411110007820472',
        out_order_no='P20150806125346'
    )
    print('code: %s, message: %s' % (code, message))

# 請求分賬回退
def profitsharing_return():
    code, message = wxpay.profitsharing_return(
        order_id='3008450740201411110007820472',
        out_return_no='R20190516001',
        return_mchid='86693852',
        amount=888,
        description='用戶退款')
    print('code: %s, message: %s' % (code, message))

# 查詢分賬回退結果
def profitsharing_return_query():
    code, message = wxpay.profitsharing_return_query(
        out_order_no='P20150806125346',
        out_return_no='R20190516001'
    )
    print('code: %s, message: %s' % (code, message))

# 解凍剩余資金
def profitsharing_unfreeze():
    code, message = wxpay.profitsharing_unfreeze(
        transaction_id='4208450740201411110007820472',
        out_order_no='P20150806125346',
        description='解凍全部剩余資金'
    )
    print('code: %s, message: %s' % (code, message))

# 查詢剩余待分金額
def profitsharing_amount_query():
    code, message = wxpay.profitsharing_amount_query(
        transaction_id='4208450740201411110007820472'
    )
    print('code: %s, message: %s' % (code, message))

# 添加分賬接收方
def profitsharing_add_receiver():
    code, message = wxpay.profitsharing_add_receiver(
        account_type='MERCHANT_ID',
        account='86693852',
        relation_type='CUSTOM',
        name='騰訊充值中心',
        custom_relation='代理商'
    )
    print('code: %s, message: %s' % (code, message))

# 刪除分賬接收方
def profitsharing_delete_receiver():
    code, message = wxpay.profitsharing_delete_receiver(
        account_type='MERCHANT_ID',
        account='86693852'
    )
    print('code: %s, message: %s' % (code, message))

# 申請分賬賬單
def profitsharing_bill():
    code, message = wxpay.profitsharing_bill(
        bill_date='2021-04-01'
    )
    print('code: %s, message: %s' % (code, message))

回調驗證失敗處理

開發者遇到的難點之一就是回調驗證失敗的問題,由於眾多的python web框架對回調消息的處理不完全一致,如果出現回調驗證失敗,請務必確認傳入的headers和body的值和類型。
通常框架傳過來的headers類型是dict,而body類型是bytes。使用以下方法可直接獲取到解密后的實際內容。

flask框架

直接傳入request.headers和request.data即可。

result = wxpay.decrypt_callback(headers=request.headers, body=request.data)

django框架

由於django框架特殊性,會將headers做一定的預處理,可以參考以下方式調用。

headers = {}
headers.update({'Wechatpay-Signature': request.META.get('HTTP_WECHATPAY_SIGNATURE'))
headers.update({'Wechatpay-Timestamp': request.META.get('HTTP_WECHATPAY_TIMESTAMP'))
headers.update({'Wechatpay-Nonce': request.META.get('HTTP_WECHATPAY_NONCE'))
headers.update({'Wechatpay-Serial': request.META.get('HTTP_WECHATPAY_SERIAL'))
result = wxpay.decrypt_callback(headers=headers, body=request.body)

其他框架

參考以上處理方法,大原則就是保證傳給decrypt_callback的參數值和收到的值一致,不要轉換為dict,也不要轉換為string。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM