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))
-