├── README.md ├── __init__.py ├── models.py ├── urls.py ├── views.py └── wxlib.py /README.md: -------------------------------------------------------------------------------- 1 | weixin_pay 2 | ========== 3 | 4 | 微信支付的python接口 5 | 6 | 因为工作需要,编写了简单的微信支付的接口,今天测试ok了,删掉其中的私密信息。 7 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinglio8520/weixin_pay/6cc40902b32ba412671d1946bc7c4630edb0f8a9/__init__.py -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinglio8520/weixin_pay/6cc40902b32ba412671d1946bc7c4630edb0f8a9/models.py -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | urlpatterns = patterns('wxpay.views', 4 | url(r'^payable/(?Pp?[0-9]+)', 'payable'), 5 | url(r'^payment_notify', 'payment_notify'), 6 | url(r'^warning_notify', 'warning_notify'), 7 | url(r'^right_notify', 'right_notify'), 8 | url(r'^address_sign', 'get_address_data') 9 | ) 10 | -------------------------------------------------------------------------------- /views.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | import logging 3 | from django.http import HttpResponse 4 | from django.shortcuts import render_to_response, redirect 5 | import time 6 | from django.utils.encoding import smart_str, smart_unicode 7 | from django.views.decorators.csrf import csrf_exempt 8 | from orders.notify import send_wechat_right_notify_mail, send_wechat_warning_notify_mail 9 | from rest_framework.decorators import api_view 10 | from orders.models import Payment 11 | from payments import payment_form_tpl 12 | import json 13 | from rest_framework.response import Response 14 | from wxpay.wxlib import verify_notify, build_form, xml_to_dict, deliver_notify, random_str, build_warning_sign, build_right_sign, get_address_sign 15 | from django.utils.translation import ugettext_lazy as _ 16 | from orders.models import Payment 17 | from rest_framework import status 18 | 19 | 20 | logger = logging.getLogger('payment') 21 | 22 | 23 | def payment_error(errMsg): 24 | return render_to_response('payment_error.html', {'errMsg': errMsg}) 25 | 26 | 27 | def payment_notify(request): 28 | params = request.GET 29 | raw_str = request.body 30 | if params: 31 | logger.info('Wechat Verifying Payment for Request: %s' % unicode(params)) 32 | logger.info('Wechat Xml Append with Request: %s' % unicode(raw_str)) 33 | verifyResult = verify_notify(params) 34 | if verifyResult: 35 | wechat_data = xml_to_dict(raw_str) 36 | out_trade_no = params['out_trade_no'] 37 | total_fee = int(params['total_fee']) 38 | trade_state = params['trade_state'] 39 | logger.info('Wechat Payment Verify Succeeded! The trade state is %s' % trade_state) 40 | 41 | if trade_state == '0': 42 | timestamp = int(time.time()) 43 | parameters = { 44 | 'openid': wechat_data['OpenId'], 45 | 'transid': params['transaction_id'], 46 | 'out_trade_no': out_trade_no, 47 | 'deliver_timestamp': str(timestamp), 48 | 'deliver_status': '1', 49 | 'deliver_msg': 'ok', 50 | } 51 | result = deliver_notify(parameters) 52 | if result['errcode'] == 0: 53 | logger.info('Wechat Delivery Notify Succeeded!') 54 | payment = Payment.objects.get(payment_no=out_trade_no) 55 | payment.confirm(float(total_fee/100.00)) 56 | return HttpResponse('success') 57 | else: 58 | logger.error('Wechat Delivery Notify Failed: %s' % result['errmsg']) 59 | return HttpResponse('Delivery Notify Failed!', status=400) 60 | else: 61 | logger.error('Trade State != 0') 62 | return HttpResponse('success') 63 | else: 64 | logger.error('Wechat Payment Verify Failed!') 65 | return HttpResponse('Verify Result Failed', status=400) 66 | else: 67 | logger.error('Missing Post Parameters') 68 | return HttpResponse('Missing Post Parameters', status=400) 69 | 70 | 71 | @api_view(['GET']) 72 | def payable(request, payment_no): 73 | payment = Payment.objects.get(payment_no=payment_no) 74 | if not payment.is_payable: 75 | return Response({'detail': _('Already Paid')}, status=status.HTTP_400_BAD_REQUEST) 76 | 77 | parameter = { 78 | 'body': payment.comment, 79 | 'out_trade_no': payment.payment_no, 80 | 'spbill_create_ip': request.META.get('REMOTE_ADDR', ''), 81 | 'total_fee': str(int(payment.amount*100)), # unit is fen check other day 82 | 'notify_url': 'http://%s/wxpay/payment_notify/' % request.META['HTTP_HOST'] 83 | } 84 | return Response(build_form(parameter)) 85 | 86 | 87 | def warning_notify(request): 88 | raw_str = request.body 89 | logger.info('Warning Notify Wechat Xml Append with Request: %s' % unicode(raw_str)) 90 | wechat_data = xml_to_dict(raw_str) 91 | parameters = {key.lower(): wechat_data[key] for key in wechat_data} 92 | # if build_warning_sign(parameters) == wechat_data['AppSignature']: 93 | send_wechat_warning_notify_mail(wechat_data) 94 | return HttpResponse('success') 95 | # else: 96 | # logger.error('Wechat Warning Notify Verify Failed!') 97 | # return HttpResponse('error') 98 | 99 | 100 | def right_notify(request): 101 | raw_str = request.body 102 | logger.info('Right Notify Wechat Xml Append with Request: %s' % unicode(raw_str)) 103 | wechat_data = xml_to_dict(raw_str) 104 | parameters = {key.lower(): wechat_data[key] for key in wechat_data} 105 | if build_right_sign(parameters) == wechat_data['AppSignature']: 106 | send_wechat_right_notify_mail(wechat_data) 107 | return HttpResponse('success') 108 | else: 109 | logger.error('Wechat Right Notify Verify Failed!') 110 | return HttpResponse('error') 111 | 112 | @api_view(['GET']) 113 | def get_address_data(request): 114 | params = {'accesstoken': request.QUERY_PARAMS.get('accesstoken'), 'url': request.META['HTTP_HOST'] + request.path} 115 | result = get_address_sign(params) 116 | return Response(result) -------------------------------------------------------------------------------- /wxlib.py: -------------------------------------------------------------------------------- 1 | # encoding=utf-8 2 | 3 | import hashlib 4 | import json 5 | from random import Random 6 | import urllib 7 | import urllib2 8 | import time 9 | from django.utils.encoding import smart_str, smart_unicode 10 | import xml.etree.ElementTree as ET 11 | 12 | DELIVER_NOTIFY_URL = 'https://api.weixin.qq.com/pay/delivernotify' 13 | ORDER_QUERY_URL = 'https://api.weixin.qq.com/pay/orderquery' 14 | ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/token' 15 | 16 | config = { 17 | 'appId': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 18 | 'appSecret': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 19 | 'paySignKey': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 20 | 'partnerId': 'xxxxxxxxxxxxxxxxxxxx', 21 | 'partnerKey': 'xxxxxxxxxxxxxxxxxxxxxx' 22 | } 23 | 24 | 25 | def build_form(parameter): 26 | base = { 27 | 'bank_type': 'WX', 28 | 'fee_type': '1', 29 | 'input_charset': 'UTF-8', 30 | 'partner': config['partnerId'] 31 | } 32 | parameter.update(base) 33 | parameter['package'] = build_package(parameter) 34 | timestamp = str(int(time.time())) 35 | noncestr = random_str() 36 | pay_sign_array = { 37 | 'appid': config['appId'], 38 | 'noncestr': noncestr, 39 | 'package': parameter['package'], 40 | 'timestamp': timestamp 41 | } 42 | pay_sign_array['paysign'] = build_sign(pay_sign_array) 43 | pay_sign_array['signtype'] = 'SHA1' 44 | del pay_sign_array['appkey'] 45 | return pay_sign_array 46 | 47 | 48 | def build_package(parameter): 49 | filterParameter = para_filter(parameter) 50 | filterKeys = filterParameter.keys() 51 | filterKeys.sort() 52 | joined_string = '&'.join(['%s=%s' % (key.lower(), unicode(filterParameter[key])) for key in filterKeys]) 53 | joined_string += '&key=' + config['partnerKey'] 54 | m = hashlib.md5(joined_string.encode('utf-8')) 55 | m.digest() 56 | sign = m.hexdigest().upper() 57 | package = '&'.join( 58 | ['%s=%s' % (key, urllib.quote(unicode(filterParameter[key]).encode('utf-8'))) for key in filterKeys]) 59 | package += '&sign=' + sign 60 | return package 61 | 62 | 63 | def para_filter(params): 64 | return {key: params[key] 65 | for key in params 66 | if key.lower() not in {'sign', 'sign_type'} and params[key]} 67 | 68 | 69 | def verify_notify(params): 70 | wx_sign = params['sign'] 71 | filterParams = para_filter(params) 72 | filterParams['sign_type'] = 'MD5' 73 | filterKeys = filterParams.keys() 74 | filterKeys.sort() 75 | joined_string = '&'.join(['%s=%s' % (key.lower(), unicode(filterParams[key])) for key in filterKeys]) 76 | joined_string += '&key=' + config['partnerKey'] 77 | m = hashlib.md5(joined_string.encode('utf-8')) 78 | m.digest() 79 | sign = m.hexdigest().upper() 80 | return wx_sign == sign 81 | 82 | 83 | def build_sign(parameter): 84 | filter = ['appid', 'timestamp', 'noncestr', 'package', 'appkey'] 85 | filter.sort() 86 | parameter['appkey'] = config['paySignKey'] 87 | joined_string = '&'.join(['%s=%s' % (key.lower(), parameter[key]) for key in filter]) 88 | sign = hashlib.sha1(joined_string).hexdigest() 89 | return sign 90 | 91 | 92 | def build_delivery_sign(parameter): 93 | filter = ['appid', 'appkey', 'openid', 'transid', 'out_trade_no', 'deliver_timestamp', 'deliver_status', 94 | 'deliver_msg'] 95 | filter.sort() 96 | parameter['appkey'] = config['paySignKey'] 97 | joined_string = '&'.join(['%s=%s' % (key.lower(), parameter[key]) for key in filter]) 98 | sign = hashlib.sha1(joined_string).hexdigest() 99 | return sign 100 | 101 | 102 | def build_right_sign(parameter): 103 | filter_key = ['appid', 'appkey', 'timestamp', 'openid'] 104 | filter_key.sort() 105 | parameter['appkey'] = config['paySignKey'] 106 | joined_string = '&'.join(['%s=%s' % (key.lower(), parameter[key]) for key in filter_key]) 107 | sign = hashlib.sha1(joined_string).hexdigest() 108 | return sign 109 | 110 | 111 | def build_warning_sign(parameter): 112 | filter_key = ['appid', 'appkey', 'timestamp'] 113 | filter_key.sort() 114 | parameter['appkey'] = config['paySignKey'] 115 | joined_string = '&'.join(['%s=%s' % (key.lower(), parameter[key]) for key in filter_key]) 116 | sign = hashlib.sha1(joined_string).hexdigest() 117 | return sign 118 | 119 | 120 | def get_access_token(): 121 | token_url = ACCESS_TOKEN_URL + '?grant_type=client_credential&appid=' + config['appId'] + '&secret=' + config['appSecret'] 122 | urlopen = urllib2.urlopen(token_url, timeout=12000) 123 | result = urlopen.read() 124 | data = json.loads(result) 125 | if 'errcode' in data: 126 | return False 127 | return data['access_token'] 128 | 129 | 130 | def deliver_notify(parameter): 131 | url = DELIVER_NOTIFY_URL + '?access_token=' + get_access_token() 132 | parameter['appid'] = config['appId'] 133 | parameter['app_signature'] = build_delivery_sign(parameter) 134 | parameter['sign_method'] = 'sha1' 135 | del parameter['appkey'] 136 | result = do_post(url, parameter) 137 | return json.loads(result) 138 | 139 | 140 | def order_query(out_trade_no): 141 | if config != None or out_trade_no != None: 142 | return False 143 | url = ORDER_QUERY_URL + '?access_token=' + get_access_token() 144 | parameter = { 145 | 'appid': config['appId'], 146 | 'package': 'out_trade_no=' + out_trade_no + 147 | '&partner=' + config['partnerId'] + 148 | '&sign=' + (hashlib.md5('out_trade_no=' + out_trade_no + 149 | '&partner=' + config['partnerId'] + 150 | '&key=' + config['partnerkey'])).lower(), 151 | 'timestamp': int(time.time()) 152 | } 153 | app_signature = build_sign(parameter) 154 | parameter['app_signature'] = app_signature 155 | parameter['sign_method'] = 'sha1' 156 | result = do_post(url, parameter) 157 | return json.load(result) 158 | 159 | 160 | def do_post(url, parameter): 161 | req = urllib2.Request(url) 162 | #enable cookie 163 | opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) 164 | response = opener.open(req, json.dumps(parameter)) 165 | return response.read() 166 | 167 | 168 | def random_str(randomlength=32): 169 | str = '' 170 | chars = 'abcdefghijklmnopqrstuvwxyz0123456789' 171 | length = len(chars) - 1 172 | random = Random() 173 | for i in range(randomlength): 174 | str += chars[random.randint(0, length)] 175 | return str 176 | 177 | 178 | def xml_to_dict(raw_str): 179 | raw_str = smart_str(raw_str) 180 | msg = {} 181 | root_elem = ET.fromstring(raw_str) 182 | if root_elem.tag == 'xml': 183 | for child in root_elem: 184 | msg[child.tag] = smart_unicode(child.text) 185 | return msg 186 | else: 187 | return None 188 | 189 | 190 | def get_address_sign(parameter): 191 | parameter['appid'] = config['appId'] 192 | parameter['noncestr'] = random_str() 193 | parameter['timestamp'] = int(time.time()) 194 | filter = ['appid', 'url', 'timestamp', 'noncestr', 'accesstoken'] 195 | filter.sort() 196 | joined_string = '&'.join(['%s=%s' % (key.lower(), parameter[key]) for key in filter]) 197 | sign = hashlib.sha1(joined_string).hexdigest() 198 | parameter['addrsign'] = sign 199 | parameter['scope'] = 'jsapi_address' 200 | parameter['signType'] = 'SHA1' 201 | return parameter --------------------------------------------------------------------------------