453 lines
20 KiB
PHP
453 lines
20 KiB
PHP
<?php
|
||
namespace app\api\controller;
|
||
|
||
use app\model\Admins;
|
||
use app\model\DyOrderProductAppointments;
|
||
use app\model\DyOrderProducts;
|
||
use app\model\DyOrders;
|
||
use app\model\OrderAfterSales;
|
||
use app\model\Orders;
|
||
use app\model\Products;
|
||
use app\server\Douyin;
|
||
use support\Log;
|
||
use support\Redis;
|
||
use support\Request;
|
||
use think\facade\Db;
|
||
|
||
/**
|
||
* 抖音回调处理
|
||
*/
|
||
class DyNotifyController extends base {
|
||
/**
|
||
* 创建预售订单
|
||
* @param Request $request
|
||
* @return string
|
||
* @throws \Exception
|
||
*/
|
||
public function orderCreate(Request $request) {
|
||
try {
|
||
$params = $request->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;
|
||
}
|
||
}
|