548 lines
17 KiB
PHP
548 lines
17 KiB
PHP
<?php
|
|
|
|
namespace douyin;
|
|
|
|
class Byte
|
|
{
|
|
|
|
private $orderParam;
|
|
private $app_id;
|
|
private $secret;
|
|
private $merchant_id;
|
|
private $salt;
|
|
private $valid_time;
|
|
private $notify_url;
|
|
private $settle_url;
|
|
private $token;
|
|
private $rsa_private_key;
|
|
private $key_version;
|
|
private $codeUrl = 'https://minigame.zijieapi.com/mgplatform/api/apps/jscode2session?';
|
|
private $tokenUrl = 'https://developer.toutiao.com/api/apps/v2/token';
|
|
private $clientTokenUrl = 'https://open.douyin.com/oauth/client_token/';
|
|
|
|
private $orderQueryUrl = 'https://open.douyin.com/api/trade_basic/v1/developer/order_query/';
|
|
protected $payUrl = 'https://developer.toutiao.com/api/apps/ecpay/v1/create_order';
|
|
protected $query = 'https://developer.toutiao.com/api/apps/ecpay/v1/query_order';
|
|
protected $pushUrl = 'https://developer.toutiao.com/api/apps/order/v2/push';
|
|
// protected $qrcodeUrl = 'https://open.douyin.com/api/apps/v1/qrcode/create/';
|
|
protected $qrcodeUrl = 'https://developer.toutiao.com/api/apps/qrcode';
|
|
// protected $qrcodeUrl = 'https://open-sandbox.douyin.com/api/apps/qrcode';
|
|
|
|
|
|
protected $refundCreateUrl = 'https://open.douyin.com/api/trade_basic/v1/developer/refund_create/';
|
|
protected $refundQueryUrl = 'https://open.douyin.com/api/trade_basic/v1/developer/refund_query/';
|
|
|
|
protected $settle = 'https://developer.toutiao.com/api/apps/ecpay/v1/settle';
|
|
protected $sendMsgUrl = 'https://developer.toutiao.com/api/apps/subscribe_notification/developer/v1/notify';
|
|
private $notifyOrder;
|
|
|
|
public static function init($config)
|
|
{
|
|
if (empty($config['app_id'])) {
|
|
throw new \Exception('not empty app_id');
|
|
}
|
|
if (empty($config['secret'])) {
|
|
throw new \Exception('not empty secret');
|
|
}
|
|
|
|
$class = new self();
|
|
$class->app_id = $config['app_id'];
|
|
$class->secret = $config['secret'];
|
|
|
|
|
|
if (!empty($config['merchant_id'])) {
|
|
$class->merchant_id = $config['merchant_id'];
|
|
}
|
|
if (!empty($config['salt'])) {
|
|
$class->salt = $config['salt'];
|
|
}
|
|
|
|
if (!empty($config['token'])) {
|
|
$class->token = $config['token'];
|
|
}
|
|
if (!empty($config['rsa_private_key'])) {
|
|
$class->rsa_private_key = $config['rsa_private_key'];
|
|
}
|
|
if (!empty($config['key_version'])) {
|
|
$class->key_version = $config['key_version'];
|
|
}
|
|
|
|
if (!empty($config['notify_url'])) {
|
|
$class->settle_url = isset($config['settle_url']) ? $config['settle_url'] : $config['notify_url'];
|
|
$class->notify_url = $config['notify_url'];
|
|
|
|
}
|
|
|
|
$class->valid_time = isset($config['valid_time']) ? $config['valid_time'] : time() + 900;
|
|
return $class;
|
|
}
|
|
/**
|
|
* 获取下单信息
|
|
*/
|
|
public function getParam()
|
|
{
|
|
return $this->orderParam;
|
|
}
|
|
/**
|
|
* 获取异步订单信息
|
|
*/
|
|
public function getNotifyOrder()
|
|
{
|
|
$data = file_get_contents("php://input");
|
|
$order = json_decode($data, true);
|
|
$order['msg'] = json_decode($order['msg'], true);
|
|
$this->notifyOrder = $order;
|
|
return $this->notifyOrder;
|
|
}
|
|
/**
|
|
* 设置订单号 金额 描述
|
|
* @param string $rder_no 平台订单号
|
|
* @param int $money 订单金额
|
|
* @param string $title 描述
|
|
*
|
|
*/
|
|
public function set($order_no, $money, $title, $desc = '')
|
|
{
|
|
$orderParam["out_order_no"] = $order_no;
|
|
$orderParam["total_amount"] = $money;
|
|
$orderParam["subject"] = $title;
|
|
$orderParam["body"] = $desc;
|
|
$orderParam["notify_url"] = $this->notify_url;
|
|
$orderParam["valid_time"] = 7200;
|
|
$orderParam["store_uid"] = $this->merchant_id;
|
|
$orderParam["app_id"] = $this->app_id;
|
|
$data = json_encode(["sign" => $this->sign($orderParam)] + $orderParam);
|
|
$this->orderParam = json_decode($this->curl_post($this->payUrl, $data), true);
|
|
return $this;
|
|
}
|
|
|
|
|
|
/**
|
|
* 获取token
|
|
*/
|
|
public function orderQuery($out_order_no)
|
|
{
|
|
$tokenData = $this->getClientToken();
|
|
if(!$tokenData || !isset($tokenData['message']) || $tokenData['message'] !== 'success'){
|
|
return false;
|
|
}
|
|
$token = $tokenData['data']['access_token'];
|
|
|
|
$header = ['access-token:'.$token,'Content-Type:'.'application/json'];
|
|
|
|
|
|
return json_decode($this->curl_post($this->orderQueryUrl, json_encode([
|
|
'out_order_no' => $out_order_no
|
|
]) ,$header), true);
|
|
}
|
|
|
|
/**
|
|
* 通用交易系统
|
|
* 获取支付参数
|
|
* @param $data
|
|
* @return mixed
|
|
*/
|
|
public function getByteAuthorization($data){
|
|
|
|
return (new \douyin\Order())->getByteAuthorization($data,$this->app_id,$this->rsa_private_key,$this->key_version);
|
|
}
|
|
|
|
/**
|
|
* @param array $map
|
|
* @return string
|
|
*/
|
|
public function sign($map)
|
|
{
|
|
$rList = array();
|
|
foreach ($map as $k => $v) {
|
|
if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id") {
|
|
continue;
|
|
}
|
|
$value = trim(strval($v));
|
|
$len = strlen($value);
|
|
if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"") {
|
|
$value = substr($value, 1, $len - 1);
|
|
}
|
|
$value = trim($value);
|
|
if ($value == "" || $value == "null") {
|
|
continue;
|
|
}
|
|
array_push($rList, $value);
|
|
}
|
|
array_push($rList, $this->salt);
|
|
sort($rList, 2);
|
|
return md5(implode('&', $rList));
|
|
}
|
|
|
|
|
|
/**
|
|
* 担保支付同步订单
|
|
* @param $openId
|
|
* @param $orderDetail
|
|
* @return false|mixed
|
|
*/
|
|
public function pushOrder($openId,$orderDetail){
|
|
$tokenData = $this->getToken();
|
|
|
|
if(!$tokenData || !isset($tokenData['err_no']) || $tokenData['err_no'] !== 0){
|
|
return false;
|
|
}
|
|
$token = $tokenData['data']['access_token'];
|
|
|
|
$data = [
|
|
'access_token'=>$token,//服务端 API 调用标识,通过 getAccessToken 获取
|
|
'app_name'=>'douyin',//做订单展示的字节系 app 名称
|
|
'open_id'=>$openId,//小程序用户的 open_id,通过 code2Session 获取
|
|
'order_type'=>0,//订单类型
|
|
'order_status'=>1,//普通小程序订单订单状态
|
|
'update_time'=>time(),//订单信息变更时间,10 位秒级时间戳
|
|
'order_detail'=>json_encode($orderDetail)
|
|
];
|
|
$res = $this->curl_post($this->pushUrl, json_encode($data));
|
|
return json_decode($res, true);
|
|
}
|
|
|
|
/**
|
|
* 获取小程序码 小程序/小游戏启动参数,小程序则格式为 encode({path}?{query})
|
|
* @param $path
|
|
* @return false|mixed
|
|
*/
|
|
public function qrcode($path){
|
|
$tokenData = $this->getToken();
|
|
// dump($tokenData);
|
|
|
|
// if(!$tokenData || !isset($tokenData['message']) || $tokenData['message'] !== 'success'){
|
|
// return false;
|
|
// }
|
|
if(!$tokenData || !isset($tokenData['err_no']) || $tokenData['err_no'] !== 0){
|
|
return false;
|
|
}
|
|
$token = $tokenData['data']['access_token'];
|
|
|
|
|
|
$data = [
|
|
'access_token'=>$token,
|
|
'path'=>urlencode($path),
|
|
'appname'=>'douyin'
|
|
];
|
|
|
|
$res = $this->curl_post($this->qrcodeUrl, json_encode($data));
|
|
|
|
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* 获取token
|
|
*/
|
|
public function getToken()
|
|
{
|
|
$arr = [
|
|
'grant_type' => 'client_credential',
|
|
'appid' => $this->app_id,
|
|
'secret' => $this->secret,
|
|
];
|
|
|
|
return json_decode($this->curl_post($this->tokenUrl, json_encode($arr)), true);
|
|
}
|
|
|
|
/**
|
|
* 生成 client_token
|
|
* 该接口用于获取接口调用的凭证 client_token。该接口适用于抖音授权。
|
|
*/
|
|
public function getClientToken()
|
|
{
|
|
$arr = [
|
|
'grant_type' => 'client_credential',
|
|
'client_key' => $this->app_id,
|
|
'client_secret' => $this->secret
|
|
];
|
|
|
|
return json_decode($this->curl_post($this->clientTokenUrl, $arr,[
|
|
'Content-Type'=>'multipart/form-data'
|
|
]), true);
|
|
}
|
|
/**
|
|
* 获取 openid
|
|
*
|
|
* @param string $code
|
|
* @param string $anonymous_code
|
|
* @return void
|
|
* @author LiJie
|
|
*/
|
|
public function getOpenid($code, $anonymous_code = "")
|
|
{
|
|
$url = $this->codeUrl . "appid=" . $this->app_id . "&secret=" . $this->secret . "&code=" . $code;
|
|
if ($anonymous_code) {
|
|
$url .= "&anonymous_code=" . $anonymous_code;
|
|
}
|
|
return json_decode($this->curl_get($url), true);
|
|
}
|
|
/**
|
|
* 异步回调
|
|
* @param $order 回调数据
|
|
* @return bool true 验签通过|false 验签不通过
|
|
*/
|
|
public function notifyCheck()
|
|
{
|
|
$order = $this->getNotifyOrder();
|
|
$data = [
|
|
$order['timestamp'],
|
|
$order['nonce'],
|
|
json_encode($order['msg']),
|
|
$this->token,
|
|
];
|
|
sort($data, SORT_STRING);
|
|
$str = implode('', $data);
|
|
if (!strcmp(sha1($str), $order['msg_signature'])) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 担保支付验签
|
|
* 回调验签
|
|
* @param array $map 验签参数
|
|
* @return stirng
|
|
*/
|
|
// public function checkSign($map){
|
|
// $rList = array();
|
|
// array_push($rList, $this->token);
|
|
// foreach($map as $k =>$v) {
|
|
// if ( $k == "type" || $k=='msg_signature')
|
|
// continue;
|
|
// $value = trim(strval($v));
|
|
// if ($value == "" || $value == "null")
|
|
// continue;
|
|
// array_push($rList, $value);
|
|
// }
|
|
// sort($rList,2);
|
|
// return sha1(implode($rList));
|
|
// }
|
|
//
|
|
|
|
/**
|
|
* 通用交易系统验签
|
|
* @param $publicKey
|
|
* @return bool
|
|
*/
|
|
public function verifySignature($publicKey) {
|
|
$reqInfo = $this->resolveReq();
|
|
$message = $reqInfo['timestamp'] . "\n" .
|
|
$reqInfo['nonce'] . "\n" .
|
|
$reqInfo['body'] . "\n";
|
|
|
|
$publicKey = openssl_get_publickey($publicKey);
|
|
$result = openssl_verify($message, base64_decode($reqInfo['signature']), $publicKey, OPENSSL_ALGO_SHA256);
|
|
return $result === 1;
|
|
}
|
|
|
|
public function resolveReq() {
|
|
$body = $this->getRequestBodyParamsStr();
|
|
$timestamp = $_SERVER['HTTP_BYTE_TIMESTAMP'];
|
|
$nonce = $_SERVER['HTTP_BYTE_NONCE_STR'];
|
|
$signature = $_SERVER['HTTP_BYTE_SIGNATURE'];
|
|
|
|
$reqInfo = [
|
|
'body'=>$body,
|
|
'nonce'=>$nonce,
|
|
'timestamp'=>$timestamp,
|
|
'signature'=>$signature
|
|
];
|
|
|
|
return $reqInfo;
|
|
}
|
|
|
|
public function getRequestBodyParamsStr() {
|
|
$input = file_get_contents('php://input');
|
|
$input = str_replace(' ', '', $input);
|
|
return $input;
|
|
}
|
|
|
|
|
|
/**
|
|
* 申请退款
|
|
*
|
|
*/
|
|
public function applyOrderRefund()
|
|
{
|
|
$order = [];
|
|
$order['order_id'] = 'N7301891140469672233';//交易系统侧订单号,长度 <= 64 byte
|
|
$order['out_refund_no'] = '202311065796282978001400';//开发者侧退款单号,长度 <= 64 byte
|
|
$order['order_entry_schema'] = '';//退款单的跳转的 schema
|
|
$order['notify_url'] = '';//退款结果通知地址,必须是 HTTPS 类型
|
|
$order['refund_reason'] = json_encode(['退款']);//退款原因,可填多个,不超过10个
|
|
$order['refund_total_amount'] = 1;//退款总金额,单位[分]
|
|
|
|
return json_decode($this->curl_post($this->refundCreateUrl, json_encode(['sign' => $this->sign($order)] + $order)), true);
|
|
}
|
|
|
|
|
|
/**
|
|
* 订单查询
|
|
* @param string $out_order_no 订单号
|
|
* @return array 订单信息
|
|
*/
|
|
public function findOrder($out_order_no)
|
|
{
|
|
if (empty($out_order_no)) {
|
|
return false;
|
|
}
|
|
$order = [
|
|
'out_order_no' => $out_order_no,
|
|
'app_id' => $this->app_id,
|
|
];
|
|
$order['sign'] = $this->sign($order);
|
|
return json_decode($this->curl_post($this->query, json_encode($order)), true);
|
|
}
|
|
/**
|
|
* 分账
|
|
*
|
|
* @param [type] $order
|
|
* @return void
|
|
* @author LiJie
|
|
*/
|
|
public function settle($order)
|
|
{
|
|
$data = [
|
|
'app_id' => $this->app_id,
|
|
'out_settle_no' => $order['out_settle_no'],
|
|
'out_order_no' => $order['out_order_no'],
|
|
'settle_desc' => $order['settle_desc'],
|
|
'notify_url' => $this->settle_url,
|
|
'cp_extra' => $order['cp_extra'],
|
|
];
|
|
$data['sign'] = $this->sign($data);
|
|
$result = json_decode($this->curl_post($this->settle, json_encode($data)), true);
|
|
return $result;
|
|
}
|
|
/**
|
|
* 发送模版消息
|
|
*
|
|
* @param [type] $data
|
|
* @param [type] $token
|
|
*/
|
|
public function sendMsg($data, $token)
|
|
{
|
|
$data['access_token'] = $token;
|
|
$data['app_id'] = $this->app_id;
|
|
return json_decode($this->curl_post($this->sendMsgUrl, json_encode($data)), true);
|
|
}
|
|
/**
|
|
* 解密手机号
|
|
*
|
|
* @param string $session_key 前端传递的session_key
|
|
* @param string $iv 前端传递的iv
|
|
* @param string $encryptedData 前端传递的encryptedData
|
|
*/
|
|
public function decryptPhone($session_key, $iv, $encryptedData)
|
|
{
|
|
if (strlen($session_key) != 24) {
|
|
return false;
|
|
}
|
|
$aesKey = base64_decode($session_key);
|
|
if (strlen($iv) != 24) {
|
|
return false;
|
|
}
|
|
$aesIV = base64_decode($iv);
|
|
$aesCipher = base64_decode($encryptedData);
|
|
$result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
|
|
$dataObj = json_decode($result);
|
|
if ($dataObj == null) {
|
|
return false;
|
|
}
|
|
if ($dataObj->watermark->appid != $this->app_id) {
|
|
return false;
|
|
}
|
|
return json_decode($result, true);
|
|
}
|
|
protected static function curl_get($url)
|
|
{
|
|
$headerArr = array("Content-type:application/x-www-form-urlencoded");
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
$output = curl_exec($ch);
|
|
if (!$output) {
|
|
throw new \Exception(curl_errno($ch));
|
|
}
|
|
curl_close($ch);
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* 发起退款
|
|
* @param $path
|
|
* @return false|mixed
|
|
*/
|
|
public function refund($params){
|
|
$tokenData = $this->getClientToken();
|
|
if(!$tokenData || !isset($tokenData['message']) || $tokenData['message'] !== 'success'){
|
|
return false;
|
|
}
|
|
$token = $tokenData['data']['access_token'];
|
|
|
|
$header = ['access-token:'.$token,'Content-Type:'.'application/json'];
|
|
|
|
return json_decode($this->curl_post($this->refundCreateUrl, json_encode($params) ,$header), true);
|
|
}
|
|
|
|
/**
|
|
* 查询退款
|
|
* @param $out_refund_no 开发者系统生成的退款单号,长度 <= 64byte
|
|
* @document https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/refund/query_refund
|
|
* @return false|mixed
|
|
*/
|
|
public function refundQuery($out_refund_no){
|
|
$tokenData = $this->getClientToken();
|
|
if(!$tokenData || !isset($tokenData['message']) || $tokenData['message'] !== 'success'){
|
|
return false;
|
|
}
|
|
$token = $tokenData['data']['access_token'];
|
|
|
|
$header = ['access-token:'.$token,'Content-Type:'.'application/json'];
|
|
|
|
return json_decode($this->curl_post($this->refundQueryUrl, json_encode([
|
|
'refund_id'=>$out_refund_no
|
|
]) ,$header), true);
|
|
}
|
|
|
|
/**
|
|
* @desc post 用于退款
|
|
*/
|
|
protected static function curl_post($url, $data,$header=[])
|
|
{
|
|
$requestHeader = array(
|
|
'Content-Type: application/json',
|
|
);
|
|
if(is_string($data)){
|
|
$requestHeader[] = 'Content-Length: '. strlen($data);
|
|
}
|
|
|
|
if($header){
|
|
$requestHeader = $header;
|
|
}
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER,$requestHeader);
|
|
curl_setopt($ch, CURLOPT_POST, 1);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
|
$output = curl_exec($ch);
|
|
if (!$output) {
|
|
throw new \Exception(curl_errno($ch));
|
|
}
|
|
curl_close($ch);
|
|
return $output;
|
|
}
|
|
} |