支持支付宝和微信支付

pull/16/head
youngS 4 years ago
parent c7e77ad46b
commit 2e21465a1e
  1. 89
      fir_client/src/components/apps/FirApps.vue
  2. 141
      fir_client/src/components/user/FirUserOrders.vue
  3. 4
      fir_client/src/main.js
  4. 17
      fir_ser/api/migrations/0042_order_payment_name.py
  5. 1
      fir_ser/api/models.py
  6. 5
      fir_ser/api/urls.py
  7. 23
      fir_ser/api/utils/pay/ali.py
  8. 38
      fir_ser/api/utils/pay/util.py
  9. 14
      fir_ser/api/utils/pay/wx.py
  10. 101
      fir_ser/api/utils/wxpay/utils.py
  11. 77
      fir_ser/api/views/order.py
  12. 55
      fir_ser/fir_ser/settings.py

@ -26,13 +26,39 @@
</div> </div>
</div> </div>
<div class="package-actions"> <div class="package-actions">
<button type="button" class="btn" @click="buy(packages)" :disabled="buy_button_disable"> <el-radio v-model="default_price_radio" :label="packages.name" border>
购买 <span>此套餐</span>
</button> <span class="pay-current" v-if="default_price_radio === packages.name"/>
</el-radio>
<!-- <button type="button" class="btn" @click="buy(packages)" :disabled="buy_button_disable">
购买
</button>-->
</div> </div>
</div> </div>
</div> </div>
<div>
<div style="margin-top: 30px;text-align: center">
<el-radio v-model="default_pay_radio" :label="pay.name" border v-for="pay in pay_choices"
:key="pay.name">
<span style="color: white">xxxxxxxxxxxxxx</span>
<span style="width: 160px; height: 45px">
<span class="pay-icon alipay" v-if="pay.type === 'ALI'"/>
<span class="pay-icon weixin" v-if="pay.type === 'WX'"/>
<span class="pay-current" v-if="default_pay_radio === pay.name"/>
</span>
</el-radio>
</div>
<div style="text-align: center">
<el-button type="primary" :disabled="buy_button_disable" @click="buy"
style="margin-top:30px;width: 166px"> 立即支付
</el-button>
</div>
</div>
<span slot="footer"> <span slot="footer">
如对充值订单有疑问请联系 nineven@qq.com 如对充值订单有疑问请联系 nineven@qq.com
</span> </span>
@ -490,6 +516,9 @@
name: "FirApps", name: "FirApps",
data() { data() {
return { return {
default_pay_radio: '',
default_price_radio: '',
pay_choices: [],
analyseappinfo: {}, analyseappinfo: {},
short: '', short: '',
keysearch: '', keysearch: '',
@ -513,7 +542,7 @@
loadingobj: null, loadingobj: null,
show_buy_download_times: false, show_buy_download_times: false,
data_package_prices: [], data_package_prices: [],
buy_button_disable: false, buy_button_disable: true,
} }
}, methods: { }, methods: {
show_package_prices() { show_package_prices() {
@ -521,6 +550,10 @@
if (res.code === 1000) { if (res.code === 1000) {
this.show_buy_download_times = true; this.show_buy_download_times = true;
this.data_package_prices = res.data; this.data_package_prices = res.data;
this.pay_choices = res.pay_choices;
if (this.pay_choices) {
this.default_pay_radio = this.pay_choices[0].name
}
} else { } else {
this.$message.error("获取价格异常"); this.$message.error("获取价格异常");
this.show_buy_download_times = false; this.show_buy_download_times = false;
@ -528,15 +561,12 @@
}, {}) }, {})
}, },
buy(packages) { buy() {
this.buy_button_disable = true; this.buy_button_disable = true;
my_order(res => { my_order(res => {
if (res.code === 1000) { if (res.code === 1000) {
this.$message.success("下订单成功,正在跳转支付平台"); this.$message.success("下订单成功,正在跳转支付页");
let pay_url = res.data; this.$router.push({name: 'FirUserOrders', params: {out_trade_no: res.data.out_trade_no}})
if (pay_url && pay_url.length > 10) {
window.location.href = pay_url
}
} else { } else {
this.$message.error("异常" + res.msg); this.$message.error("异常" + res.msg);
this.buy_button_disable = false; this.buy_button_disable = false;
@ -544,7 +574,8 @@
} }
}, { }, {
methods: 'POST', data: { methods: 'POST', data: {
price_id: packages.name price_id: this.default_price_radio,
pay_id: this.default_pay_radio,
} }
}) })
}, },
@ -930,7 +961,14 @@
this.loadingobj.close(); this.loadingobj.close();
}, },
watch: { watch: {
// eslint-disable-next-line no-unused-vars
default_pay_radio: function (val, oldVal) {
this.buy_button_disable = !(this.default_pay_radio.length > 2 && this.default_price_radio.length > 2);
},
// eslint-disable-next-line no-unused-vars
default_price_radio: function (val, oldVal) {
this.buy_button_disable = !(this.default_pay_radio.length > 2 && this.default_price_radio.length > 2);
},
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
keysearch: function (val, oldVal) { keysearch: function (val, oldVal) {
// this.searchapps() // this.searchapps()
@ -1470,4 +1508,31 @@
margin-top: -58px; margin-top: -58px;
z-index: 999; z-index: 999;
} }
.weixin {
background-image: url(https://img.jiguang.cn/app-portal/assets/img/account/pay_weixin.png);
}
.pay-current {
background: url(https://img.jiguang.cn/app-portal/assets/img/account/pay_selected.png) right bottom/100% no-repeat;
width: 18px;
height: 19px;
position: absolute;
bottom: -1px;
right: -1px;
}
.pay-icon {
width: 120px;
height: 30px;
background-size: 100%;
position: absolute;
top: 6px;
left: 15px;
}
.alipay {
background-image: url(https://img.jiguang.cn/app-portal/assets/img/account/pay_alipay.png);
}
</style> </style>

@ -1,5 +1,44 @@
<template> <template>
<el-main> <el-main>
<el-dialog
:visible.sync="wx_pay"
width="49%"
:close-on-click-modal="false"
:close-on-press-escape="false"
center>
<div slot="title" style="margin-bottom: 10px">
<el-row>
<el-col :span="12">
<p class="detail">请在<span style="color: #f89303">24小时</span>内完成支付超时订单将自动取消</p>
<p class="detail">支付订单号 <span>{{ current_order_info.order_number }}</span></p>
</el-col>
<el-col :span="12">
<p style="margin-top: 26px"><span class="order_text">订单金额</span><span class="amount">{{format_actual_amount(current_order_info) }}</span>
</p>
</el-col>
</el-row>
</div>
<div class="pay_wx">
<div>
<div class="icon-weixin"></div>
<vue-qr :margin="qrinfo.margin"
class="code-wrap"
:logoScale="qrinfo.logoScale"
:logoCornerRadius="qrinfo.logoCornerRadius"
:correctLevel="qrinfo.correctLevel"
:text="pay_code_url" :size="266"
ref="qr">
</vue-qr>
<div class="tip-btn pay_wx"><span class="icon"/><span class="text">请使用微信扫码支付</span></div>
<p style="margin-top: 30px">支付完成之后请刷新该页面确认支付状态</p>
</div>
</div>
</el-dialog>
<el-dialog <el-dialog
:visible.sync="show_order_info" :visible.sync="show_order_info"
width="780px" width="780px"
@ -205,11 +244,23 @@
import {my_order} from "@/restful"; import {my_order} from "@/restful";
import {getUserInfoFun} from '@/utils' import {getUserInfoFun} from '@/utils'
import VueQr from 'vue-qr';
export default { export default {
name: "FirUserOrders", name: "FirUserOrders",
components: {
VueQr
},
data() { data() {
return { return {
wx_pay: false,
pay_code_url: '',
qrinfo: {
logoScale: 0.3,
logoCornerRadius: 12,
correctLevel: 3,
margin: 20
},
order_info_list: [], order_info_list: [],
order_id_seach: "", order_id_seach: "",
pagination: {"currentPage": 1, "total": 0, "pagesize": 10}, pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
@ -238,12 +289,23 @@
}) })
}, },
goto_pay(order) { goto_pay(order) {
this.current_order_info = order;
my_order(res => { my_order(res => {
if (res.code === 1000) { if (res.code === 1000) {
this.$message.success("正在跳转支付平台"); let data = res.data;
let pay_url = res.data; if (data && data.type === 'WX') {
if (pay_url && pay_url.length > 10) { this.pay_code_url = data.url;
window.location.href = pay_url this.wx_pay = true;
this.$message.success("请用微信扫描支付");
} else if (data && data.type === 'ALI') {
let pay_url = data.url;
if (pay_url && pay_url.length > 10) {
this.$message.success("正在跳转支付宝支付平台");
window.location.href = pay_url
// window.open(pay_url, '_blank', '');
}
} else {
this.$message.error("支付获取失败 " + res.msg)
} }
} else { } else {
this.$message.error("失败了 " + res.msg) this.$message.error("失败了 " + res.msg)
@ -341,7 +403,13 @@
this.payment_type_choices = data.payment_type_choices; this.payment_type_choices = data.payment_type_choices;
this.status_choices = data.status_choices; this.status_choices = data.status_choices;
this.order_type_choices = data.order_type_choices; this.order_type_choices = data.order_type_choices;
if (this.order_info_list.length === 1) {
this.current_order_info = this.order_info_list[0];
let out_trade_no = this.$route.params.out_trade_no;
if (out_trade_no) {
this.goto_pay(this.current_order_info);
}
}
} else if (data.code === 1008) { } else if (data.code === 1008) {
this.$message.error(data.msg); this.$message.error(data.msg);
} else { } else {
@ -352,7 +420,11 @@
}, },
}, mounted() { }, mounted() {
getUserInfoFun(this); getUserInfoFun(this);
this.get_data_from_tabname() let out_trade_no = this.$route.params.out_trade_no;
if (out_trade_no) {
this.order_id_seach = out_trade_no;
}
this.get_data_from_tabname();
}, filters: {} }, filters: {}
} }
</script> </script>
@ -369,4 +441,61 @@
border-radius: 1%; border-radius: 1%;
} }
.icon-weixin {
width: 137px;
height: 35px;
background: url(https://img.jiguang.cn/app-portal/assets/img/account/order-weixin.png) 0 0/100%;
margin-left: 50px;
}
.code-wrap {
width: 240px;
height: 240px;
border: 1px solid #c3c3c3;
margin-top: 23px;
margin-bottom: 20px;
}
.tip-btn {
background: #2bbc4d;
width: 240px;
height: 60px;
}
.icon {
width: 26px;
height: 26px;
background-image: url(https://img.jiguang.cn/app-portal/assets/img/account/pay-scan.png);
background-size: 100%;
display: inline-block;
margin-right: 11px;
}
.text {
font-family: PingFang-SC-Bold;
font-size: 16px;
color: #fff;
letter-spacing: -.1px;
}
.pay_wx {
display: flex;
justify-content: center;
align-items: center;
}
.order_text {
font-family: PingFang-SC-Medium;
font-size: 14px;
color: #787e85;
letter-spacing: 0;
vertical-align: text-top;
}
.amount {
font-family: Avenir-Heavy;
font-size: 24px;
color: #ff8000;
letter-spacing: 0;
}
</style> </style>

@ -60,7 +60,8 @@ import {
Timeline, Timeline,
TimelineItem, TimelineItem,
Tooltip, Tooltip,
Upload Upload,
Radio,
} from "element-ui"; } from "element-ui";
import VueLazyload from 'vue-lazyload' import VueLazyload from 'vue-lazyload'
// 引入点击复制组件 // 引入点击复制组件
@ -142,6 +143,7 @@ Vue.use(Tooltip);
Vue.use(Switch); Vue.use(Switch);
Vue.use(Footer); Vue.use(Footer);
Vue.use(InputNumber); Vue.use(InputNumber);
Vue.use(Radio);
Vue.prototype.$message = Message; Vue.prototype.$message = Message;
Vue.prototype.$notify = Notification; Vue.prototype.$notify = Notification;
Vue.prototype.$loading = Loading.service; Vue.prototype.$loading = Loading.service;

@ -0,0 +1,17 @@
# Generated by Django 3.0.3 on 2021-04-18 17:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0041_auto_20210416_1438'),
]
operations = [
migrations.AddField(
model_name='order',
name='payment_name',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='支付商家名称'),
),
]

@ -355,6 +355,7 @@ class Order(models.Model):
payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (4, '银联')) payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (4, '银联'))
payment_type = models.SmallIntegerField(choices=payment_type_choices) payment_type = models.SmallIntegerField(choices=payment_type_choices)
payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True) payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True)
payment_name = models.CharField(max_length=128, verbose_name="支付商家名称", null=True, blank=True)
order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题 order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题
account = models.ForeignKey("UserInfo", on_delete=models.CASCADE) account = models.ForeignKey("UserInfo", on_delete=models.CASCADE)
actual_amount = models.BigIntegerField(verbose_name="实付金额,单位分") actual_amount = models.BigIntegerField(verbose_name="实付金额,单位分")

@ -23,7 +23,7 @@ from api.views.download import ShortDownloadView
from api.views.uploads import AppAnalyseView, UploadView from api.views.uploads import AppAnalyseView, UploadView
from api.views.storage import StorageView from api.views.storage import StorageView
from api.views.receiveudids import IosUDIDView from api.views.receiveudids import IosUDIDView
from api.views.order import PriceView, OrderView, AliPaySuccess, WxPaySuccess from api.views.order import PriceView, OrderView, PaySuccess
from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView
# router=DefaultRouter() # router=DefaultRouter()
@ -52,6 +52,5 @@ urlpatterns = [
re_path("^package_prices$", PriceView.as_view()), re_path("^package_prices$", PriceView.as_view()),
re_path("^orders$", OrderView.as_view()), re_path("^orders$", OrderView.as_view()),
re_path("^certification$", CertificationView.as_view()), re_path("^certification$", CertificationView.as_view()),
re_path("^ali_pay_success$", AliPaySuccess.as_view()), re_path(r"^pay_success/(?P<name>\w+)$", PaySuccess.as_view()),
re_path("^wx_pay_success$", WxPaySuccess.as_view()),
] ]

@ -5,9 +5,6 @@
# date: 2021/3/18 # date: 2021/3/18
# pip install alipay-sdk-python==3.3.398 # pip install alipay-sdk-python==3.3.398
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from api.utils.alipay import AliPay from api.utils.alipay import AliPay
from api.utils.alipay.utils import AliPayConfig from api.utils.alipay.utils import AliPayConfig
@ -21,14 +18,16 @@ logger = logging.getLogger(__file__)
class Alipay(object): class Alipay(object):
def __init__(self): def __init__(self, name, p_type, auth):
self.ali_config = PAY_CONFIG.get("ALI") self.p_type = p_type
self.name = name
self.ali_config = auth
self.alipay = self.__get_ali_pay() self.alipay = self.__get_ali_pay()
def __get_ali_pay(self): def __get_ali_pay(self):
return AliPay( return AliPay(
appid=self.ali_config.get("APP_ID"), appid=self.ali_config.get("APP_ID"),
app_notify_url=self.ali_config.get("APP_NOTIFY_URL"), app_notify_url="%s/%s" % (self.ali_config.get("APP_NOTIFY_URL"), self.name),
app_private_key_string=self.ali_config.get("APP_PRIVATE_KEY"), app_private_key_string=self.ali_config.get("APP_PRIVATE_KEY"),
alipay_public_key_string=self.ali_config.get("ALI_PUBLIC_KEY"), alipay_public_key_string=self.ali_config.get("ALI_PUBLIC_KEY"),
sign_type="RSA2", # RSA 或者 RSA2 sign_type="RSA2", # RSA 或者 RSA2
@ -38,21 +37,23 @@ class Alipay(object):
) )
def get_pay_pc_url(self, out_trade_no, total_amount, passback_params): def get_pay_pc_url(self, out_trade_no, total_amount, passback_params):
passback_params.update({'name': self.name})
time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")
order_string = self.alipay.api_alipay_trade_page_pay( order_string = self.alipay.api_alipay_trade_page_pay(
out_trade_no=out_trade_no, out_trade_no=out_trade_no,
total_amount=total_amount, total_amount=total_amount / 100,
subject=self.ali_config.get("SUBJECT"), subject=self.ali_config.get("SUBJECT"),
body="充值 %s" % total_amount, body="充值 %s" % str(total_amount / 100),
time_expire=time_expire, time_expire=time_expire,
return_url=self.ali_config.get("RETURN_URL"), return_url=self.ali_config.get("RETURN_URL"),
notify_url=self.ali_config.get("APP_NOTIFY_URL"),
passback_params=json.dumps(passback_params) passback_params=json.dumps(passback_params)
) )
return "https://openapi.alipay.com/gateway.do?%s" % order_string return {'type': self.p_type, 'url': "https://openapi.alipay.com/gateway.do?%s" % order_string,
'out_trade_no': out_trade_no}
def valid_order(self, data): def valid_order(self, request):
data = request.data.copy().dict()
signature = data.pop("sign") signature = data.pop("sign")
success = self.alipay.verify(data, signature) success = self.alipay.verify(data, signature)
if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"): if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):

@ -0,0 +1,38 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 4月
# author: NinEveN
# date: 2021/4/18
from fir_ser.settings import PAY_CONFIG
from api.utils.pay.ali import Alipay
from api.utils.pay.wx import Weixinpay
def get_pay_obj_form_name(pay_name):
for pay_info in PAY_CONFIG:
if pay_name == pay_info.get('NAME', '') and pay_info.get('ENABLED', False):
auth_info = pay_info.get('AUTH', None)
p_type = pay_info.get('TYPE', '')
if auth_info:
if p_type == 'ALI':
return Alipay(pay_name, p_type, auth_info)
elif p_type == 'WX':
return Weixinpay(pay_name, p_type, auth_info)
else:
pass
def get_enable_pay_choices():
pay_choices = []
for pay_info in PAY_CONFIG:
if pay_info.get('ENABLED', False):
pay_choices.append({'type': pay_info.get('TYPE'), 'name': pay_info.get('NAME', '')})
return pay_choices
def get_payment_type(p_type):
if p_type == 'ALI':
return 1
elif p_type == 'WX':
return 0

@ -5,7 +5,6 @@
# date: 2021/3/18 # date: 2021/3/18
from api.utils.wxpay import WeChatPay, WeChatPayType from api.utils.wxpay import WeChatPay, WeChatPayType
from fir_ser.settings import PAY_CONFIG
from datetime import datetime, timedelta from datetime import datetime, timedelta
from api.utils.storage.caches import update_order_info, update_order_status from api.utils.storage.caches import update_order_info, update_order_status
import json import json
@ -15,8 +14,10 @@ logger = logging.getLogger(__file__)
class Weixinpay(object): class Weixinpay(object):
def __init__(self): def __init__(self, name, p_type, auth):
self.wx_config = PAY_CONFIG.get("WX") self.p_type = p_type
self.wx_config = auth
self.name = name
self.wxpay = self.__get_wx_pay() self.wxpay = self.__get_wx_pay()
def __get_wx_pay(self): def __get_wx_pay(self):
@ -25,11 +26,12 @@ class Weixinpay(object):
parivate_key=self.wx_config.get('APP_PRIVATE_KEY'), parivate_key=self.wx_config.get('APP_PRIVATE_KEY'),
cert_serial_no=self.wx_config.get('SERIAL_NO'), cert_serial_no=self.wx_config.get('SERIAL_NO'),
appid=self.wx_config.get('APP_ID'), appid=self.wx_config.get('APP_ID'),
notify_url=self.wx_config.get('APP_NOTIFY_URL'), notify_url="%s/%s" % (self.wx_config.get('APP_NOTIFY_URL'), self.name),
apiv3_key=self.wx_config.get('API_V3_KEY') apiv3_key=self.wx_config.get('API_V3_KEY')
) )
def get_pay_pc_url(self, out_trade_no, total_amount, passback_params): def get_pay_pc_url(self, out_trade_no, total_amount, passback_params):
passback_params.update({'name': self.name})
time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S+08:00") time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S+08:00")
code, data = self.wxpay.pay( code, data = self.wxpay.pay(
description=self.wx_config.get('SUBJECT'), description=self.wx_config.get('SUBJECT'),
@ -41,7 +43,7 @@ class Weixinpay(object):
time_expire=time_expire, time_expire=time_expire,
attach=json.dumps(passback_params), attach=json.dumps(passback_params),
) )
print(code, data) return {'type': self.p_type, 'url': json.loads(data).get('code_url', ''), 'out_trade_no': out_trade_no}
def valid_order(self, request): def valid_order(self, request):
headers = { headers = {
@ -60,7 +62,7 @@ class Weixinpay(object):
ext_parms = json.loads(passback_params) ext_parms = json.loads(passback_params)
user_id = ext_parms.get("user_id") user_id = ext_parms.get("user_id")
transaction_id = data.get("transaction_id", "") transaction_id = data.get("transaction_id", "")
return update_order_info(user_id, out_trade_no, transaction_id, 1) return update_order_info(user_id, out_trade_no, transaction_id, 0)
else: else:
logger.error("passback_params %s user_id not exists" % passback_params) logger.error("passback_params %s user_id not exists" % passback_params)
else: else:

@ -1,101 +0,0 @@
# -*- coding: utf-8 -*-
import time
import uuid
from base64 import b64decode, b64encode
import json
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.serialization import (load_pem_private_key,
load_pem_public_key)
from OpenSSL import crypto
def build_authorization(path,
method,
mchid,
serial_no,
mch_private_key,
data=None,
nonce_str=None):
timeStamp = str(int(time.time()))
nonce_str = nonce_str or ''.join(str(uuid.uuid4()).split('-')).upper()
body = json.dumps(data) if data else ''
sign_str = method + '\n' + path + '\n' + \
timeStamp + '\n' + nonce_str + '\n' + body + '\n'
signature = sign(private_key=mch_private_key, sign_str=sign_str)
authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"' % (
mchid, nonce_str, signature, timeStamp, serial_no)
return authorization
def sign(private_key, sign_str):
private_key = load_pem_private_key(data=format_private_key(
private_key).encode('UTF-8'), password=None, backend=default_backend())
message = sign_str.encode('UTF-8')
signature = private_key.sign(message, PKCS1v15(), SHA256())
sign = b64encode(signature).decode('UTF-8').replace('\n', '')
return sign
def decrypt(nonce, ciphertext, associated_data, apiv3_key):
key_bytes = apiv3_key.encode('UTF-8')
nonce_bytes = nonce.encode('UTF-8')
associated_data_bytes = associated_data.encode('UTF-8')
data = b64decode(ciphertext)
aesgcm = AESGCM(key_bytes)
return aesgcm.decrypt(nonce_bytes, data, associated_data_bytes).decode('UTF-8')
def format_private_key(private_key):
pem_start = '-----BEGIN PRIVATE KEY-----\n'
pem_end = '\n-----END PRIVATE KEY-----'
if not private_key.startswith(pem_start):
private_key = pem_start + private_key
if not private_key.endswith(pem_end):
private_key = private_key + pem_end
return private_key
def format_certificate(certificate):
pem_start = '-----BEGIN CERTIFICATE-----\n'
pem_end = '\n-----END CERTIFICATE-----'
if not certificate.startswith(pem_start):
certificate = pem_start + certificate
if not certificate.endswith(pem_end):
certificate = certificate + pem_end
return certificate
def verify(timestamp, nonce, body, signature, certificate):
sign_str = '%s\n%s\n%s\n' % (timestamp, nonce, body)
public_key_str = dump_public_key(certificate)
public_key = load_pem_public_key(data=public_key_str.encode('UTF-8'), backend=default_backend())
message = sign_str.encode('UTF-8')
signature = b64decode(signature)
try:
public_key.verify(signature, sign_str.encode('UTF-8'), PKCS1v15(), SHA256())
except InvalidSignature:
return False
return True
def certificate_serial_number(certificate):
cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
try:
res = cert.get_signature_algorithm().decode('UTF-8')
if res != 'sha256WithRSAEncryption':
return None
return hex(cert.get_serial_number()).upper()[2:]
except:
return None
def dump_public_key(certificate):
cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
public_key = crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8")
return public_key

@ -14,8 +14,7 @@ from rest_framework.pagination import PageNumberPagination
from api.utils.utils import get_order_num, get_choices_dict from api.utils.utils import get_order_num, get_choices_dict
from api.utils.storage.caches import update_order_status from api.utils.storage.caches import update_order_status
import logging import logging
from api.utils.pay.ali import Alipay from api.utils.pay.util import get_pay_obj_form_name, get_enable_pay_choices, get_payment_type
from api.utils.pay.wx import Weixinpay
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -53,31 +52,30 @@ class OrderView(APIView):
def post(self, request): def post(self, request):
res = BaseResponse() res = BaseResponse()
price_id = request.data.get("price_id", None) price_id = request.data.get("price_id", None)
pay_id = request.data.get("pay_id", None)
order_number = request.data.get("order_number", None) order_number = request.data.get("order_number", None)
if price_id or order_number: if (price_id and pay_id) or order_number:
price_obj = Price.objects.filter(name=price_id).first() price_obj = Price.objects.filter(name=price_id).first()
order_obj = Order.objects.filter(account=request.user, order_number=order_number).first() order_obj = Order.objects.filter(account=request.user, order_number=order_number).first()
if order_obj and order_obj.status in [1, 2]: if order_obj and order_obj.status in [1, 2] and order_obj.payment_name:
# alipay = Alipay() pay_obj = get_pay_obj_form_name(order_obj.payment_name)
# pay_url = alipay.get_pay_pc_url(order_obj.order_number, order_obj.actual_amount / 100, pay_url = pay_obj.get_pay_pc_url(order_number, int(order_obj.actual_amount),
# {'user_id': request.user.id}) {'user_id': request.user.id})
wxpay = Weixinpay()
pay_url = wxpay.get_pay_pc_url(order_number, order_obj.actual_amount, {'user_id': request.user.id})
res.data = pay_url res.data = pay_url
logger.info("%s 下单成功 %s" % (request.user, res.dict))
return Response(res.dict) return Response(res.dict)
if price_obj: if price_obj:
try: try:
order_number = get_order_num() order_number = get_order_num()
actual_amount = price_obj.price actual_amount = price_obj.price
Order.objects.create(payment_type=1, order_number=order_number, pay_obj = get_pay_obj_form_name(pay_id)
pay_url = pay_obj.get_pay_pc_url(order_number, int(actual_amount), {'user_id': request.user.id})
Order.objects.create(payment_type=get_payment_type(pay_obj.p_type), order_number=order_number,
account=request.user, status=1, order_type=0, actual_amount=actual_amount, account=request.user, status=1, order_type=0, actual_amount=actual_amount,
actual_download_times=price_obj.package_size, actual_download_times=price_obj.package_size, payment_name=pay_obj.name,
actual_download_gift_times=price_obj.download_count_gift) actual_download_gift_times=price_obj.download_count_gift)
# alipay = Alipay()
# pay_url = alipay.get_pay_pc_url(order_number, actual_amount / 100, {'user_id': request.user.id})
wxpay = Weixinpay()
pay_url = wxpay.get_pay_pc_url(order_number, actual_amount, {'user_id': request.user.id})
res.data = pay_url res.data = pay_url
logger.info("%s 下单成功 %s" % (request.user, res.dict))
return Response(res.dict) return Response(res.dict)
except Exception as e: except Exception as e:
logger.error("%s 订单 %s 保存失败 Exception:%s" % (request.user, price_id, e)) logger.error("%s 订单 %s 保存失败 Exception:%s" % (request.user, price_id, e))
@ -86,7 +84,7 @@ class OrderView(APIView):
else: else:
logger.error("%s 价格 %s 获取失败" % (request.user, price_id)) logger.error("%s 价格 %s 获取失败" % (request.user, price_id))
res.code = 1002 res.code = 1002
res.msg = "价格获取失败" res.msg = "价格获取失败,请稍后重试"
else: else:
res.code = 1001 res.code = 1001
res.msg = "错误的价格" res.msg = "错误的价格"
@ -129,6 +127,7 @@ class PriceView(APIView):
res = BaseResponse() res = BaseResponse()
price_obj_lists = Price.objects.filter(is_enable=True).all().order_by("updated_time").order_by("price") price_obj_lists = Price.objects.filter(is_enable=True).all().order_by("updated_time").order_by("price")
res.data = PriceSerializer(price_obj_lists, many=True).data res.data = PriceSerializer(price_obj_lists, many=True).data
res.pay_choices = get_enable_pay_choices()
return Response(res.dict) return Response(res.dict)
def delete(self, request, price_id): def delete(self, request, price_id):
@ -140,7 +139,7 @@ class PriceView(APIView):
return Response(res.dict) return Response(res.dict)
class AliPaySuccess(APIView): class PaySuccess(APIView):
# authentication_classes = [ExpiringTokenAuthentication] # authentication_classes = [ExpiringTokenAuthentication]
def get(self, request): def get(self, request):
@ -151,33 +150,19 @@ class AliPaySuccess(APIView):
# return HttpResponseRedirect(PAY_SUCCESS_URL) # return HttpResponseRedirect(PAY_SUCCESS_URL)
def post(self, request): def post(self, request, name):
alipay = Alipay() pay_obj = get_pay_obj_form_name(name)
msg = 'failure' msg = 'failure'
logger.info("支付回调参数:%s" % request.data) if pay_obj:
logger.info("----- %s" % request.META) logger.info("支付回调参数:%s" % request.body)
data = request.data.copy().dict() logger.info("支付回调头部:%s" % request.META)
if alipay.valid_order(data): if pay_obj.p_type == 'ALI':
msg = 'success' if pay_obj.valid_order(request):
return Response(msg) msg = 'success'
return Response(msg)
elif pay_obj.p_type == 'WX':
class WxPaySuccess(APIView): if pay_obj.valid_order(request):
# authentication_classes = [ExpiringTokenAuthentication] return Response(msg)
else:
def get(self, request): return Response(status=201)
alipay = Alipay() return Response(status=201, data=msg)
alipay.update_order_status('1202141610723105226256209')
return Response(111)
# return HttpResponseRedirect(PAY_SUCCESS_URL)
def post(self, request):
msg = 'failure'
logger.info("支付回调参数:%s" % request.body)
logger.info("----- %s" % request.META)
wxpay = Weixinpay()
if wxpay.valid_order(request):
return Response(msg)
else:
return Response(status=201)

@ -435,25 +435,33 @@ LOGGING = {
} }
PAY_SUCCESS_URL = 'https://app.hehelucky.cn/user/orders' # 前端页面,支付成功跳转页面 PAY_SUCCESS_URL = 'https://app.hehelucky.cn/user/orders' # 前端页面,支付成功跳转页面
PAY_CONFIG = { PAY_CONFIG = [
'ALI': { {
'APP_ID': "2021002138691845", 'NAME': 'alipay',
'APP_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY----- 'TYPE': 'ALI',
MIIEowIBAAKCAQEAhqf2mwftoxZDNpl4eWsQ6mEfXgMlNPr6jv72ecA4hbKWqChXQmGS1T+0VsRTSoOXRDlu1MMqkTGISzHvmGb7Gmw+Myfs/ojoonD9r8fODvIo1MHolFBhr3GNQu7tqBlVJ76QgiYft+c4kkqCguuyCrd3Te6C5zCIuh6O98r4D3A3LFcm6OdScWGcfEbR+FUv+jSi2oezHeSpkhhpHGBLSsI0L9JOdHetdUE/TwN8V1HABdpnPXtp9SIu6ioIrrligX1ZRlwht2YUt0BPqPp/ApLdRIsqlhD4/ejmtMlaRqqiN6PulEThBew/qaLVSXIr2HCSXtwbki3pFMFOcsjF2wIDAQABAoIBADp4sQL83FnXDvSki8XdkgjUh7RhFUT+PtLdL9YKfADCXd1DNzDiAcqL0RlkQu62WXcMoW3OGavWoGJWmr3I6fy9R/0atzSH6syu19n+nyGqUcShNwdAKErwufB4o8Y8yddqToHVYCyRQOV1aVrEUhmJNUsn6LvPPW/kWRyMjE7XQDFHpL5/Ly7pXe+f9Btm37ZuePTPsm65P88C3GznjZxXhY1LBWFKLPG1470xdReduyeJFZS/TmK0nUxLwkACm9Gfvp7S2KJ3okUXohsGBAgJ68B9YeGiuIJiZhH2DZ1pm3/R9bSpOX3H+6vjaCsacXT5w7LZB+O0Vkthcm9vqeECgYEAvozFkIkFXXCEmCr3QVCJs4Fc6onbXEJU45xxubPhkA1wwwPrSqdubo4RHvNIus45Fn4mLzuQsaPRyJJZajvaKWC00GxhChMYj+nWgkAmABPKGwkMxzjC7wvEJkGyt87fHpK1XMFWQgfJ42VwUtmyemCMuh+A2SOekIJay93xTtkCgYEAtOhmQ4pu2cyqTzT+SD7p/VnS4sNqqM4I8NSvTuLkEo2IHnUj7YG6XoPZjn35dBvYUWWN2dwgfHXGEEzCOIwfy8GPA4eoKCDNEkMvoBVLdrEzMqg5QwG5GsIGvOuFnAzAw+D5YwEym/qmC2oBbat5jsAGT2rMmU5MnaS8a7lvcdMCgYEAiusQQb5TZfrZACMa3cg8i9y9A9R7UzicsM/mbW+B+8aAtfxOdr+4F+uE+d594IrmPcq8ReUUKR34nFRt0bBO7amuSOEqofCoEIt3MsBXs+i5iJpBcaClJSeb2hQ9mhm8uopUpInjPAJ3okva5twFbYikMDE1e5inSk1uqoBlI4kCgYB4rzDJjeg1U9upy2h3OcFPSkTtEgBtbEV6o+fvcF1GIzTTXMIDB7AUrVDNRizL0GeWpXDkDX1+ifL/nLVUk+YCP7XwXOdJHdiwfjGfUZVuMPg+qwrIMLYTq6xjC5uuZrOR+NtluL7SX3u10ZnyV5pYKLIM+OpUu29RGzy3gJVgEQKBgCC9vXS7P9RHTAxYEG4WOzv0tjFUtPOsaHenvNbc7nVe2Kkre0/TO+EtnuhINmJp2y5UEve6cLK2sPnbT8raarjPuomDfN0hwEx3jZd+rPdB/tdRH0LMLBu28TlzHllJYjbINn+NXc0adbqeuA4ziXTZow5yX5J+i9dy55A1bvie 'ENABLED': True,
-----END RSA PRIVATE KEY-----''', 'AUTH': {
'ALI_PUBLIC_KEY': '''-----BEGIN CERTIFICATE----- 'APP_ID': "2021002138691845",
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkru1ulQV1v4q+q38nyzgkdd3evf7C1/Ipu6K+ZFb5FiuxJ7mildkBSuKz/8+TRd+tjgk2lfc2ehK5pja3cxDO/nb25sBoWiU09rtxgXLehLsgRRhatbICrlOnYxg5aiB5odAp3NMRqore4lnVYwfIyL9M49I0G/NbQzYjUQvAQJsnHwc6a6Kuqi1CwR1WXI0sDF9w7KXC4vRFFIUTwI4bVq4HQWI7NhbgEajHM/j6D6Bh/OMcTYnJJzCja0WmZRe5flfCsELlPESOCWUMbYoaNfBzpNvvyOpmRgs9jgy2WY9SeaB9hxwkpr8tOd2Sc7j3221JKCyDaFAX+4zPy7/fQIDAQAB 'APP_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY-----
-----END CERTIFICATE-----''', MIIEowIBAAKCAQEAhqf2mwftoxZDNpl4eWsQ6mEfXgMlNPr6jv72ecA4hbKWqChXQmGS1T+0VsRTSoOXRDlu1MMqkTGISzHvmGb7Gmw+Myfs/ojoonD9r8fODvIo1MHolFBhr3GNQu7tqBlVJ76QgiYft+c4kkqCguuyCrd3Te6C5zCIuh6O98r4D3A3LFcm6OdScWGcfEbR+FUv+jSi2oezHeSpkhhpHGBLSsI0L9JOdHetdUE/TwN8V1HABdpnPXtp9SIu6ioIrrligX1ZRlwht2YUt0BPqPp/ApLdRIsqlhD4/ejmtMlaRqqiN6PulEThBew/qaLVSXIr2HCSXtwbki3pFMFOcsjF2wIDAQABAoIBADp4sQL83FnXDvSki8XdkgjUh7RhFUT+PtLdL9YKfADCXd1DNzDiAcqL0RlkQu62WXcMoW3OGavWoGJWmr3I6fy9R/0atzSH6syu19n+nyGqUcShNwdAKErwufB4o8Y8yddqToHVYCyRQOV1aVrEUhmJNUsn6LvPPW/kWRyMjE7XQDFHpL5/Ly7pXe+f9Btm37ZuePTPsm65P88C3GznjZxXhY1LBWFKLPG1470xdReduyeJFZS/TmK0nUxLwkACm9Gfvp7S2KJ3okUXohsGBAgJ68B9YeGiuIJiZhH2DZ1pm3/R9bSpOX3H+6vjaCsacXT5w7LZB+O0Vkthcm9vqeECgYEAvozFkIkFXXCEmCr3QVCJs4Fc6onbXEJU45xxubPhkA1wwwPrSqdubo4RHvNIus45Fn4mLzuQsaPRyJJZajvaKWC00GxhChMYj+nWgkAmABPKGwkMxzjC7wvEJkGyt87fHpK1XMFWQgfJ42VwUtmyemCMuh+A2SOekIJay93xTtkCgYEAtOhmQ4pu2cyqTzT+SD7p/VnS4sNqqM4I8NSvTuLkEo2IHnUj7YG6XoPZjn35dBvYUWWN2dwgfHXGEEzCOIwfy8GPA4eoKCDNEkMvoBVLdrEzMqg5QwG5GsIGvOuFnAzAw+D5YwEym/qmC2oBbat5jsAGT2rMmU5MnaS8a7lvcdMCgYEAiusQQb5TZfrZACMa3cg8i9y9A9R7UzicsM/mbW+B+8aAtfxOdr+4F+uE+d594IrmPcq8ReUUKR34nFRt0bBO7amuSOEqofCoEIt3MsBXs+i5iJpBcaClJSeb2hQ9mhm8uopUpInjPAJ3okva5twFbYikMDE1e5inSk1uqoBlI4kCgYB4rzDJjeg1U9upy2h3OcFPSkTtEgBtbEV6o+fvcF1GIzTTXMIDB7AUrVDNRizL0GeWpXDkDX1+ifL/nLVUk+YCP7XwXOdJHdiwfjGfUZVuMPg+qwrIMLYTq6xjC5uuZrOR+NtluL7SX3u10ZnyV5pYKLIM+OpUu29RGzy3gJVgEQKBgCC9vXS7P9RHTAxYEG4WOzv0tjFUtPOsaHenvNbc7nVe2Kkre0/TO+EtnuhINmJp2y5UEve6cLK2sPnbT8raarjPuomDfN0hwEx3jZd+rPdB/tdRH0LMLBu28TlzHllJYjbINn+NXc0adbqeuA4ziXTZow5yX5J+i9dy55A1bvie
'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/ali_pay_success', # 支付支付回调URL -----END RSA PRIVATE KEY-----''',
# 'RETURN_URL': 'https://app.hehelucky.cn/api/v1/fir/server/ali_pay_success', # 支付前端页面回调URL 'ALI_PUBLIC_KEY': '''-----BEGIN CERTIFICATE-----
'RETURN_URL': PAY_SUCCESS_URL, # 支付前端页面回调URL MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkru1ulQV1v4q+q38nyzgkdd3evf7C1/Ipu6K+ZFb5FiuxJ7mildkBSuKz/8+TRd+tjgk2lfc2ehK5pja3cxDO/nb25sBoWiU09rtxgXLehLsgRRhatbICrlOnYxg5aiB5odAp3NMRqore4lnVYwfIyL9M49I0G/NbQzYjUQvAQJsnHwc6a6Kuqi1CwR1WXI0sDF9w7KXC4vRFFIUTwI4bVq4HQWI7NhbgEajHM/j6D6Bh/OMcTYnJJzCja0WmZRe5flfCsELlPESOCWUMbYoaNfBzpNvvyOpmRgs9jgy2WY9SeaB9hxwkpr8tOd2Sc7j3221JKCyDaFAX+4zPy7/fQIDAQAB
'SUBJECT': '向 FLY分发平台 充值', -----END CERTIFICATE-----''',
'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/pay_success', # 支付支付回调URL
'RETURN_URL': PAY_SUCCESS_URL, # 支付前端页面回调URL
'SUBJECT': '向 FLY分发平台 充值',
}
}, },
'WX': { {
'APP_ID': "wx390e5985fd3699e6", 'TYPE': 'WX',
'MCH_ID': "1608486112", 'NAME': 'wxpay',
'SERIAL_NO': "27DADA4D2921CDD66B8B20A68276F09B90754922", 'ENABLED': True,
'APP_PRIVATE_KEY': '''-----BEGIN PRIVATE KEY----- 'AUTH': {
'APP_ID': "wx390e5985fd3699e6",
'MCH_ID': "1608486112",
'SERIAL_NO': "27DADA4D2921CDD66B8B20A68276F09B90754922",
'APP_PRIVATE_KEY': '''-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbXhNoHljrkS8T MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbXhNoHljrkS8T
jXg3+tTkaoOol8FDt0jSGckhzX46gkS16CWYwTthBKurfFtynsJe4uDOphS1ge/r jXg3+tTkaoOol8FDt0jSGckhzX46gkS16CWYwTthBKurfFtynsJe4uDOphS1ge/r
QEU3+rWNxqa8o6gHSpp2UTYAz/1oYOlXuSa4NA1uD47lmVZJzad2ybWDSsoeRjFj QEU3+rWNxqa8o6gHSpp2UTYAz/1oYOlXuSa4NA1uD47lmVZJzad2ybWDSsoeRjFj
@ -481,9 +489,10 @@ aaInnu9bitb9rVENCNGXQHdWmIYBMM5zrg8nX8xNJ+yeGQhgxE+YeSq4FOpe0JkA
daWIhg++OHN2MBRutj7oL/AFAxyu467YA5+itEJLHNATbOr/s13S66nePNXox/hr daWIhg++OHN2MBRutj7oL/AFAxyu467YA5+itEJLHNATbOr/s13S66nePNXox/hr
bIX1aWjPxirQX9mzaL3oEQI= bIX1aWjPxirQX9mzaL3oEQI=
-----END PRIVATE KEY-----''', -----END PRIVATE KEY-----''',
'API_V3_KEY': '60DbP621a9C3162dDd4AB9c2O15a005L', 'API_V3_KEY': '60DbP621a9C3162dDd4AB9c2O15a005L',
'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/wx_pay_success', # 支付支付回调URL 'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/pay_success', # 支付支付回调URL
'RETURN_URL': PAY_SUCCESS_URL, # 支付前端页面回调URL 'RETURN_URL': PAY_SUCCESS_URL, # 支付前端页面回调URL
'SUBJECT': '向 FLY分发平台 充值', 'SUBJECT': '向 FLY分发平台 充值',
}
} }
} ]

Loading…
Cancel
Save