mapItems($orderPreview->payload['items']); [ $productsTotalAmount, $discountReductionAmount, $paymentAmount, ] = $this->calculateFees($items); // 积分抵扣金额 $pointsDeductionAmount = $points; $paymentAmount -= $pointsDeductionAmount; if ($paymentAmount < 0) { $paymentAmount = 0; } do { $sn = serial_number(); } while(OfflineOrder::where('sn', $sn)->exists()); /** @var \App\Models\OfflineOrder */ $order = OfflineOrder::create([ 'user_id' => $user->id, 'store_id' => $orderPreview->store_id, 'staff_id' => $orderPreview->staff_id, 'sn' => $sn, 'products_total_amount' => $productsTotalAmount, 'discount_reduction_amount' => $discountReductionAmount, 'points_deduction_amount' => $pointsDeductionAmount, 'payment_amount' => $paymentAmount, 'status' => OfflineOrderStatus::Pending, 'orderable_type' => $orderPreview->getMorphClass(), 'orderable_id' => $orderPreview->id, ]); $this->insertOrderItems($order, $items); // 扣除积分 if ($points > 0) { (new PointService)->change($user, -$points, PointLogAction::Consumption, "线下订单{$order->sn}使用积分", $order); } if ($order->payment_amount === 0) { $this->pay($order, PayWay::None); $order->refresh(); } return $order; } public function check(User $user, array $items): array { $productCategoryIds = collect($items)->pluck('product_category_id'); $productCategories = OfflineProductCategory::query() ->whereIn('id', $productCategoryIds->all()) ->get() ->keyBy('id'); if ($productCategories->count() != $productCategoryIds->count()) { throw new BizException('商品分类异常'); } $mapItems = $this->mapItems($items); [ $productsTotalAmount, $discountReductionAmount, $paymentAmount, ] = $this->calculateFees($mapItems); //--------------------------------------- // 积分当钱花 //--------------------------------------- $remainingPoints = $user->userInfo->points; // 用户剩余积分 $availablePoints = $paymentAmount > $remainingPoints ? $remainingPoints : $paymentAmount; // 可用积分 return [ 'items' => collect($mapItems)->map(fn($item) => [ 'product_category' => $productCategories->get($item['product_category_id']), 'discount_reduction_amount' => bcdiv($item['discount_reduction_amount'], 100, 2), 'products_total_amount' => bcdiv($item['products_total_amount'], 100, 2), 'payment_amount' => bcdiv($item['payment_amount'], 100, 2), ]), 'products_total_amount' => bcdiv($productsTotalAmount, 100, 2), 'discount_reduction_amount' => bcdiv($discountReductionAmount, 100, 2), 'payment_amount' => bcdiv($paymentAmount, 100, 2), 'remaining_points' => bcdiv($remainingPoints, 100, 2), // 剩余积分 'available_points' => bcdiv($availablePoints, 100, 2), // 可用积分 'points_discount_amount' => bcdiv($availablePoints, 100, 2), // 积分抵扣金额 ]; } public function pay(OfflineOrder $order, PayWay $payWay) { if (! $order->isPending()) { throw new BizException('订单状态不是待付款'); } $payLog = $order->payLogs()->create([ 'pay_way' => $payWay, ]); $data = null; if ($order->payment_amount === 0) { (new PayService())->handleSuccess($payLog, [ 'pay_at' => now(), ]); } elseif ($payLog->isWxpay()) { if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) { throw new BizException('支付方式 非法'); } $params = [ 'body' => app_settings('app.app_name').'-线下订单', 'out_trade_no' => $payLog->pay_sn, 'total_fee' => $order->payment_amount, 'trade_type' => $tradeType->value, ]; if ($payLog->pay_way === PayWay::WxpayMiniProgram) { $socialite = SocialiteUser::where([ 'user_id' => $order->user_id, 'socialite_type' => SocialiteType::WechatMiniProgram, ])->first(); if ($socialite === null) { throw new BizException('未绑定微信小程序'); } $params['openid'] = $socialite->socialite_id; } $data = (new WxpayService())->pay($params, match ($payLog->pay_way) { PayWay::WxpayMiniProgram => 'mini_program', default => 'default', }); } return [ 'pay_way' => $payLog->pay_way, 'data' => $data, ]; } protected function insertOrderItems(OfflineOrder $order, array $items) { $remainingPointDiscountAmount = $order->points_deduction_amount; OfflineOrderItem::insert( collect($items)->map(function ($item) use ($order, &$remainingPointDiscountAmount) { $pointsDeductionAmount = $item['payment_amount']; if ($item['payment_amount'] > $remainingPointDiscountAmount) { $pointsDeductionAmount = $remainingPointDiscountAmount; } $remainingPointDiscountAmount -= $pointsDeductionAmount; return [ 'order_id' => $order->id, 'product_category_id' => $item['product_category_id'], 'products_total_amount' => $item['products_total_amount'], 'discount_reduction_amount' => $item['discount_reduction_amount'], 'points_deduction_amount' => $pointsDeductionAmount, 'payment_amount' => $item['payment_amount'] - $pointsDeductionAmount, 'created_at' => $order->created_at, 'updated_at' => $order->updated_at, ]; })->all() ); } protected function mapItems(array $items): array { return collect($items)->map(function ($item) { $productsTotalAmount = bcmul($item['products_total_amount'], 100); if ($productsTotalAmount < 0) { throw new BizException('商品总额不能小于0'); } $paymentAmount = $productsTotalAmount; if (is_numeric($item['discount'])) { if ($item['discount'] <= 0) { throw new BizException('折扣必须大于0'); } elseif ($item['discount'] >= 10) { throw new BizException('折扣必须小于10'); } $discount = bcdiv($item['discount'], 10, 3); $paymentAmount = round(bcmul($productsTotalAmount, $discount, 2)); } return [ 'product_category_id' => $item['product_category_id'], 'products_total_amount' => (int) $productsTotalAmount, 'discount_reduction_amount' => (int) ($productsTotalAmount - $paymentAmount), 'payment_amount' => (int) $paymentAmount, ]; })->all(); } protected function calculateFees(array $items) { $totalProductsTotalAmount = 0; $totalDiscountReductionAmount = 0; $totalPaymentAmount = 0; foreach ($items as $item) { $totalProductsTotalAmount += $item['products_total_amount']; $totalDiscountReductionAmount += $item['discount_reduction_amount']; $totalPaymentAmount += $item['payment_amount']; } return [$totalProductsTotalAmount, $totalDiscountReductionAmount, $totalPaymentAmount]; } }