微信, 支付宝扫码支付总结
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
¬ify_url=http://domain.merchant.com/payment_notify
&sign_type=RSA2
×tamp=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
- 上述所有大写参数请自行添加
- 写的不好,还望见谅与建议