初始化项目:添加后端代码、ThinkPHP框架、前端资源
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the overtrue/wechat.
|
||||
*
|
||||
* (c) overtrue <i@overtrue.me>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
namespace douyin;
|
||||
use app\common\exception\Exception;
|
||||
/**
|
||||
* Class AES.
|
||||
*
|
||||
* @author overtrue <i@overtrue.me>
|
||||
*/
|
||||
class AES
|
||||
{
|
||||
/**
|
||||
* @param string $text
|
||||
* @param string $key
|
||||
* @param string $iv
|
||||
* @param int $option
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string
|
||||
{
|
||||
self::validateKey($key);
|
||||
self::validateIv($iv);
|
||||
|
||||
return openssl_encrypt($text, self::getMode($key), $key, $option, $iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cipherText
|
||||
* @param string $key
|
||||
* @param string $iv
|
||||
* @param int $option
|
||||
* @param string|null $method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string
|
||||
{
|
||||
self::validateKey($key);
|
||||
self::validateIv($iv);
|
||||
|
||||
return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMode($key)
|
||||
{
|
||||
return 'aes-'.(8 * strlen($key)).'-cbc';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public static function validateKey(string $key)
|
||||
{
|
||||
if (!in_array(strlen($key), [16, 24, 32], true)) {
|
||||
throw new Exception(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $iv
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function validateIv(string $iv)
|
||||
{
|
||||
if (!empty($iv) && 16 !== strlen($iv)) {
|
||||
throw new Exception('IV length must be 16 bytes.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the overtrue/wechat.
|
||||
*
|
||||
* (c) overtrue <i@overtrue.me>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
namespace douyin;
|
||||
|
||||
use app\common\exception\Exception;
|
||||
use douyin\AES;
|
||||
|
||||
/**
|
||||
* Class Encryptor.
|
||||
*
|
||||
* @author mingyoung <mingyoungcheung@gmail.com>
|
||||
*/
|
||||
class Encryptor
|
||||
{
|
||||
/**
|
||||
* Decrypt data.
|
||||
*
|
||||
* @param string $sessionKey
|
||||
* @param string $iv
|
||||
* @param string $encrypted
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function decryptData(string $sessionKey, string $iv, string $encrypted): array
|
||||
{
|
||||
|
||||
$decrypted = AES::decrypt(
|
||||
base64_decode($encrypted, false), base64_decode($sessionKey, false), base64_decode($iv, false)
|
||||
);
|
||||
|
||||
$decrypted = json_decode($this->pkcs7Unpad($decrypted), true);
|
||||
if (!$decrypted) {
|
||||
throw new Exception('The given payload is invalid.');
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
|
||||
const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed
|
||||
const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed
|
||||
const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey
|
||||
const ERROR_INVALID_APP_ID = -40005; // Check AppID failed
|
||||
const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed
|
||||
const ERROR_DECRYPT_AES = -40007; // AES decryption failed
|
||||
const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed
|
||||
const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed
|
||||
const ILLEGAL_BUFFER = -41003; // Illegal buffer
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $aesKey;
|
||||
|
||||
/**
|
||||
* Block size.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $blockSize = 32;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $appId
|
||||
* @param string|null $token
|
||||
* @param string|null $aesKey
|
||||
*/
|
||||
public function __construct(string $aesKey = null)
|
||||
{
|
||||
$this->aesKey = base64_decode($aesKey.'=', true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get SHA1.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws self
|
||||
*/
|
||||
public function signature(): string
|
||||
{
|
||||
$array = func_get_args();
|
||||
sort($array, SORT_STRING);
|
||||
|
||||
return sha1(implode($array));
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCS#7 pad.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $blockSize
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function pkcs7Pad($text, $blockSize)
|
||||
{
|
||||
if ($blockSize > 256) {
|
||||
throw new Exception('$blockSize may not be more than 256');
|
||||
}
|
||||
$padding = $blockSize - (strlen($text) % $blockSize);
|
||||
$pattern = chr($padding);
|
||||
|
||||
return $text.str_repeat($pattern, $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCS#7 unpad.
|
||||
*
|
||||
* @param string $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function pkcs7Unpad($text)
|
||||
{
|
||||
$pad = ord(substr($text, -1));
|
||||
if ($pad < 1 || $pad > $this->blockSize) {
|
||||
$pad = 0;
|
||||
}
|
||||
|
||||
return substr($text, 0, (strlen($text) - $pad));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace douyin;
|
||||
|
||||
class order{
|
||||
|
||||
public function getByteAuthorization($data, $appId,$privateKeyStr, $keyVersion = "1", $nonceStr = '', $timestamp = '') {
|
||||
|
||||
if(!$timestamp){
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
if(!$nonceStr){
|
||||
$nonceStr = $this->randStr(10);
|
||||
}
|
||||
|
||||
// 读取私钥
|
||||
$privateKey = openssl_pkey_get_private($privateKeyStr);
|
||||
if (!$privateKey) {
|
||||
throw new InvalidArgumentException("Invalid private key");
|
||||
}
|
||||
// 生成签名
|
||||
$signature = $this->getSignature("POST", "/requestOrder", $timestamp, $nonceStr, json_encode($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
* @param $method
|
||||
* @param $url
|
||||
* @param $timestamp
|
||||
* @param $nonce
|
||||
* @param $data
|
||||
* @param $privateKey
|
||||
* @return string
|
||||
*/
|
||||
public function getSignature($method, $url, $timestamp, $nonce, $data, $privateKey) {
|
||||
$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 function randStr($length = 8) {
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$str = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$str .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
namespace Douyin;
|
||||
|
||||
class Pay{
|
||||
|
||||
public $api_url='https://developer.toutiao.com/api/apps/ecpay/v1/';
|
||||
public $app_id;
|
||||
public $token;
|
||||
public $salt;
|
||||
public $notifyUrl;
|
||||
|
||||
public function __construct($config) {
|
||||
|
||||
if (empty($config['app_id'])) {
|
||||
throw new \Exception('not empty app_id');
|
||||
}
|
||||
if (empty($config['secret'])) {
|
||||
throw new \Exception('not empty secret');
|
||||
}
|
||||
if (empty($config['merchant_id'])) {
|
||||
throw new \Exception('not empty merchant_id');
|
||||
}
|
||||
if (empty($config['salt'])) {
|
||||
throw new \Exception('not empty salt');
|
||||
}
|
||||
if (empty($config['notify_url'])) {
|
||||
throw new \Exception('not empty notify_url');
|
||||
}
|
||||
if (empty($config['token'])) {
|
||||
throw new \Exception('not empty notify_url');
|
||||
}
|
||||
$this->app_id = $config['app_id'];
|
||||
$this->token = $config['token'];
|
||||
$this->salt = $config['salt'];
|
||||
$this->notifyUrl = $config['notify_url'];
|
||||
$this->store_uid = $config['merchant_id'];
|
||||
}
|
||||
|
||||
public function run(){
|
||||
$action=addslashes($_GET['ac']);
|
||||
$action=$action?$action:'order';
|
||||
if(!in_array($action,['order','query','refund','settle','notify','set'])){
|
||||
echo '非法请求';die;
|
||||
}
|
||||
call_user_func(array($this,$action));
|
||||
}
|
||||
|
||||
//下单
|
||||
public function order($name,$price,$out_order_no){
|
||||
$data=[
|
||||
'out_order_no'=>$out_order_no,
|
||||
'total_amount'=>$price,
|
||||
'subject'=>$name,
|
||||
'body'=>$name,
|
||||
'store_uid'=>$this->store_uid,
|
||||
'valid_time'=>7200
|
||||
];
|
||||
$res = $this->post('create_order',$data);
|
||||
return $res;
|
||||
}
|
||||
|
||||
//查询订单
|
||||
public function query(){
|
||||
$data=[
|
||||
'out_order_no'=>'2021110117254573565'
|
||||
];
|
||||
$res=$this->post('query_order',$data,false);
|
||||
echo json_encode($res);die;
|
||||
}
|
||||
|
||||
//订单退款
|
||||
public function refund(){
|
||||
$data=[
|
||||
'out_order_no'=>'2021110118351347832',
|
||||
'out_refund_no'=>$this->order_number(),
|
||||
'reason'=>'退款原因',
|
||||
'refund_amount'=>1,
|
||||
];
|
||||
$res=$this->post('create_refund',$data);
|
||||
echo json_encode($res);die;
|
||||
}
|
||||
|
||||
//订单分账
|
||||
public function settle(){
|
||||
$data=[
|
||||
'out_order_no'=>'2021110118301265990',
|
||||
'out_settle_no'=>$this->order_number(),
|
||||
'settle_desc'=>'分账描述',
|
||||
'settle_params'=>json_encode([]),//分润方参数 如[['merchant_uid'=>'商户号','amount'=>'10']] 可以有多个分账商户
|
||||
];
|
||||
$res=$this->post('settle',$data);
|
||||
echo json_encode($res);die;
|
||||
}
|
||||
|
||||
//支付设置回调测试
|
||||
public function set(){
|
||||
$content=file_get_contents('php://input');
|
||||
$this->log('log.txt',$content);
|
||||
}
|
||||
|
||||
//回调
|
||||
public function notify(){
|
||||
$content=file_get_contents('php://input');
|
||||
if(empty($content)) return false;
|
||||
$this->log('notify.txt',$content);
|
||||
$content=json_decode($content,true);
|
||||
$sign=$this->handler($content);
|
||||
if($sign==$content['msg_signature']){
|
||||
$msg=json_decode($content['msg'],true);
|
||||
echo '回调----'.$content['type']."\n";
|
||||
//这里更新应用业务逻辑代码,使用$msg跟应用订单比对更新订单,可以用 $content['type']判断是支付回调还是退款回调,payment支付回调 refund退款回调。
|
||||
$res=['err_no'=>0,'err_tips'=>'success'];
|
||||
echo json_encode($res);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试订单号,实际应用根据自己应用实际生成
|
||||
* @return string
|
||||
*/
|
||||
public function order_number(){
|
||||
return date('YmdHis').rand(10000,99999);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求小程序平台服务端
|
||||
* @param string $url 接口地址
|
||||
* @param array $data 参数内容
|
||||
* @param boolean $notify 是否有回调
|
||||
* @return array
|
||||
*/
|
||||
public function post($method,$data,$notify=true){
|
||||
$data['app_id'] = $this->app_id;
|
||||
if(!empty($notify)){
|
||||
$data['notify_url']=$this->notifyUrl;//也可以在调用的时候分别设置
|
||||
}
|
||||
$data['sign']=$this->sign($data);
|
||||
$url=$this->api_url.$method;
|
||||
$res=$this->http('POST',$url,json_encode($data),['Content-Type: application/json'],true);
|
||||
return json_decode($res,true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回调验签
|
||||
* @param array $map 验签参数
|
||||
* @return stirng
|
||||
*/
|
||||
public function handler($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 array $map 请求参数
|
||||
* @return stirng
|
||||
*/
|
||||
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 string $path 日志路径
|
||||
* @param string $content 内容
|
||||
*/
|
||||
public function log($path, $content){
|
||||
$file=fopen($path, "a");
|
||||
fwrite($file, date('Y-m-d H:i:s').'-----'.$content."\n");
|
||||
fclose($file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 网络请求
|
||||
* @param stirng $method 请求模式
|
||||
* @param stirng $url请求网关
|
||||
* @param array $params 请求参数
|
||||
* @param stirng $header 自定义头
|
||||
* @param boolean $multi 文件上传
|
||||
* @return array
|
||||
*/
|
||||
public function http( $method = 'GET', $url,$params,$header = array(), $multi = false){
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_HTTPHEADER => $header
|
||||
);
|
||||
/* 根据请求类型设置特定参数 */
|
||||
switch(strtoupper($method)){
|
||||
case 'GET':
|
||||
$opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
|
||||
break;
|
||||
case 'POST':
|
||||
//判断是否传输文件
|
||||
$params = $multi ? $params : http_build_query($params);
|
||||
$opts[CURLOPT_URL] = $url;
|
||||
$opts[CURLOPT_POST] = 1;
|
||||
$opts[CURLOPT_POSTFIELDS] = $params;
|
||||
break;
|
||||
default:
|
||||
throw new Exception('不支持的请求方式!');
|
||||
}
|
||||
|
||||
/* 初始化并执行curl请求 */
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, $opts);
|
||||
$data = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if($error) throw new Exception('请求发生错误:' . $error);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user