微信支付
在实现这个功能之前,你需要在微信开放平台注册账号并激活。在平台里创建一个应用,得到app_id和weixin_key等关键信息。
创建微信支付请求
申请完成后,我们尝试创建微信支付的请求。
def create_weixin_request(product_id, trade_no, body, total_fee, client_ip):
total_fee = int(total_fee * 100)
nonce_str = __get_random_sign(32) # 获取随机字符串函数
stringA = "appid="+APP_ID+"&body="+BODY + body + "&mch_id="+MCH_ID+"&nonce_str="+nonce_str + "¬ify_url="+NOTIFY_URL + \
"&out_trade_no="+trade_no+"&product_id="+product_id+"&spbill_create_ip="+client_ip+"&total_fee=" + str(total_fee) + \
"&trade_type="+TRADE_TYPE
stringSignTemp = stringA + "&key="+WEIXIN_KEY
sign = __get_md5_sign(stringSignTemp) # 加密
data = "<xml><appid><![CDATA["+APP_ID+"]]></appid><body><![CDATA["+BODY+body+"]]></body><mch_id><![CDATA["+MCH_ID + \
"]]></mch_id><nonce_str><![CDATA["+nonce_str+"]]></nonce_str><notify_url><![CDATA["+NOTIFY_URL + \
"]]></notify_url><out_trade_no><![CDATA["+trade_no+"]]></out_trade_no><product_id><![CDATA["+product_id + \
"]]></product_id><spbill_create_ip><![CDATA["+client_ip+"]]></spbill_create_ip><total_fee><![CDATA[" + \
str(total_fee) + "]]></total_fee><trade_type><![CDATA[NATIVE]]></trade_type><sign>" + \
sign+"</sign></xml>"
return nonce_str, data
获取微信支付二维码
def get_weixin_qrcode(data):
import urllib3
urllib3.disable_warnings()
http = urllib3.PoolManager()
r = http.request('POST', WEIXIN_PAY_URL, headers={
'Content-Type': 'text/xml'}, fields={'data': data})
result_xml = bytes.decode(r.data)
import xml.etree.ElementTree as ET
tree = ET.fromstring(result_xml)
return tree[-1].text
以上两个函数都是在购买的视图函数中进行调用
def purchase(request, product):
user_id = TYPE_MARK + str(request.user.id)
user_product = CATEGORY_MARK + str(request.user.id)
if request.method == 'GET':
request.session[user_id] = str(time.time())
request.session[user_product] = product
price = PRICES.get(product)
rebate = REBATE.get(product)
years = 1
if price:
return render(request, 'purchase.html', locals())
else:
return server_error(request)
elif request.method == 'POST':
if request.is_ajax():
purchase_user = request.session.get(user_id, None)
purchase_product = request.session.get(user_product, None)
if not purchase_user:
return JsonResponse({'error': '订单购买人有误,请重新下单。'})
elif purchase_product != product:
return JsonResponse({'error': '订单产品有误,请重新下单。'})
if request.user.has_vip_permission():
return JsonResponse({'error': '您的VIP权限尚未过期,请勿重复下单。'})
total_fee = int(request.POST.get('totalFee'))
is_rebate = request.POST.get('isRebate') == 'true'
years = int(request.POST.get('years'))
price = PRICES.get(product)
rebate = REBATE.get(product) if is_rebate else 0
compute_total_fee = (
price * years - rebate) if is_rebate else (price * years)
checks = compute_total_fee == total_fee
if checks:
client_ip = request.META['REMOTE_ADDR']
order = Orders.purchase(
product, price, years, 'W', request.user, rebate=rebate)
# 测试订单处理
from persona.product_settings import SOURCE_HOSTS as PROD_HOST
if SOURCE_HOSTS != PROD_HOST:
order.modify(totalFee=0.01)
number = 'T' + order.number
order.modify(number=number)
(nonce_str, data) = create_weixin_request(order.product.name,
order.number, order.product.displayName, order.totalFee, client_ip)
qrcode = get_weixin_qrcode(data)
if qrcode.find('weixin://wxpay/') >= 0:
order.modify(paymentRnd=nonce_str)
return JsonResponse({'qrcode': qrcode, 'number': order.number, 'total_fee': total_fee})
else:
order.delete()
return JsonResponse({'error': qrcode})
else:
return JsonResponse({'error': '订单总价有误,请重新下单。'})
else:
return server_error(request)
二维码通过js进行呈现
<script type="text/javascript">
var mrUpdatePrice = (function () {
var priceElements = document.querySelectorAll('.js-price');
var hideSignup = document.querySelectorAll('.js-pricing-charge-description');
var sign = document.querySelector('.js-rmb-sign');
var price = Number($("#price").val());
var rebate = Number($("#rebate").val());
var prices = ['免费体验'];
var years = $('.js-years-per-order');
var titleText = '<h4>微信支付</h4><p>订单提交成功,请完成支付。</p>'
$("#customSwitch1").click(function () {
if ($(priceElements).text() != '免费体验') {
if ($("#customSwitch1")[0].checked) {
$(priceElements).text(Number($(priceElements).text()) - rebate)
} else {
$(priceElements).text(Number($(priceElements).text()) + rebate)
}
}
})
$("#buy").on('click', function () {
var currentUrl = location.pathname
var totalFee = Number($('.js-price').text());
var yearsNum = Number(years.text());
$('#anew-buy').attr('href',currentUrl)
$('#pay-ques').attr('href',currentUrl)
data = {
'totalFee': totalFee,
'isRebate': $("#customSwitch1")[0].checked,
'years': yearsNum,
};
$.ajax({
type: "POST",
url: currentUrl,
data: data,
dataType: "JSON",
success: function (data) {
if (data.qrcode != undefined) {
$("#pay-message").removeClass('hide');
$('#title').html(titleText);
$('#price-dom').addClass('hide');
$('#totalprice').html('<p>订单编号:'+ data.number +'</p>'+
'<p>应付金额 <span class="text-primary">¥'+data.total_fee +'</span> 元</p>');
$('#price-dom').next().removeClass('mt-5');
$("#title").parent().removeClass('mb-4');
new QRCode(document.getElementById("qrcode"), data.qrcode);
$('#cost').addClass('hide')
$('#cost-code').removeClass('hide')
var orderNumber = data.number;
var timesRun = 0;
setTimeout(function () {
var isPayRequest = false;
var interval = setInterval(function () {
if (isPayRequest) {
$("#order-modal").modal("show");
clearInterval(interval);
}
if (!isPayRequest) {
$.ajax({
url: "/orders/status",
type: "POST",
data: { "number": orderNumber },
dataType: "json",
success: function (data) {
if (data.status == "success") {
isPayRequest = true;
}
}
});
}
timesRun += 1;
if (timesRun === 30) {
clearInterval(interval);
}
}, 1000);
}, 20)
} else if (data.error != undefined) {
$('#error-msg').text(data.error)
if (data.error.indexOf("VIP") >= 0) {
$("#anew-buy").addClass('hide');
} else {
$("#anew-buy").removeClass('hide');
}
$('#error-modal').modal('show');
}
}
});
})
var updatePrice = function (data) {
if (sign) {
sign.classList[data.from < 1 ? 'add' : 'remove']('d-none');
}
theme.mrUtil.forEach(priceElements, function (index, element) {
if (data.from >= 1) {
if ($("#customSwitch1")[0].checked) {
element.textContent = price * data.from - rebate;
} else {
element.textContent = price * data.from;
}
$('.js-pricing-charge-description').text('每年可创建5个项目,运行60次分析');
$('#exp').addClass('hide')
$('#buy').removeClass('hide')
} else {
element.textContent = prices[data.from];
$('.js-years-per-order').text(1);
$('.js-years-word').text('月');
$('.js-pricing-charge-description').text('可创建1个项目,运行1次基线分析');
$('#buy').addClass('hide')
$('#exp').removeClass('hide')
}
});
};
return updatePrice;
})();
</script>
扫描二维码后,支付函数
@csrf_exempt
def pay(request):
if request.method == 'POST':
response = HttpResponse()
import xml.etree.ElementTree as et
return_xml = bytes.decode(request.body)
tree = et.fromstring(return_xml)
return_code = tree.find("return_code").text
_return_number = tree.find("out_trade_no").text
# 测试订单不作处理
if _return_number.find('T') >= 0:
response.status_code = 200
return response
if return_code == 'FAIL':
result = '支付失败'
_send_order_email("test@163.com", "", _return_number, xml=return_xml, error=result)
elif return_code == 'SUCCESS':
_return_nonce_str = tree.find("nonce_str").text
_return_total_fee = tree.find("total_fee").text
order = Orders.check_weixin_order(
_return_number, _return_total_fee, _return_nonce_str)
if order:
if order.dateOfPayment and order.dateOfPayment < datetime.now():
response.status_code = 302
else:
order.modify(dateOfPayment=datetime.now())
order.by.subscribe(order.product.name, order.product.amount)
_send_order_email(order.by.account, order.product.displayName, _return_number)
_send_order_email("test@163.com", order.product.displayName, _return_number)
response.status_code = 200
else:
_send_order_email("test@163.com", "", _return_number, xml=return_xml, error="找不到订单")
response.status_code = 400
return response
return Http404