liugc 1 год назад
Родитель
Сommit
392b63c01d
3 измененных файлов с 216 добавлено и 21 удалено
  1. 27 5
      app/api/controller/DouYinController.php
  2. 164 16
      app/api/service/DouYinService.php
  3. 25 0
      config/douyin.php

+ 27 - 5
app/api/controller/DouYinController.php

@@ -147,7 +147,29 @@ class DouYinController extends BaseApiController
             $params['user_id'] = $this->userId;
             $params['user_info'] = $this->userInfo;
             $order_number = DouYinService::submitOrder($params);
-            return $this->success('',['order_number'=>$order_number]);
+            $requestOrderData = DouYinService::getByteAuthorization($order_number);
+            $requestOrderData['order_number'] = $order_number;
+            return $this->success('',$requestOrderData);
+        } catch (\Exception $e) {
+            return $this->fail($e->getMessage());
+        }
+    }
+
+    /**
+     * 拉起支付所需参数
+     * @return \think\response\Json
+     * @author liugc <466014217@qq.com>
+     * @date 2025/5/22 14:35
+     */
+    public function requestOrderData()
+    {
+        try {
+            $params = $this->request->post();
+            $params['user_id'] = $this->userId;
+            $params['user_info'] = $this->userInfo;
+            $requestOrderData = DouYinService::getByteAuthorization($params['order_number']);
+            $requestOrderData['order_number'] = $params['order_number'];
+            return $this->success('',$requestOrderData);
         } catch (\Exception $e) {
             return $this->fail($e->getMessage());
         }
@@ -178,12 +200,12 @@ class DouYinController extends BaseApiController
     {
         try {
             $params = $this->request->post();
-            if(DouYinService::payNotify($params)){
-                return $this->success();
+            $msg = is_array($params['msg'])?$params['msg']:json_decode($params['msg'],true);
+            if(DouYinService::payNotify($msg)){
+                return json(["err_no"=>0,"err_tips"=>"success"], 200);
             }
-            return $this->fail('fail');
         } catch (\Exception $e) {
-            return $this->fail($e->getMessage());
+            return json(["err_no"=>1001,"err_tips"=>$e->getMessage()], 200);
         }
     }
 

+ 164 - 16
app/api/service/DouYinService.php

@@ -121,6 +121,60 @@ class DouYinService
         }
     }
 
+    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" => $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)
     {
@@ -144,24 +198,21 @@ class DouYinService
         }
     }
 
-
-
     public static function payNotify($params)
     {
-            //Log::write(json_encode($params));
-
+            Log::write(json_encode($params));
             // 查询抖音订单是否完成支付
-
-            if ($params['trade_state'] === 'SUCCESS') {
-                $transaction_id = $params['transaction_id']??'';
-                $paid_amount = $params['paid_amount']??0;
-                $out_trade_no = $params['out_trade_no'];
-                $order = DouyinOrder::where('order_number', $out_trade_no)->findOrEmpty();
+            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 = time();
+                    $order->pay_time = $pay_time;
                     $order->paid_amount = $paid_amount;
 
                     $user = User::where('id',$order->user_id)->findOrEmpty()->toArray();
@@ -169,8 +220,9 @@ class DouYinService
                         'user_name' => $user['real_name']??'',
                         'mobile' => $user['mobile'],
                         'transaction_id' => $transaction_id,
-                        'out_trade_no' => $out_trade_no,
+                        'out_trade_no' => $out_order_no,
                         'paid_amount' => $paid_amount,
+                        'params' => $params,
                     ];
                     $consultation = ExternalConsultation::create([
                         'external_platform_id' => self::$external_platform_id,
@@ -358,11 +410,10 @@ class DouYinService
 
             Db::commit();
 
-            // TODO 需接抖音支付接口
-            /*if($params['is_examine_ok'] === 'pass'){
+            if($params['is_examine_ok'] === 'pass'){
                 //通过后向抖音申请退款
-                //https://open.douyin.com/api/trade_basic/v1/developer/refund_create/
-            }*/
+                self::sendRefundCreate($params['order_number']);
+            }
             return true;
         } catch (\Exception $e) {
             Db::rollback();
@@ -400,4 +451,101 @@ class DouYinService
         }
     }
 
+
+    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 = 'https://open.douyin.com/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,json_encode($data));
+            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 = 'https://open.douyin.com/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" => "https://xxx",
+                    "refund_reason" => [
+                        [
+                            "code" => 101,
+                            "text" => "不想要了"
+                        ]
+                    ]
+                ];
+                $res = http_request($url,json_encode($data));
+                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;
+        }
+    }
+
+
 }

+ 25 - 0
config/douyin.php

@@ -0,0 +1,25 @@
+<?php
+
+return [
+    'appId' => 'tt4dc83b75d2c06f6301',
+    'appSecret' => '4a043ceb98f9c05607c6ed39cdbdcb33c6f23a75',
+    'payNotifyUrl' => env('PAY_NOTIFY_URL')?:'https://weixiudev.kyjlkj.com/api/dou_yin/payNotify',
+
+    'privateKeyStr' => "-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDOxypy0oUD8qLPJQaXJdTh8ny39g5lGNXvZy6Jv+i7NWK4Ps8i
+0joIj2mKTzwpLNR1k/gvoeX/y2tB4660DNJ2qq3WbUdkNkFqcteobkxyUQLGUbI5
+r+s5VeN9MHo3DisIREafHUw2XYA7LUdoFIpSExLcGo+jeWlJYzTkZxJr2QIDAQAB
+AoGAUntDxXWQNZjttK9SR6yBlHeDpGX0LAOSIuMHv07b6QFMla4INh2hpJSARvmA
+R/Rn81bI40czVauw5yGJKK0Mri2rYXEab1yJ1FdcwExzXkR7t91WTPrDQVvVoBIy
+MTvC5pdp0mKHm9CvnFLszYPcYrI1dgR2LJAjiQU+B743aQECQQDoTteWLYt8JUHY
+aD1KSRFHc2pS/0HWLij6pdKZbBl0GljNxhi4lPYmVB/9QqA8jbVcrcvl73edjoyo
+Kq1I4M+pAkEA493Hs+hFg5eitMlRjOphkU5fLU+CvUda1Cu1IPP0iCStt3tvTIIm
+z3k5L+PfeglvYtycKcYFDqNltJEC9bMYsQJAFVbIrt7YJTs25S4OEzjLAYk0hxMz
+dP0gfoWk5rrrS6Lv7Vb5BluIkNTBa34nQeiydCTyydhJYIurl/bzm/IqoQJBAIn9
+3NpOZwpx4c6pvAEiT0O3uluuMvZ9J7wlW+NA2W05CRp8XcJvbyFlwAQIc2VsIVA1
+WTzS9m0O9qCK0MgKVYECQEuwnU2kufaxpxW5nY8lE6RRDmjU+zX/RrFtSSy7pgnE
+sbW1RRQsOWYi19XgaUuHtminAL1FHCGfJq7wZI1XhcU=
+-----END RSA PRIVATE KEY-----",
+
+
+];