all(); Log::info('orderCreate params:' . json_encode($params)); if (!empty($order = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->find())) { return $this->success(['order_out_id' => $order->order_id, 'order_id' => $order->dy_order_id]); } $orderId = getNewOrderId('Dy'); $orderData = [ 'order_id' => $orderId, 'dy_order_id' => $params['order_id'], 'biz_type' => $params['biz_type'] ?? '', 'total_amount' => $params['total_amount'] ?? 0, 'total_coupon_count' => $params['total_coupon_count'] ?? 0, 'each_coupon_amount' => $params['each_coupon_amount'] ?? 0, 'pay_amount' => $params['pay_amount'] ?? 0, 'actual_amount' => $params['actual_amount'] ?? 0, 'merchant_discount_amount' => $params['merchant_discount_amount'] ?? 0, 'pay_time_unix' => $params['pay_info']['pay_time_unix'] ?? 0, 'status' => 0, 'phone' => $this->decrypt($params['buyer_info']['phone'], env('DY_APPSECRET')), ]; // 已支付 if ($orderData['pay_time_unix']) { $orderData['status'] = 10; } $res = DyOrders::create($orderData); Log::info('res:' . json_encode($res)); if (!empty($res) && isset($params['product_snap_shot']) && !empty($params['product_snap_shot'])) { $orderProducts = []; foreach ($params['product_snap_shot'] as $product) { array_push($orderProducts, [ 'dy_order_id' => $orderData['dy_order_id'], 'sku_id' => $product['sku_id'] ?? '', 'product_id' => $product['product_id'] ?? '', 'out_product_id' => $product['out_product_id'] ?? '', 'name' => $product['name'] ?? '', 'category_id' => $product['category_id'] ?? '', 'category_full_name' => $product['category_full_name'] ?? '', 'travel_details' => json_encode($product['travel_details'] ?? []), 'commodity' => json_encode($product['commodity'] ?? []), 'is_superimposed_discounts' => $product['is_superimposed_discounts'] ? 1: 0, 'date_add_price_rule' => isset($product['date_add_price_rule']) ? json_encode($product['date_add_price_rule']) : '', 'user_add_price_rule' => isset($product['user_add_price_rule']) ? json_encode($product['user_add_price_rule']) : '', 'room_add_price_rule' => isset($product['room_add_price_rule']) ? json_encode($product['room_add_price_rule']) : '', 'room_upgrade_rule' => isset($product['room_upgrade_rule']) ? json_encode($product['room_upgrade_rule']) : '', 'add_price_policy' => isset($product['add_price_policy']) ? json_encode($product['add_price_policy']) : '', 'description_rich_text' => isset($product['description_rich_text']) ? json_encode($product['description_rich_text']) : '', 'fee_include' => isset($product['fee_include']) ? json_encode($product['fee_include']) : '', 'fee_not_include' => isset($product['fee_not_include']) ? json_encode($product['fee_not_include']) : '', 'self_pay_expense_rule' => isset($product['self_pay_expense_rule']) ? json_encode($product['self_pay_expense_rule']) : '', 'earliest_appointment' => isset($product['earliest_appointment']) ? json_encode($product['earliest_appointment']) : '', 'appointment' => isset($product['appointment']) ? json_encode($product['appointment']) : '', 'refund_rule' => isset($product['refund_rule']) ? json_encode($product['refund_rule']) : '', 'use_date' => isset($product['use_date']) ? json_encode($product['use_date']) : '', 'refund_description' => isset($product['refund_description']) ? json_encode($product['refund_description']) : '', 'merchant_break_rule' => isset($product['merchant_break_rule']) ? json_encode($product['merchant_break_rule']) : '', 'merchant_break_description' => isset($product['merchant_break_description']) ? json_encode($product['merchant_break_description']) : '', 'presale_appointment_cancel_policy' => isset($product['presale_appointment_cancel_policy']) ? json_encode($product['presale_appointment_cancel_policy']) : '', ]); } Log::info('orderProducts:' . json_encode($orderProducts)); // 批量插入 /** @var DyOrderProducts $dyOrderProducts */ $dyOrderProducts = (new DyOrderProducts()); $dyOrderProducts->insertAll($orderProducts); if ($orderData['status'] == 10) { $this->createPlatformOrder($orderData, $orderProducts[0]); } } // order_out_id - 第三方订单ID, order_id - 抖音订单号 return $this->success(['order_out_id' => $orderId, 'order_id' => $params['order_id']]); } catch (\Exception $exception) { return $this->error('-1', $exception->getMessage()); } } // 通知支付结果 public function pay(Request $request) { try { $params = $request->all(); // Validator::input($params, [ // 'order_id' => Validator::notEmpty()->setName('订单号'), // 'biz_type' => Validator::notEmpty()->setName('旅行社预售券'), // 'pay_amount' => Validator::notEmpty()->setName('支付金额'), // 'pay_time_unix' => Validator::notEmpty()->setName('支付时间'), // ]); Log::info('pay params:' . json_encode($params)); // » biz_type - 旅行社预售券:3011;旅行社预定订单:3012 // » pay_amount 用户实付金额(分) // » pay_time_unix 支付时间戳 if (isset($params['biz_type']) && $params['biz_type'] == 3012) { if (empty($order = DyOrderProductAppointments::query()->where(['dy_order_id' => $params['source_order_id']])->find())) { return $this->error('-2', '订单信息未找到'); } $res = DyOrderProductAppointments::query()->where(['order_id' => $params['order_id']])->update(['pay_amount' => $params['pay_amount'] ?? 0]); } else { if (empty($order = DyOrders::query()->where(['dy_order_id' => $params['source_order_id']])->find())) { return $this->error('-2', '订单信息未找到'); } $res = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->update(['status' => 10, 'pay_amount' => $params['pay_amount'], 'pay_time_unix' => $params['pay_time_unix']]); $orderProduct = DyOrderProducts::query()->where(['dy_order_id' => $params['order_id']])->find(); if (!$res) { $this->createPlatformOrder($order, $orderProduct); } } if ($res) { return $this->success([]); } else { return $this->error('-2', '操作失败'); } } catch (\Exception $exception) { return $this->error('-1', $exception->getMessage()); } } /** * @param $order * @param $orderProduct * @return Orders */ private function createPlatformOrder($order, $orderProduct) { $item = Orders::query()->where(['sn' => $order['dy_order_id']])->find(); if (!empty($item)) { return $item; } $adminId = 0; // 查找用户归属 try { // 直播间uid $dyService = new Douyin(5); $dyRes = $dyService->_certificateList(1, null, null, $order['dy_order_id']); if (!empty($dyRes) && $dyRes->data) { $dyRes = json_decode(json_encode($dyRes->data), true); $saleUserNickName = trim($dyRes['list'][0]['extra']['attribute']['sale_user_nickname']); Log::info('$saleUserNickName:' . $saleUserNickName); if (!empty($saleUserNickName)) { $adminId = Admins::where('status', 1)->whereLike('dy_nickname', "%{$saleUserNickName}%")->value('id'); } } } catch (\Exception $e) { Log::info('get admin_info error:' . $e->getMessage()); } if (!$adminId) { $product = Products::query()->where('third_product_id', $orderProduct['product_id'])->find(); if (!empty($product)) { $adminId = Admins::where('status', 1)->whereFindInSet('product_ids', $product->id)->value('id'); } } if ($adminId) { // 新获得一个用户的,提示管理员有新的订单 Redis::incrBy('CRM:USER:ONLINE:NEW:' . $adminId, 1); } // 订单支付成功,写入平台订单表 $item = new Orders(); $item->os = 5; $item->sn = $order['dy_order_id']; $item->product_id = $orderProduct['product_id']; $item->product_name = $orderProduct['name']; $item->category_id = $orderProduct['category_id']; $item->create_at = $order['pay_time_unix'] * 1000; $item->mobile = $order['phone'] ?? null; $item->unit_price = $order['pay_amount']; $item->total_price = $order['pay_amount']; $item->actual_price = $order['pay_amount']; $item->quantity = $order['total_coupon_count']; $item->order_status = 1; $item->asset_status = 0; $item->category_desc = $orderProduct['category_full_name']; $item->asset_price = 0; $item->is_refunded = 0; // 是否已退款(1-已退款,0-未退款) $item->admin_id = $adminId; $item->is_direct_mode = 1; // 是否直连模式(0-否,1-是) $item->save(); return $item; } /** * 处理预约信息 * @param $params * @return array */ private function handleBookInfo($params) { $bookInfo = []; if (isset($params['book_info']) && !empty($params['book_info'])) { $bookInfo = $params['book_info']; if (isset($bookInfo['occupancies']) && is_array($bookInfo['occupancies'])) { foreach ($bookInfo['occupancies'] as &$occupancy) { if (isset($occupancy['name']) && !empty($occupancy['name'])) { $occupancy['name'] = $this->decrypt($occupancy['name'], env('DY_APPSECRET')); } if (isset($occupancy['license_id']) && !empty($occupancy['license_id'])) { $occupancy['license_id'] = $this->decrypt($occupancy['license_id'], env('DY_APPSECRET')); } } } } return $bookInfo; } // 用户创建预约订单 public function userMakeAppointment(Request $request) { $params = $request->all(); Log::info('userMakeAppointment params:' . json_encode($params)); $bookInfo = $this->handleBookInfo($params); $orderData = [ 'dy_order_id' => $params['order_id'] ?? '', 'source_order_id' => $params['source_order_id'] ?? '', 'presale_coupon_id' => $params['presale_coupon_id'] ?? '', 'out_presale_coupon_id' => $params['out_presale_coupon_id'] ?? '', 'biz_type' => $params['biz_type'] ?? 0, 'pay_amount' => $params['pay_amount'] ?? 0, 'actual_amount' => $params['actual_amount'] ?? 0, 'merchant_discount_amount' => $params['merchant_discount_amount'] ?? 0, 'discount_amount' => $params['discount_amount'] ?? 0, 'original_amount' => $params['original_amount'] ?? 0, 'pay_info' => isset($params['pay_info']) ? json_encode($params['pay_info']) : '', 'book_info' => $bookInfo, 'buyer_info' => isset($params['buyer_info']) ? json_encode($params['buyer_info']) : '', 'remark_from_guest' => $params['remark_from_guest'], ]; if (empty($order = DyOrders::query()->where(['dy_order_id' => $orderData['source_order_id']])->find())) { return $this->error('-2', '订单信息未找到'); } $orderData['appoint_order_id'] = getNewOrderId('Yy'); if (!empty($appoint = DyOrderProductAppointments::query()->where(['dy_order_id' => $orderData['dy_order_id']])->find())) { return $this->success(['order_id' => $appoint->dy_order_id, 'order_out_id' => $appoint->appoint_order_id]); } // biz_type - 旅行社预售券:3011;旅行社预定订单:3012 $res = DyOrderProductAppointments::create($orderData); if ($res) { // 更新平台订单表 Orders::query()->where('sn', $params['source_order_id'])->update(['appointment_status' => 1, 'travel_date' => $bookInfo['book_start_date']]); return $this->success(['order_id' => $orderData['dy_order_id'], 'order_out_id' => $orderData['appoint_order_id']]); } else { return $this->error('-1'); } } // 用户取消订单 public function userCancelOrder(Request $request) { $params = $request->all(); Log::info('userCancelOrder params:' . json_encode($params)); if (!isset($params['order_id']) || empty($params['order_id'])) { throw new \Exception('订单号缺失'); } if (isset($params['biz_type']) && $params['biz_type'] == 3012) { // 取消预约 $appoint = DyOrderProductAppointments::query()->where('dy_order_id', $params['order_id'])->find(); Orders::query()->where('sn', $appoint['source_order_id'])->update(['appointment_status' => 0, 'travel_date' => null]); OrderAfterSales::query()->where('order_id', $appoint['source_order_id'])->update(['status' => 1]); return $this->success([]); } if (empty($order = Orders::query()->where(['sn' => $params['order_id']])->find())) { return $this->error('-2', '订单信息未找到'); } // » refund_type 枚举值:1、订单退款,2、补差价退款 $res = Db::transaction(function () use ($order, $params) { DyOrders::query()->where('dy_order_id', $params['order_id'])->update(['status' => 30]); // 更新平台订单表 if ($order->order_status < 3) { Orders::query()->where('sn', $params['order_id'])->update(['order_status' => 3]); } return true; }); if ($res) { return $this->success([]); } return $this->error('-1', '取消失败'); } // 订单退款结果通知 public function orderRefundNotify(Request $request) { $params = $request->all(); Log::info('orderRefundNotify params:' . json_encode($params)); if (!isset($params['order_id']) || empty($params['order_id'])) { throw new \Exception('订单号缺失'); } if (isset($params['biz_type']) && $params['biz_type'] == 3012) { // 取消预约退款, 暂不处理 return $this->success([]); } if (empty($order = DyOrders::query()->where(['dy_order_id' => $params['order_id']])->find())) { return $this->error('-2', '订单信息未找到'); } $res = Db::transaction(function () use ($order, $params) { DyOrders::query()->where(['order_id' => $params['order_id']])->update(['status' => 40]); // 更新平台订单表 Orders::query()->where(['sn' => $params['order_id']])->update(['order_status' => 4, 'is_refunded' => 1, 'refund_status' => 3]); return true; }); if ($res) { return $this->success([]); } return $this->error('-1', '取消失败'); } public function success($data,$mess = null, $ext = null) { $res = [ 'error_code' => 0, 'description' => 'success', ]; if (isset($data['order_id'])) { $res['order_id'] = $data['order_id']; } if (isset($data['order_out_id'])) { $res['order_out_id'] = $data['order_out_id']; } return json([ 'data' => $res ]); } public function error($code, $mess = null) { $res = [ 'error_code' => $code, 'description' => $mess, ]; return json([ 'data' => $res ]); } /** 1.根据ClientKev找到ClientSecret,将ClientSecret向左右使用字符补齐32位/裁剪至32位,补齐:补位字符:#,先补左侧再补右侧再补左侧………直到补满32位。 裁剪:先裁剪左侧再裁右侧再裁左侧………直到剩余32位。(正常不需要补齐,secret默认为32位,此举是为了 以防万一) 2.将ClientSecret作为Key,右侧16位为向量IV 3.将密文进行base64解码。 4.使用AES-256-CBC模式解密解码后的密文,对齐使用PKCS5Padding方式 * @param $cipherText * @param $clientSecret * @return false|string */ private function decrypt($cipherText, $clientSecret) { Log::info('$cipherText:' . $cipherText); // 补齐或裁剪 ClientSecret 为 32 位 $clientSecret = $this->padOrTruncate($clientSecret); Log::info('$cipherText step 2:' . $clientSecret); // 使用 ClientSecret 作为密钥 $key = $clientSecret; // 右侧16位为初始化向量IV $iv = substr($key, 16, 16); // 将密文 base64 解码 $cipherTextDecoded = base64_decode($cipherText); // 使用 AES-256-CBC 解密 return openssl_decrypt($cipherTextDecoded, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); } /** * @param $clientSecret * @return false|string */ private function padOrTruncate($clientSecret) { // 1. 如果 clientSecret 长度为32,直接返回 if (strlen($clientSecret) == 32) { return $clientSecret; } // 2. 如果 length < 32,补齐至32位 if (strlen($clientSecret) < 32) { // 补齐过程:先补左侧,再补右侧,再补左侧,直到长度为32 $paddingChar = '#'; $padded = $clientSecret; // 左侧补齐 $left = true; while (strlen($padded) < 32) { if ($left) { $padded = $paddingChar . $padded; $left = false; } else { $padded = $padded . $paddingChar; $left = true; } } return substr($padded, 0, 32); // 确保返回32位 } // 3. 如果 length > 32,裁剪至32位 if (strlen($clientSecret) > 32) { // 裁剪过程:先裁左侧,再裁右侧,再裁左侧,直到长度为32 $cropped = $clientSecret; // 左侧裁剪 while (strlen($cropped) > 32) { $cropped = substr($cropped, 1); } // 右侧裁剪 while (strlen($cropped) > 32) { $cropped = substr($cropped, 0, -1); } // 左侧裁剪(再一次) while (strlen($cropped) > 32) { $cropped = substr($cropped, 1); } return substr($cropped, 0, 32); // 确保返回32位 } return $clientSecret; } }