mirror of
https://gitee.com/yansongda/pay.git
synced 2024-11-29 10:47:57 +08:00
feat: 增加抖音支付 (#1014)
This commit is contained in:
parent
e73c8bfcca
commit
2ea20b79f1
@ -1,3 +1,9 @@
|
||||
## v3.7.9
|
||||
|
||||
### added
|
||||
|
||||
- feat: 新增抖音支付(#1014)
|
||||
|
||||
## v3.7.8
|
||||
|
||||
### added
|
||||
|
140
README.md
140
README.md
@ -77,6 +77,11 @@ yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商
|
||||
- 刷卡支付
|
||||
- ...
|
||||
|
||||
### 抖音
|
||||
|
||||
- 小程序支付
|
||||
- ...
|
||||
|
||||
### 银联
|
||||
|
||||
- 手机网站支付
|
||||
@ -146,7 +151,9 @@ class AlipayController
|
||||
|
||||
public function web()
|
||||
{
|
||||
$result = Pay::alipay($this->config)->web([
|
||||
Pay::config($this->config);
|
||||
|
||||
$result = Pay::alipay()->web([
|
||||
'out_trade_no' => ''.time(),
|
||||
'total_amount' => '0.01',
|
||||
'subject' => 'yansongda 测试 - 1',
|
||||
@ -157,7 +164,9 @@ class AlipayController
|
||||
|
||||
public function returnCallback()
|
||||
{
|
||||
$data = Pay::alipay($this->config)->callback(); // 是的,验签就这么简单!
|
||||
Pay::config($this->config);
|
||||
|
||||
$data = Pay::alipay()->callback(); // 是的,验签就这么简单!
|
||||
|
||||
// 订单号:$data->out_trade_no
|
||||
// 支付宝交易号:$data->trade_no
|
||||
@ -166,10 +175,10 @@ class AlipayController
|
||||
|
||||
public function notifyCallback()
|
||||
{
|
||||
$alipay = Pay::alipay($this->config);
|
||||
|
||||
Pay::config($this->config);
|
||||
|
||||
try{
|
||||
$data = $alipay->callback(); // 是的,验签就这么简单!
|
||||
$data = Pay::alipay()->callback(); // 是的,验签就这么简单!
|
||||
|
||||
// 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
|
||||
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号;
|
||||
@ -177,11 +186,11 @@ class AlipayController
|
||||
// 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email);
|
||||
// 4、验证app_id是否为该商户本身。
|
||||
// 5、其它业务逻辑情况
|
||||
} catch (\Exception $e) {
|
||||
// $e->getMessage();
|
||||
} catch (\Throwable $e) {
|
||||
dd($e);
|
||||
}
|
||||
|
||||
return $alipay->success();
|
||||
return Pay::alipay()->success();
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -249,6 +258,8 @@ class WechatController
|
||||
|
||||
public function index()
|
||||
{
|
||||
Pay::config($this->config);
|
||||
|
||||
$order = [
|
||||
'out_trade_no' => time().'',
|
||||
'description' => 'subject-测试',
|
||||
@ -260,7 +271,7 @@ class WechatController
|
||||
],
|
||||
];
|
||||
|
||||
$pay = Pay::wechat($this->config)->mp($order);
|
||||
$pay = Pay::wechat()->mp($order);
|
||||
|
||||
// $pay->appId
|
||||
// $pay->timeStamp
|
||||
@ -269,17 +280,96 @@ class WechatController
|
||||
// $pay->signType
|
||||
}
|
||||
|
||||
public function notifyCallback()
|
||||
public function callback()
|
||||
{
|
||||
$pay = Pay::wechat($this->config);
|
||||
|
||||
Pay::config($this->config);
|
||||
|
||||
try{
|
||||
$data = $pay->callback(); // 是的,验签就这么简单!
|
||||
} catch (\Exception $e) {
|
||||
// $e->getMessage();
|
||||
$data = Pay::wechat()->callback(); // 是的,验签就这么简单!
|
||||
} catch (\Throwable $e) {
|
||||
dd($e);
|
||||
}
|
||||
|
||||
return $pay->success();
|
||||
return Pay::wechat()->success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 抖音
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Yansongda\Pay\Pay;
|
||||
|
||||
class DouyinController
|
||||
{
|
||||
protected $config = [
|
||||
'douyin' => [
|
||||
'default' => [
|
||||
// 选填-商户号
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号
|
||||
'mch_id' => '73744242495132490630',
|
||||
// 必填-支付 Token,用于支付回调签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌)
|
||||
'mch_secret_token' => 'douyin_mini_token',
|
||||
// 必填-支付 SALT,用于支付签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT
|
||||
'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA',
|
||||
// 必填-小程序 app_id
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid
|
||||
'mini_app_id' => 'tt226e54d3bd581bf801',
|
||||
// 选填-抖音开放平台服务商id
|
||||
'thirdparty_id' => '',
|
||||
// 选填-抖音支付回调地址
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
],
|
||||
],
|
||||
'logger' => [ // optional
|
||||
'enable' => false,
|
||||
'file' => './logs/alipay.log',
|
||||
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
|
||||
'type' => 'single', // optional, 可选 daily.
|
||||
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
|
||||
],
|
||||
'http' => [ // optional
|
||||
'timeout' => 5.0,
|
||||
'connect_timeout' => 5.0,
|
||||
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
|
||||
],
|
||||
];
|
||||
|
||||
public function pay()
|
||||
{
|
||||
Pay::config($this->config);
|
||||
|
||||
$result = Pay::douyin()->mini([
|
||||
'out_order_no' => date('YmdHis').mt_rand(1000, 9999),
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
'expand_order_info' => json_encode([
|
||||
'original_delivery_fee' => 15,
|
||||
'actual_delivery_fee' => 10
|
||||
])
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function callback()
|
||||
{
|
||||
Pay::config($this->config);
|
||||
|
||||
try{
|
||||
$data = Pay::douyin()->callback(); // 是的,验签就这么简单!
|
||||
} catch (\Throwable $e) {
|
||||
dd($e)
|
||||
}
|
||||
|
||||
return Pay::douyin()->success();
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -292,7 +382,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Yansongda\Pay\Pay;
|
||||
|
||||
class EpayController
|
||||
class JsbController
|
||||
{
|
||||
protected $config = [
|
||||
'jsb' => [
|
||||
@ -331,33 +421,35 @@ class EpayController
|
||||
|
||||
public function index()
|
||||
{
|
||||
Pay::config($this->config);
|
||||
|
||||
$order = [
|
||||
'outTradeNo' => time().'',
|
||||
'proInfo' => 'subject-测试',
|
||||
'totalFee'=> 1,
|
||||
];
|
||||
|
||||
$pay = Pay::jsb($this->config)->scan($order);
|
||||
$pay = Pay::jsb()->scan($order);
|
||||
}
|
||||
|
||||
public function notifyCallback()
|
||||
{
|
||||
$pay = Pay::jsb($this->config);
|
||||
Pay::config($this->config);
|
||||
|
||||
try{
|
||||
$data = $pay->callback(); // 是的,验签就这么简单!
|
||||
} catch (\Exception $e) {
|
||||
// $e->getMessage();
|
||||
$data = Pay::jsb()->callback(); // 是的,验签就这么简单!
|
||||
} catch (\Throwable $e) {
|
||||
dd($e);
|
||||
}
|
||||
|
||||
return $pay->success();
|
||||
return Pay::jsb()->success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 代码贡献
|
||||
|
||||
由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。
|
||||
由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。
|
||||
|
||||
如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_**
|
||||
|
||||
|
@ -22,8 +22,8 @@
|
||||
"ext-libxml": "*",
|
||||
"ext-json": "*",
|
||||
"ext-bcmath": "*",
|
||||
"yansongda/artful": "~1.1.0",
|
||||
"yansongda/supports": "~4.0.9"
|
||||
"yansongda/artful": "~1.1.1",
|
||||
"yansongda/supports": "~4.0.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0",
|
||||
|
@ -37,6 +37,8 @@ class Exception extends \Exception
|
||||
|
||||
public const PARAMS_CALLBACK_REQUEST_INVALID = 9221;
|
||||
|
||||
public const PARAMS_DOUYIN_URL_MISSING = 9222;
|
||||
|
||||
/**
|
||||
* 关于响应.
|
||||
*/
|
||||
@ -57,6 +59,8 @@ class Exception extends \Exception
|
||||
|
||||
public const CONFIG_JSB_INVALID = 9404;
|
||||
|
||||
public const CONFIG_DOUYIN_INVALID = 9405;
|
||||
|
||||
/**
|
||||
* 关于签名.
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ use Yansongda\Pay\Plugin\Wechat\ResponsePlugin;
|
||||
use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Wechat\V3\WechatPublicCertsPlugin;
|
||||
use Yansongda\Pay\Provider\Alipay;
|
||||
use Yansongda\Pay\Provider\Douyin;
|
||||
use Yansongda\Pay\Provider\Jsb;
|
||||
use Yansongda\Pay\Provider\Unipay;
|
||||
use Yansongda\Pay\Provider\Wechat;
|
||||
@ -596,6 +597,7 @@ function verify_unipay_sign_qra(array $config, array $destination): void
|
||||
function get_jsb_url(array $config, ?Collection $payload): string
|
||||
{
|
||||
$url = get_radar_url($config, $payload) ?? '';
|
||||
|
||||
if (str_starts_with($url, 'http')) {
|
||||
return $url;
|
||||
}
|
||||
@ -629,3 +631,21 @@ function verify_jsb_sign(array $config, string $content, string $sign): void
|
||||
throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
function get_douyin_url(array $config, ?Collection $payload): string
|
||||
{
|
||||
$url = get_radar_url($config, $payload);
|
||||
|
||||
if (empty($url)) {
|
||||
throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`');
|
||||
}
|
||||
|
||||
if (str_starts_with($url, 'http')) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
|
||||
}
|
||||
|
@ -10,10 +10,12 @@ use Yansongda\Artful\Artful;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Pay\Provider\Alipay;
|
||||
use Yansongda\Pay\Provider\Douyin;
|
||||
use Yansongda\Pay\Provider\Jsb;
|
||||
use Yansongda\Pay\Provider\Unipay;
|
||||
use Yansongda\Pay\Provider\Wechat;
|
||||
use Yansongda\Pay\Service\AlipayServiceProvider;
|
||||
use Yansongda\Pay\Service\DouyinServiceProvider;
|
||||
use Yansongda\Pay\Service\JsbServiceProvider;
|
||||
use Yansongda\Pay\Service\UnipayServiceProvider;
|
||||
use Yansongda\Pay\Service\WechatServiceProvider;
|
||||
@ -23,6 +25,7 @@ use Yansongda\Pay\Service\WechatServiceProvider;
|
||||
* @method static Wechat wechat(array $config = [], $container = null)
|
||||
* @method static Unipay unipay(array $config = [], $container = null)
|
||||
* @method static Jsb jsb(array $config = [], $container = null)
|
||||
* @method static Douyin douyin(array $config = [], $container = null)
|
||||
*/
|
||||
class Pay
|
||||
{
|
||||
@ -46,6 +49,7 @@ class Pay
|
||||
WechatServiceProvider::class,
|
||||
UnipayServiceProvider::class,
|
||||
JsbServiceProvider::class,
|
||||
DouyinServiceProvider::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,9 @@ use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Artful\get_radar_method;
|
||||
use function Yansongda\Pay\get_alipay_url;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
@ -30,7 +32,8 @@ class AddRadarPlugin implements PluginInterface
|
||||
$payload = $rocket->getPayload();
|
||||
|
||||
$rocket->setRadar(new Request(
|
||||
strtoupper($params['_method'] ?? 'POST'),
|
||||
// 这里因为支付宝的 payload 里不包含 _method,所以需要取 params 中的
|
||||
get_radar_method(new Collection($params)) ?? 'POST',
|
||||
get_alipay_url($config, $payload),
|
||||
$this->getHeaders(),
|
||||
// 不能用 packer,支付宝接收的是 x-www-form-urlencoded 返回的又是 json,packer 用的是返回.
|
||||
|
@ -13,7 +13,6 @@ use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\InvalidSignException;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Artful\filter_params;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
@ -36,7 +35,7 @@ class CallbackPlugin implements PluginInterface
|
||||
|
||||
$value = filter_params($params, fn ($k, $v) => '' !== $v && 'sign' != $k && 'sign_type' != $k);
|
||||
|
||||
verify_alipay_sign($config, Collection::wrap($value)->sortKeys()->toString(), $params['sign'] ?? '');
|
||||
verify_alipay_sign($config, $value->sortKeys()->toString(), $params['sign'] ?? '');
|
||||
|
||||
$rocket->setPayload($params)
|
||||
->setDirection(NoHttpRequestDirection::class)
|
||||
|
94
src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php
Normal file
94
src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidConfigException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Artful\filter_params;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
class AddPayloadSignaturePlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidConfigException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][AddPayloadSignaturePlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$config = get_provider_config('douyin', $rocket->getParams());
|
||||
$payload = $rocket->getPayload();
|
||||
|
||||
$rocket->mergePayload(['sign' => $this->getSign($config, filter_params($payload))]);
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
protected function getSign(array $config, Collection $payload): string
|
||||
{
|
||||
$salt = $config['mch_secret_salt'] ?? null;
|
||||
|
||||
if (empty($salt)) {
|
||||
throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_salt]');
|
||||
}
|
||||
|
||||
foreach ($payload as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
if (in_array($key, ['other_settle_params', 'app_id', 'sign', 'thirdparty_id']) || empty($value) || 'null' === $value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = $this->arrayToString($value);
|
||||
}
|
||||
|
||||
$signData[] = $value;
|
||||
}
|
||||
|
||||
$signData[] = $salt;
|
||||
|
||||
sort($signData, SORT_STRING);
|
||||
|
||||
return md5(implode('&', $signData));
|
||||
}
|
||||
|
||||
protected function arrayToString(array $value): string
|
||||
{
|
||||
$isJsonArray = isset($value[0]);
|
||||
$keys = array_keys($value);
|
||||
|
||||
if ($isJsonArray) {
|
||||
sort($keys);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$val = $value[$key];
|
||||
|
||||
$result[] = is_array($val) ? $this->arrayToString($val) : (($isJsonArray ? '' : $key.':').trim(strval($val)));
|
||||
}
|
||||
|
||||
$result = '['.implode(' ', $result ?? []).']';
|
||||
|
||||
return ($isJsonArray ? '' : 'map').$result;
|
||||
}
|
||||
}
|
55
src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php
Normal file
55
src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Closure;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
|
||||
use function Yansongda\Artful\get_radar_body;
|
||||
use function Yansongda\Artful\get_radar_method;
|
||||
use function Yansongda\Pay\get_douyin_url;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
class AddRadarPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][AddRadarPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$params = $rocket->getParams();
|
||||
$payload = $rocket->getPayload();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
$rocket->setRadar(new Request(
|
||||
get_radar_method($payload),
|
||||
get_douyin_url($config, $payload),
|
||||
$this->getHeaders(),
|
||||
get_radar_body($payload),
|
||||
));
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
protected function getHeaders(): array
|
||||
{
|
||||
return [
|
||||
'User-Agent' => 'yansongda/pay-v3',
|
||||
'Content-Type' => 'application/json; charset=utf-8',
|
||||
];
|
||||
}
|
||||
}
|
74
src/Plugin/Douyin/V1/Pay/CallbackPlugin.php
Normal file
74
src/Plugin/Douyin/V1/Pay/CallbackPlugin.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Direction\NoHttpRequestDirection;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidConfigException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Exception\InvalidSignException;
|
||||
|
||||
use function Yansongda\Artful\filter_params;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
class CallbackPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidConfigException
|
||||
* @throws InvalidSignException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$params = $rocket->getParams();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
$value = filter_params($params, fn ($k, $v) => '' !== $v && 'msg_signature' != $k && 'type' != $k);
|
||||
|
||||
$this->verifySign($config, $value->all(), $params['msg_signature'] ?? '');
|
||||
|
||||
$rocket->setPayload($params)
|
||||
->setDirection(NoHttpRequestDirection::class)
|
||||
->setDestination($rocket->getPayload());
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidConfigException
|
||||
* @throws InvalidSignException
|
||||
*/
|
||||
protected function verifySign(array $config, array $contents, string $sign): void
|
||||
{
|
||||
if (empty($sign)) {
|
||||
throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证抖音签名失败-抖音签名为空', func_get_args());
|
||||
}
|
||||
|
||||
$contents['token'] = $config['mch_secret_token'] ?? null;
|
||||
|
||||
if (empty($contents['token'])) {
|
||||
throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_token]');
|
||||
}
|
||||
|
||||
sort($contents, SORT_STRING);
|
||||
$data = trim(implode('', $contents));
|
||||
|
||||
$result = $sign === sha1($data);
|
||||
|
||||
if (!$result) {
|
||||
throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证抖音签名失败', func_get_args());
|
||||
}
|
||||
}
|
||||
}
|
72
src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php
Normal file
72
src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
/**
|
||||
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/pay
|
||||
*/
|
||||
class PayPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][Mini][PayPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$payload = $rocket->getPayload();
|
||||
$params = $rocket->getParams();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
if (is_null($payload)) {
|
||||
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序下单,参数为空');
|
||||
}
|
||||
|
||||
if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
|
||||
$data = $this->service($payload, $config);
|
||||
}
|
||||
|
||||
$rocket->mergePayload(array_merge(
|
||||
[
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_order',
|
||||
'app_id' => $config['mini_app_id'] ?? '',
|
||||
'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config),
|
||||
],
|
||||
$data ?? [],
|
||||
));
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
protected function service(Collection $payload, array $config): array
|
||||
{
|
||||
return [
|
||||
'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNotifyUrl(array $config): ?string
|
||||
{
|
||||
return empty($config['notify_url']) ? null : $config['notify_url'];
|
||||
}
|
||||
}
|
66
src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php
Normal file
66
src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
/**
|
||||
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/query
|
||||
*/
|
||||
class QueryPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][Mini][QueryPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$payload = $rocket->getPayload();
|
||||
$params = $rocket->getParams();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
if (is_null($payload)) {
|
||||
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询订单,参数为空');
|
||||
}
|
||||
|
||||
if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
|
||||
$data = $this->service($payload, $config);
|
||||
}
|
||||
|
||||
$rocket->mergePayload(array_merge(
|
||||
[
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_order',
|
||||
'app_id' => $config['mini_app_id'] ?? '',
|
||||
],
|
||||
$data ?? [],
|
||||
));
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
protected function service(Collection $payload, array $config): array
|
||||
{
|
||||
return [
|
||||
'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
66
src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php
Normal file
66
src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
/**
|
||||
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/query
|
||||
*/
|
||||
class QueryRefundPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][Mini][QueryRefundPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$payload = $rocket->getPayload();
|
||||
$params = $rocket->getParams();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
if (is_null($payload)) {
|
||||
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询退款订单,参数为空');
|
||||
}
|
||||
|
||||
if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
|
||||
$data = $this->service($payload, $config);
|
||||
}
|
||||
|
||||
$rocket->mergePayload(array_merge(
|
||||
[
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_refund',
|
||||
'app_id' => $config['mini_app_id'] ?? '',
|
||||
],
|
||||
$data ?? [],
|
||||
));
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
protected function service(Collection $payload, array $config): array
|
||||
{
|
||||
return [
|
||||
'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
72
src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php
Normal file
72
src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Closure;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
|
||||
/**
|
||||
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/refund
|
||||
*/
|
||||
class RefundPlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
Logger::debug('[Douyin][V1][Pay][Mini][RefundPlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$payload = $rocket->getPayload();
|
||||
$params = $rocket->getParams();
|
||||
$config = get_provider_config('douyin', $params);
|
||||
|
||||
if (is_null($payload)) {
|
||||
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序退款订单,参数为空');
|
||||
}
|
||||
|
||||
if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
|
||||
$data = $this->service($payload, $config);
|
||||
}
|
||||
|
||||
$rocket->mergePayload(array_merge(
|
||||
[
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_refund',
|
||||
'app_id' => $config['mini_app_id'] ?? '',
|
||||
'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config),
|
||||
],
|
||||
$data ?? [],
|
||||
));
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $next($rocket);
|
||||
}
|
||||
|
||||
protected function service(Collection $payload, array $config): array
|
||||
{
|
||||
return [
|
||||
'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNotifyUrl(array $config): ?string
|
||||
{
|
||||
return empty($config['notify_url']) ? null : $config['notify_url'];
|
||||
}
|
||||
}
|
51
src/Plugin/Douyin/V1/Pay/ResponsePlugin.php
Normal file
51
src/Plugin/Douyin/V1/Pay/ResponsePlugin.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Closure;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Yansongda\Artful\Contract\PluginInterface;
|
||||
use Yansongda\Artful\Exception\InvalidResponseException;
|
||||
use Yansongda\Artful\Logger;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
|
||||
class ResponsePlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* @throws InvalidResponseException
|
||||
*/
|
||||
public function assembly(Rocket $rocket, Closure $next): Rocket
|
||||
{
|
||||
/* @var Rocket $rocket */
|
||||
$rocket = $next($rocket);
|
||||
|
||||
Logger::debug('[Douyin][V1][Pay][ResponsePlugin] 插件开始装载', ['rocket' => $rocket]);
|
||||
|
||||
$this->validateResponse($rocket);
|
||||
|
||||
Logger::info('[Douyin][V1][Pay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
|
||||
|
||||
return $rocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidResponseException
|
||||
*/
|
||||
protected function validateResponse(Rocket $rocket): void
|
||||
{
|
||||
$destination = $rocket->getDestination();
|
||||
$response = $rocket->getDestinationOrigin();
|
||||
|
||||
if ($response instanceof ResponseInterface
|
||||
&& ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) {
|
||||
throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '抖音返回状态码异常,请检查参数是否错误', $destination);
|
||||
}
|
||||
|
||||
if (0 !== $destination->get('err_no')) {
|
||||
throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '抖音返回业务异常: '.$destination->get('err_tips'), $destination);
|
||||
}
|
||||
}
|
||||
}
|
154
src/Provider/Douyin.php
Normal file
154
src/Provider/Douyin.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Provider;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Yansongda\Artful\Artful;
|
||||
use Yansongda\Artful\Event;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Exception\ServiceNotFoundException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\AddRadarPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Contract\ProviderInterface;
|
||||
use Yansongda\Pay\Event\CallbackReceived;
|
||||
use Yansongda\Pay\Event\MethodCalled;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\CallbackPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Supports\Collection;
|
||||
use Yansongda\Supports\Str;
|
||||
|
||||
/**
|
||||
* @method Collection|Rocket mini(array $order) 小程序支付
|
||||
*/
|
||||
class Douyin implements ProviderInterface
|
||||
{
|
||||
public const URL = [
|
||||
Pay::MODE_NORMAL => 'https://developer.toutiao.com/',
|
||||
Pay::MODE_SANDBOX => 'https://open-sandbox.douyin.com/',
|
||||
Pay::MODE_SERVICE => 'https://developer.toutiao.com/',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket
|
||||
{
|
||||
$plugin = '\Yansongda\Pay\Shortcut\Douyin\\'.Str::studly($shortcut).'Shortcut';
|
||||
|
||||
return Artful::shortcut($plugin, ...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket
|
||||
{
|
||||
return Artful::artful($plugins, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function query(array $order): Collection|Rocket
|
||||
{
|
||||
Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null));
|
||||
|
||||
return $this->__call('query', [$order]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function cancel(array $order): Collection|Rocket
|
||||
{
|
||||
throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 cancel API');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function close(array $order): Collection|Rocket
|
||||
{
|
||||
throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 close API');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function refund(array $order): Collection|Rocket
|
||||
{
|
||||
Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null));
|
||||
|
||||
return $this->__call('refund', [$order]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerException
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket
|
||||
{
|
||||
$request = $this->getCallbackParams($contents);
|
||||
|
||||
Event::dispatch(new CallbackReceived('douyin', $request->all(), $params, null));
|
||||
|
||||
return $this->pay([CallbackPlugin::class], $request->merge($params)->all());
|
||||
}
|
||||
|
||||
public function success(): ResponseInterface
|
||||
{
|
||||
return new Response(
|
||||
200,
|
||||
['Content-Type' => 'application/json'],
|
||||
json_encode(['err_no' => 0, 'err_tips' => 'success']),
|
||||
);
|
||||
}
|
||||
|
||||
public function mergeCommonPlugins(array $plugins): array
|
||||
{
|
||||
return array_merge(
|
||||
[StartPlugin::class],
|
||||
$plugins,
|
||||
[AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class],
|
||||
);
|
||||
}
|
||||
|
||||
protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection
|
||||
{
|
||||
if (is_array($contents)) {
|
||||
return Collection::wrap($contents);
|
||||
}
|
||||
|
||||
if (!$contents instanceof ServerRequestInterface) {
|
||||
$contents = ServerRequest::fromGlobals();
|
||||
}
|
||||
|
||||
$body = Collection::wrap($contents->getParsedBody());
|
||||
|
||||
if ($body->isNotEmpty()) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
return Collection::wrapJson((string) $contents->getBody());
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ class Wechat implements ProviderInterface
|
||||
* @throws InvalidParamsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function __call(string $shortcut, array $params): null|Collection|MessageInterface
|
||||
public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket
|
||||
{
|
||||
$plugin = '\Yansongda\Pay\Shortcut\Wechat\\'.Str::studly($shortcut).'Shortcut';
|
||||
|
||||
|
24
src/Service/DouyinServiceProvider.php
Normal file
24
src/Service/DouyinServiceProvider.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Service;
|
||||
|
||||
use Yansongda\Artful\Contract\ServiceProviderInterface;
|
||||
use Yansongda\Artful\Exception\ContainerException;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Pay\Provider\Douyin;
|
||||
|
||||
class DouyinServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* @throws ContainerException
|
||||
*/
|
||||
public function register(mixed $data = null): void
|
||||
{
|
||||
$service = new Douyin();
|
||||
|
||||
Pay::set(Douyin::class, $service);
|
||||
Pay::set('douyin', $service);
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ class CancelShortcut implements ShortcutInterface
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Cancel action [{$method}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -37,7 +37,7 @@ class CloseShortcut implements ShortcutInterface
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Close action [{$method}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -50,7 +50,7 @@ class QueryShortcut implements ShortcutInterface
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$method}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -38,7 +38,7 @@ class RefundShortcut implements ShortcutInterface
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$method}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
30
src/Shortcut/Douyin/MiniShortcut.php
Normal file
30
src/Shortcut/Douyin/MiniShortcut.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Contract\ShortcutInterface;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
|
||||
class MiniShortcut implements ShortcutInterface
|
||||
{
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
return [
|
||||
StartPlugin::class,
|
||||
PayPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
];
|
||||
}
|
||||
}
|
71
src/Shortcut/Douyin/QueryShortcut.php
Normal file
71
src/Shortcut/Douyin/QueryShortcut.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Contract\ShortcutInterface;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin as MiniQueryPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin as MiniQueryRefundPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Supports\Str;
|
||||
|
||||
class QueryShortcut implements ShortcutInterface
|
||||
{
|
||||
/**
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
{
|
||||
return $this->miniPlugins();
|
||||
}
|
||||
|
||||
protected function refundPlugins(): array
|
||||
{
|
||||
return $this->refundMiniPlugins();
|
||||
}
|
||||
|
||||
protected function miniPlugins(): array
|
||||
{
|
||||
return [
|
||||
StartPlugin::class,
|
||||
MiniQueryPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
];
|
||||
}
|
||||
|
||||
protected function refundMiniPlugins(): array
|
||||
{
|
||||
return [
|
||||
StartPlugin::class,
|
||||
MiniQueryRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
];
|
||||
}
|
||||
}
|
52
src/Shortcut/Douyin/RefundShortcut.php
Normal file
52
src/Shortcut/Douyin/RefundShortcut.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Contract\ShortcutInterface;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\RefundPlugin as MiniRefundPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Supports\Str;
|
||||
|
||||
class RefundShortcut implements ShortcutInterface
|
||||
{
|
||||
/**
|
||||
* @throws InvalidParamsException
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
{
|
||||
return $this->miniPlugins();
|
||||
}
|
||||
|
||||
protected function miniPlugins(): array
|
||||
{
|
||||
return [
|
||||
StartPlugin::class,
|
||||
MiniRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
];
|
||||
}
|
||||
}
|
@ -28,13 +28,13 @@ class CancelShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $typeMethod)) {
|
||||
return $this->{$typeMethod}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Cancel action [{$typeMethod}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -28,13 +28,13 @@ class PosShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $typeMethod)) {
|
||||
return $this->{$typeMethod}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Pos action [{$typeMethod}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -29,13 +29,13 @@ class QueryShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $typeMethod)) {
|
||||
return $this->{$typeMethod}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$typeMethod}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -28,13 +28,13 @@ class RefundShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $typeMethod)) {
|
||||
return $this->{$typeMethod}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$typeMethod}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -26,13 +26,13 @@ class ScanShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $typeMethod)) {
|
||||
return $this->{$typeMethod}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Scan action [{$typeMethod}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -33,13 +33,13 @@ class CloseShortcut implements ShortcutInterface
|
||||
return $this->combinePlugins();
|
||||
}
|
||||
|
||||
$action = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $action)) {
|
||||
return $this->{$action}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Close action [{$action}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -28,13 +28,13 @@ class PapayShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$action = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $action)) {
|
||||
return $this->{$action}($params);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($params);
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Papay action [{$action}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,13 +40,13 @@ class QueryShortcut implements ShortcutInterface
|
||||
return $this->combinePlugins();
|
||||
}
|
||||
|
||||
$action = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $action)) {
|
||||
return $this->{$action}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Query action [{$action}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -29,13 +29,13 @@ class RefundShortcut implements ShortcutInterface
|
||||
*/
|
||||
public function getPlugins(array $params): array
|
||||
{
|
||||
$action = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
$method = Str::camel($params['_action'] ?? 'default').'Plugins';
|
||||
|
||||
if (method_exists($this, $action)) {
|
||||
return $this->{$action}();
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "Refund action [{$action}] not supported");
|
||||
throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认");
|
||||
}
|
||||
|
||||
protected function defaultPlugins(): array
|
||||
|
@ -21,6 +21,7 @@ use function Yansongda\Pay\decrypt_wechat_contents;
|
||||
use function Yansongda\Pay\decrypt_wechat_resource;
|
||||
use function Yansongda\Pay\decrypt_wechat_resource_aes_256_gcm;
|
||||
use function Yansongda\Pay\encrypt_wechat_contents;
|
||||
use function Yansongda\Pay\get_douyin_url;
|
||||
use function Yansongda\Pay\get_private_cert;
|
||||
use function Yansongda\Pay\get_provider_config;
|
||||
use function Yansongda\Pay\get_public_cert;
|
||||
@ -42,6 +43,7 @@ use function Yansongda\Pay\get_wechat_type_key;
|
||||
use function Yansongda\Pay\get_wechat_url;
|
||||
use function Yansongda\Pay\reload_wechat_public_certs;
|
||||
use function Yansongda\Pay\verify_alipay_sign;
|
||||
use function Yansongda\Pay\verify_douyin_sign;
|
||||
use function Yansongda\Pay\verify_unipay_sign;
|
||||
use function Yansongda\Pay\verify_unipay_sign_qra;
|
||||
use function Yansongda\Pay\verify_wechat_sign;
|
||||
@ -566,10 +568,10 @@ Q0C300Eo+XOoO4M1WvsRBAF13g9RPSw=\r
|
||||
|
||||
public function testGetUnipayUrl()
|
||||
{
|
||||
self::assertEquals('https://yansongda.cn', get_wechat_url([], new Collection(['_url' => 'https://yansongda.cn'])));
|
||||
self::assertEquals('https://api.mch.weixin.qq.com/api/v1/yansongda', get_wechat_url([], new Collection(['_url' => 'api/v1/yansongda'])));
|
||||
self::assertEquals('https://api.mch.weixin.qq.com/api/v1/service/yansongda', get_wechat_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda'])));
|
||||
self::assertEquals('https://api.mch.weixin.qq.com/api/v1/service/yansongda', get_wechat_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda'])));
|
||||
self::assertEquals('https://yansongda.cn', get_unipay_url([], new Collection(['_url' => 'https://yansongda.cn'])));
|
||||
self::assertEquals('https://gateway.95516.com/api/v1/yansongda', get_unipay_url([], new Collection(['_url' => 'api/v1/yansongda'])));
|
||||
self::assertEquals('https://gateway.95516.com/api/v1/service/yansongda', get_unipay_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda'])));
|
||||
self::assertEquals('https://gateway.95516.com/api/v1/service/yansongda', get_unipay_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda'])));
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_UNIPAY_URL_MISSING);
|
||||
@ -693,4 +695,16 @@ Q0C300Eo+XOoO4M1WvsRBAF13g9RPSw=\r
|
||||
self::assertEquals('https://mybank.jsbchina.cn:577/eis/merchant/merchantServices.htm', get_jsb_url(['mode' => Pay::MODE_NORMAL], new Collection()));
|
||||
self::assertEquals('https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm', get_jsb_url(['mode' => Pay::MODE_SANDBOX], new Collection()));
|
||||
}
|
||||
|
||||
public function testGetDouyinUrl()
|
||||
{
|
||||
self::assertEquals('https://yansongda.cn', get_douyin_url([], new Collection(['_url' => 'https://yansongda.cn'])));
|
||||
self::assertEquals('https://developer.toutiao.com/api/v1/yansongda', get_douyin_url([], new Collection(['_url' => 'api/v1/yansongda'])));
|
||||
self::assertEquals('https://developer.toutiao.com/api/v1/service/yansongda', get_douyin_url(['mode' => Pay::MODE_SERVICE], new Collection(['_service_url' => 'api/v1/service/yansongda'])));
|
||||
self::assertEquals('https://developer.toutiao.com/api/v1/service/yansongda', get_douyin_url(['mode' => Pay::MODE_SERVICE], new Collection(['_url' => 'foo', '_service_url' => 'api/v1/service/yansongda'])));
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_DOUYIN_URL_MISSING);
|
||||
get_douyin_url([], new Collection([]));
|
||||
}
|
||||
}
|
||||
|
81
tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php
Normal file
81
tests/Plugin/Douyin/V1/Pay/AddPayloadSignaturePluginTest.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidConfigException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
|
||||
class AddPayloadSignaturePluginTest extends TestCase
|
||||
{
|
||||
protected AddPayloadSignaturePlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new AddPayloadSignaturePlugin();
|
||||
}
|
||||
|
||||
public function testSignNormal()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
$rocket->setPayload([
|
||||
'_foo' => 'bar',
|
||||
'out_order_no' => '202406100423024876',
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
]);
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $result->getPayload()->get('sign'));
|
||||
}
|
||||
|
||||
public function testSignContainsJsonString()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
$rocket->setPayload([
|
||||
'_foo' => 'bar',
|
||||
'out_order_no' => '202406101307142575',
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'expand_order_info' => '{"original_delivery_fee":15,"actual_delivery_fee":10}',
|
||||
]);
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals('259702d0e950991b0bd494c9357f3ca4', $result->getPayload()->get('sign'));
|
||||
}
|
||||
|
||||
public function testEmptySalt()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'empty_salt']);
|
||||
|
||||
$rocket->setPayload([
|
||||
'_foo' => 'bar',
|
||||
'out_order_no' => '202406100423024876',
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
]);
|
||||
|
||||
self::expectException(InvalidConfigException::class);
|
||||
self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
}
|
41
tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php
Normal file
41
tests/Plugin/Douyin/V1/Pay/AddRadarPluginTest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class AddRadarPluginTest extends TestCase
|
||||
{
|
||||
protected AddRadarPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new AddRadarPlugin();
|
||||
}
|
||||
|
||||
public function testNormal()
|
||||
{
|
||||
$params = [];
|
||||
$payload = new Collection([
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_order',
|
||||
'_body' => '123',
|
||||
]);
|
||||
|
||||
$rocket = (new Rocket())->setParams($params)->setPayload($payload);
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
$radar = $result->getRadar();
|
||||
|
||||
self::assertEquals('yansongda/pay-v3', $radar->getHeaderLine('User-Agent'));
|
||||
self::assertEquals('application/json; charset=utf-8', $radar->getHeaderLine('Content-Type'));
|
||||
self::assertEquals('123', (string) $radar->getBody());
|
||||
self::assertEquals('POST', $radar->getMethod());
|
||||
self::assertStringContainsString('api/apps/ecpay/v1/create_order', (string) $radar->getUri());
|
||||
}
|
||||
}
|
79
tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php
Normal file
79
tests/Plugin/Douyin/V1/Pay/CallbackPluginTest.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugin\Douyin\V1\Pay;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidConfigException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Exception\InvalidSignException;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\CallbackPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
|
||||
class CallbackPluginTest extends TestCase
|
||||
{
|
||||
private CallbackPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new CallbackPlugin();
|
||||
}
|
||||
|
||||
public function testCallback()
|
||||
{
|
||||
$post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}';
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(json_decode($post, true));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;});
|
||||
|
||||
self::assertNotEmpty($result->getPayload()->all());
|
||||
self::assertNotEmpty($result->getDestination()->all());
|
||||
}
|
||||
|
||||
public function testVerifyDouyinSignEmpty()
|
||||
{
|
||||
$post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"","nonce":"5280","timestamp":"1722769986","type":"payment"}';
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(json_decode($post, true));
|
||||
|
||||
self::expectException(InvalidSignException::class);
|
||||
self::expectExceptionCode(Exception::SIGN_EMPTY);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) {return $rocket;});
|
||||
}
|
||||
|
||||
public function testVerifyDouyinSignError()
|
||||
{
|
||||
$post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"foo","nonce":"5280","timestamp":"1722769986","type":"payment"}';
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(json_decode($post, true));
|
||||
|
||||
self::expectException(InvalidSignException::class);
|
||||
self::expectExceptionCode(Exception::SIGN_ERROR);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) {return $rocket;});
|
||||
}
|
||||
|
||||
public function testVerifyDouyinSignConfigError()
|
||||
{
|
||||
$post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}';
|
||||
|
||||
$params = json_decode($post, true);
|
||||
$params['_config'] = 'empty_salt';
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams($params);
|
||||
|
||||
self::expectException(InvalidConfigException::class);
|
||||
self::expectExceptionCode(Exception::CONFIG_DOUYIN_INVALID);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) {return $rocket;});
|
||||
}
|
||||
}
|
90
tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php
Normal file
90
tests/Plugin/Douyin/V1/Pay/Mini/PayPluginTest.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class PayPluginTest extends TestCase
|
||||
{
|
||||
protected PayPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new PayPlugin();
|
||||
}
|
||||
|
||||
public function testEmptyPayload()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING);
|
||||
self::expectExceptionMessage('参数异常: 抖音小程序下单,参数为空');
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
|
||||
public function testNormal()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setPayload(new Collection( [
|
||||
"name" => "yansongda",
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
"name" => "yansongda",
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testServiceParams()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'name' => 'yansongda',
|
||||
'thirdparty_id' => 'service_provider111',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'name' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'thirdparty_id' => 'service_provider111'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testService()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'name' => 'yansongda',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'name' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'thirdparty_id' => 'service_provider'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
}
|
87
tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php
Normal file
87
tests/Plugin/Douyin/V1/Pay/Mini/QueryPluginTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class QueryPluginTest extends TestCase
|
||||
{
|
||||
protected QueryPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new QueryPlugin();
|
||||
}
|
||||
|
||||
public function testEmptyPayload()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING);
|
||||
self::expectExceptionMessage('参数异常: 抖音小程序查询订单,参数为空');
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
|
||||
public function testNormal()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setPayload(new Collection( [
|
||||
"out_order_no" => "yansongda",
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
"out_order_no" => "yansongda",
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testServiceParams()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => 'yansongda',
|
||||
'thirdparty_id' => 'service_provider111',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider111'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testService()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => 'yansongda',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_order',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
}
|
87
tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php
Normal file
87
tests/Plugin/Douyin/V1/Pay/Mini/QueryRefundPluginTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class QueryRefundPluginTest extends TestCase
|
||||
{
|
||||
protected QueryRefundPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new QueryRefundPlugin();
|
||||
}
|
||||
|
||||
public function testEmptyPayload()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING);
|
||||
self::expectExceptionMessage('参数异常: 抖音小程序查询退款订单,参数为空');
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
|
||||
public function testNormal()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setPayload(new Collection( [
|
||||
"out_order_no" => "yansongda",
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
"out_order_no" => "yansongda",
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testServiceParams()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => 'yansongda',
|
||||
'thirdparty_id' => 'service_provider111',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider111'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testService()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => 'yansongda',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => 'yansongda',
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/query_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider'
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
}
|
108
tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php
Normal file
108
tests/Plugin/Douyin/V1/Pay/Mini/RefundPluginTest.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Plugin\Douyin\V1\Pay\Mini;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\RefundPlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class RefundPluginTest extends TestCase
|
||||
{
|
||||
protected RefundPlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new RefundPlugin();
|
||||
}
|
||||
|
||||
public function testEmptyPayload()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING);
|
||||
self::expectExceptionMessage('参数异常: 抖音小程序退款订单,参数为空');
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
|
||||
public function testNormal()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setPayload(new Collection( [
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testServiceParams()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
'thirdparty_id' => 'service_provider111',
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider111',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
|
||||
public function testService()
|
||||
{
|
||||
$rocket = new Rocket();
|
||||
$rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
]));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertEquals([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
'_method' => 'POST',
|
||||
'_url' => 'api/apps/ecpay/v1/create_refund',
|
||||
'app_id' => 'tt226e54d3bd581bf801',
|
||||
'thirdparty_id' => 'service_provider',
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
], $result->getPayload()->all());
|
||||
}
|
||||
}
|
64
tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php
Normal file
64
tests/Plugin/Douyin/V1/Pay/ResponsePluginTest.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Plugin\Douyin\V1\Pay;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Yansongda\Artful\Exception\InvalidResponseException;
|
||||
use Yansongda\Artful\Rocket;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class ResponsePluginTest extends TestCase
|
||||
{
|
||||
protected ResponsePlugin $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new ResponsePlugin();
|
||||
}
|
||||
|
||||
public function testOriginalResponseDestination()
|
||||
{
|
||||
$destination = ['err_no' => 0, 'err_tips' => 'ok', 'data' => ['foo' => 'bar']];
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setDestinationOrigin(new Response());
|
||||
$rocket->setDestination(new Collection($destination));
|
||||
|
||||
$result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
|
||||
self::assertInstanceOf(Collection::class, $result->getDestination());
|
||||
self::assertEquals($destination, $result->getDestination()->all());
|
||||
}
|
||||
|
||||
public function testOriginalResponseCodeErrorDestination()
|
||||
{
|
||||
$destination = new Response(500);
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setDestinationOrigin($destination);
|
||||
|
||||
self::expectException(InvalidResponseException::class);
|
||||
self::expectExceptionCode(Exception::RESPONSE_CODE_WRONG);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
|
||||
public function testDestinationErrorCode()
|
||||
{
|
||||
$destination = new Response(200);
|
||||
|
||||
$rocket = new Rocket();
|
||||
$rocket->setDestinationOrigin($destination);
|
||||
$rocket->setDestination(new Collection(['err_no' => 1, 'err_tips' => 'error']));
|
||||
|
||||
self::expectException(InvalidResponseException::class);
|
||||
self::expectExceptionCode(Exception::RESPONSE_BUSINESS_CODE_WRONG);
|
||||
|
||||
$this->plugin->assembly($rocket, function ($rocket) { return $rocket; });
|
||||
}
|
||||
}
|
204
tests/Provider/DouyinTest.php
Normal file
204
tests/Provider/DouyinTest.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace Yansongda\Pay\Tests\Provider;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use Mockery;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Yansongda\Artful\Contract\HttpClientInterface;
|
||||
use Yansongda\Artful\Exception\Exception;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\AddRadarPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Pay;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Pay\Tests\Stubs\Plugin\FooPluginStub;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
use Yansongda\Supports\Collection;
|
||||
|
||||
class DouyinTest extends TestCase
|
||||
{
|
||||
public function testShortcutNotFound()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_INVALID);
|
||||
|
||||
Pay::douyin()->foo();
|
||||
}
|
||||
|
||||
public function testShortcutIncompatible()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_INVALID);
|
||||
|
||||
Pay::douyin()->foo();
|
||||
}
|
||||
|
||||
public function testMergeCommonPlugins()
|
||||
{
|
||||
Pay::config([]);
|
||||
$plugins = [FooPluginStub::class];
|
||||
|
||||
self::assertEquals(array_merge(
|
||||
[StartPlugin::class],
|
||||
$plugins,
|
||||
[AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class],
|
||||
), Pay::douyin()->mergeCommonPlugins($plugins));
|
||||
}
|
||||
|
||||
public function testCallMini()
|
||||
{
|
||||
$response = new Response(
|
||||
200,
|
||||
[],
|
||||
'{"err_no":0,"err_tips":"","data":{"order_id":"7376826336364513572","order_token":"CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W"}}',
|
||||
);
|
||||
|
||||
$http = Mockery::mock(Client::class);
|
||||
$http->shouldReceive('sendRequest')->andReturn($response);
|
||||
Pay::set(HttpClientInterface::class, $http);
|
||||
|
||||
$response = Pay::douyin()->mini([
|
||||
'out_order_no' => '202406100423024876',
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
// 'notify_url' => 'https://yansongda.cn/unipay/notify',
|
||||
'_return_rocket' => true,
|
||||
]);
|
||||
|
||||
$result = $response->getDestination();
|
||||
$payload = $response->getPayload();
|
||||
|
||||
self::assertInstanceOf(Collection::class, $result);
|
||||
self::assertEquals('7376826336364513572', $result->get('data.order_id'));
|
||||
self::assertEquals('CgwIARDPKBjKMCABKAESTgpMTgGUG+Ms5klBoqYlsymcJWNMvgWCR8XH+9OO5vFPSl2zZcVKFX0sKRuG9zxMNlT43OJotxNNHaO4KLMbiqo6HYxMiRS5tkoeILFzexoA.W', $result->get('data.order_token'));
|
||||
self::assertEquals('771c1952ffb5e0744fc0ad1337aafa6a', $payload->get('sign'));
|
||||
}
|
||||
|
||||
public function testClose()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(\Yansongda\Pay\Exception\Exception::PARAMS_METHOD_NOT_SUPPORTED);
|
||||
|
||||
Pay::douyin()->close([]);
|
||||
}
|
||||
|
||||
public function testCancel()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(\Yansongda\Pay\Exception\Exception::PARAMS_METHOD_NOT_SUPPORTED);
|
||||
|
||||
Pay::douyin()->cancel([]);
|
||||
}
|
||||
|
||||
public function testQuery()
|
||||
{
|
||||
$response = new Response(
|
||||
200,
|
||||
[],
|
||||
'{"err_no":0,"err_tips":"","out_order_no":"202408040747147327","order_id":"7398075047971440922","payment_info":{"total_fee":1,"order_status":"SUCCESS","pay_time":"2024-08-04 15:49:48","way":2,"channel_no":"","channel_gateway_no":"","seller_uid":"73744242495132490630","item_id":"","cp_extra":""}}',
|
||||
);
|
||||
|
||||
$http = Mockery::mock(Client::class);
|
||||
$http->shouldReceive('sendRequest')->andReturn($response);
|
||||
Pay::set(HttpClientInterface::class, $http);
|
||||
|
||||
$response = Pay::douyin()->query([
|
||||
'out_order_no' => '202406100423024876',
|
||||
'_return_rocket' => true,
|
||||
]);
|
||||
|
||||
$result = $response->getDestination();
|
||||
$payload = $response->getPayload();
|
||||
|
||||
self::assertInstanceOf(Collection::class, $result);
|
||||
self::assertEquals('7517fb55db55327c396e5b7c9cb1be31', $payload->get('sign'));
|
||||
self::assertEquals('202408040747147327', $result->get('out_order_no'));
|
||||
self::assertEquals('7398075047971440922', $result->get('order_id'));
|
||||
self::assertEquals('SUCCESS', $result->get('payment_info.order_status'));
|
||||
}
|
||||
|
||||
public function testQueryRefund()
|
||||
{
|
||||
$response = new Response(
|
||||
200,
|
||||
[],
|
||||
'{"err_no":0,"err_tips":"success","refundInfo":{"refund_no":"7398108028894988571","refund_amount":1,"refund_status":"SUCCESS","refunded_at":1722762159,"is_all_settled":true,"cp_extra":""}}',
|
||||
);
|
||||
|
||||
$http = Mockery::mock(Client::class);
|
||||
$http->shouldReceive('sendRequest')->andReturn($response);
|
||||
Pay::set(HttpClientInterface::class, $http);
|
||||
|
||||
$response = Pay::douyin()->query([
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'_action' => 'refund',
|
||||
'_return_rocket' => true,
|
||||
]);
|
||||
|
||||
$result = $response->getDestination();
|
||||
$payload = $response->getPayload();
|
||||
|
||||
self::assertInstanceOf(Collection::class, $result);
|
||||
self::assertEquals('fa6511979b1185cf98df2538f63ee1a3', $payload->get('sign'));
|
||||
self::assertEquals('7398108028894988571', $result->get('refundInfo.refund_no'));
|
||||
}
|
||||
|
||||
public function testRefund()
|
||||
{
|
||||
$response = new Response(
|
||||
200,
|
||||
[],
|
||||
'{"err_no":0,"err_tips":"受理成功","refund_no":"7398108028894988571"}',
|
||||
);
|
||||
|
||||
$http = Mockery::mock(Client::class);
|
||||
$http->shouldReceive('sendRequest')->andReturn($response);
|
||||
Pay::set(HttpClientInterface::class, $http);
|
||||
|
||||
$response = Pay::douyin()->refund([
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
'_return_rocket' => true,
|
||||
]);
|
||||
|
||||
$result = $response->getDestination();
|
||||
$payload = $response->getPayload();
|
||||
|
||||
self::assertInstanceOf(Collection::class, $result);
|
||||
self::assertEquals('32f9c840085091f5c84a346d87bd2b4e', $payload->get('sign'));
|
||||
self::assertEquals('7398108028894988571', $result->get('refund_no'));
|
||||
}
|
||||
|
||||
public function testCallback()
|
||||
{
|
||||
$post = '{"msg":"{\"appid\":\"tt226e54d3bd581bf801\",\"cp_orderno\":\"202408041111312119\",\"cp_extra\":\"\",\"way\":\"2\",\"channel_no\":\"\",\"channel_gateway_no\":\"\",\"payment_order_no\":\"\",\"out_channel_order_no\":\"\",\"total_amount\":1,\"status\":\"SUCCESS\",\"seller_uid\":\"73744242495132490630\",\"extra\":\"\",\"item_id\":\"\",\"paid_at\":1722769986,\"message\":\"\",\"order_id\":\"7398108028895054107\"}","msg_signature":"840bdf067c1d6056becfe88735c8ebb7e1ab809c","nonce":"5280","timestamp":"1722769986","type":"payment"}';
|
||||
|
||||
$callback = Pay::douyin()->callback(json_decode($post, true));
|
||||
self::assertInstanceOf(Collection::class, $callback);
|
||||
self::assertNotEmpty($callback->all());
|
||||
|
||||
$request = new ServerRequest('POST', 'https://yansongda.cn/unipay/notify', [], $post);
|
||||
$callback = Pay::douyin()->callback($request);
|
||||
|
||||
self::assertInstanceOf(Collection::class, $callback);
|
||||
self::assertNotEmpty($callback->all());
|
||||
}
|
||||
|
||||
public function testSuccess()
|
||||
{
|
||||
$result = Pay::douyin()->success();
|
||||
|
||||
self::assertInstanceOf(ResponseInterface::class, $result);
|
||||
self::assertStringContainsString('success', (string) $result->getBody());
|
||||
}
|
||||
}
|
40
tests/Shortcut/Douyin/MiniShortcutTest.php
Normal file
40
tests/Shortcut/Douyin/MiniShortcutTest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Tests\Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Pay\Shortcut\Douyin\MiniShortcut;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
|
||||
class MiniShortcutTest extends TestCase
|
||||
{
|
||||
protected MiniShortcut $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new MiniShortcut();
|
||||
}
|
||||
|
||||
public function testDefault()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
PayPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins([]));
|
||||
}
|
||||
}
|
90
tests/Shortcut/Douyin/QueryShortcutTest.php
Normal file
90
tests/Shortcut/Douyin/QueryShortcutTest.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yansongda\Pay\Tests\Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin as MiniQueryPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin as MiniQueryRefundPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Pay\Shortcut\Douyin\QueryShortcut;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
|
||||
class QueryShortcutTest extends TestCase
|
||||
{
|
||||
protected QueryShortcut $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new QueryShortcut();
|
||||
}
|
||||
|
||||
public function testFoo()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
||||
public function testDefault()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniQueryPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins([]));
|
||||
}
|
||||
|
||||
public function testRefund()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniQueryRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins(['_action' => 'refund']));
|
||||
}
|
||||
|
||||
public function testMini()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniQueryPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins(['_action' => 'mini']));
|
||||
}
|
||||
|
||||
public function testRefundMini()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniQueryRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins(['_action' => 'refund_mini']));
|
||||
}
|
||||
}
|
63
tests/Shortcut/Douyin/RefundShortcutTest.php
Normal file
63
tests/Shortcut/Douyin/RefundShortcutTest.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shortcut\Douyin;
|
||||
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Artful\Plugin\StartPlugin;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\RefundPlugin as MiniRefundPlugin;
|
||||
use Yansongda\Pay\Plugin\Douyin\V1\Pay\ResponsePlugin;
|
||||
use Yansongda\Pay\Shortcut\Douyin\RefundShortcut;
|
||||
use Yansongda\Pay\Tests\TestCase;
|
||||
|
||||
class RefundShortcutTest extends TestCase
|
||||
{
|
||||
protected RefundShortcut $plugin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->plugin = new RefundShortcut();
|
||||
}
|
||||
|
||||
public function testFoo()
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
||||
public function testDefault()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins([]));
|
||||
}
|
||||
|
||||
public function testMini()
|
||||
{
|
||||
self::assertEquals([
|
||||
StartPlugin::class,
|
||||
MiniRefundPlugin::class,
|
||||
AddPayloadSignaturePlugin::class,
|
||||
AddPayloadBodyPlugin::class,
|
||||
AddRadarPlugin::class,
|
||||
ResponsePlugin::class,
|
||||
ParserPlugin::class,
|
||||
], $this->plugin->getPlugins(['_action' => 'mini']));
|
||||
}
|
||||
}
|
@ -75,7 +75,6 @@ class CancelShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Cancel action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ class PosShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Pos action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ class QueryShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Query action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ namespace Yansongda\Pay\Tests\Shortcut\Unipay;
|
||||
use Yansongda\Artful\Exception\InvalidParamsException;
|
||||
use Yansongda\Artful\Plugin\AddPayloadBodyPlugin;
|
||||
use Yansongda\Artful\Plugin\ParserPlugin;
|
||||
use Yansongda\Pay\Exception\Exception;
|
||||
use Yansongda\Pay\Plugin\Unipay\AddRadarPlugin;
|
||||
use Yansongda\Pay\Plugin\Unipay\Open\AddPayloadSignaturePlugin;
|
||||
use Yansongda\Pay\Plugin\Unipay\Open\Pay\QrCode\RefundPlugin as QrCodeRefundPlugin;
|
||||
@ -72,8 +73,8 @@ class RefundShortcutTest extends TestCase
|
||||
|
||||
public function testFoo()
|
||||
{
|
||||
$this->expectException(InvalidParamsException::class);
|
||||
$this->expectExceptionMessage('Refund action [fooPlugins] not supported');
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -86,7 +86,6 @@ class ScanShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Scan action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -86,7 +86,6 @@ class CloseShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Close action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -123,7 +123,6 @@ class PapayShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Papay action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -179,7 +179,6 @@ class QueryShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Query action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -136,7 +136,6 @@ class RefundShortcutTest extends TestCase
|
||||
{
|
||||
self::expectException(InvalidParamsException::class);
|
||||
self::expectExceptionCode(Exception::PARAMS_SHORTCUT_ACTION_INVALID);
|
||||
self::expectExceptionMessage('Refund action [fooPlugins] not supported');
|
||||
|
||||
$this->plugin->getPlugins(['_action' => 'foo']);
|
||||
}
|
||||
|
@ -171,6 +171,63 @@ class TestCase extends \PHPUnit\Framework\TestCase
|
||||
'mode' => Pay::MODE_SANDBOX,
|
||||
],
|
||||
],
|
||||
'douyin' => [
|
||||
'default' => [
|
||||
// 选填-商户号
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号
|
||||
'mch_id' => '73744242495132490630',
|
||||
// 必填-支付 Token,用于支付回调签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌)
|
||||
'mch_secret_token' => 'douyin_mini_token',
|
||||
// 必填-支付 SALT,用于支付签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT
|
||||
'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA',
|
||||
// 必填-小程序 app_id
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid
|
||||
'mini_app_id' => 'tt226e54d3bd581bf801',
|
||||
// 选填-抖音开放平台服务商id
|
||||
'thirdparty_id' => '',
|
||||
// 选填-抖音支付回调地址
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'mode' => Pay::MODE_SANDBOX,
|
||||
],
|
||||
'service_provider' => [
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号
|
||||
'mch_id' => '73744242495132490630',
|
||||
// 必填-支付 Token,用于支付回调签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌)
|
||||
'mch_secret_token' => 'douyin_mini_token',
|
||||
// 必填-支付 SALT,用于支付签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT
|
||||
'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA',
|
||||
// 必填-小程序 app_id
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid
|
||||
'mini_app_id' => 'tt226e54d3bd581bf801',
|
||||
// 选填-抖音开放平台服务商id
|
||||
'thirdparty_id' => 'service_provider',
|
||||
// 选填-抖音支付回调地址
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'mode' => Pay::MODE_SERVICE,
|
||||
],
|
||||
'empty_salt' => [
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号
|
||||
'mch_id' => '73744242495132490630',
|
||||
// 必填-支付 Token,用于支付回调签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌)
|
||||
'mch_secret_token' => '',
|
||||
// 必填-支付 SALT,用于支付签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT
|
||||
'mch_secret_salt' => '',
|
||||
// 必填-小程序 app_id
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid
|
||||
'mini_app_id' => 'tt226e54d3bd581bf801',
|
||||
// 选填-抖音开放平台服务商id
|
||||
'thirdparty_id' => 'service_provider',
|
||||
// 选填-抖音支付回调地址
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
'mode' => Pay::MODE_SANDBOX,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// hyperf 单测时,未在 hyperf 框架内,所以 sdk 没有 container, 手动设置一个
|
||||
|
@ -19,6 +19,7 @@ export default [
|
||||
{ text: '初始化', link: '/docs/v3/quick-start/init' },
|
||||
{ text: '支付宝', link: '/docs/v3/quick-start/alipay' },
|
||||
{ text: '微信', link: '/docs/v3/quick-start/wechat' },
|
||||
{ text: '抖音', link: '/docs/v3/quick-start/douyin' },
|
||||
{ text: '银联', link: '/docs/v3/quick-start/unipay' },
|
||||
{ text: '江苏银行', link: '/docs/v3/quick-start/jsb' },
|
||||
{ text: '返回格式', link: '/docs/v3/quick-start/return-format' }
|
||||
@ -53,8 +54,22 @@ export default [
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '银联',
|
||||
text: '抖音',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '支付', link: '/docs/v3/douyin/pay' },
|
||||
{ text: '查询', link: '/docs/v3/douyin/query' },
|
||||
{ text: '退款', link: '/docs/v3/douyin/refund' },
|
||||
{ text: '关闭', link: '/docs/v3/douyin/close' },
|
||||
{ text: '取消', link: '/docs/v3/douyin/cancel' },
|
||||
{ text: '接收回调', link: '/docs/v3/douyin/callback' },
|
||||
{ text: '确认回调', link: '/docs/v3/douyin/response' },
|
||||
{ text: '所有内置插件', link: '/docs/v3/douyin/all' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '银联',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '支付', link: '/docs/v3/unipay/pay' },
|
||||
{ text: '查询', link: '/docs/v3/unipay/query' },
|
||||
@ -80,14 +95,14 @@ export default [
|
||||
},
|
||||
{
|
||||
text: '核心架构',
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '核心思想', link: '/docs/v3/kernel/kernel' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '其它',
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '事件', link: '/docs/v3/others/event' },
|
||||
{ text: '日志', link: '/docs/v3/others/logger' },
|
||||
@ -96,7 +111,7 @@ export default [
|
||||
},
|
||||
{
|
||||
text: '升级指南',
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'v3.7 升级指南', link: '/docs/v3/upgrade/v3.7' },
|
||||
{ text: 'v3.6 升级指南', link: '/docs/v3/upgrade/v3.6' },
|
||||
|
43
web/docs/v3/douyin/all.md
Normal file
43
web/docs/v3/douyin/all.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 抖音更多方便的插件
|
||||
|
||||
得益于 yansongda/pay 的基础架构和良好的插件机制,
|
||||
您可以自由的使用任何内置插件和自定义插件调用微信的任何 API。
|
||||
|
||||
诸如签名、API调用、解密、验签、解包等基础插件已经内置在 Pay 中,
|
||||
您可以使用 `Pay::douyin()->mergeCommonPlugins(array $plugins)` 来获取调用 API 所必须的常用插件
|
||||
|
||||
首先,查找你想使用的插件,然后
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$params = [
|
||||
'out_trade_no' => '202408040747147327',
|
||||
];
|
||||
|
||||
$allPlugins = Pay::douyin()->mergeCommonPlugins([QueryPlugin::class]);
|
||||
|
||||
$result = Pay::douyin()->pay($allPlugins, $params);
|
||||
```
|
||||
|
||||
关于插件的详细介绍,如果您感兴趣,可以参考 [yansongda/artful](https://artful.yansongda.cn/)
|
||||
|
||||
## 支付产品
|
||||
|
||||
### 小程序支付
|
||||
|
||||
- 小程序下单
|
||||
|
||||
`\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\PayPlugin`
|
||||
|
||||
- 商户订单号查询订单
|
||||
|
||||
`\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryPlugin`
|
||||
|
||||
- 退款申请
|
||||
|
||||
`\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\RefundPlugin`
|
||||
|
||||
- 查询单笔退款(通过商户退款单号)
|
||||
|
||||
`\Yansongda\Pay\Plugin\Douyin\V1\Pay\Mini\QueryRefundPlugin`
|
40
web/docs/v3/douyin/callback.md
Normal file
40
web/docs/v3/douyin/callback.md
Normal file
@ -0,0 +1,40 @@
|
||||
# 接收抖音回调
|
||||
|
||||
| 方法名 | 参数 | 返回值 |
|
||||
|:--------:|:------------------------------:|:----------:|
|
||||
| callback | 无/array/ServerRequestInterface | Collection |
|
||||
|
||||
## 例子
|
||||
|
||||
```php
|
||||
Pay::config($this->config);
|
||||
|
||||
// 是的,你没有看错,就是这么简单!
|
||||
$result = Pay::douyin()->callback();
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
### 第一个参数
|
||||
|
||||
#### `null`
|
||||
|
||||
如果您没有传参,或传 `null` 则 `yansongda/pay` 会自动识别抖音的回调请求并处理,通过 `Collection` 实例返回抖音的处理参数
|
||||
|
||||
:::warning
|
||||
建议仅在 php-fpm 下使用,swoole 方式请使用 `ServerRequestInterface` 参数传递方式
|
||||
:::
|
||||
|
||||
#### `ServerRequestInterface`
|
||||
|
||||
推荐在 swoole 环境下传递此参数,传递此参数后, yansongda/pay 会自动进行后续处理
|
||||
|
||||
#### `array`
|
||||
|
||||
也可以自行解析请求参数,传递一个 array 会自动进行后续处理
|
||||
|
||||
### 第二个参数
|
||||
|
||||
第二个参数主要是传递相关自定义变量的,类似于 `web()` 中的 `_config` / `_method` 等参数。
|
||||
|
||||
例如,如果你想在回调的时候使用非默认配置,则可以 `Pay::douyin()->callback(null, ['_config' => 'yansongda'])` 切换为 `yansongda` 这个租户的配置信息。
|
9
web/docs/v3/douyin/cancel.md
Normal file
9
web/docs/v3/douyin/cancel.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 抖音取消订单
|
||||
|
||||
:::danger
|
||||
抖音官方无此 API,如有退款需求,可使用 `refund` 方法。
|
||||
:::
|
||||
|
||||
## 异常
|
||||
|
||||
Yansongda\Pay\Exceptions\InvalidParamsException
|
9
web/docs/v3/douyin/close.md
Normal file
9
web/docs/v3/douyin/close.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 抖音关闭订单
|
||||
|
||||
:::danger
|
||||
抖音官方无此 API,如有退款需求,可使用 `refund` 方法。
|
||||
:::
|
||||
|
||||
## 异常
|
||||
|
||||
Yansongda\Pay\Exceptions\InvalidParamsException
|
37
web/docs/v3/douyin/pay.md
Normal file
37
web/docs/v3/douyin/pay.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 抖音支付
|
||||
|
||||
抖音支付目前直接内置支持以下快捷方式支付方法,对应的支付 method 如下:
|
||||
|
||||
| method | 说明 | 参数 | 返回值 |
|
||||
|:--------:|:------:|:------------:|:----------:|
|
||||
| mini | 小程序支付 | array $order | Collection |
|
||||
|
||||
## 小程序支付
|
||||
|
||||
### 例子
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$order = [
|
||||
'out_order_no' => date('YmdHis').rand(1000, 9999),
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
];
|
||||
|
||||
$result = Pay::douyin()->mini($order);
|
||||
// 可直接通过 $result->data['order_id'], $result->['order_token'] 获取相关值。
|
||||
// 后续调用不在本文档讨论范围内,请自行参考官方文档。
|
||||
```
|
||||
|
||||
### 订单配置参数
|
||||
|
||||
**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了**,比如,`app_id`,`sign` 等参数,大家只需传入订单类主观参数即可。
|
||||
|
||||
所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考[这里](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/pay),查看「请求参数」一栏。
|
||||
|
||||
### 调用支付
|
||||
|
||||
后续调起支付不再本文档讨论范围内,请参考[官方文档](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/tt-pay)
|
44
web/docs/v3/douyin/query.md
Normal file
44
web/docs/v3/douyin/query.md
Normal file
@ -0,0 +1,44 @@
|
||||
# 抖音查询订单
|
||||
|
||||
| 方法名 | 参数 | 返回值 |
|
||||
|:-----:|:------------:|:----------:|
|
||||
| query | array $order | Collection |
|
||||
|
||||
## 查询支付订单
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$order = [
|
||||
'out_trade_no' => '202408040747147327',
|
||||
// '_action' => 'mini', // 查询小程序支付,默认
|
||||
];
|
||||
|
||||
$result = Pay::douyin()->query($order);
|
||||
```
|
||||
|
||||
### 订单配置参数
|
||||
|
||||
所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。
|
||||
|
||||
- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/pay-list/query)
|
||||
|
||||
## 查询退款订单
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$order = [
|
||||
'transaction_id' => '1217752501201407033233368018',
|
||||
'_action' => 'refund',
|
||||
// '_action' => 'refund_mini', // 查询小程序退款订单,refund action 默认
|
||||
];
|
||||
|
||||
$result = Pay::douyin()->query($order);
|
||||
```
|
||||
|
||||
### 订单配置参数
|
||||
|
||||
所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。
|
||||
|
||||
- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/query)
|
28
web/docs/v3/douyin/refund.md
Normal file
28
web/docs/v3/douyin/refund.md
Normal file
@ -0,0 +1,28 @@
|
||||
# 抖音退款
|
||||
|
||||
| 方法名 | 参数 | 返回值 |
|
||||
|:------:|:------------:|:----------:|
|
||||
| refund | array $order | Collection |
|
||||
|
||||
## 退款操作
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$order = [
|
||||
'out_order_no' => '202408040747147327',
|
||||
'out_refund_no' => '202408040747147327',
|
||||
'reason' => '测试',
|
||||
'refund_amount' => 1,
|
||||
// '_action' => 'mini', // 小程序退款,默认
|
||||
|
||||
];
|
||||
|
||||
$result = Pay::douyin()->refund($order);
|
||||
```
|
||||
|
||||
### 订单配置参数
|
||||
|
||||
所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏。
|
||||
|
||||
- [小程序订单](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/refund-list/refund)
|
19
web/docs/v3/douyin/response.md
Normal file
19
web/docs/v3/douyin/response.md
Normal file
@ -0,0 +1,19 @@
|
||||
# 抖音确认回调
|
||||
|
||||
| 方法名 | 参数 | 返回值 |
|
||||
|:-------:|:---:|:--------:|
|
||||
| success | 无 | Response |
|
||||
|
||||
## 例子
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
// $result = Pay::douyin()->callback();
|
||||
|
||||
return Pay::douyin()->success();
|
||||
```
|
||||
|
||||
## 订单配置参数
|
||||
|
||||
无
|
@ -1,6 +1,6 @@
|
||||
# 参与开发
|
||||
|
||||
由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。
|
||||
由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。
|
||||
|
||||
如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_**
|
||||
|
||||
|
21
web/docs/v3/quick-start/douyin.md
Normal file
21
web/docs/v3/quick-start/douyin.md
Normal file
@ -0,0 +1,21 @@
|
||||
# 抖音快速入门
|
||||
|
||||
在初始化完毕后,就可以直接方便的享受 `yansongda/pay` 带来的便利了。
|
||||
|
||||
## 小程序支付
|
||||
|
||||
```php
|
||||
Pay::config($config);
|
||||
|
||||
$order = [
|
||||
'out_order_no' => date('YmdHis') . rand(1000, 9999),
|
||||
'total_amount' => 1,
|
||||
'subject' => '闫嵩达 - test - subject - 01',
|
||||
'body' => '闫嵩达 - test - body - 01',
|
||||
'valid_time' => 600,
|
||||
];
|
||||
|
||||
$result = Pay::douyin()->mini($order);
|
||||
// 可直接通过 $result->order_id, $result->order_token 获取相关值。
|
||||
// 后续调用不在本文档讨论范围内,请自行参考官方文档。
|
||||
```
|
@ -96,6 +96,26 @@ $config = [
|
||||
'mode' => Pay::MODE_NORMAL,
|
||||
],
|
||||
],
|
||||
'douyin' => [
|
||||
'default' => [
|
||||
// 选填-商户号
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号
|
||||
'mch_id' => '73744242495132490630',
|
||||
// 必填-支付 Token,用于支付回调签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌)
|
||||
'mch_secret_token' => 'douyin_mini_token',
|
||||
// 必填-支付 SALT,用于支付签名
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT
|
||||
'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA',
|
||||
// 必填-小程序 app_id
|
||||
// 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid
|
||||
'mini_app_id' => 'tt226e54d3bd581bf801',
|
||||
// 选填-抖音开放平台服务商id
|
||||
'thirdparty_id' => '',
|
||||
// 选填-抖音支付回调地址
|
||||
'notify_url' => 'https://yansongda.cn/douyin/notify',
|
||||
],
|
||||
],
|
||||
'jsb' => [
|
||||
'default' => [
|
||||
// 服务代码
|
||||
@ -114,7 +134,7 @@ $config = [
|
||||
'notify_url' => '',
|
||||
// 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:测试环境
|
||||
'mode' => Pay::MODE_NORMAL,
|
||||
]
|
||||
],
|
||||
],
|
||||
'logger' => [
|
||||
'enable' => false,
|
||||
|
@ -12,6 +12,12 @@
|
||||
| scan | 扫码支付 | array $order | Collection |
|
||||
| transfer | 转账 | array $order | Collection |
|
||||
|
||||
:::tip
|
||||
默认情况下,除 APP支付、小程序支付 外所使用的 appid 均是微信公众号的 appid,即配置文件中的 `mp_app_id` 参数
|
||||
|
||||
如果想使用其他类型的 appid,则只需要在调用参数中增加 `_type` 参数即可,例如,如果想使用小程序的 appid,则:`['_type' => 'mini']`
|
||||
:::
|
||||
|
||||
## 公众号支付
|
||||
|
||||
### 例子
|
||||
|
@ -21,7 +21,7 @@ $order = [
|
||||
// '_action' => 'app', // app 退款
|
||||
// '_action' => 'combine', // 合单退款
|
||||
// '_action' => 'h5', // h5 退款
|
||||
// '_action' => 'miniapp', // 小程序退款
|
||||
// '_action' => 'mini', // 小程序退款
|
||||
// '_action' => 'native', // native 退款
|
||||
|
||||
];
|
||||
|
@ -6,12 +6,12 @@
|
||||
"web:serve": "vitepress serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/node": "^22.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"sass": "^1.77.1",
|
||||
"vite": "^5.2.11",
|
||||
"vitepress": "^1.1.4",
|
||||
"vue": "^3.4.27"
|
||||
"sass": "^1.77.8",
|
||||
"vite": "^5.3.5",
|
||||
"vitepress": "^1.3.1",
|
||||
"vue": "^3.4.35"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
|
1073
web/pnpm-lock.yaml
1073
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user