微信app支付功能-服務(wù)端的實(shí)現(xiàn)-版
一:需求說明
在自有的 app中加入微信支付功能微信支付接口web端能用app嗎,app內(nèi)點(diǎn)擊支付按鈕,需app調(diào)起本機(jī)上的微信app進(jìn)行支付。
二:微信app支付處理流程
上圖為微信官方支付流程圖微信支付接口web端能用app嗎,由圖可見,商戶服務(wù)端需要實(shí)現(xiàn)3部分業(yè)務(wù),其中2部分業(yè)務(wù)與商戶客戶端調(diào)用相關(guān),另一部分業(yè)務(wù)與微信服務(wù)端回調(diào)相關(guān)。
注:開發(fā)文檔路徑:訪問微信開發(fā)文檔
三:所需依賴 3.1 支付配置
在微信開放平臺()獲取APPID
如果沒有創(chuàng)建應(yīng)用,請先創(chuàng)建
在微信商戶平臺獲取
API密鑰
獲取API密鑰
四:接口開發(fā) 4.1 創(chuàng)建訂單接口
創(chuàng)建訂單,對訂單數(shù)據(jù)進(jìn)行簽名,然后向微信服務(wù)器發(fā)送創(chuàng)建訂單請求。
#!/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'
# 服務(wù)器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '費(fèi)用充值'
# 微信KEY值 【API密鑰】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信統(tǒng)一下單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'
# 微信支付回調(diào)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):
"""
微信統(tǒng)一下單,并返回客戶端數(shù)據(jù)
:param notify_url: 回調(diào)地址
:param out_trade_no: 訂單編號
:param total_fee: 充值金額
:return: app所需結(jié)果數(shù)據(jù)
"""
def generate_call_app_data(params_dict, prepay_id):
"""
客戶端APP的數(shù)據(jù)參數(shù)包裝
"""
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簽名的參數(shù)
"""
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():
"""
生成隨機(jī)字符串
"""
return str(uuid.uuid4()).replace('-', '')
def generate_request_data(params_dict):
"""
生成統(tǒng)一下單請求所需要提交的數(shù)據(jù)
"""
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的數(shù)據(jù)參數(shù)
"""
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):
"""
創(chuàng)建訂單信息,存入庫中
: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'] # 應(yīng)用ID
seller_id = data['mch_id'] # 商戶號
device_info = data['device_info'] # 微信支付分配的終端設(shè)備號
trade_status = data['result_code'] # 業(yè)務(wù)結(jié)果 SUCCESS/FAIL
total_amount = data['total_amount'] # 總金額
if trade_status == "SUCCESS":
trade_type = data['trade_type'] # 交易類型
prepay_id = data['prepay_id'] # 預(yù)支付交易會話標(biāo)識
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 # 將微信的分轉(zhuǎn)為元
)
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為隨機(jī)數(shù)作為訂單號的一部分
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】: 創(chuàng)建訂單,供商戶app調(diào)用
"""
res = {
'code': 1,
'msg': 'error'
}
try:
price = 0.99 # 0.99元,微信的單位為分,需要轉(zhuǎn)為分
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)
# 調(diào)用統(tǒng)一創(chuàng)建訂單接口失敗
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 微信異步回調(diào)接口
微信異步回調(diào)通知商戶服務(wù)端。
#!/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'
# 服務(wù)器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '費(fèi)用充值'
# 微信KEY值 【API密鑰】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信統(tǒng)一下單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'
# 微信支付回調(diào)API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'
def weixinpay_call_back(request):
"""
微信支付回調(diào)
:param request: 回調(diào)參數(shù)
:return:
"""
def generate_sign(params):
"""
生成md5簽名的參數(shù)
"""
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):
"""
驗(yàn)證微信返回的簽名
"""
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格式數(shù)據(jù)
"""
resp_dict = xmltodict.parse(params)['xml']
return_code = resp_dict.get('return_code')
if return_code == 'SUCCESS': # 僅僅判斷通信標(biāo)識成功,非交易標(biāo)識成功,交易需判斷result_code
if validate_sign(resp_dict):
return resp_dict
else:
print('FAIL')
return
args = request.body
# 驗(yàn)證平臺簽名
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):
"""

字典轉(zhuǎn)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】: 微信寶支付結(jié)果回調(diào)接口,供微信服務(wù)端調(diào)用
"""
try:
# 支付異步回調(diào)驗(yàn)證
data = weixinpay_call_back(request)
if data:
res = "success"
trade_status = data['result_code'] # 業(yè)務(wù)結(jié)果 SUCCESS/FAIL
out_trade_no = data['out_trade_no'] # 商戶訂單號
if trade_status == "SUCCESS":
status = 1
app_id = data['appid'] # 應(yīng)用ID
bank_type = data['bank_type'] # 付款銀行
cash_fee = data['cash_fee'] # 現(xiàn)金支付金額(分)
device_info = data['device_info'] # 微信支付分配的終端設(shè)備號
fee_type = data['fee_type'] # 貨幣種類
gmt_create = data['time_end'] # 支付完成時(shí)間
total_amount = int(data['total_fee'])/100 # 總金額(單位由分轉(zhuǎn)元)
trade_type = data['trade_type'] # 交易類型
trade_no = data['transaction_id'] # 微信支付訂單號
seller_id = data['mch_id'] # 商戶號
buyer_id = data['openid'] # 用戶標(biāo)識
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'] # 錯(cuò)誤代碼
err_code_des = data['err_code_des'] # 錯(cuò)誤代碼描述
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 訂單狀態(tài)查詢
商戶app調(diào)用此接口查詢【微信服務(wù)端】某個(gè)訂單的支付狀態(tài),并同步更新商戶服務(wù)端訂單狀態(tài)。
可根據(jù)需要,將此步驟做成異步任務(wù),實(shí)時(shí)更新商戶服務(wù)端訂單狀態(tài)。
#!/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'
# 服務(wù)器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '費(fèi)用充值'
# 微信KEY值 【API密鑰】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信統(tǒng)一下單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'
# 微信支付回調(diào)API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'
def update_order(data):
"""
查詢支付訂單,并更新訂單
:param data:
:return:
"""
# err_code = data['err_code'] # 錯(cuò)誤代碼
# err_code_des = data['err_code_des'] # 錯(cuò)誤代碼描述
trade_status = data['result_code'] # 業(yè)務(wù)結(jié)果 SUCCESS/FAIL
app_id = data['appid'] # 應(yīng)用ID
seller_id = data['mch_id'] # 商戶號
if trade_status == "SUCCESS":
buyer_id = data['openid'] # 用戶標(biāo)識
total_amount = int(data['total_fee']) / 100 # 總金額(元)
out_trade_no = data['out_trade_no'] # 商戶訂單號
gmt_create = data['time_end'] # 支付完成時(shí)間
trade_no = data['transaction_id'] # 微信支付訂單號
trade_status = data['trade_state'] # 交易狀態(tài)
if trade_status == "SUCCESS":
status = 1
elif trade_status == "USERPAYING":
status = 2
else:
status = 0
msg = data['trade_state_desc']
# SUCCESS—支付成功
# REFUND—轉(zhuǎn)入退款
# NOTPAY—未支付
# CLOSED—已關(guān)閉
# REVOKED—已撤銷(刷卡支付)
# USERPAYING--用戶支付中
# PAYERROR--支付失敗(其他原因,如銀行返回失敗)
device_info = data['device_info'] # 微信支付分配的終端設(shè)備號
trade_type = data['trade_type'] # 交易類型
bank_type = data['bank_type'] # 付款銀行
fee_type = data['fee_type'] # 貨幣種類
cash_fee = data['cash_fee'] # 現(xiàn)金支付金額(分)
# cash_fee_type = data['cash_fee_type'] # 現(xiàn)金支付貨幣類型
# nonce_str = data['nonce_str'] # 隨機(jī)字符串
# coupon_fee = data['coupon_fee'] # 代金券金額
# coupon_count = data['coupon_count'] # 代金券使用數(shù)量
# coupon_id_$n = data['coupon_id_$n'] # 代金券ID
# coupon_fee_$n = data['coupon_fee_$n'] # 單個(gè)代金券支付金額
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):
"""
生成查詢訂單返回的數(shù)據(jù)參數(shù)
"""
def generate_nonce_str():
"""
生成隨機(jī)字符串
"""
return str(uuid.uuid4()).replace('-', '')
def generate_sign(params):
"""
生成md5簽名的參數(shù)
"""
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):
"""
生成統(tǒng)一下單請求所需要提交的數(shù)據(jù)
"""
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】:支付狀態(tài)查詢,供商戶客戶端app調(diào)用
"""
res = {
'code': 1,
'status': 0,
'msg': '支付失敗!未知錯(cuò)誤!'
}
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'] = '支付失敗!'
# 支付過程中, 查詢微信服務(wù)器支付狀態(tài)
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'] # 業(yè)務(wù)結(jié)果 SUCCESS/FAIL
if trade_status == "SUCCESS":
res['status'], res['msg'] = update_order(data)
elif trade_status == "ORDERNOTEXIST":
res['msg'] = "支付錯(cuò)誤! 微信服務(wù)器返回的訂單號不存在!"
res['status'] = 0
elif trade_status == "SYSTEMERROR":
res['msg'] = "支付錯(cuò)誤! 微信服務(wù)器錯(cuò)誤!"
res['status'] = 0
else:
res['status'] = 0
res['msg'] = "支付錯(cuò)誤! 微信服務(wù)器支付錯(cuò)誤!"
else:
res['status'] = 0
res['msg'] = data['return_msg']
else:
res['msg'] = "支付錯(cuò)誤! 微信服務(wù)器通信錯(cuò)誤!"
else:
res['status'] = 0
res['msg'] = "訂單號不存在!"
except Exception:
traceback.print_exc()
finally:
return json.dumps(res)