486 lines
18 KiB
JavaScript
486 lines
18 KiB
JavaScript
import { CentToString } from "oak-domain/lib/utils/money";
|
||
import { DATA_SUBSCRIBER_KEYS } from "../../../config/constants";
|
||
import assert from "assert";
|
||
import { isWeiXin } from 'oak-frontend-base/es/utils/utils';
|
||
import { merge } from "oak-domain/lib/utils/lodash";
|
||
import { canStartPay } from "../../../utils/wpProductFrontend";
|
||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||
import { StartPayFailure } from "../../../types/Exception";
|
||
const PayRoutineDict = {
|
||
wpProduct: {
|
||
projection: {
|
||
wpProduct: {
|
||
id: 1,
|
||
type: 1,
|
||
},
|
||
},
|
||
routine: async (pay, features) => {
|
||
const { iState, wpProduct, meta } = pay;
|
||
switch (wpProduct.type) {
|
||
case 'mp': {
|
||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||
const { prepayMeta } = meta;
|
||
if (prepayMeta) {
|
||
try {
|
||
const result = await wx.requestPayment(prepayMeta);
|
||
process.env.NODE_ENV === 'development' && console.log(result);
|
||
}
|
||
catch (err) {
|
||
throw new StartPayFailure(err.errMsg.includes('cancel')
|
||
? features.locales.t('startPayError.userCancel')
|
||
: features.locales.t('startPayError.unknown'));
|
||
}
|
||
}
|
||
else {
|
||
throw new StartPayFailure(features.locales.t('startPayError.illegalPrepayData'));
|
||
}
|
||
}
|
||
else {
|
||
throw new StartPayFailure(features.locales.t('startPayError.falseEnv', { env: 'wechatMp' }));
|
||
}
|
||
return;
|
||
}
|
||
case 'jsapi': {
|
||
if (process.env.OAK_PLATFORM === 'web' && isWeiXin) {
|
||
const { prepayMeta } = meta;
|
||
if (prepayMeta) {
|
||
const { timeStamp, ...rest } = prepayMeta;
|
||
// chooseWXPay文档都找不到了,网上查出来这里timestamp的s要小写,吐血了
|
||
await features.wechatSdk.loadWxAPi('chooseWXPay', {
|
||
timestamp: timeStamp,
|
||
...rest,
|
||
});
|
||
}
|
||
else {
|
||
throw new StartPayFailure(features.locales.t('startPayError.illegalPrepayData'));
|
||
}
|
||
}
|
||
}
|
||
default: {
|
||
assert('尚未实现');
|
||
}
|
||
}
|
||
},
|
||
judgeCanPay: canStartPay,
|
||
}
|
||
};
|
||
export function registerFrontendPayRoutine(entity, routine, projection, judgeCanPay) {
|
||
PayRoutineDict[entity] = {
|
||
routine,
|
||
projection,
|
||
judgeCanPay,
|
||
};
|
||
}
|
||
const baseProjection = {
|
||
id: 1,
|
||
applicationId: 1,
|
||
price: 1,
|
||
meta: 1,
|
||
iState: 1,
|
||
paid: 1,
|
||
refunded: 1,
|
||
timeoutAt: 1,
|
||
forbidRefundAt: 1,
|
||
externalId: 1,
|
||
orderId: 1,
|
||
depositId: 1,
|
||
deposit: {
|
||
id: 1,
|
||
accountId: 1,
|
||
price: 1,
|
||
loss: 1,
|
||
iState: 1,
|
||
shipId: 1,
|
||
ship: {
|
||
id: 1,
|
||
iState: 1,
|
||
}
|
||
},
|
||
order: {
|
||
id: 1,
|
||
creatorId: 1,
|
||
},
|
||
entity: 1,
|
||
entityId: 1,
|
||
creatorId: 1,
|
||
phantom3: 1,
|
||
};
|
||
export default OakComponent({
|
||
entity: 'pay',
|
||
isList: false,
|
||
projection: () => {
|
||
for (const k in PayRoutineDict) {
|
||
merge(baseProjection, PayRoutineDict[k].projection);
|
||
}
|
||
return baseProjection;
|
||
},
|
||
properties: {
|
||
onClose: () => undefined,
|
||
onPaid: () => undefined,
|
||
onPayFailure: () => undefined,
|
||
goBackable: false,
|
||
disableAutoPay: false,
|
||
closeWhenFailure: false,
|
||
mode: 'frontend',
|
||
autoSuccessAt: true,
|
||
disableClose: false,
|
||
},
|
||
data: {
|
||
showCloseConfirmMp: false,
|
||
showChannelSelectMp: false,
|
||
unsub: undefined,
|
||
depositUnsub: undefined,
|
||
},
|
||
formData({ data, props }) {
|
||
const application = this.features.application.getApplication();
|
||
const iState = data?.iState;
|
||
let iStateStr = this.t(`pay:v.iState.${iState}`);
|
||
let iStateColor = iState && this.features.style.getColor('pay', 'iState', iState);
|
||
let showDepositConfirm = false, depositConfirmLoading = false;
|
||
if (data?.depositId && data?.deposit?.shipId && data?.deposit?.iState === 'shipped') {
|
||
iStateStr = this.t('ship.shipped');
|
||
iStateColor = this.features.style.getColor('deposit', 'iState', data?.deposit?.iState);
|
||
showDepositConfirm = true;
|
||
depositConfirmLoading = data?.deposit?.ship?.iState !== 'receiving';
|
||
}
|
||
const startPayable = iState === 'paying' && !['account', 'offlineAccount'].includes(data.entity) && (PayRoutineDict[data.entity] && PayRoutineDict[data.entity].judgeCanPay(data, this.features));
|
||
const { mode } = this.props;
|
||
const succeedable = data["#oakLegalActions"]?.includes('succeedPaying');
|
||
const payChannels = this.features.pay.getPayChannels();
|
||
const offlines = this.features.cache.get('offlineAccount', {
|
||
data: {
|
||
id: 1,
|
||
type: 1,
|
||
channel: 1,
|
||
name: 1,
|
||
qrCode: 1,
|
||
},
|
||
filter: {
|
||
systemId: this.features.application.getApplication().systemId,
|
||
}
|
||
}).map(ele => {
|
||
const color = this.features.style.getColor('offlineAccount', 'type', ele.type);
|
||
return {
|
||
color,
|
||
...ele,
|
||
};
|
||
});
|
||
const offline = offlines?.find(ele => ele.id === data.entityId);
|
||
return {
|
||
type: data?.orderId ? 'order' : 'deposit',
|
||
pay: data,
|
||
application,
|
||
iStateColor,
|
||
closable: !props.disableClose && !!(data?.["#oakLegalActions"]?.includes('close')),
|
||
startPayable,
|
||
succeedable,
|
||
offline,
|
||
offlines,
|
||
notSameApp: data && data.applicationId !== application.id && data.entity !== 'offlineAccount',
|
||
priceStr: data?.price && CentToString(data.price, 2),
|
||
oakExecutable: this.tryExecute(),
|
||
iStateStr,
|
||
shipId: data?.deposit?.shipId,
|
||
ship: data?.deposit?.ship,
|
||
depositId: data?.depositId,
|
||
showDepositConfirm,
|
||
depositConfirmLoading,
|
||
};
|
||
},
|
||
features: [{
|
||
feature: 'application',
|
||
callback() {
|
||
this.refreshOfflineAccounts();
|
||
}
|
||
}],
|
||
actions: ['close', 'startPaying', {
|
||
action: 'update',
|
||
attrs: ['entityId'],
|
||
}, 'succeedPaying'],
|
||
methods: {
|
||
refreshOfflineAccounts() {
|
||
const { entity } = this.state.pay || {};
|
||
if (entity === 'offlineAccount') {
|
||
this.features.cache.refresh('offlineAccount', {
|
||
data: {
|
||
id: 1,
|
||
channel: 1,
|
||
name: 1,
|
||
type: 1,
|
||
qrCode: 1,
|
||
},
|
||
filter: {
|
||
systemId: this.features.application.getApplication().systemId,
|
||
}
|
||
});
|
||
}
|
||
},
|
||
succeed(successAt) {
|
||
const { pay } = this.state;
|
||
this.update({
|
||
successAt,
|
||
paid: pay.price,
|
||
});
|
||
return this.execute('succeedPaying');
|
||
},
|
||
executeMp() {
|
||
return this.execute();
|
||
},
|
||
resetMp() {
|
||
return this.clean();
|
||
},
|
||
closeMp() {
|
||
this.setState({
|
||
showCloseConfirmMp: true,
|
||
});
|
||
},
|
||
cancelCloseMp() {
|
||
this.setState({
|
||
showCloseConfirmMp: false,
|
||
});
|
||
},
|
||
async confirmCloseMp() {
|
||
await this.execute('close');
|
||
this.cancelCloseMp();
|
||
const { onClose } = this.props;
|
||
onClose && onClose();
|
||
},
|
||
goBack() {
|
||
this.navigateBack();
|
||
},
|
||
async startPay() {
|
||
const { onPaid, onClose, onPayFailure } = this.props;
|
||
const { pay } = this.state;
|
||
try {
|
||
await PayRoutineDict[pay.entity].routine(pay, this.features);
|
||
onPaid && onPaid();
|
||
}
|
||
catch (err) {
|
||
if (this.props.closeWhenFailure) {
|
||
await this.execute(undefined, undefined, undefined, [
|
||
{
|
||
entity: 'pay',
|
||
operation: {
|
||
id: await generateNewIdAsync(),
|
||
action: 'close',
|
||
data: {},
|
||
filter: {
|
||
id: this.props.oakId,
|
||
},
|
||
},
|
||
}
|
||
]);
|
||
onClose && onClose();
|
||
}
|
||
else {
|
||
onPayFailure && onPayFailure();
|
||
}
|
||
this.features.message.setMessage({
|
||
type: 'warning',
|
||
content: err.message,
|
||
});
|
||
}
|
||
},
|
||
openChannelSelectMp() {
|
||
this.setState({
|
||
showChannelSelectMp: true,
|
||
});
|
||
},
|
||
closeChannelSelectMp() {
|
||
this.setState({
|
||
showChannelSelectMp: false,
|
||
});
|
||
},
|
||
async updateOfflineIdMp(touch) {
|
||
const { detail } = touch;
|
||
const { currentKey } = detail;
|
||
const { oakId } = this.props;
|
||
if (currentKey) {
|
||
await this.execute(undefined, undefined, undefined, [
|
||
{
|
||
entity: 'pay',
|
||
operation: {
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
entity: 'offlineAccount',
|
||
entityId: currentKey,
|
||
},
|
||
filter: {
|
||
id: oakId,
|
||
},
|
||
}
|
||
}
|
||
]);
|
||
this.setState({
|
||
showChannelSelectMp: false,
|
||
});
|
||
}
|
||
},
|
||
updateExternalIdMp(input) {
|
||
const { detail } = input;
|
||
this.update({
|
||
externalId: detail.value,
|
||
});
|
||
},
|
||
clearExternalIdMp() {
|
||
this.update({
|
||
externalId: null,
|
||
});
|
||
},
|
||
async confirmShipMp() {
|
||
//小程序唤起确认收货组件
|
||
const { pay, ship, depositId } = this.state;
|
||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||
const shipId = ship.id;
|
||
const shipState = ship.iState;
|
||
if (process.env.NODE_ENV === 'development') {
|
||
if (shipId && shipState === 'receiving') {
|
||
await this.features.cache.operate('ship', [{
|
||
id: await generateNewIdAsync(),
|
||
action: 'succeedReceiving',
|
||
data: {},
|
||
filter: {
|
||
id: shipId,
|
||
},
|
||
}]);
|
||
this.setMessage({
|
||
type: 'success',
|
||
content: this.t('ship.success')
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
if (pay.meta.transaction_id) {
|
||
if (wx.openBusinessView) {
|
||
wx.openBusinessView({
|
||
businessType: 'weappOrderConfirm',
|
||
extraData: {
|
||
transaction_id: pay.meta.transaction_id,
|
||
},
|
||
success: async () => {
|
||
console.log('success');
|
||
// const { result: mpShipState } = await this.features.cache.exec('getMpShipState', {
|
||
// shipId,
|
||
// })
|
||
// if (mpShipState === 'received') {
|
||
// await this.features.cache.operate('ship',
|
||
// [{
|
||
// id: await generateNewIdAsync(),
|
||
// action: 'succeedReceiving',
|
||
// data: {},
|
||
// filter: {
|
||
// id: shipId,
|
||
// },
|
||
// }]
|
||
// );
|
||
// this.reRender();
|
||
// this.setMessage({
|
||
// type: 'success',
|
||
// content: this.t('ship.success')
|
||
// });
|
||
// } else {
|
||
// console.log(mpShipState);
|
||
// // this.setMessage({
|
||
// // type: 'warning',
|
||
// // content: this.t('ship.wait'),
|
||
// // })
|
||
// this.reRender();
|
||
// }
|
||
const { result } = await this.features.cache.exec('shipConfirmSuccess', {
|
||
shipId,
|
||
});
|
||
if (result) {
|
||
this.setMessage({
|
||
type: 'success',
|
||
content: this.t('ship.success')
|
||
});
|
||
}
|
||
else {
|
||
this.reRender();
|
||
}
|
||
},
|
||
fail: () => {
|
||
console.log('fail');
|
||
this.setMessage({
|
||
type: 'warning',
|
||
content: this.t('ship.fail'),
|
||
});
|
||
},
|
||
complete: () => {
|
||
}
|
||
});
|
||
}
|
||
else {
|
||
//引导用户升级微信版本
|
||
this.setMessage({
|
||
type: 'warning',
|
||
content: this.t('ship.update wechat'),
|
||
});
|
||
}
|
||
}
|
||
else {
|
||
this.setMessage({
|
||
type: 'warning',
|
||
content: this.t('no transactionId'),
|
||
});
|
||
}
|
||
}
|
||
}
|
||
},
|
||
lifetimes: {
|
||
async ready() {
|
||
const { oakId } = this.props;
|
||
assert(typeof oakId === 'string');
|
||
const unsub = await this.subDataEvents([
|
||
`${DATA_SUBSCRIBER_KEYS.payStateChanged}-${oakId}`,
|
||
`${DATA_SUBSCRIBER_KEYS.depositStateChanged}-${oakId}`
|
||
], async (event, opRecords) => {
|
||
for (const record of opRecords) {
|
||
console.log(record);
|
||
if (record.e === 'pay' || record.e === 'deposit') {
|
||
const { f } = record;
|
||
const [pay] = this.features.cache.get('pay', {
|
||
data: baseProjection,
|
||
filter: f,
|
||
});
|
||
console.log(JSON.stringify(pay));
|
||
}
|
||
}
|
||
});
|
||
let depositUnsub = undefined;
|
||
const { depositId } = this.state;
|
||
if (depositId) {
|
||
depositUnsub = await this.subDataEvents([`${DATA_SUBSCRIBER_KEYS.shipStateChanged}-${depositId}`]);
|
||
}
|
||
this.setState({
|
||
unsub,
|
||
depositUnsub,
|
||
});
|
||
},
|
||
async mature() {
|
||
const { ship } = this.state;
|
||
if (ship?.id && ship?.iState === 'receiving') {
|
||
await this.confirmShipMp();
|
||
}
|
||
},
|
||
detached() {
|
||
const { unsub, depositUnsub } = this.state;
|
||
unsub && unsub();
|
||
depositUnsub && depositUnsub();
|
||
},
|
||
},
|
||
listeners: {
|
||
startPayable(prev, next) {
|
||
if (next.startPayable && !this.props.disableAutoPay) {
|
||
this.startPay();
|
||
}
|
||
},
|
||
pay(prev, next) {
|
||
if (!prev.pay && next.pay) {
|
||
this.refreshOfflineAccounts();
|
||
}
|
||
},
|
||
},
|
||
});
|