django --微信h5,JSAPI支付,退款;支付宝支付
H5支付
from xml.etree import ElementTree as ET
def md5(string):
'''md5加密'''
import hashlib
m = hashlib.md5()
m.update(string.encode('utf-8'))
return m.hexdigest()
class WX_Payment(APIView):
'''微信h5支付'''
def get(self, request, *args, **kwargs):
# 按照微信的规则生成请求参数
info = {
'appid': wx_appid, # 商户id
'mch_id': wx_merchant_number, #商户号
'device_info': "WEB",
'nonce_str': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'sign_type': 'MD5',
'body': '金币充值',
'out_trade_no': str(time.strftime('%Y%m%d%H%M%S') + ''.join(map(str, random.sample(range(0, 9), 6)))),
# 订单号
'total_fee': 0.01, # 总金额
'spbill_create_ip': str(self.request.META.get('REMOTE_ADDR')), # 用户ip
'notify_url': 'http://www.baidu.com', # 异步通知地址
'trade_type': 'MWEB',
'scene_info': {"h5_info": {"type": "Wap", "wap_url": "https://www.baisanxinxi.cn", "wap_name": "金币充值"}}
}
# 签名
temp = '&'.join(
['{0}={1}'.format(k, info[k]) for k in sorted(info)] + ['{0}={1}'.format('key', wx_pay_key, ), ])
sign = md5(temp).upper()
info['sign'] = sign
# https://api.mch.weixin.qq.com/pay/unifiedorder 发请求
xml_string = "<xml>{0}</xml>".format(''.join(['<{0}>{1}</{0}>'.format(k, v) for k, v in info.items()]))
prepay = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_string.encode('utf-8'))
print(prepay.content.decode('utf-8'))
# 从xml中提取 preay_id
root = ET.XML(prepay.content.decode('utf-8'))
prepay_dict = {child.tag: child.text for child in root} # 把xml转为字典
return Response(prepay_dict)
小程序支付
class WXPayment(APIView):
"""
支付
:param {参数名: total_fee, 类型: int, 是否必选: 必选, 说明: 支付金额}
:param {参数名: user_id, 类型: int, 是否必选: 必选, 说明: 用户id}
:param {参数名: obtain_hams, 类型: str, 是否必选: 必选, 说明: 支付后得到的哈姆币}
:return 生成用于支付的签名
"""
def get(self, request, *args, **kwargs):
# 按照微信的规则生成请求参数
total_fee = self.request.GET.get('total_fee') # 支付金额
user_id = self.request.GET.get('user_id') # 主键
obtain_hams = self.request.GET.get('obtain_hams') # 支付后得到的哈姆币
user = User.objects.get(user_id=user_id)
out_trade_no = str(time.strftime('%Y%m%d%H%M%S') + ''.join(map(str, random.sample(range(0, 9), 6)))) # 订单号
info = {
'appid': appid,
'mch_id': much_id,
'nonce_str': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'sign_type': 'MD5',
'body': '哈姆币充值',
'out_trade_no': out_trade_no,
# 订单号
'total_fee': int(total_fee) * 100, # 总金额
'openid': str(user.open_id),
'spbill_create_ip': str(self.request.META.get('REMOTE_ADDR')), # 用户ip
'notify_url': 'https://www.hamudongmanshe.com/front_end/api/wxpayment/', # 异步通知地址
'trade_type': 'JSAPI',
}
print(info)
# 签名
temp = '&'.join(
['{0}={1}'.format(k, info[k]) for k in sorted(info)] + ['{0}={1}'.format('key', pay_key, ), ])
sign = WXPayment.md5(temp).upper()
info['sign'] = sign
print(temp)
# https://api.mch.weixin.qq.com/pay/unifiedorder 发请求
xml_string = "<xml>{0}</xml>".format(''.join(['<{0}>{1}</{0}>'.format(k, v) for k, v in info.items()]))
prepay = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_string.encode('utf-8'))
print(prepay.content.decode('utf-8'))
# 从xml中提取 preay_id
root = ET.XML(prepay.content.decode('utf-8'))
prepay_dict = {child.tag: child.text for child in root} # 把xml转为字典
prepay_id = prepay_dict.get('prepay_id')
print(prepay_dict)
# -------再次签名-------
info_dict = {
'appId': appid,
'timeStamp': str(int(time.time())),
'nonceStr': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'package': 'prepay_id={0}'.format(prepay_id),
'signType': 'MD5',
}
temp = '&'.join(
['{0}={1}'.format(k, info_dict[k]) for k in sorted(info_dict)] + ['{0}={1}'.format('key', pay_key, ), ])
sign2 = WXPayment.md5(temp).upper()
info_dict['sign'] = sign2
info_dict['out_trade_no'] = out_trade_no
WXPayment.establish(user_id, total_fee, obtain_hams, out_trade_no)
return Response(info_dict)
微信支付完成的异步通知
class NotifyView(APIView):
'''微信支付完成的通知'''
def post(self, request, *args, **kwargs):
root = ET.XML(request.body.decode('utf-8'))
result = {child.tag: child.text for child in root}
# 校验签名是否正确
sign = result.pop('sign')
# 商户自己的支付key
temp = '&'.join(
['{0}={1}'.format(k, result[k]) for k in sorted(result)] + ['{0}={1}'.format('key', wx_pay_key, ), ])
local_sign = md5(temp).upper()
if local_sign == sign:
out_trade_no = result.get('out_trade_no') # 自己生成的订单号
resp = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
return Response(resp)
else:
return Response('签名失败')
PC 或 h5 支付
https://github.com/fzlee/alipay/blob/master/docs/apis.zh-hans.md
'''
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.request.AlipayTradeWapPayRequest import AlipayTradeWapPayModel, AlipayTradeWapPayRequest
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
'''
class Payment(APIView):
# 支付宝支付(h5, 移动端)
def get(self, request, *args, **kwargs):
alipay_client_config = AlipayClientConfig()
alipay_client_config.server_url = server_url
alipay_client_config.app_id = ALIPAY_APPID
alipay_client_config.app_private_key = app_private_key
alipay_client_config.alipay_public_key = alipay_public_key
client = DefaultAlipayClient(alipay_client_config)
out_trade_no = str(time.strftime('%Y%m%d%H%M%S') + ''.join(map(str, random.sample(range(0, 9), 6)))) # 订单号
PC_OR_MOVE = self.request.GET.get('PC_OR_MOVE')
recharge_id = self.request.GET.get('recharge_id')
user_id = self.request.GET.get('user_id')
recharge = Recharge.objects.filter(recharge_id=recharge_id).first()
total_amount = str(round(float(recharge.recharge_money_numbers), 2))
notify_url = 'http://www.baisanxinxi.cn/front_end/api/alipay_notify/'
return_url = 'https://www.baisanxinxi.cn/static/index.html#/recharge'
if PC_OR_MOVE == 'MOVE':
model = AlipayTradeWapPayModel() # h5移动端
model.out_trade_no = out_trade_no
model.total_amount = total_amount
model.subject = "金币充值"
model.product_code = 'QUICK_WAP_WAY'
request = AlipayTradeWapPayRequest(biz_model=model)
request.return_url = return_url
request.notify_url = notify_url
alipay_url = client.page_execute(request, "GET")
else:
model = AlipayTradePagePayModel() # PC端
model.out_trade_no = out_trade_no
model.total_amount = total_amount
model.subject = "金币充值"
model.product_code = 'FAST_INSTANT_TRADE_PAY'
request = AlipayTradePagePayRequest(biz_model=model)
request.return_url = return_url
request.notify_url = notify_url
alipay_url = client.page_execute(request, "GET")
return Response({'alipay_url': alipay_url})
支付宝异步通知
@api_view(['POST', ])
def alipay_notify(request: HttpRequest) -> HttpResponse:
'''金币充值支付宝支付异步通知'''
params = request.POST.dict()
out_trade_no = params.get('out_trade_no')
rec = RechargeRecord.objects.filter(recharge_record_out_trade_no=str(out_trade_no)).last() # 数据库的订单号
if rec.recharge_record_out_trade_no == out_trade_no: # 示例验证订单号,实际则验证签名
# 逻辑
return Response('success')
else:
return Response('fail')
官文
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4
demo
import random
import time
from abc import abstractmethod, ABCMeta
from decimal import Decimal
from decimal import getcontext
import requests
from examination.settings import appid, much_id, pay_key, notify_url, BASE_DIR
from xml.etree import ElementTree as ET
def Singleton(cls):
'''单例装饰器'''
_instance = {}
def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
class WxPay(metaclass=ABCMeta):
'''微信支付(元类)'''
def __init__(self, total_fee, out_trade_no, body, open_id, notify_url):
self.total_fee = int(Decimal(str(total_fee)) * Decimal(str(100)))
self.out_trade_no = out_trade_no
self.body = body
self.open_id = open_id
self.notify_url = notify_url
@property
def pay_keys(self):
return {'appid': appid, 'much_id': much_id, 'pay_key': pay_key}
@property
def structure(self):
return {
'appid': self.pay_keys.get('appid'),
'mch_id': self.pay_keys.get('much_id'),
'nonce_str': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'sign_type': 'MD5',
'body': self.body,
'out_trade_no': self.out_trade_no, # 订单号
'total_fee': self.total_fee, # 总金额
'openid': str(self.open_id),
'spbill_create_ip': str('127.0.0.1'), # 用户ip
'notify_url': self.notify_url, # 异步通知地址
'trade_type': 'JSAPI',
}
@staticmethod
def md5(string):
'''md5加密'''
import hashlib
m = hashlib.md5()
m.update(string.encode('utf-8'))
return m.hexdigest()
def fist_sign(self):
'''首次加密'''
info: dict = self.structure
print('预首次签名:' + str(info))
temp = '&'.join(
['{0}={1}'.format(k, info[k]) for k in sorted(info)] + [
'{0}={1}'.format('key', self.pay_keys.get('pay_key'), ), ])
info['sign'] = WxPay.md5(temp).upper()
return info
def request_pay(self):
'''
请求拿预支付标识
:return prepay_dict
'''
info: dict = self.fist_sign()
xml_string = "<xml>{0}</xml>". \
format(''.join(['<{0}>{1}</{0}>'.format(k, v) for k, v in info.items()]))
prepay = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_string.encode('utf-8'))
print(prepay.content.decode('utf-8'))
root = ET.XML(prepay.content.decode('utf-8'))
prepay_dict = {child.tag: child.text for child in root} # 把xml转为字典
if not prepay_dict.get('prepay_id'):
raise Exception
return prepay_dict
def jsapi_sign(self):
'''JSAPI 支付加签'''
info_dict = {
'appId': appid,
'timeStamp': str(int(time.time())),
'nonceStr': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'package': 'prepay_id={0}'.format(self.request_pay().get('prepay_id')),
'signType': 'MD5',
}
temp = '&'.join(
['{0}={1}'.format(k, info_dict[k]) for k in sorted(info_dict)] +
['{0}={1}'.format('key', self.pay_keys.get('pay_key'), ), ])
info_dict['sign'] = self.md5(temp).upper()
info_dict['out_trade_no'] = self.out_trade_no
print('JSAPI返回参数')
print(info_dict)
return info_dict
def pay_notify(self, xml_str: str):
'''
解析支付回调
:param xml_str: 微信传来的xml参数
:return 校验结果;微信规定好的返回值
'''
root = ET.XML(xml_str)
result = {child.tag: child.text for child in root}
sign = result.pop('sign') # 校验签名
temp = '&'.join(
['{0}={1}'.format(k, result[k]) for k in sorted(result)] +
['{0}={1}'.format('key', self.pay_keys.get('pay_key'), ), ])
local_sign = WxPay.md5(temp).upper()
if local_sign == sign:
resp = '<xml>' \
'<return_code><![CDATA[SUCCESS]]></return_code>' \
'<return_msg><![CDATA[OK]]></return_msg>' \
'</xml>'
return True, resp
return False, '<xml>fail</xml>'
@Singleton # 单例装饰器
class WxPayBack(object):
'''微信退款'''
@classmethod
def startback(cls, out_trade_no, out_refund_no, total_fee, refund_fee) -> bool:
'''
:param out_trade_no: 创建订单时自动生成的订单号
:param out_refund_no: 商户退款单号
:param total_fee: 订单金额
:param refund_fee: 退款金额
:return:
'''
info = {
'appid': appid,
'mch_id': much_id,
'nonce_str': ''.join([chr(random.randint(65, 90)) for _ in range(12)]),
'out_trade_no': out_trade_no,
'out_refund_no': out_refund_no,
'total_fee': int(float(total_fee) * 100),
'refund_fee': int(float(refund_fee) * 100),
}
string = "&".join([f"{k}={info[k]}" for k in sorted(info)] + [f"{'key'}={pay_key}"])
info['sign'] = WxPay.md5(string).upper()
xml = "<xml>{}</xml>".format("".join([f"<{k}>{v}</{k}>" for k, v in info.items()]))
cert = f"{BASE_DIR}/apiclient_cert.pem"
key = f"{BASE_DIR}/apiclient_key.pem"
res = requests.post(
url='https://api.mch.weixin.qq.com/secapi/pay/refund',
data=xml.encode('utf-8'),
headers={
'Accept-Language': 'zh-CN,zh;q=0.9'
},
cert=(cert, key),
verify=True
)
result = {child.tag: child.text for child in ET.XML(res.content.decode('utf-8'))}
if result.get('return_code') == 'SUCCESS':
print('参数合法,成功进入请求成功环节!')
if result.get('result_code') == 'SUCCESS':
print('退款请求申请成功')
return True
else:
print(f'失败信息描述:{result.get("err_code_des")}')
print(f'微信返回值dict:{result}')
else:
print(f'提交业务失败,失败原因:{result}')
return False
if __name__ == '__main__':
'''
data = {
'total_fee': 0.01,
'out_trade_no': '112121',
'body': '考研',
'open_id': 'oOKN-5L0G1VHFLyuj8cD315r3Bd0',
'notify_url': notify_url
}
a = WxPay(**data)
a.jsapi_sign()
'''
print(WxPayBack().startback(out_trade_no='112121', out_refund_no='123456', total_fee=0.01, refund_fee=0.01))