$userSn, 'avatar' => $avatar, 'nickname' => '用户' . $userSn, 'account' => $params['account'], 'mobile' => !empty($params['mobile'])?$params['mobile']:'', 'password' => $password, 'channel' => self::$terminal, 'user_type' => $params['user_type']??0, ]); return $user; } public static function phoneLogin(array $params) { try { $where = ['mobile' => $params['mobile']]; $params['account'] = $params['mobile']; $user = User::where($where)->findOrEmpty(); if ($user->isEmpty()) { //直接注册用户 $params['channel'] = self::$terminal; $user = self::register($params); } //更新登录信息 $user->login_time = time(); $user->login_ip = request()->ip(); $user->save(); $userInfo = UserTokenService::setToken($user->id, self::$terminal); //返回登录信息 $avatar = $user->avatar ?: Config::get('project.default_image.user_avatar'); $avatar = FileService::getFileUrl($avatar); return [ 'nickname' => $userInfo['nickname'], 'sn' => $userInfo['sn'], 'mobile' => $userInfo['mobile'], 'avatar' => $avatar, 'token' => $userInfo['token'], ]; } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } /** * 提交订单 * @param array $params * @return array|false */ public static function submitOrder($params) { Db::startTrans(); try { $goods = Goods::findOrEmpty($params['goods_id']); if($goods->isEmpty()){ throw new \Exception('产品不存在!'); } if(empty($params['user_info']['mobile'])){ throw new \Exception('请先补充您的联系方式后在提交订单'); } // TODO tmp防抖1m $isExist = DouyinOrder::where(['user_id'=>$params['user_id'],'goods_id'=>$goods['id']])->where('create_time','>',(time() - 60))->findOrEmpty(); if(!$isExist->isEmpty()){ throw new \Exception('请勿重复下单!'); } $quantity = $params['quantity']??1; //生成订单 $create_data = [ 'user_id' => $params['user_id'], 'mobile' => $params['user_info']['mobile'], 'title' => $goods['goods_name'], 'goods_id'=>$goods['id'], 'unit_price' => $goods['service_fee'], 'quantity' => $quantity, 'total_amount' => $goods['service_fee'] * $quantity, 'order_number' => generate_sn(DouyinOrder::class, 'order_number'), ]; $order = DouyinOrder::create($create_data); Db::commit(); return $create_data['order_number']; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function getByteAuthorization($order_number) { /*{ "skuList": [{ "skuId": "商品ID", "price": 100,//单价-分 "quantity": 1, "title": "商品标题", "imageList": ["https://cdn.weixiu.kyjlkj.com/uploads/images/20240914/202409141528015aeaa2357.png"], "type": 701, "tagGroupId": "tag_group_7272625659887960076" }], "outOrderNo": "202411121413333930", "totalAmount": 100,//分 "orderEntrySchema": { "path": "page/path/index", "params": '{"id":1234, "name":"hello"}' }, "payNotifyUrl": "https://weixiudev.kyjlkj.com/api/dou_yin/payNotify" }*/ try { $douyinOrder = DouyinOrder::where('order_number',$order_number)->findOrEmpty(); if($douyinOrder->isEmpty()){ throw new \Exception('订单不存在!'); } $order = $douyinOrder->toArray(); $goods_image = Goods::where('id',$order['goods_id'])->value('goods_image')??''; $data = [ "skuList" => [ [ "skuId" => (string)$order['goods_id'], "price" => $order['unit_price'] * 100, "quantity" => $order['quantity'], "title" => $order['title'], "imageList" => [$goods_image], "type" => 701, "tagGroupId" => "tag_group_7272625659887960076" ] ], "outOrderNo" => $order['order_number'], "totalAmount" => $order['total_amount'] * 100, "orderEntrySchema" => [ "path" => "page/index/index", "params" => json_encode(['order_number' => $order['order_number']]) ], "payNotifyUrl" => config('douyin.payNotifyUrl'), ]; $byteAuthorization = self::byteAuthorization(config('douyin.privateKeyStr'), json_encode($data), config('douyin.appId'), self::randStr(10), time(), 1); return ['byteAuthorization'=>$byteAuthorization,'data'=>json_encode($data)]; } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } public static function cancelOrder($params) { // $params['order_number'] Db::startTrans(); try { $order = DouyinOrder::where('order_number', $params['order_number'])->findOrEmpty(); if(!$order->isEmpty()){ if($order->order_status == 1 && $order->pay_status == 0){ $order->order_status = 4; $order->save(); }else{ throw new \Exception('订单状态不可取消!'); } } Db::commit(); return $order['id']; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function payNotify($params) { Log::write(json_encode($params)); // 查询抖音订单是否完成支付 if ($params['status'] === 'SUCCESS') { $transaction_id = $params['order_id']??''; $paid_amount = bcdiv(bcsub($params['total_amount'] ,$params['discount_amount']), '100', 2)??0; $out_order_no = $params['out_order_no']; $pay_time = $params['event_time']??time(); $order = DouyinOrder::where('order_number', $out_order_no)->findOrEmpty(); if(!$order->isEmpty()){ // 更新充值订单状态 $order->transaction_id = $transaction_id; $order->order_status = 2; $order->pay_time = $pay_time; $order->paid_amount = $paid_amount; $user = User::where('id',$order->user_id)->findOrEmpty()->toArray(); $form_detail = [ 'user_name' => $user['real_name']??'', 'mobile' => $user['mobile'], 'transaction_id' => $transaction_id, 'out_trade_no' => $out_order_no, 'paid_amount' => $paid_amount, 'params' => $params, ]; $consultation = ExternalConsultation::create([ 'external_platform_id' => self::$external_platform_id, 'form_detail' => json_encode($form_detail), 'user_name' => $user['real_name']??'', 'mobile' => $user['mobile'], 'goods_id' => $order->goods_id, 'amount' => $paid_amount ]); $order->consultation_id = $consultation->id; $order->save(); return true; } } return false; } public static function reservation($params) { /*$lon_lat = get_address_lat_lng($params['user_address']); $params['lon'] = $lon_lat['lon']; $params['lat'] = $lon_lat['lat'];*/ // $params['order_number'] Db::startTrans(); try { $order = DouyinOrder::where('order_number', $params['order_number'])->findOrEmpty(); if(!$order->isEmpty()){ $consultation = ExternalConsultation::where('id', $order->consultation_id)->findOrEmpty()->toArray(); $consultation['user_name'] = $params['user_name']??$consultation['user_name']; $consultation['mobile'] = $params['mobile']??$consultation['mobile']; $consultation['user_address'] = $params['user_address']; $consultation['lon'] = $params['lon']; $consultation['lat'] = $params['lat']; $consultation['appointment_time'] = $params['appointment_time']; $result = ExternalConsultationLogic::order($consultation); if (false === $result) { throw new \Exception('预约失败'); } $consultationOrder = ExternalConsultationOrder::where('consultation_id', $order->consultation_id)->where('goods_id', $order->goods_id)->where('amount', $order->paid_amount) ->findOrEmpty()->toArray(); $work_status = ServiceWork::where('id', $consultationOrder['work_id'])->value('work_status'); $order->work_id = $consultationOrder['work_id']; $order->fulfillment_status = $work_status; $order->save(); } Db::commit(); return $order['id']; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function upReservation($params) { // $params['order_number'] Db::startTrans(); try { $order = DouyinOrder::where('order_number', $params['order_number'])->findOrEmpty(); if(!$order->isEmpty()){ // sn appointment_time $result = ServiceOrderLogic::approvalChangeAppointment(['sn'=>RechargeOrder::where('work_id', $order->work_id)->value('sn'),'appointment_time'=>$params['appointment_time']]); if (false === $result) { throw new \Exception(ServiceOrderLogic::getError()); } $order->fulfillment_status = ServiceWork::where('id', $order->work_id)->value('work_status'); $order->save(); } Db::commit(); return $order['id']; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function getOrderDetail($params) { //抖音订单信息/商品信息/预约信息(地址、时间、履约状态与信息) // $params['order_number'] user_id $order = DouyinOrder::with(['goods','serviceWork','douyinRefundOrder'])->where('order_number', $params['order_number'])->where('user_id', $params['user_id'])->findOrEmpty(); if($order->isEmpty()){ return []; } $orderInfo = $order->toArray(); empty($orderInfo['goods']) && $orderInfo['goods'] = []; empty($orderInfo['serviceWork']) && $orderInfo['serviceWork'] = []; empty($orderInfo['douyinRefundOrder']) && $orderInfo['douyinRefundOrder'] = []; $work_status = $orderInfo['serviceWork']['work_status']??0; $performance = []; // tmp switch ($work_status){ case 0: $performance[] = ['status' => '待派单','title' => '待派单','time' => date('Y-m-d H:i:s',time())]; break; case 1: case 2: case 3: $performance[] = ['status' => '待派单','title' => '待派单','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '已派单','title' => '已派单','time' => date('Y-m-d H:i:s',time())]; break; case 4: case 5: case 6: $performance[] = ['status' => '待派单','title' => '待派单','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '已派单','title' => '已派单','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '服务中','title' => '服务中','time' => date('Y-m-d H:i:s',time())]; break; case 7: case 8: $performance[] = ['status' => '待派单','title' => '待派单','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '已派单','title' => '已派单','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '服务中','title' => '服务中','time' => date('Y-m-d H:i:s',time())]; $performance[] = ['status' => '已完结','title' => '已完结','time' => date('Y-m-d H:i:s',time())]; break; } $orderInfo['performance'] = $performance; return $orderInfo; } public static function refund($params) { Db::startTrans(); try { // $params['order_number'] user_id $order = DouyinOrder::with(['goods','serviceWork'])->where('order_number', $params['order_number'])->where('user_id', $params['user_id'])->findOrEmpty(); if($order->isEmpty()){ throw new \Exception('订单不存在'); } $orderInfo = $order->toArray(); $work_status = $orderInfo['serviceWork']['work_status']??0; if(3 < $work_status){ throw new \Exception('该订单禁止退款'); } DouyinRefundOrder::create([ 'refund_number' => generate_sn(DouyinRefundOrder::class, 'refund_number'), 'order_number' => $orderInfo['order_number'], 'transaction_id' => $orderInfo['transaction_id'], 'reason' => $params['reason']??'', 'refund_status' => 0, 'user_id' => $orderInfo['user_id'], 'refund_amount' => $orderInfo['paid_amount'], ]); Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function refundExamine($params) { Db::startTrans(); try { // $params['order_number'] $order = DouyinOrder::with(['goods','serviceWork'])->where('order_number', $params['order_number'])->findOrEmpty(); if($order->isEmpty()){ throw new \Exception('订单不存在'); } $orderInfo = $order->toArray(); //$refund_number = $params['refund_number']??''; $douyinRefundOrder = DouyinRefundOrder::where('order_number', $params['order_number'])->order('id', 'desc')->findOrEmpty(); if($params['is_examine_ok'] === 'pass'){ $douyinRefundOrder->refund_status = 2; RechargeOrder::where('work_id', $orderInfo['work_id'])->update([ 'pay_status' => 2, 'pay_time' => 0, 'paid_amount' => 0, ]); ServiceWork::where('id', $orderInfo['work_id'])->update([ 'work_status' => 0, 'user_confirm_status' => 0, 'service_status' => 4, 'work_pay_status' => 0 ]); }else{ $douyinRefundOrder->refund_status = 1; } $douyinRefundOrder->save(); Db::commit(); if($params['is_examine_ok'] === 'pass'){ //通过后向抖音申请退款 self::sendRefundCreate($params['order_number']); } return true; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function refundNotify($params) { Db::startTrans(); try { $douyinRefundOrder = DouyinRefundOrder::where('refund_number', $params['out_refund_no'])->findOrEmpty(); if($douyinRefundOrder->isEmpty()){ throw new \Exception('退款订单不存在'); } if($douyinRefundOrder->refund_status == 0){ if($params['status'] === 'SUCCESS'){ $douyinRefundOrder->refund_status = 3; DouyinOrder::where('order_number', $douyinRefundOrder->order_number)->update([ 'order_status' => 4, 'pay_status' => 3, ]); }elseif($params['status'] === 'FAIL'){ $douyinRefundOrder->refund_status = 4; }else{ throw new \Exception('退款状态未知'); } $douyinRefundOrder->save(); } Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); throw new \Exception($e->getMessage()); } } public static function byteAuthorization($privateKeyStr, $data, $appId, $nonceStr, $timestamp, $keyVersion) { $byteAuthorization = ''; // 读取私钥 $privateKey = openssl_pkey_get_private($privateKeyStr); if (!$privateKey) { throw new \Exception("Invalid private key"); } // 生成签名 $signature = self::getSignature("POST", "/requestOrder", $timestamp, $nonceStr, $data, $privateKey); if ($signature === false) { return null; } // 构造 byteAuthorization $byteAuthorization = sprintf("SHA256-RSA2048 appid=%s,nonce_str=%s,timestamp=%s,key_version=%s,signature=%s", $appId, $nonceStr, $timestamp, $keyVersion, $signature); return $byteAuthorization; } public static function getSignature($method, $url, $timestamp, $nonce, $data, $privateKey) { Log::info("method:{$method}\n url:{$url}\n timestamp:{$timestamp}\n nonce:{$nonce}\n data:{$data}"); $targetStr = $method. "\n" . $url. "\n" . $timestamp. "\n" . $nonce. "\n" . $data. "\n"; openssl_sign($targetStr, $sign, $privateKey, OPENSSL_ALGO_SHA256); $sign = base64_encode($sign); return $sign; } public static function randStr($length = 8) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str = ''; for ($i = 0; $i < $length; $i++) { $str .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $str; } public static function getClientToken() { $url = config('douyin.host').'oauth/client_token/'; $cache_name = 'dy_client_token'; $cache_data = cache($cache_name); if(empty($cache_data) || $cache_data == null){ $data = [ 'client_key'=> config('douyin.appId'), 'client_secret'=> config('douyin.appSecret'), 'grant_type'=> "client_credential" ]; $res = http_request($url,$data,['Content-Type' => 'application/json;charset=utf-8']); if($res['message'] === 'success'){ cache($cache_name, json_encode($res['data']), (time()+$res['data']['expires_in']-1)); $cache_data = $res['data']; } } return json_decode($cache_data, true)['access_token']; } public static function sendRefundCreate($order_number) { try { // $params['order_number'] $order = DouyinOrder::with(['goods','serviceWork'])->where('order_number', $order_number)->findOrEmpty(); if($order->isEmpty()){ throw new \Exception('订单不存在'); } $orderInfo = $order->toArray(); $douyinRefundOrder = DouyinRefundOrder::where('order_number', $order_number)->order('id', 'desc')->findOrEmpty(); //通过后向抖音申请退款 //getClientToken() $url = config('douyin.host').'api/trade_basic/v1/developer/refund_create/'; $data = [ "order_id" => $orderInfo['transaction_id'], "out_refund_no" => $douyinRefundOrder->refund_number, "cp_extra" => $orderInfo['id'].'|'.$douyinRefundOrder->id, "order_entry_schema" => [ "path" => "page/index/index", "params" => json_encode(['refund_number'=>$douyinRefundOrder->refund_number]) ], "refund_total_amount " => $douyinRefundOrder->refund_amount * 100, "notify_url" => config('douyin.refundNotifyUrl'), "refund_reason" => [ [ "code" => 101, "text" => "不想要了" ] ] ]; $res = http_request($url,$data,['Content-Type' => 'application/json;charset=utf-8','access_token' => self::getClientToken()]); if(isset($res['err_msg']) && $res['err_msg'] === 'success'){ $douyinRefundOrder->transaction_id = $res['data']['refund_id']; $douyinRefundOrder->save(); } return true; } catch (\Exception $e) { Log::info($e->getMessage()); return false; } } }