Skip to content

Commit

Permalink
feat: 增加抖音支付 (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
yansongda authored Aug 4, 2024
1 parent e73c8bf commit 2ea20b7
Show file tree
Hide file tree
Showing 74 changed files with 3,048 additions and 609 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v3.7.9

### added

- feat: 新增抖音支付(#1014)

## v3.7.8

### added
Expand Down
140 changes: 116 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商
- 刷卡支付
- ...

### 抖音

- 小程序支付
- ...

### 银联

- 手机网站支付
Expand Down Expand Up @@ -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',
Expand All @@ -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
Expand All @@ -166,22 +175,22 @@ 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是否为商户系统中创建的订单号;
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额);
// 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();
}
}
```
Expand Down Expand Up @@ -249,6 +258,8 @@ class WechatController

public function index()
{
Pay::config($this->config);

$order = [
'out_trade_no' => time().'',
'description' => 'subject-测试',
Expand All @@ -260,7 +271,7 @@ class WechatController
],
];

$pay = Pay::wechat($this->config)->mp($order);
$pay = Pay::wechat()->mp($order);

// $pay->appId
// $pay->timeStamp
Expand All @@ -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();
}
}
```
Expand All @@ -292,7 +382,7 @@ namespace App\Http\Controllers;

use Yansongda\Pay\Pay;

class EpayController
class JsbController
{
protected $config = [
'jsb' => [
Expand Down Expand Up @@ -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!_**

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/Exception/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class Exception extends \Exception

public const PARAMS_CALLBACK_REQUEST_INVALID = 9221;

public const PARAMS_DOUYIN_URL_MISSING = 9222;

/**
* 关于响应.
*/
Expand All @@ -57,6 +59,8 @@ class Exception extends \Exception

public const CONFIG_JSB_INVALID = 9404;

public const CONFIG_DOUYIN_INVALID = 9405;

/**
* 关于签名.
*/
Expand Down
20 changes: 20 additions & 0 deletions src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
4 changes: 4 additions & 0 deletions src/Pay.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
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;
Expand All @@ -23,6 +25,7 @@
* @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
{
Expand All @@ -46,6 +49,7 @@ class Pay
WechatServiceProvider::class,
UnipayServiceProvider::class,
JsbServiceProvider::class,
DouyinServiceProvider::class,
];

/**
Expand Down
5 changes: 4 additions & 1 deletion src/Plugin/Alipay/V2/AddRadarPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
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;

Expand All @@ -30,7 +32,8 @@ public function assembly(Rocket $rocket, Closure $next): Rocket
$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 用的是返回.
Expand Down
3 changes: 1 addition & 2 deletions src/Plugin/Alipay/V2/CallbackPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
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;
Expand All @@ -36,7 +35,7 @@ public function assembly(Rocket $rocket, Closure $next): Rocket

$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)
Expand Down
Loading

0 comments on commit 2ea20b7

Please sign in to comment.