Skip to content

Commit

Permalink
feat: 新增江苏银行e融支付 (#1002)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: wanghaojie <[email protected]>
Co-authored-by: yansongda <[email protected]>
  • Loading branch information
3 people authored Jun 22, 2024
1 parent df334dd commit db85283
Show file tree
Hide file tree
Showing 48 changed files with 2,104 additions and 7 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 3.7.7

### added

- feat: 新增江苏银行e融支付(#1002)

## v3.7.6

### fixed
Expand Down
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商
- 刷卡支付
- 扫码支付
- ...
-
### 江苏银行(e融支付)

- 聚合扫码支付(微信,支付宝,银联,e融)
- ...

## 安装
```shell
Expand Down Expand Up @@ -279,9 +284,80 @@ class WechatController
}
```

### 江苏银行(e融支付)
```php
<?php

namespace App\Http\Controllers;

use Yansongda\Pay\Pay;

class EpayController
{
protected $config = [
'jsb' => [
'default' => [
// 服务代码
'svr_code' => '',
// 必填-合作商ID
'partner_id' => '',
// 必填-公私钥对编号
'public_key_code' => '00',
// 必填-商户私钥(加密签名)
'mch_secret_cert_path' => '',
// 必填-商户公钥证书路径(提供江苏银行进行验证签名用)
'mch_public_cert_path' => '',
// 必填-江苏银行的公钥(用于解密江苏银行返回的数据)
'jsb_public_cert_path' => '',
//支付通知地址
'notify_url' => '',
// 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:测试环境
'mode' => Pay::MODE_NORMAL,
]
],
'logger' => [ // optional
'enable' => false,
'file' => './logs/epay.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 index()
{
$order = [
'outTradeNo' => time().'',
'proInfo' => 'subject-测试',
'totalFee'=> 1,
];

$pay = Pay::jsb($this->config)->scan($order);
}

public function notifyCallback()
{
$pay = Pay::jsb($this->config);

try{
$data = $pay->callback(); // 是的,验签就这么简单!
} catch (\Exception $e) {
// $e->getMessage();
}

return $pay->success();
}
}
```

## 代码贡献

由于测试及使用环境的限制,本项目中只开发了「支付宝」「微信支付」的相关支付网关。
由于测试及使用环境的限制,本项目中只开发了「支付宝」「微信支付」、「银联」、「江苏银行」的相关支付网关。

如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_**

Expand Down
2 changes: 2 additions & 0 deletions src/Exception/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class Exception extends \Exception

public const CONFIG_UNIPAY_INVALID = 9403;

public const CONFIG_JSB_INVALID = 9404;

/**
* 关于签名.
*/
Expand Down
38 changes: 38 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\Jsb;
use Yansongda\Pay\Provider\Unipay;
use Yansongda\Pay\Provider\Wechat;
use Yansongda\Supports\Collection;
Expand Down Expand Up @@ -591,3 +592,40 @@ function verify_unipay_sign_qra(array $config, array $destination): void
throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination);
}
}

function get_jsb_url(array $config, ?Collection $payload): string
{
$url = get_radar_url($config, $payload) ?? '';
if (str_starts_with($url, 'http')) {
return $url;
}

return Jsb::URL[$config['mode'] ?? Pay::MODE_NORMAL];
}

/**
* @throws InvalidConfigException
* @throws InvalidSignException
*/
function verify_jsb_sign(array $config, string $content, string $sign): void
{
if (empty($sign)) {
throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 江苏银行签名为空', func_get_args());
}

$publicCert = $config['jsb_public_cert_path'] ?? null;

if (empty($publicCert)) {
throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [jsb_public_cert_path]');
}

$result = 1 === openssl_verify(
$content,
base64_decode($sign),
get_public_cert($publicCert)
);

if (!$result) {
throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args());
}
}
4 changes: 4 additions & 0 deletions src/Pay.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Pay\Provider\Alipay;
use Yansongda\Pay\Provider\Jsb;
use Yansongda\Pay\Provider\Unipay;
use Yansongda\Pay\Provider\Wechat;
use Yansongda\Pay\Service\AlipayServiceProvider;
use Yansongda\Pay\Service\JsbServiceProvider;
use Yansongda\Pay\Service\UnipayServiceProvider;
use Yansongda\Pay\Service\WechatServiceProvider;

/**
* @method static Alipay alipay(array $config = [], $container = null)
* @method static Wechat wechat(array $config = [], $container = null)
* @method static Unipay unipay(array $config = [], $container = null)
* @method static Jsb jsb(array $config = [], $container = null)
*/
class Pay
{
Expand All @@ -42,6 +45,7 @@ class Pay
AlipayServiceProvider::class,
WechatServiceProvider::class,
UnipayServiceProvider::class,
JsbServiceProvider::class,
];

/**
Expand Down
65 changes: 65 additions & 0 deletions src/Plugin/Jsb/AddPayloadSignPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Jsb;

use Closure;
use Yansongda\Artful\Contract\PluginInterface;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidConfigException;
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\Supports\Collection;

use function Yansongda\Pay\get_private_cert;
use function Yansongda\Pay\get_provider_config;

class AddPayloadSignPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[Jsb][AddPayloadSignPlugin] 插件开始装载', ['rocket' => $rocket]);

$params = $rocket->getParams();
$config = get_provider_config('jsb', $params);
$payload = $rocket->getPayload();

if (empty($payload) || $payload->isEmpty()) {
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少支付必要参数。可能插件用错顺序,应该先使用 `业务插件`');
}

$privateCertPath = $config['mch_secret_cert_path'] ?? '';

if (empty($privateCertPath)) {
throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [mch_secret_cert_path]');
}

$rocket->mergePayload([
'signType' => 'RSA',
'sign' => $this->getSignature(get_private_cert($privateCertPath), $payload),
]);

Logger::info('[Jsb][AddPayloadSignPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $next($rocket);
}

protected function getSignature(string $pkey, Collection $payload): string
{
$content = $payload->sortKeys()->toString();

openssl_sign($content, $signature, $pkey);

return base64_encode($signature);
}
}
68 changes: 68 additions & 0 deletions src/Plugin/Jsb/AddRadarPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Jsb;

use Closure;
use GuzzleHttp\Psr7\Request;
use Yansongda\Artful\Contract\PluginInterface;
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\Pay\get_jsb_url;
use function Yansongda\Pay\get_provider_config;

class AddRadarPlugin implements PluginInterface
{
/**
* @throws ServiceNotFoundException
* @throws ContainerException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[Jsb][AddRadarPlugin] 插件开始装载', ['rocket' => $rocket]);

$params = $rocket->getParams();
$config = get_provider_config('jsb', $params);
$payload = $rocket->getPayload();

$rocket->setRadar(new Request(
strtoupper($params['_method'] ?? 'POST'),
get_jsb_url($config, $payload),
$this->getHeaders(),
$this->getBody($payload),
));

Logger::info('[Jsb][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $next($rocket);
}

protected function getHeaders(): array
{
return [
'Content-Type' => 'text/html',
'User-Agent' => 'yansongda/pay-v3',
];
}

protected function getBody(Collection $payload): string
{
$sign = $payload->get('sign');
$signType = $payload->get('signType');

$payload->forget('sign');
$payload->forget('signType');

$payload = $payload->sortKeys();

$payload->set('sign', $sign);
$payload->set('signType', $signType);

return $payload->toString();
}
}
70 changes: 70 additions & 0 deletions src/Plugin/Jsb/CallbackPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Jsb;

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\InvalidParamsException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Logger;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidSignException;
use Yansongda\Supports\Collection;

use function Yansongda\Pay\get_provider_config;
use function Yansongda\Pay\verify_jsb_sign;

class CallbackPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
* @throws InvalidSignException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[Jsb][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);

$this->formatRequestAndParams($rocket);

$params = $rocket->getParams();
$config = get_provider_config('jsb', $params);

$payload = $rocket->getPayload();
$signature = $payload->get('sign');

$payload->forget('sign');
$payload->forget('signType');

verify_jsb_sign($config, $payload->sortKeys()->toString(), $signature);

$rocket->setDirection(NoHttpRequestDirection::class)
->setDestination($rocket->getPayload());

Logger::info('[Jsb][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $next($rocket);
}

/**
* @throws InvalidParamsException
*/
protected function formatRequestAndParams(Rocket $rocket): void
{
$request = $rocket->getParams()['request'] ?? null;

if (!$request instanceof Collection) {
throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID);
}

$rocket->setPayload($request)->setParams($rocket->getParams()['params'] ?? []);
}
}
Loading

0 comments on commit db85283

Please sign in to comment.