From f552fb957f2ae7807a0014118a6859b4af554e94 Mon Sep 17 00:00:00 2001 From: Dev Date: Wed, 28 Oct 2026 15:37:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=87=91=E9=A2=9D=E5=8F=96?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/service/DemandService.php | 19 ++--- .../common/service/OrderService.php | 72 +++++++++++++++++-- .../miniapp/pages/demand/contract/index.js | 65 +++++++++++++++-- .../miniapp/pages/demand/contract/index.wxml | 2 +- 4 files changed, 134 insertions(+), 24 deletions(-) diff --git a/backend/application/common/service/DemandService.php b/backend/application/common/service/DemandService.php index 673ab80..47cef90 100644 --- a/backend/application/common/service/DemandService.php +++ b/backend/application/common/service/DemandService.php @@ -351,17 +351,18 @@ class DemandService $winContract = ContractService::updateCustomerContractData($demandId, $formData); //如果新的合同金额与旧的合同金额不符,则通过修改尾款订单的金额的方式来弥补差价 $firstContract = ContractService::getFirstContract($demandId); - if ($firstContract && $firstContract->total_amount != $winContract->total_amount && $winContract->status2 != ContractService::STATUS_FINISHED) { - $diffAmount = $winContract->total_amount - $firstContract->total_amount; - //更新尾款订单的金额 + if ($firstContract && bccomp((string)$firstContract->total_amount, (string)$winContract->total_amount, 2) !== 0 && $winContract->status2 != ContractService::STATUS_FINISHED) { + // 尾款 = 新合同总额 - 首付款,不按固定 50% 重算 + $firstOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FIRST_PAYMENT); $finalOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FINAL_PAYMENT); - $finalOrder->total_amount += $diffAmount; - $finalOrder->goods_amount += $diffAmount; - $finalOrder->payment_amount += $diffAmount; - $saveRes = $finalOrder->save(); - if (!$saveRes) { - exception('弥补订单差价金额失败'); + if (!$firstOrder || !$finalOrder) { + exception('获取分期订单失败'); } + $finalAmount = bcsub((string)$winContract->total_amount, (string)$firstOrder->payment_amount, 2); + if (bccomp($finalAmount, '0', 2) < 0) { + exception('合同金额不能小于已确定的首付款'); + } + OrderService::applyOrderAmount($finalOrder, $finalAmount); } //获取签约订单用于支付 $order = OrderService::getDemandOrder($demand->parent_order_no, $contractType); diff --git a/backend/application/common/service/OrderService.php b/backend/application/common/service/OrderService.php index b00c556..f64c778 100644 --- a/backend/application/common/service/OrderService.php +++ b/backend/application/common/service/OrderService.php @@ -202,16 +202,19 @@ class OrderService } $winBid = DemandBiddingService::getOne(['demand_id' => $demandId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]); $orderInfo = self::where(['parent_order_no' => $demand->parent_order_no])->find(); - $ratio = 0.5; //分期比率50% + // 首付为总金额 50% 四舍五入到整数元,尾款为剩余未付金额 + list($firstAmount, $finalAmount) = self::splitDemandPayment($totalCost); if (!$orderInfo) { $promoterId = PromotionService::getPromoterId(intval($demand->user_id)); - $installments = 2;//分期数 - for ($i = 1; $i <= $installments; $i++) { - $total_amount = $i == 1 ? $totalCost * $ratio : $totalCost * (1 - $ratio); + $installments = [ + self::ORDER_TYPE_FIRST_PAYMENT => $firstAmount, + self::ORDER_TYPE_FINAL_PAYMENT => $finalAmount, + ]; + foreach ($installments as $orderType => $total_amount) { $goodsDataList = [[ 'spu_id' => 0, 'sku_id' => 0, - 'goods_name' => '需求签约-' . ($i == 1 ? '首付款' : '尾款') . $demand->parent_order_no, + 'goods_name' => '需求签约-' . ($orderType == self::ORDER_TYPE_FIRST_PAYMENT ? '首付款' : '尾款') . $demand->parent_order_no, 'goods_picture_url' => 'https://soft-market.oss-cn-shenzhen.aliyuncs.com/images/contract.png', 'origin_price' => $total_amount, 'actual_price' => $total_amount, @@ -229,7 +232,7 @@ class OrderService 'parent_order_no' => $demand->parent_order_no, 'order_no' => generate_order_sn(), 'order_status' => OrderService::ORDER_STATUS_WAIT_PAY, - 'order_type' => $i, + 'order_type' => $orderType, 'total_amount' => $total_amount, 'payment_amount' => $total_amount, 'goods_amount' => $total_amount, @@ -259,7 +262,7 @@ class OrderService if (!$contractInfo) { //创建合同数据 $firstContract = ContractService::getFirstContract($demandId); - $depositAmount = $firstContract ? $firstContract->deposit_amount : $formData['totalCost'] * $ratio; + $depositAmount = $firstContract ? $firstContract->deposit_amount : $firstAmount; ContractService::createData($demandId, $winBid->id ?? 0, $depositAmount, $formData); } return $contractInfo; @@ -268,6 +271,61 @@ class OrderService } } + /** + * 需求分期。首付为总金额的 50% 四舍五入到整数元,尾款为总额减去首付 + * @param float|string $totalCost + * @return array [首付款, 尾款],均为两位小数字符串 + * @throws \Exception + */ + public static function splitDemandPayment($totalCost) + { + $total = bcadd((string)$totalCost, '0', 2); + if (bccomp($total, '0.01', 2) < 0) { + throw new \Exception('合同金额不能小于0.01元'); + } + $half = bcmul($total, '0.5', 4); + $first = bcadd(bcadd($half, '0.5', 0), '0', 2); + if (bccomp($first, $total, 2) === 1) { + $first = $total; + } + $final = bcsub($total, $first, 2); + + return [$first, $final]; + } + + /** + * 把订单和订单商品金额改成指定金额 + * @param \think\Model $order + * @param float|string $amount + * @return \think\Model + * @throws \Exception + */ + public static function applyOrderAmount($order, $amount) + { + $amount = bcadd((string)$amount, '0', 2); + $order->total_amount = $amount; + $order->goods_amount = $amount; + $order->payment_amount = $amount; + if ($order->save() === false) { + throw new \Exception('更新订单金额失败'); + } + $goodsList = $order->orderGoods; + if ($goodsList) { + foreach ($goodsList as $goods) { + $goods->origin_price = $amount; + $goods->actual_price = $amount; + $goods->item_total_amount = $amount; + $goods->item_payment_amount = $amount; + $goods->goods_payment_price = $amount; + if ($goods->save() === false) { + throw new \Exception('更新订单商品金额失败'); + } + } + } + + return $order; + } + public static function getDemandOrder($parentOrderNo, $contractType) { $res = self::getOne(['parent_order_no' => $parentOrderNo, 'order_type' => $contractType], ['orderGoods', 'orderPayment']); diff --git a/frontend/miniapp/pages/demand/contract/index.js b/frontend/miniapp/pages/demand/contract/index.js index 433f86f..104c857 100644 --- a/frontend/miniapp/pages/demand/contract/index.js +++ b/frontend/miniapp/pages/demand/contract/index.js @@ -54,7 +54,10 @@ Page({ attachFiles: [], // 签约条款 agreementChecked: false - } + }, + firstAmount: '', + finalAmount: '', + costTips: '首付款按总费用的50%四舍五入到整数元,尾款为剩余未付金额' }, /** @@ -88,20 +91,65 @@ Page({ platformName: payPlatform === 'ios' ? 'iOS' : '安卓', }; }, + + // 首付为总金额 50% 四舍五入到整数元,尾款为剩余未付金额。与后端 splitDemandPayment 一致 + installmentPreview(amount) { + const text = String(amount == null ? '' : amount).trim(); + if (!text || Number(text) <= 0) { + return { firstAmount: '', finalAmount: '' }; + } + const parts = text.split('.'); + const yuanPart = parseInt(parts[0], 10); + if (Number.isNaN(yuanPart)) { + return { firstAmount: '', finalAmount: '' }; + } + const dec = (parts[1] || '0').padEnd(2, '0').slice(0, 2); + const fen = yuanPart * 100 + parseInt(dec, 10); + const firstYuan = Math.floor((fen + 100) / 200); + const firstFen = Math.min(firstYuan * 100, fen); + const finalFen = fen - firstFen; + const format = (value) => (value / 100).toFixed(2); + return { + firstAmount: format(firstFen), + finalAmount: format(finalFen), + }; + }, + + buildCostTips(installment, income, commissionPercent) { + const percent = commissionPercent !== undefined ? commissionPercent : this.data.commissionPercent; + const splitText = installment.firstAmount + ? `首付款${installment.firstAmount}元(总费用的50%四舍五入到整数元),尾款${installment.finalAmount}元(剩余未付金额)` + : '首付款按总费用的50%四舍五入到整数元,尾款为剩余未付金额'; + if (this.data.source == 'dev') { + return `预计到手金额 ${income.actualAccount}元,已扣平台服务费${percent}%和${income.platformName}支付费率${income.payFeePercent}%。${splitText}`; + } + return splitText; + }, + + fillPaymentPreview(amount, incomePatch, commissionPercent) { + const income = this.incomePreview(amount, incomePatch); + const installment = this.installmentPreview(amount); + return { + ...income, + ...installment, + costTips: this.buildCostTips(installment, income, commissionPercent), + }; + }, // 拉取平台服务费和支付费率,并重算预计到手金额 async loadCommissionRatio() { const rates = await fetchIncomeRates(); + const commissionPercent = toPercent(rates.commissionRatio); this.setData({ commissionRatio: rates.commissionRatio, - commissionPercent: toPercent(rates.commissionRatio), + commissionPercent, androidPayFeeRate: rates.androidPayFeeRate, iosPayFeeRate: rates.iosPayFeeRate, androidFeePercent: toPercent(rates.androidPayFeeRate), iosFeePercent: toPercent(rates.iosPayFeeRate), - ...this.incomePreview(this.data.formData?.totalCost, { + ...this.fillPaymentPreview(this.data.formData?.totalCost, { ...rates, payPlatform: this.data.payPlatform, - }), + }, commissionPercent), }); }, @@ -235,7 +283,7 @@ Page({ onTotalCostChange(e) { this.setData({ 'formData.totalCost': e.detail.value, - ...this.incomePreview(e.detail.value), + ...this.fillPaymentPreview(e.detail.value), }); }, @@ -559,7 +607,7 @@ Page({ 'formData.taskContent': resData.task_content, 'formData.needDays': resData.need_days, 'formData.totalCost': resData.total_amount, - ...this.incomePreview(resData.total_amount, { payPlatform: resData.pay_platform }), + ...this.fillPaymentPreview(resData.total_amount, { payPlatform: resData.pay_platform }), }); // 客户签约时,附件展示开发者签约时上传的附件,仅可预览,不可删除和上传 @@ -690,10 +738,13 @@ Page({ }); return; } + const payAmount = res.data && res.data.payment_amount ? res.data.payment_amount : ''; wx.showModal({ title: '提示', showCancel: false, - content: '签约完成,去支付首付款(总金额的50%)', + content: payAmount + ? `签约完成,去支付首付款${payAmount}元。尾款为合同剩余未付金额` + : '签约完成,去支付首付款', success: (res2) => { if (res2.confirm) { //支付预付款 diff --git a/frontend/miniapp/pages/demand/contract/index.wxml b/frontend/miniapp/pages/demand/contract/index.wxml index ce2e179..92b9252 100644 --- a/frontend/miniapp/pages/demand/contract/index.wxml +++ b/frontend/miniapp/pages/demand/contract/index.wxml @@ -64,7 +64,7 @@ /> - +