支付金融支付互联网科技

微信, 支付宝扫码支付总结

2017-03-05  本文已影响300人  single430

好不容易折腾微信和支付宝的开发文档才搞出来的


一: 支付宝扫码支付

import datetime
import random
import hashlib
import requests
import time
import base64

from urllib import parse
from OpenSSL.crypto import load_privatekey, FILETYPE_PEM, sign
from OpenSSL import crypto


# 支付宝 扫码支付
def alipay_url(order_id, total_amount, timeout_express, subject, body=None):
    '''
    TODO: 扫码支付 - 430
    公共参数
    app_id          String  是   32  支付宝分配给开发者的应用ID  2014072300007148
    method          String  是   128 接口名称    alipay.trade.app.pay
    charset         String  是   10  请求使用的编码格式,如utf-8,gbk,gb2312等    utf-8
    sign_type       String  是   10  商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2   RSA2
    sign            String  是   256 商户请求参数的签名串,详见签名 详见示例
    timestamp       String  是   19  发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
    version         String  是   3   调用的接口版本,固定为:1.0 1.0
    notify_url      String  是   256 支付宝服务器主动通知商户服务器里指定的页面http/https路径。建议商户使用https   https://api.xx.com/receive_notify.htm
    biz_content     String  是   -   业务请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档

    业务参数
    body            String  否   128 对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。 Iphone6 16G
    subject         String  是   256 商品的标题/交易标题/订单标题/订单关键字等。 大乐透
    out_trade_no    String  是   64  商户网站唯一订单号   70501111111S001111119
    timeout_express String  否   6   设置未付款支付宝交易的超时时间,一旦超时,该笔交易就会自动被关闭。当用户进入支付宝收银台页面(不包括登录页面),会触发即刻创建支付宝交易,此时开始计时。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。   90m
    total_amount    String  是   9   订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]   9.00
    seller_id       String  否   16  收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID  2088102147948060
    product_code    String  是   64  销售产品码,商家和支付宝签约的产品码,为固定值QUICK_MSECURITY_PAY  QUICK_MSECURITY_PAY
    goods_type      String  否   2   商品主类型:0—虚拟类商品,1—实物类商品 注:虚拟类商品不支持使用花呗渠道  0

    app_id=2015052600090779
    &biz_content={
        "timeout_express":"30m",
        "seller_id":"",
        "product_code":"QUICK_MSECURITY_PAY",
        "total_amount":"0.01",
        "subject":"1",
        "body":"我是测试数据",
        "out_trade_no":"IQJZSRC1YMQB5HU"
    }
    &charset=utf-8
    &format=json
    &method=alipay.trade.app.pay
    &notify_url=http://domain.merchant.com/payment_notify
    &sign_type=RSA2
    &timestamp=2016-08-25 20:26:31
    &version=1.0
    &sign=cYmuUnKi5QdBsoZEAbMXVMmRWjsuUj+y48A2DvWAVVBuYkiBj13CFDHu2vZQvmOfkjE0YqCUQE04kqm9Xg3tIX8tPeIGIFtsIyp/M45w1ZsDOiduBbduGfRo1XRsvAyVAv2hCrBLLrDI5Vi7uZZ77Lo5J0PpUUWwyQGt0M4cj8g=
    '''
    url = {
        'app_id': ALIPAY_APP_ID,
        'version': '1.0',
        'format': 'json',
        'sign_type': 'RSA',
        'method': ALIPAY_PRECREATE,
        'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        'notify_url': ALIPAY_NOTIFY_URL,
        'charset': 'utf-8',
    }

    biz_content = {
        "timeout_express": "{}".format(timeout_express),
        # "product_code": "QUICK_MSECURITY_PAY",
        "total_amount": "{}".format(total_amount),
        "subject": subject,
        "body": body,
        "out_trade_no": "{}".format(order_id),
        "goods_type": "0",
    }
    # print(biz_content)
    url['biz_content'] = str(biz_content)

    s_url = format_data(url, False)
    key = load_privatekey(FILETYPE_PEM, open("you_private_key.pem").read())
    sign_alipay = sign(key, s_url, 'sha1')
    sign_alipay = base64.b64encode(sign_alipay)
    url['sign'] = str(sign_alipay, encoding='utf-8')

    return url

上面就是支付宝扫码支付,如果成功会返回二维码链接,只需调用库生成二维码就可以。

if result_dict['code'] == "10000" and result_dict['msg'] == "Success":
    print("订单号: {0} 支付宝扫码支付二维码url为: {1}".format(order_id, result_dict['qr_code'].replace('\\', '')))

字典排序函数

def format_data(data, urlencode):
    """格式化参数,签名过程需要使用"""
    keys = sorted(data.keys())
    buff = []
    for key in keys:
        value = parse.quote(data[key]) if urlencode else data[key]
        buff.append("{0}={1}".format(key, value))

    return "&".join(buff)

微信扫码支付

# 微信 扫码支付
class WeiXinPay(object):
    """
    TODO: 微信 - 430
    """

    # 随机字符串生成(15<num<32位)
    def create_nonce_str(self):
        length = random.randrange(15, 32)
        chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        strs = []
        for x in range(length):
            strs.append(chars[random.randrange(0, len(chars))])
        return "".join(strs)

    def get_sign(self, obj):
        """生成签名"""
        # 签名步骤一:按字典序排序参数
        str = format_data(obj, False)
        # 签名步骤二:在string后加入KEY
        str = "{0}&key={1}".format(str, WEIXIN_KEY)
        # 签名步骤三:MD5加密
        str = hashlib.md5(str.encode('utf-8')).hexdigest()
        # 签名步骤四:所有字符转为大写
        sign = str.upper()
        return sign

    def dict_to_xml(self, dict):
        """dict转xml"""
        xml = ["<xml>"]
        for key in dict:
            if dict[key]:
                xml.append("<{0}>{1}</{0}>".format(key, dict[key]))
            else:
                xml.append("<{0}><![CDATA[{1}]]></{0}>".format(key, dict[key]))
        xml.append("</xml>")
        return "".join(xml)

    def weixin_native_pay_url(self, order_id, total_fee, ip, time_expire = 5):
        '''
        字段名 变量名 必填  类型  示例值 描述
        应用ID    appid   是   String(32)  wxd678efh567hg6787  微信开放平台审核通过的应用APPID
        商户号 mch_id  是   String(32)  1230000109  微信支付分配的商户号
        设备号 device_info 否   String(32)  013467007045764 终端设备号(门店号或收银设备ID),默认请传"WEB"
        随机字符串   nonce_str   是   String(32)  5K8264ILTKCH16CQ2502SI8ZNMTM67VS    随机字符串,不长于32位。推荐随机数生成算法
        签名  sign    是   String(32)  C380BEC2BFD727A4B6845133519F3AD6    签名,详见签名生成算法
        签名类型    sign_type   否   String(32)  HMAC-SHA256 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
        商品描述    body    是   String(128) 腾讯充值中心-QQ会员充值
        商品描述交易字段格式根据不同的应用场景按照以下格式:
        APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。
        商品详情    detail  否   String(8192)
        {
            "goods_detail":[
                {
                    "goods_id":"iphone6s_16G",
                    "wxpay_goods_id":"1001",
                    "goods_name":"iPhone6s 16G",
                    "quantity":1,
                    "price":528800,
                    "goods_category":"123456",
                    "body":"苹果手机"
                },
                {
                    "goods_id":"iphone6s_32G",
                    "wxpay_goods_id":"1002",
                    "goods_name":"iPhone6s 32G",
                    "quantity":1,
                    "price":608800,
                    "goods_category":"123789",
                    "body":"苹果手机"
                }
            ]
        }
        商品详细列表,使用Json格式,传输签名前请务必使用CDATA标签将JSON文本串保护起来。
        goods_detail 服务商必填 []:
        └ goods_id String 必填 32 商品的编号
        └ wxpay_goods_id String 可选 32 微信支付定义的统一商品编号
        └ goods_name String 必填 256 商品名称
        └ quantity Int 必填 商品数量
        └ price Int 必填 商品单价,单位为分
        └ goods_category String 可选 32 商品类目ID
        └ body String 可选 1000 商品描述信息
        附加数据    attach  否   String(127) 深圳分店    附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
        商户订单号   out_trade_no    是   String(32)  20150806125346  商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
        货币类型    fee_type    否   String(16)  CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
        总金额 total_fee   是   Int 888 订单总金额,单位为分,详见支付金额
        终端IP    spbill_create_ip    是   String(16)  123.12.12.123   用户端实际ip
        交易起始时间  time_start  否   String(14)  20091225091010  订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
        交易结束时间  time_expire 否   String(14)  20091227091010
        订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
        注意:最短失效时间间隔必须大于5分钟
        商品标记    goods_tag   否   String(32)  WXG 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
        通知地址    notify_url  是   String(256) http://www.weixin.qq.com/wxpay/pay.php  接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
        交易类型    trade_type  是   String(16)  APP 支付类型
        指定支付方式  limit_pay   否   String(32)  no_credit   no_credit--指定不能使用信用卡支付
        '''
        order_time = datetime.datetime.now()
        weixin_data = {
            'appid': WEIXIN_APPID,
            'mch_id': WEXIIN_MCHID,
            'device_info': WEIXIN_DEVICE_INFO,
            'nonce_str': self.create_nonce_str(),
            'sign_type': 'MD5',
            'body': '费用支付',
            # 'detail': {},
            'out_trade_no': '{}'.format(order_id),
            'total_fee': int(total_fee*100),    # 分,不能有小数
            'spbill_create_ip': '{}'.format(ip),
            'time_start': order_time.strftime('%Y%m%d%H%M%S'),
            'time_expire': (order_time + datetime.timedelta(minutes=time_expire)).strftime('%Y%m%d%H%M%S'),
            'notify_url': WEIXIN_NOTIFY_URL,
            'trade_type': 'NATIVE'
        }
        weixin_data['sign'] = self.get_sign(weixin_data)
        url = format_data(weixin_data, False)

        # 检测必填参数
        if any(weixin_data[key] is None for key in ("out_trade_no", "body", "total_fee", "notify_url", "trade_type")):
            raise ValueError("丢失必要参数")

        # return 'weixin://wxpay/bizpayurl?' + url
        return self.dict_to_xml(weixin_data)

上面就是微信扫码支付,如果成功会返回二维码链接,只需调用库生成二维码就可以。不过返回的结果是xml格式的,需要自己调用库区解析。

from xml.etree import ElementTree
xml_tree = ElementTree.fromstring(weixin_result.text)
if xml_tree.find('return_code').text == "SUCCESS" and xml_tree.find('result_code').text == "SUCCESS":
    print("订单号: {0} 微信扫码支付二维码url为: {1}".format(order_id, xml_tree.find('code_url').text))

最后在写一个银联的签名过程,也是折腾了一番

    # 1. 按ascii排序。【注意不是字母顺序】
    # 2. 对1的结果sha1得到byte数组。
    # 3. 对2的结果用16进制小写字符串表示。【注意必须是小写】
    # 4. 对3的结果取byte数组。【注意不是16进制字符串转byte数组,而是当普通字符串转】
    # 5. 对4的结果用私钥算签名,算法为rsawithsha1,得到一个byte数组。
    # 6. 对5的结果做base64,得到一个字符串就是签名。
    # * C语言开发请特别注意测试公私钥可能和生产的长度不同,实现时请注意不要固定长度做。
    
    data = {'自己的参数': '自己的数据'}
    fs = open('700000000000001_acp.pfx', 'rb')    # 官方测试私钥
    cert_pfx = crypto.load_pkcs12(fs.read(), '000000')
    fs.close()

    cert_id = cert_pfx.get_certificate().get_serial_number()

    data_str = format_data(data, False)
    sign_digest = hashlib.sha1(data_str.encode('utf-8')).hexdigest().lower()
    private_key = cert_pfx.get_privatekey()
    soft_sign = sign(private_key, sign_digest, 'sha1')
    base_sign = base64.b64encode(soft_sign)

银联官方技术服务之签名算法与注意点

推荐一个工具,做get, post等测试的时候很不错
Postman

By: single430
上一篇下一篇

猜你喜欢

热点阅读