feat: add WeChat Pay, Redis logging, storage tiers and personal albums
Replace virtual payments with WeChat Pay API v3 and verify callbacks and order polling. Cache WeChat access tokens in Redis and write structured Zap logs. Support configurable capacity plans across the API, admin and mini program, with purchase snapshots and quota enforcement. Allow personal albums before binding and share them with the current partner. Update deployment configuration and domain routing.
This commit is contained in:
@@ -21,12 +21,15 @@ Page({
|
||||
percent: 0,
|
||||
overQuota: false,
|
||||
isVip: false,
|
||||
isUnlimited:false,
|
||||
quotaText:"200 MB",
|
||||
vipExpireText: '',
|
||||
plans: [],
|
||||
paying: false,
|
||||
},
|
||||
|
||||
onLoad: function () { this.setData(getLayout()); },
|
||||
onLoad: function () { this._unloaded = false; this.setData(getLayout()); },
|
||||
onUnload: function () { this._unloaded = true; },
|
||||
onShow: function () { this.loadInfo(); },
|
||||
onPullDownRefresh: function () { this.loadInfo().then(function () { wx.stopPullDownRefresh(); }); },
|
||||
|
||||
@@ -34,7 +37,8 @@ Page({
|
||||
var self = this;
|
||||
this.setData({ loading: true, error: false });
|
||||
return api.storageInfo().then(function (d) {
|
||||
var freeBytes = d.free_bytes || d.freeBytes || 0;
|
||||
var freeBytes = d.quota_bytes != null ? d.quota_bytes : (d.quotaBytes != null ? d.quotaBytes : (d.free_bytes || d.freeBytes || 0));
|
||||
var unlimited=!!(d.is_unlimited || d.isUnlimited);
|
||||
var usedBytes = d.used_bytes || d.usedBytes || 0;
|
||||
var percent = freeBytes > 0 ? Math.min(100, Math.round(usedBytes / freeBytes * 100)) : 0;
|
||||
var overQuota = usedBytes > freeBytes;
|
||||
@@ -43,25 +47,30 @@ Page({
|
||||
return {
|
||||
key: p.key,
|
||||
name: p.name,
|
||||
priceText: (cents / 100).toFixed(cents % 100 === 0 ? 0 : 1),
|
||||
unit: p.key === 'year' ? '年' : '月',
|
||||
productId: p.product_id || p.productId,
|
||||
priceCents:cents,
|
||||
version:p.updated_at || p.updatedAt,
|
||||
priceText: (cents / 100).toFixed(2),
|
||||
unit: (p.months || (p.key==='year'?12:1))+'个月',
|
||||
description: (Number(p.quota_mb != null ? p.quota_mb : p.quotaMb || 0)===0 ? '无限空间' : formatBytes(Number(p.quota_mb != null ? p.quota_mb : p.quotaMb)*1024*1024))+' · 有效期'+(p.months || (p.key==='year'?12:1))+'个月',
|
||||
|
||||
};
|
||||
});
|
||||
var vipExpireText = '';
|
||||
if (d.is_vip || d.isVip) {
|
||||
var expire = d.vip_expire_at || d.vipExpireAt ? String(d.vip_expire_at || d.vipExpireAt).slice(0, 10) : '';
|
||||
vipExpireText = expire ? ('有效期至 ' + expire) : '已开通无限空间';
|
||||
vipExpireText = expire ? ('有效期至 ' + expire) : '已开通容量套餐';
|
||||
}
|
||||
self.setData({
|
||||
loading: false,
|
||||
usedBytes: usedBytes,
|
||||
usedText: formatBytes(usedBytes),
|
||||
freeMb: d.free_mb || d.freeMb || 200,
|
||||
freeMb: d.free_mb != null ? d.free_mb : (d.freeMb != null ? d.freeMb : 200),
|
||||
freeText: formatBytes(freeBytes),
|
||||
percent: percent,
|
||||
overQuota: overQuota,
|
||||
isVip: !!(d.is_vip || d.isVip),
|
||||
isUnlimited:unlimited,
|
||||
quotaText:unlimited?"无限空间":formatBytes(freeBytes),
|
||||
vipExpireText: vipExpireText,
|
||||
plans: plans,
|
||||
});
|
||||
@@ -83,14 +92,14 @@ Page({
|
||||
|
||||
wx.showLoading({ title: '下单中...', mask: true });
|
||||
this.setData({ paying: true });
|
||||
api.storageCreateOrder({ plan_key: key }).then(function (order) {
|
||||
api.storageCreateOrder({ plan_key: key, expected_price_cents:plan.priceCents, expected_plan_version:plan.version }).then(function (order) {
|
||||
wx.hideLoading();
|
||||
if (!order || !(order.sign_data || order.signData)) {
|
||||
self.setData({ paying: false });
|
||||
wx.showToast({ title: '下单失败', icon: 'none' });
|
||||
if (order && order.pay_provider === 'wechat' && order.payment) {
|
||||
self.callWechatPay(order);
|
||||
return;
|
||||
}
|
||||
self.callVirtualPay(order);
|
||||
self.setData({ paying: false });
|
||||
wx.showToast({ title: '支付参数不完整,请重新下单', icon: 'none' });
|
||||
}).catch(function (err) {
|
||||
wx.hideLoading();
|
||||
self.setData({ paying: false });
|
||||
@@ -98,37 +107,54 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
callVirtualPay: function (order) {
|
||||
callWechatPay: function (order) {
|
||||
var self = this;
|
||||
if (!wx.requestVirtualPayment) {
|
||||
var payment = order.payment;
|
||||
if (!wx.requestPayment) {
|
||||
this.setData({ paying: false });
|
||||
wx.showModal({
|
||||
title: '版本过低',
|
||||
content: '请升级微信至最新版本后再试。',
|
||||
showCancel: false
|
||||
});
|
||||
wx.showToast({ title: '当前环境不支持微信支付,请使用微信真机', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// wx.requestVirtualPayment 仅接受 signData / paySig / signature / mode 四个参数;
|
||||
// offerId、env、productId、goodsPrice 等均已编码在 signData JSON 字符串内,不可再平铺为顶层参数。
|
||||
wx.requestVirtualPayment({
|
||||
signData: order.sign_data || order.signData,
|
||||
paySig: order.pay_sig || order.paySig,
|
||||
signature: order.signature,
|
||||
mode: order.mode || 'short_series_goods',
|
||||
success: function () {
|
||||
self.setData({ paying: false });
|
||||
wx.showToast({ title: '支付成功', icon: 'success' });
|
||||
setTimeout(function () { self.loadInfo(); }, 1200);
|
||||
},
|
||||
fail: function (err) {
|
||||
self.setData({ paying: false });
|
||||
var msg = '支付未完成';
|
||||
var m = (err && err.errMsg) || '';
|
||||
if (m.indexOf('cancel') >= 0) msg = '已取消支付';
|
||||
else if (m.indexOf('fail') >= 0) msg = '支付失败,请稍后重试';
|
||||
wx.showToast({ title: msg, icon: 'none' });
|
||||
wx.requestPayment({
|
||||
timeStamp: payment.time_stamp,
|
||||
nonceStr: payment.nonce_str,
|
||||
package: payment.package,
|
||||
signType: payment.sign_type,
|
||||
paySign: payment.pay_sign,
|
||||
success: function () { return self.confirmWechatPayment(order.out_trade_no); },
|
||||
fail: function (error) {
|
||||
// Even a cashier error can occur after payment; query the server once.
|
||||
var message = String(error && error.errMsg || '').indexOf('cancel') >= 0 ? '已取消支付' : '支付未完成';
|
||||
return self.confirmWechatPayment(order.out_trade_no, message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
confirmWechatPayment: async function (number, failureMessage) {
|
||||
if (this._confirming) return;
|
||||
this._confirming = true;
|
||||
try {
|
||||
for (var attempt = 0; attempt < (failureMessage ? 1 : 5); attempt++) {
|
||||
var result = await api.storageOrderStatus(number);
|
||||
if (this._unloaded) return;
|
||||
if (result.status === 'success') {
|
||||
wx.showToast({ title: '支付成功,会员已开通', icon: 'success' });
|
||||
await this.loadInfo();
|
||||
return;
|
||||
}
|
||||
if (result.status === 'closed') {
|
||||
wx.showToast({ title: '订单已关闭,请重新下单', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!failureMessage && attempt < 4) await new Promise(function (resolve) { setTimeout(resolve, 1200); });
|
||||
}
|
||||
if (!this._unloaded) wx.showToast({ title: failureMessage || '支付结果确认中,请稍后刷新', icon: 'none' });
|
||||
} catch (e) {
|
||||
if (!this._unloaded) wx.showToast({ title: '支付结果暂未确认,请稍后刷新', icon: 'none' });
|
||||
} finally {
|
||||
this._confirming = false;
|
||||
if (!this._unloaded) this.setData({ paying: false });
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user