微信app支付功能-服务端的实现-python3版
- 一:需求说明
- 二:微信app支付处理流程
- 三:所需依赖
- 3.1 支付配置
- 四:接口开发
- 4.1 创建订单接口
- 4.2 微信异步回调接口
- 4.3 订单状态查询
一:需求说明
在自有的android app中加入微信支付功能,app内点击支付按钮,需app调起本机上的微信app进行支付。
二:微信app支付处理流程
上图为微信官方支付流程图,由图可见,商户服务端需要实现3部分业务,其中2部分业务与商户客户端调用相关,另一部分业务与微信服务端回调相关。
注:开发文档路径:访问微信开发文档https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3
三:所需依赖
3.1 支付配置
APP_ID
在微信开放平台(https://open.weixin.qq.com)获取APPID
如果没有创建应用,请先创建
MCH_ID
在微信商户平台获取MCH_ID
API密钥
获取API密钥
四:接口开发
4.1 创建订单接口
创建订单,对订单数据进行签名,然后向微信服务器发送创建订单请求。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import traceback
import logging
import uuid
import requests
import json
import xmltodict
import time
import pymysql
import datetime
import random
from hashlib import md5
MYSQL = dict(
host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()
###############################################
############# 微信支付配置 #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'
def make_payment_info(notify_url=None, out_trade_no=None, total_fee=None):
order_info = {'appid': WEIXIN_APP_ID,
'mch_id': WEIXIN_MCH_ID,
'device_info': 'WEB',
'nonce_str': '',
'sign_type': WEIXIN_SIGN_TYPE,
'body': WEIXIN_BODY,
'out_trade_no': str(out_trade_no),
'total_fee': total_fee,
'spbill_create_ip': WEIXIN_SPBILL_CREATE_IP,
'notify_url': notify_url,
'trade_type': 'APP'}
return order_info
def make_payment_request_wx(notify_url, out_trade_no, total_fee):
"""
微信统一下单,并返回客户端数据
:param notify_url: 回调地址
:param out_trade_no: 订单编号
:param total_fee: 充值金额
:return: app所需结果数据
"""
def generate_call_app_data(params_dict, prepay_id):
"""
客户端APP的数据参数包装
"""
request_order_info = {'appid': params_dict['appid'],
'partnerid': params_dict['mch_id'],
'prepayid': prepay_id,
'package': 'Sign=WXPay',
'noncestr': generate_nonce_str(),
'timestamp': str(int(time.time()))}
request_order_info['sign'] = generate_sign(request_order_info)
return request_order_info
def generate_sign(params):
"""
生成md5签名的参数
"""
if 'sign' in params:
params.pop('sign')
src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
return md5(src.encode('utf-8')).hexdigest().upper()
def generate_nonce_str():
"""
生成随机字符串
"""
return str(uuid.uuid4()).replace('-', '')
def generate_request_data(params_dict):
"""
生成统一下单请求所需要提交的数据
"""
params_dict['nonce_str'] = generate_nonce_str()
params_dict['sign'] = generate_sign(params_dict)
return xmltodict.unparse({'xml': params_dict}, pretty=True, full_document=False).encode('utf-8')
def make_payment_request(params_dict, unified_order_url):
"""
生成返回给客户端APP的数据参数
"""
data = generate_request_data(params_dict)
headers = {'Content-Type': 'application/xml'}
res = requests.post(unified_order_url, data=data, headers=headers)
if res.status_code == 200:
result = json.loads(json.dumps(xmltodict.parse(res.content)))
if result['xml']['return_code'] == 'SUCCESS':
prepay_id = result['xml']['prepay_id']
return generate_call_app_data(params_dict, prepay_id), result['xml']
else:
return result['xml']['return_msg'], None
return None, None
if float(total_fee) < 0.01:
raise Exception('充值金额不能小于0.01')
payment_info = make_payment_info(notify_url=notify_url, out_trade_no=out_trade_no, total_fee=total_fee)
res, info = make_payment_request(payment_info, WEIXIN_UNIFIED_ORDER_URL)
return res, info
def create_order(data, out_trade_no):
"""
创建订单信息,存入库中
:return:
"""
insert_sql = ''' insert into {table}(status, app_id, seller_id, device_info, trade_type, prepay_id, trade_status,
out_trade_no, total_amount)
values (3, '{app_id}', '{seller_id}', '{device_info}', '{trade_type}', '{prepay_id}', '{trade_status}',
'{out_trade_no}', '{total_amount}')'''
app_id = data['appid'] # 应用ID
seller_id = data['mch_id'] # 商户号
device_info = data['device_info'] # 微信支付分配的终端设备号
trade_status = data['result_code'] # 业务结果 SUCCESS/FAIL
total_amount = data['total_amount'] # 总金额
if trade_status == "SUCCESS":
trade_type = data['trade_type'] # 交易类型
prepay_id = data['prepay_id'] # 预支付交易会话标识
insert_sql = insert_sql.format(
app_id=app_id,
seller_id=seller_id,
device_info=device_info,
trade_type=trade_type,
prepay_id=prepay_id,
trade_status=trade_status,
out_trade_no=out_trade_no,
total_amount=total_amount/100 # 将微信的分转为元
)
cur_dict.execute(insert_sql)
return True
else:
return False
def create_order_number():
"""
生成订单号
:return:
"""
date = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
# 生成4为随机数作为订单号的一部分
random_str = str(random.randint(1, 9999))
random_str = random_str.rjust(4, '0')
rtn = '%s%s%s' % (date, random_str)
return rtn
def weixin_create_order(request):
"""
【API】: 创建订单,供商户app调用
"""
res = {
'code': 1,
'msg': 'error'
}
try:
price = 0.99 # 0.99元,微信的单位为分,需要转为分
out_trade_no = create_order_number()
order_info, info = make_payment_request_wx(WEIXIN_CALLBACK_API, out_trade_no, int(price * 100))
if order_info and info:
info['total_amount'] = int(price * 100)
if info['result_code'] == "SUCCESS":
order_info['out_trade_no'] = out_trade_no
res['order_info'] = order_info
create_order(info, out_trade_no)
# 调用统一创建订单接口失败
else:
res['msg'] = info['result_code']
elif order_info:
res['msg'] = order_info
res['code'] = -1
else:
res['code'] = -2
except Exception:
traceback.print_exc()
finally:
return json.dumps(res)
4.2 微信异步回调接口
微信异步回调通知商户服务端。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import traceback
import logging
import xmltodict
import pymysql
from hashlib import md5
MYSQL = dict(
host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()
###############################################
############# 微信支付配置 #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'
def weixinpay_call_back(request):
"""
微信支付回调
:param request: 回调参数
:return:
"""
def generate_sign(params):
"""
生成md5签名的参数
"""
if 'sign' in params:
params.pop('sign')
src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
return md5(src.encode('utf-8')).hexdigest().upper()
def validate_sign(resp_dict):
"""
验证微信返回的签名
"""
if 'sign' not in resp_dict:
return False
wx_sign = resp_dict['sign']
sign = generate_sign(resp_dict)
if sign == wx_sign:
return True
return False
def handle_wx_response_xml(params):
"""
处理微信支付返回的xml格式数据
"""
resp_dict = xmltodict.parse(params)['xml']
return_code = resp_dict.get('return_code')
if return_code == 'SUCCESS': # 仅仅判断通信标识成功,非交易标识成功,交易需判断result_code
if validate_sign(resp_dict):
return resp_dict
else:
print('FAIL')
return
args = request.body
# 验证平台签名
resp_dict = handle_wx_response_xml(args)
if resp_dict is None:
return None
return resp_dict
def weixinpay_response_xml(params):
"""
生成交易成功返回信息
"""
def generate_response_data(resp_dict):
"""
字典转xml
"""
return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')
return_info = {
'return_code': params,
'return_msg': 'OK'
}
return generate_response_data(return_info)
def weixin_rollback(request):
"""
【API】: 微信宝支付结果回调接口,供微信服务端调用
"""
try:
# 支付异步回调验证
data = weixinpay_call_back(request)
if data:
res = "success"
trade_status = data['result_code'] # 业务结果 SUCCESS/FAIL
out_trade_no = data['out_trade_no'] # 商户订单号
if trade_status == "SUCCESS":
status = 1
app_id = data['appid'] # 应用ID
bank_type = data['bank_type'] # 付款银行
cash_fee = data['cash_fee'] # 现金支付金额(分)
device_info = data['device_info'] # 微信支付分配的终端设备号
fee_type = data['fee_type'] # 货币种类
gmt_create = data['time_end'] # 支付完成时间
total_amount = int(data['total_fee'])/100 # 总金额(单位由分转元)
trade_type = data['trade_type'] # 交易类型
trade_no = data['transaction_id'] # 微信支付订单号
seller_id = data['mch_id'] # 商户号
buyer_id = data['openid'] # 用户标识
update_sql = ''' update orders set trade_status='{trade_status}', app_id='{app_id}',
seller_id='{seller_id}', buyer_id='{buyer_id}', total_amount='{total_amount}',
out_trade_no='{out_trade_no}', gmt_create='{gmt_create}', trade_no='{trade_no}',
device_info='{device_info}', trade_type='{trade_type}', bank_type='{bank_type}',
fee_type='{fee_type}', cash_fee='{cash_fee}',
status='{status}' where out_trade_no='{out_trade_no}' '''
update_sql = update_sql.format(
app_id=app_id,
bank_type=bank_type,
cash_fee=cash_fee,
device_info=device_info,
fee_type=fee_type,
out_trade_no=out_trade_no,
gmt_create=gmt_create,
total_amount=total_amount,
trade_type=trade_type,
trade_no=trade_no,
seller_id=seller_id,
buyer_id=buyer_id,
trade_status=trade_status,
status=status
)
else:
res = "error: pay failed! "
status = 0
err_code = data['err_code'] # 错误代码
err_code_des = data['err_code_des'] # 错误代码描述
update_sql = ''' update orders set trade_status='{trade_status}', err_code='{err_code}',
err_code_des='{err_code_des}', status='{status}' where out_trade_no='{out_trade_no}' '''
update_sql = update_sql.format(
out_trade_no=out_trade_no,
trade_status=trade_status,
status=status,
err_code=err_code,
err_code_des=err_code_des,
)
cur_dict.execute(update_sql)
conn.commit()
else:
res = "error: verify failed! "
except Exception:
traceback.print_exc()
finally:
return weixinpay_response_xml(res)
4.3 订单状态查询
商户app调用此接口查询【微信服务端】某个订单的支付状态,并同步更新商户服务端订单状态。
可根据需要,将此步骤做成异步任务,实时更新商户服务端订单状态。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import traceback
import logging
import xmltodict
import pymysql
import uuid
import json
import requests
from hashlib import md5
MYSQL = dict(
host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()
###############################################
############# 微信支付配置 #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'
def update_order(data):
"""
查询支付订单,并更新订单
:param data:
:return:
"""
# err_code = data['err_code'] # 错误代码
# err_code_des = data['err_code_des'] # 错误代码描述
trade_status = data['result_code'] # 业务结果 SUCCESS/FAIL
app_id = data['appid'] # 应用ID
seller_id = data['mch_id'] # 商户号
if trade_status == "SUCCESS":
buyer_id = data['openid'] # 用户标识
total_amount = int(data['total_fee']) / 100 # 总金额(元)
out_trade_no = data['out_trade_no'] # 商户订单号
gmt_create = data['time_end'] # 支付完成时间
trade_no = data['transaction_id'] # 微信支付订单号
trade_status = data['trade_state'] # 交易状态
if trade_status == "SUCCESS":
status = 1
elif trade_status == "USERPAYING":
status = 2
else:
status = 0
msg = data['trade_state_desc']
# SUCCESS—支付成功
# REFUND—转入退款
# NOTPAY—未支付
# CLOSED—已关闭
# REVOKED—已撤销(刷卡支付)
# USERPAYING--用户支付中
# PAYERROR--支付失败(其他原因,如银行返回失败)
device_info = data['device_info'] # 微信支付分配的终端设备号
trade_type = data['trade_type'] # 交易类型
bank_type = data['bank_type'] # 付款银行
fee_type = data['fee_type'] # 货币种类
cash_fee = data['cash_fee'] # 现金支付金额(分)
# cash_fee_type = data['cash_fee_type'] # 现金支付货币类型
# nonce_str = data['nonce_str'] # 随机字符串
# coupon_fee = data['coupon_fee'] # 代金券金额
# coupon_count = data['coupon_count'] # 代金券使用数量
# coupon_id_$n = data['coupon_id_$n'] # 代金券ID
# coupon_fee_$n = data['coupon_fee_$n'] # 单个代金券支付金额
update_sql = ''' update orders set app_id='{app_id}',
seller_id='{seller_id}', buyer_id='{buyer_id}', total_amount='{total_amount}',
out_trade_no='{out_trade_no}', gmt_create='{gmt_create}', trade_no='{trade_no}',
device_info='{device_info}', trade_type='{trade_type}', bank_type='{bank_type}',
fee_type='{fee_type}', cash_fee='{cash_fee}',
status='{status}',
trade_status='{trade_status}' where out_trade_no='{out_trade_no}' '''
update_sql = update_sql.format(
trade_status=trade_status,
app_id=app_id,
seller_id=seller_id,
buyer_id=buyer_id,
total_amount=total_amount,
out_trade_no=out_trade_no,
gmt_create=gmt_create,
trade_no=trade_no,
device_info=device_info,
trade_type=trade_type,
bank_type=bank_type,
fee_type=fee_type,
cash_fee=cash_fee,
status=status
# err_code_des=err_code_des,
# err_code=err_code
)
cur_dict.execute(update_sql)
else:
msg = trade_status
status = 0
update_sql = '''update {table} set
trade_status='{trade_status}', app_id='{app_id}', seller_id='{seller_id}', status='{status}'
where id={id} and status!=1 '''
update_sql = update_sql.format(
# err_code=err_code,
# err_code_des=err_code_des,
trade_status=trade_status,
app_id=app_id,
seller_id=seller_id,
id=id,
status=status
)
cur_dict.execute(update_sql)
conn.commit()
return status, msg
def make_querypayment_request(params_dict, query_order_url):
"""
生成查询订单返回的数据参数
"""
def generate_nonce_str():
"""
生成随机字符串
"""
return str(uuid.uuid4()).replace('-', '')
def generate_sign(params):
"""
生成md5签名的参数
"""
if 'sign' in params:
params.pop('sign')
src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
return md5(src.encode('utf-8')).hexdigest().upper()
def generate_request_data(params_dict):
"""
生成统一下单请求所需要提交的数据
"""
params_dict['nonce_str'] = generate_nonce_str()
params_dict['sign'] = generate_sign(params_dict)
return xmltodict.unparse({'xml': params_dict}, pretty=True, full_document=False).encode('utf-8')
data = generate_request_data(params_dict)
headers = {'Content-Type': 'application/xml'}
res = requests.post(query_order_url, data=data, headers=headers)
if res.status_code == 200:
result = json.loads(json.dumps(xmltodict.parse(res.content)))
# if result['xml']['return_code'] == 'SUCCESS':
# prepay_id = result['xml']['prepay_id']
# return generate_call_app_data(params_dict, prepay_id)
# else:
return result['xml']
return None
def weixin_orderquery(request):
"""
【API】:支付状态查询,供商户客户端app调用
"""
res = {
'code': 1,
'status': 0,
'msg': '支付失败!未知错误!'
}
out_trade_no = request.POST.get('out_trade_no') # 商户订单号
try:
select_sql = '''select id, app_id, trade_no, seller_id, status from orders
where out_trade_no={out_trade_no} '''
select_sql = select_sql.format(out_trade_no=out_trade_no)
cur_dict.execute(select_sql)
order_data = cur_dict.fetchone()
if order_data:
id = order_data['id']
# 支付成功
if order_data['status'] == 1:
res['status'] = 1
res['msg'] = '支付成功!'
# 支付失败
elif order_data['status'] == 0:
res['status'] = 0
res['msg'] = '支付失败!'
# 支付过程中, 查询微信服务器支付状态
else:
params_dict = {
'appid': order_data['app_id'],
'mch_id': order_data['seller_id'],
'transaction_id': order_data['trade_no']
}
data = make_querypayment_request(params_dict, WEIXIN_QUERY_ORDER_URL)
if data:
if data['return_code'] == 'SUCCESS':
trade_status = data['result_code'] # 业务结果 SUCCESS/FAIL
if trade_status == "SUCCESS":
res['status'], res['msg'] = update_order(data)
elif trade_status == "ORDERNOTEXIST":
res['msg'] = "支付错误! 微信服务器返回的订单号不存在!"
res['status'] = 0
elif trade_status == "SYSTEMERROR":
res['msg'] = "支付错误! 微信服务器错误!"
res['status'] = 0
else:
res['status'] = 0
res['msg'] = "支付错误! 微信服务器支付错误!"
else:
res['status'] = 0
res['msg'] = data['return_msg']
else:
res['msg'] = "支付错误! 微信服务器通信错误!"
else:
res['status'] = 0
res['msg'] = "订单号不存在!"
except Exception:
traceback.print_exc()
finally:
return json.dumps(res)