抖音开放平台api

<?php

namespace app\waimai\service\api;

use Exception;

// 抖音开放平台接口类
class DouyinOpenApi
{
    const LOG_DIR = 'service/douyin';

    private   $apiUrl;
    private   $accountId;
    private   $appId;
    private   $appSecret;
    protected $clientToken;
    protected $isSandbox    = true;
    protected $sandboxToken = '';
    public    $redis;

    private static $instance = null;

    public function __construct()
    {
        $config = config('douyin');

        $this->apiUrl    = $config['open_api_url'];
        $this->accountId = $config['account_id'];
        $this->appId     = $config['app_id'];
        $this->appSecret = $config['app_secret'];
        $this->isSandbox = (bool)$config['is_sandbox'];

        if ($this->isSandbox) {
            $this->sandboxToken = $config['sandbox_token'];
        }

        $this->redis = get_redis(18);

        $this->getClientToken();
    }

    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * 获取门店信息
     *
     * @param int $storeId 抖音门店ID
     *
     * @return array
     */
    public function getStoreInfo($storeId)
    {
        $path = '/goodlife/v1/hermes/delivery/delivery_info/get/';

        return $this->request('GET', $path, ['poi_id' => $storeId]);
    }

    /**
     * 更新门店信息
     *
     * @param int   $storeId      抖音门店ID
     * @param array $deliveryInfo 门店信息
     *
     * @return array
     */
    public function updateStoreInfo($storeId, $deliveryInfo)
    {
        $path = '/goodlife/v1/hermes/delivery/delivery_info/save/';

        return $this->request('POST', $path, ['poi_id' => $storeId, 'delivery_info' => $deliveryInfo]);
    }

    /**
     * 获取门店接单状态
     *
     * @param int $storeId 抖音门店ID
     * @param int $status  0:关闭 1:开启
     *
     * @return array
     */
    public function updateStoreStatus($storeId, $status)
    {
        $path = '/goodlife/v1/hermes/delivery/accept_status/save/';

        return $this->request('POST', $path, ['poi_id' => $storeId, 'status' => $status]);
    }

    /**
     * 确认接单
     *
     * @param string $orderId 订单ID
     *
     * @return array
     */
    public function confirmOrder($orderId)
    {
        $path = '/goodlife/v1/trade/buy/merchant_confirm_order/';

        return $this->request('POST', $path, ['order_id' => (string)$orderId]);
    }

    /**
     * 备餐完成
     *
     * @param string $orderId 订单ID
     *
     * @return array
     */
    public function setOrderPrepared($orderId)
    {
        $path = '/goodlife/v1/hermes/preparation/complete/confirm/';

        return $this->request('POST', $path, ['order_id' => (string)$orderId]);
    }

    /**
     * 获取订单详情
     *
     * @param string $orderId 订单ID
     *
     * @return array
     * @throws Exception
     */
    public function getOrderDetail($orderId)
    {
        $path = '/goodlife/v1/hermes/trade/order/query/';

        return $this->request('GET', $path, ['account_id' => $this->accountId, 'order_id' => (string)$orderId]);
    }

    /**
     * 同意/拒绝退款
     *
     * @param string $afterSaleId 售后单ID
     * @param bool   $isApproved  是否同意
     *
     * @return array
     */
    public function dealRefund($afterSaleId, $isApproved)
    {
        $path = '/goodlife/v1/after_sale/audit/notify/';

        return $this->request('POST', $path, ['after_sale_id' => (string)$afterSaleId, 'is_approved' => (bool)$isApproved]);
    }

    /**
     * 配送状态同步
     * https://developer.open-douyin.com/docs/resource/zh-CN/local-life/develop/OpenAPI/takeout/takeout_order/takeout_order_distribution_sync
     *
     * @param array $params 配送信息
     *
     * @return array
     * @throws Exception
     */
    public function riderPosition($params)
    {
        $path = '/goodlife/v1/fulfilment/distribution/order/sync_status/';

        return $this->request('POST', $path, $params);
    }

    /**
     * 签名OpenApi
     *
     * @param string $httpBody 请求体
     *
     * @return string
     */
    public function signOpenApi($httpBody)
    {
        return sha1($this->appSecret . $httpBody);
    }

    /**
     * 验证Spi签名
     *
     * @param string $clientKey clientKey
     * @param string $timestamp 时间戳
     * @param string $httpBody  请求体
     *
     * @return string
     */
    public function signSpi($clientKey, $timestamp, $httpBody)
    {
        $params = [
            'client_key' => $clientKey,
            'timestamp'  => $timestamp,
            'http_body'  => $httpBody
        ];

        // 构建待签名字符串
        $str = $this->appSecret;
        foreach ($params as $key => $value) {
            if ($key != 'sign') {
                $str .= '&' . $key . '=' . $value;
            }
        }

        return hash('sha256', $str);
    }


    /**
     * 获取抖音开放平台的client_token
     *
     * @return string
     * @throws Exception
     */
    public function getClientToken()
    {
        // 沙箱环境是抖音写死的token
        if ($this->isSandbox) {
            $this->clientToken = $this->sandboxToken;

            return $this->clientToken;
        }

        $authName = 'douyin_client_token:' . $this->appId;
        $cache    = $this->redis->get($authName);
        if ($cache) {
            $this->clientToken = $cache['access_token'];

            return $this->clientToken;
        }

        $params = [
            'client_key'    => $this->appId,
            'client_secret' => $this->appSecret,
            'grant_type'    => 'client_credential',
        ];

        $path   = '/oauth/client_token/';
        $result = $this->request('POST', $path, $params);
        if ($result['code'] === 0) {
            return false;
        }

        $this->clientToken = $result['data']['access_token'];

        $expire = $result['data']['expires_in'] ? intval($result['data']['expires_in']) - 600 : 3600;
        $cache  = [
            'access_token' => $this->clientToken,
            'expire'       => time() + $expire,
        ];
        $this->redis->set($authName, $cache, $expire);

        return $this->clientToken;
    }

    /**
     * 格式化结果输出
     *
     * @param array|false $output 输出结果
     *
     * @return array
     */
    private function parseOutput($output)
    {
        if ($output === false) {
            $result['code'] = 0;
            $result['msg']  = '请求服务器失败';
            $result['data'] = [];

            return $result;
        }

        // 抖音文档垃圾,有的key是大写,有的是小写,统一转成小写
        $output = array_change_key_case($output);

        if (!isset($output['extra'])) {
            if (isset($output['data'])) {
                if (isset($output['data']['error_code']) && $output['data']['error_code'] == 0) {
                    $result['code'] = 1;
                    $result['msg']  = 'success';
                } else {
                    $result['code'] = 0;
                    $result['msg']  = $output['data']['description'];
                }
                $result['data'] = $output['data'];

            } else {
                $result['code'] = 0;
                $result['msg']  = '请求结果格式错误';
                $result['data'] = [];
            }

        } elseif ($output['extra']['error_code'] != 0) {
            $result['code'] = 0;
            $result['msg']  = $output['extra']['description'];
            $result['data'] = [];
        } else {
            $result['code'] = 1;
            $result['msg']  = 'success';
            $result['data'] = isset($output['data']) ? $output['data'] : [];
        }

        if ($result['code'] == 0) {
            saveLog($result, 3, '抖音返回异常', self::LOG_DIR);
        }

        return $result;
    }

    /**
     * 抖音开放平台 - 新
     *
     * @param string $method 请求方法
     * @param string $path   请求地址
     * @param array  $params 请求参数
     *
     * @return array
     * @throws Exception
     */
    public function request($method, $path, $params = [])
    {
        $headers = ['access-token: ' . $this->clientToken];
        if ($this->isSandbox) {
            $headers[] = 'x-sandbox-token: 1';
        }

        $retry = 0;
        do {
            $respObject = $this->curl($method, $path, $params, $headers);

            if ($respObject === false) {
                $retry++;
                usleep(200000);
            } else {
                break;
            }

        } while ($retry < 3);

        return $this->parseOutput($respObject);
    }

    /**
     * 向抖音发起POST请求
     *
     * @param string $method  请求方法
     * @param string $path    请求地址
     * @param array  $params  请求参数
     * @param array  $headers 请求header
     *
     * @return false|string
     * @throws Exception
     */
    public function curl($method, $path, $params = [], $headers = [])
    {
        try {
            if ($method === 'GET' && !empty($params)) {
                $path .= '?' . http_build_query($params);
            }
            if ($method === 'POST') {
                $headers[] = 'content-type: application/json';
            }

            $oCurl = curl_init();
            curl_setopt($oCurl, CURLOPT_URL, $this->apiUrl . $path);
            curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($oCurl, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($oCurl, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($oCurl, CURLOPT_TIMEOUT, 30);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1);
            if ($method === 'POST') {
                curl_setopt($oCurl, CURLOPT_POST, true);
                curl_setopt($oCurl, CURLOPT_POSTFIELDS, json_encode($params));
            }

            $response = curl_exec($oCurl);

            saveLog($response, 1, '抖音接口请求结果-' . $method, self::LOG_DIR, ['params' => $params, 'headers' => $headers, 'url' => $path]);

            if (curl_errno($oCurl)) {
                throw new Exception(curl_error($oCurl), curl_errno($oCurl));
            } else {
                $httpStatusCode = curl_getinfo($oCurl, CURLINFO_HTTP_CODE);
                if (200 !== $httpStatusCode) {
                    throw new Exception($response, $httpStatusCode);
                }
            }
            curl_close($oCurl);

            return json_decode($response, true);

        } catch (Exception $e) {
            saveLog(exceptionInfo($e), 2, '抖音请求失败-' . $method, self::LOG_DIR, ['params' => $params, 'headers' => $headers, 'url' => $path]);

            return false;
        }
    }

}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值