diff --git a/CHANGELOG.md b/CHANGELOG.md index a068e9ecc..50d2bdfec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.7.7 + +### added + +- feat: 新增江苏银行e融支付(#1002) + ## v3.7.6 ### fixed diff --git a/README.md b/README.md index 3b6d1b5f3..026c62666 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,11 @@ yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商 - 刷卡支付 - 扫码支付 - ... +- +### 江苏银行(e融支付) + +- 聚合扫码支付(微信,支付宝,银联,e融) +- ... ## 安装 ```shell @@ -279,9 +284,80 @@ class WechatController } ``` +### 江苏银行(e融支付) +```php + [ + '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!_** diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php index 73801209e..e18089035 100644 --- a/src/Exception/Exception.php +++ b/src/Exception/Exception.php @@ -55,6 +55,8 @@ class Exception extends \Exception public const CONFIG_UNIPAY_INVALID = 9403; + public const CONFIG_JSB_INVALID = 9404; + /** * 关于签名. */ diff --git a/src/Functions.php b/src/Functions.php index 5f7c30292..51801a88d 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -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; @@ -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()); + } +} diff --git a/src/Pay.php b/src/Pay.php index a6d1dd8e9..b0d40b13c 100644 --- a/src/Pay.php +++ b/src/Pay.php @@ -10,9 +10,11 @@ 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; @@ -20,6 +22,7 @@ * @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 { @@ -42,6 +45,7 @@ class Pay AlipayServiceProvider::class, WechatServiceProvider::class, UnipayServiceProvider::class, + JsbServiceProvider::class, ]; /** diff --git a/src/Plugin/Jsb/AddPayloadSignPlugin.php b/src/Plugin/Jsb/AddPayloadSignPlugin.php new file mode 100644 index 000000000..a3c029552 --- /dev/null +++ b/src/Plugin/Jsb/AddPayloadSignPlugin.php @@ -0,0 +1,65 @@ + $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); + } +} diff --git a/src/Plugin/Jsb/AddRadarPlugin.php b/src/Plugin/Jsb/AddRadarPlugin.php new file mode 100644 index 000000000..870382a1b --- /dev/null +++ b/src/Plugin/Jsb/AddRadarPlugin.php @@ -0,0 +1,68 @@ + $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(); + } +} diff --git a/src/Plugin/Jsb/CallbackPlugin.php b/src/Plugin/Jsb/CallbackPlugin.php new file mode 100644 index 000000000..bb9940dd5 --- /dev/null +++ b/src/Plugin/Jsb/CallbackPlugin.php @@ -0,0 +1,70 @@ + $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'] ?? []); + } +} diff --git a/src/Plugin/Jsb/Pay/Scan/PayPlugin.php b/src/Plugin/Jsb/Pay/Scan/PayPlugin.php new file mode 100644 index 000000000..dc9392d11 --- /dev/null +++ b/src/Plugin/Jsb/Pay/Scan/PayPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + $backUrl = $rocket->getPayload()['notify_url'] ?? $config['notify_url'] ?? null; + + if (!$backUrl) { + throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [notify_url]'); + } + + $rocket->mergePayload([ + 'service' => 'atPay', + 'backUrl' => $backUrl, + ]); + + Logger::info('[Jsb][Pay][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php b/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php new file mode 100644 index 000000000..47b4e8968 --- /dev/null +++ b/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'deviceNo' => '1234567890', + 'service' => 'payCheck', + ]); + + Logger::info('[Jsb][Pay][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php b/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php new file mode 100644 index 000000000..e77081036 --- /dev/null +++ b/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'deviceNo' => '1234567890', + 'service' => 'payRefund', + ]); + + Logger::info('[Jsb][Pay][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/src/Plugin/Jsb/ResponsePlugin.php b/src/Plugin/Jsb/ResponsePlugin.php new file mode 100644 index 000000000..023ba9538 --- /dev/null +++ b/src/Plugin/Jsb/ResponsePlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $this->validateResponse($rocket); + + Logger::info('[Jsb][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): void + { + $destination = $rocket->getDestination(); + $destinationOrigin = $rocket->getDestinationOrigin(); + + if ($destinationOrigin instanceof ResponseInterface + && ($destinationOrigin->getStatusCode() < 200 || $destinationOrigin->getStatusCode() >= 300)) { + throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '江苏银行返回状态码异常,请检查参数是否错误', $rocket->getDestination()); + } + + if ($destination instanceof Collection && '000000' !== $destination->get('respCode')) { + throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, sprintf('江苏银行返回错误: respCode:%s respMsg:%s', $destination->get('respCode'), $destination->get('respMsg')), $rocket->getDestination()); + } + } +} diff --git a/src/Plugin/Jsb/StartPlugin.php b/src/Plugin/Jsb/StartPlugin.php new file mode 100644 index 000000000..22a1b1246 --- /dev/null +++ b/src/Plugin/Jsb/StartPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload(array_merge($params, [ + 'createData' => date('Ymd'), + 'createTime' => date('His'), + 'bizDate' => date('Ymd'), + 'msgId' => Str::uuidV4(), + 'svrCode' => $config['svr_code'] ?? '', + 'partnerId' => $config['partner_id'] ?? '', + 'channelNo' => 'm', + 'publicKeyCode' => $config['public_key_code'] ?? '', + 'version' => 'v1.0.0', + 'charset' => 'utf-8', + ])); + + Logger::info('[Jsb][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/src/Plugin/Jsb/VerifySignaturePlugin.php b/src/Plugin/Jsb/VerifySignaturePlugin.php new file mode 100644 index 000000000..635cbf1dd --- /dev/null +++ b/src/Plugin/Jsb/VerifySignaturePlugin.php @@ -0,0 +1,76 @@ + $rocket]); + + if (should_do_http_request($rocket->getDirection())) { + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + + $body = (string) $rocket->getDestinationOrigin()->getBody(); + $signatureData = $this->getSignatureData($body); + + verify_jsb_sign($config, $signatureData['data'] ?? '', $signatureData['sign'] ?? ''); + } + + Logger::info('[Jsb][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + private function getSignatureData(string $body): array + { + if (Str::contains($body, '&-&')) { + $beginIndex = strpos($body, '&signType='); + $endIndex = strpos($body, '&-&'); + $data = substr($body, 0, $beginIndex).substr($body, $endIndex); + + $signIndex = strpos($body, '&sign='); + $signature = substr($body, $signIndex + strlen('&sign='), $endIndex - ($signIndex + strlen('&sign='))); + } else { + $result = Arr::wrapQuery($body, true); + $result = Collection::wrap($result); + $signature = $result->get('sign'); + $result->forget('sign'); + $result->forget('signType'); + $data = $result->sortKeys()->toString(); + } + + return [ + 'sign' => $signature, + 'data' => $data, + ]; + } +} diff --git a/src/Provider/Jsb.php b/src/Provider/Jsb.php new file mode 100644 index 000000000..11908465e --- /dev/null +++ b/src/Provider/Jsb.php @@ -0,0 +1,155 @@ + 'https://mybank.jsbchina.cn:577/eis/merchant/merchantServices.htm', + Pay::MODE_SANDBOX => 'https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm', + ]; + + /** + * @param mixed $name + * @param mixed $params + * + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call($name, $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Jsb\\'.Str::studly($name).'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); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + /** + * @throws InvalidParamsException + */ + public function cancel(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support cancel api'); + } + + /** + * @throws InvalidParamsException + */ + public function close(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support close api'); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('jsb', __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('jsb', $request->all(), $params, null)); + + return $this->pay( + [CallbackPlugin::class], + ['request' => $request, 'params' => $params] + ); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'text/html'], + 'success', + ); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('jsb', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + protected function getCallbackParams($contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if ($contents instanceof ServerRequestInterface) { + return Collection::wrap($contents->getParsedBody()); + } + + $request = ServerRequest::fromGlobals(); + + return Collection::wrap($request->getParsedBody()); + } +} diff --git a/src/Service/JsbServiceProvider.php b/src/Service/JsbServiceProvider.php new file mode 100644 index 000000000..67997424d --- /dev/null +++ b/src/Service/JsbServiceProvider.php @@ -0,0 +1,24 @@ + 'qra']), $payload); } + + public function testGetEpayUrl() + { + self::assertEquals('https://yansongda.cn', get_jsb_url([], new Collection(['_url' => 'https://yansongda.cn']))); + 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())); + } } diff --git a/tests/PayTest.php b/tests/PayTest.php index ce919eac9..b274e70de 100644 --- a/tests/PayTest.php +++ b/tests/PayTest.php @@ -8,6 +8,7 @@ use Yansongda\Artful\Exception\ServiceNotFoundException; use Yansongda\Pay\Pay; use Yansongda\Pay\Provider\Alipay; +use Yansongda\Pay\Provider\Jsb; use Yansongda\Pay\Provider\Unipay; use Yansongda\Pay\Provider\Wechat; @@ -34,6 +35,8 @@ public function testConfig() self::assertInstanceOf(Wechat::class, Pay::get(Wechat::class)); self::assertInstanceOf(Unipay::class, Pay::get('unipay')); self::assertInstanceOf(Unipay::class, Pay::get(Unipay::class)); + self::assertInstanceOf(Jsb::class, Pay::get('jsb')); + self::assertInstanceOf(Jsb::class, Pay::get(Jsb::class)); // force $result1 = Pay::config(['name' => 'yansongda1', '_force' => true]); diff --git a/tests/Plugin/Jsb/AddPayloadSignPluginTest.php b/tests/Plugin/Jsb/AddPayloadSignPluginTest.php new file mode 100644 index 000000000..5101d0710 --- /dev/null +++ b/tests/Plugin/Jsb/AddPayloadSignPluginTest.php @@ -0,0 +1,63 @@ +plugin = new AddPayloadSignPlugin(); + } + + public function testSignNormal() + { + $payload = ['outTradeNo'=>'YC202406170003','totalFee'=>0.01,'proInfo'=>'充值','backUrl'=>'http:\/\/127.0.0.1:8000\/epay\/return','createData'=>'20240618','createTime'=>'022522','bizDate'=>'20240618','msgId'=>'16253083-49c4-4142-8c56-997accf3d667','svrCode'=>'','partnerId'=>'6a13eab71c4f4b0aa4757eda6fc59710','channelNo'=>'m','publicKeyCode'=>'00','version'=>'v1.0.0','charset'=>'utf-8','service'=>'atPay']; + $sign = "Bho3LZvuv6wrQUAk6EP5lpGTCf5nDA1KDQwJy5Cog6m9S3UMVqpn0AC8+rrv5va63z5zAC6aQ7qrVH1OQ3hCeEUhhGix5HRUNgs2lzCkpywQnNsjeuapAAmfzVnDfBncPv9HuZSfdGxCOPqlkaSxonSXbB5ZpUfXbH3QjQo2F2w="; + $rocket = new Rocket(); + $rocket->setParams([])->setPayload(new Collection($payload)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($sign, $result->getPayload()->get('sign')); + } + + public function testEmptyPayload() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 缺少支付必要参数。可能插件用错顺序,应该先使用 `业务插件`'); + $rocket = new Rocket(); + $rocket->setParams([])->setPayload(new Collection()); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + + public function testMissMchSecretCertPath() + { + $payload = ['outTradeNo'=>'YC202406170003','totalFee'=>0.01,'proInfo'=>'充值','backUrl'=>'http:\/\/127.0.0.1:8000\/epay\/return','createData'=>'20240618','createTime'=>'022522','bizDate'=>'20240618','msgId'=>'16253083-49c4-4142-8c56-997accf3d667','svrCode'=>'','partnerId'=>'6a13eab71c4f4b0aa4757eda6fc59710','channelNo'=>'m','publicKeyCode'=>'00','version'=>'v1.0.0','charset'=>'utf-8','service'=>'atPay']; + $sign = "Bho3LZvuv6wrQUAk6EP5lpGTCf5nDA1KDQwJy5Cog6m9S3UMVqpn0AC8+rrv5va63z5zAC6aQ7qrVH1OQ3hCeEUhhGix5HRUNgs2lzCkpywQnNsjeuapAAmfzVnDfBncPv9HuZSfdGxCOPqlkaSxonSXbB5ZpUfXbH3QjQo2F2w="; + $rocket = new Rocket(); + $rocket->setParams([])->setPayload(new Collection($payload)); + + Pay::set(ConfigInterface::class, new Config()); + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_JSB_INVALID); + self::expectExceptionMessage('配置异常: 缺少配置参数 -- [mch_secret_cert_path]'); + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($sign, $result->getPayload()->get('sign')); + } +} diff --git a/tests/Plugin/Jsb/AddRadarPluginTest.php b/tests/Plugin/Jsb/AddRadarPluginTest.php new file mode 100644 index 000000000..76ca9cb89 --- /dev/null +++ b/tests/Plugin/Jsb/AddRadarPluginTest.php @@ -0,0 +1,40 @@ +plugin = new AddRadarPlugin(); + } + + + public function testRadarPostNormal() + { + $rocket = new Rocket(); + $rocket->setParams([])->setPayload(new Collection(['name' => 'yansongda'])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + + self::assertEquals('https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm', (string) $result->getRadar()->getUri()); + self::stringContains('name=yansongda', (string) $result->getRadar()->getBody()); + self::assertEquals('POST', $result->getRadar()->getMethod()); + + //是否存在Content-Type和User-Agent + self::assertArrayHasKey('Content-Type', $result->getRadar()->getHeaders()); + self::assertArrayHasKey('User-Agent', $result->getRadar()->getHeaders()); + //验证值 + self::assertEquals('text/html', $result->getRadar()->getHeader('Content-Type')[0]); + self::assertEquals('yansongda/pay-v3', $result->getRadar()->getHeader('User-Agent')[0]); + } +} diff --git a/tests/Plugin/Jsb/CallbackPluginTest.php b/tests/Plugin/Jsb/CallbackPluginTest.php new file mode 100644 index 000000000..bedfe5524 --- /dev/null +++ b/tests/Plugin/Jsb/CallbackPluginTest.php @@ -0,0 +1,153 @@ +plugin = new CallbackPlugin(); + } + + public function testNormal() + { + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $request = new ServerRequest('POST', 'http://localhost'); + $request = $request->withParsedBody($payload); + + $rocket = new Rocket(); + $rocket->setParams(['request'=>Collection::wrap($request->getParsedBody())]); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + } + + public function testErrorSign() + { + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_ERROR); + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030315', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $request = new ServerRequest('POST', 'http://localhost'); + $request = $request->withParsedBody($payload); + + $rocket = new Rocket(); + $rocket->setParams(['request'=>Collection::wrap($request->getParsedBody())]); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + } + + public function testEmptySign() + { + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_EMPTY); + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> '' + ]; + $request = new ServerRequest('POST', 'http://localhost'); + $request = $request->withParsedBody($payload); + + $rocket = new Rocket(); + $rocket->setParams(['request'=>Collection::wrap($request->getParsedBody())]); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + } + + public function testErrorRequestType() + { + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_CALLBACK_REQUEST_INVALID); + $payload = [ + ]; + $request = new ServerRequest('POST', 'http://localhost'); + $request = $request->withParsedBody($payload); + + $rocket = new Rocket(); + $rocket->setParams(['request'=>$request->getParsedBody()]); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + } + + public function testMissingEpayPublicCertPath() + { + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_JSB_INVALID); + Pay::set(ConfigInterface::class, new Config()); + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $request = new ServerRequest('POST', 'http://localhost'); + $request = $request->withParsedBody($payload); + + $rocket = new Rocket(); + $rocket->setParams(['request'=>Collection::wrap($request->getParsedBody())]); + + $result = $this->plugin->assembly($rocket, function ($rocket) {return $rocket;}); + + self::assertNotEmpty($result->getPayload()->all()); + } +} diff --git a/tests/Plugin/Jsb/Pay/Scan/PayPluginTest.php b/tests/Plugin/Jsb/Pay/Scan/PayPluginTest.php new file mode 100644 index 000000000..f3b35b624 --- /dev/null +++ b/tests/Plugin/Jsb/Pay/Scan/PayPluginTest.php @@ -0,0 +1,29 @@ +plugin = new PayPlugin(); + } + + public function testNormal() + { + $rocket = (new Rocket()) + ->setParams([]); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertStringContainsString('atPay', $result->getPayload()->toJson()); + } +} diff --git a/tests/Plugin/Jsb/Pay/Scan/QueryPluginTest.php b/tests/Plugin/Jsb/Pay/Scan/QueryPluginTest.php new file mode 100644 index 000000000..226af8961 --- /dev/null +++ b/tests/Plugin/Jsb/Pay/Scan/QueryPluginTest.php @@ -0,0 +1,29 @@ +plugin = new QueryPlugin(); + } + + public function testNormal() + { + $rocket = (new Rocket()) + ->setParams([]); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertStringContainsString('payCheck', $result->getPayload()->toJson()); + self::assertStringContainsString('deviceNo', $result->getPayload()->toJson()); + } +} diff --git a/tests/Plugin/Jsb/Pay/Scan/RefundPluginTest.php b/tests/Plugin/Jsb/Pay/Scan/RefundPluginTest.php new file mode 100644 index 000000000..bd6eb5848 --- /dev/null +++ b/tests/Plugin/Jsb/Pay/Scan/RefundPluginTest.php @@ -0,0 +1,29 @@ +plugin = new RefundPlugin(); + } + + public function testNormal() + { + $rocket = (new Rocket()) + ->setParams([]); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertStringContainsString('payRefund', $result->getPayload()->toJson()); + self::assertStringContainsString('deviceNo', $result->getPayload()->toJson()); + } +} diff --git a/tests/Plugin/Jsb/ResponsePluginTest.php b/tests/Plugin/Jsb/ResponsePluginTest.php new file mode 100644 index 000000000..a2bf2f16c --- /dev/null +++ b/tests/Plugin/Jsb/ResponsePluginTest.php @@ -0,0 +1,62 @@ +plugin = new ResponsePlugin(); + } + + public function testNormal() + { + $body = 'errCode=&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)) + ->setDestination(new Collection(Arr::wrapQuery($body))); + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + public function testCodeWrong() + { + self::expectException(InvalidResponseException::class); + self::expectExceptionCode(Exception::RESPONSE_BUSINESS_CODE_WRONG); + $body = 'errCode=1&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000001&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)) + ->setDestination(new Collection(Arr::wrapQuery($body))); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + public function testHttpWrong() + { + self::expectException(InvalidResponseException::class); + self::expectExceptionCode(Exception::RESPONSE_CODE_WRONG); + self::expectExceptionMessage('江苏银行返回状态码异常,请检查参数是否错误'); + $body = 'errCode=1&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(500, [], $body)) + ->setDestination(new Collection(Arr::wrapQuery($body))); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } +} diff --git a/tests/Plugin/Jsb/StartPluginTest.php b/tests/Plugin/Jsb/StartPluginTest.php new file mode 100644 index 000000000..6edb69e32 --- /dev/null +++ b/tests/Plugin/Jsb/StartPluginTest.php @@ -0,0 +1,35 @@ +plugin = new StartPlugin(); + } + + public function testNormal() + { + $rocket = new Rocket(); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $payload = $result->getPayload(); + self::assertEquals('6a13eab71c4f4b0aa4757eda6fc59710', $payload->get('partnerId')); + self::assertEquals('v1.0.0', $payload->get('version')); + self::assertEquals('utf-8', $payload->get('charset')); + self::assertEquals(date('Ymd'), $payload->get('createData')); + self::assertEquals(date('His'), $payload->get('createTime')); + self::assertEquals(date('Ymd'), $payload->get('bizDate')); + self::assertEquals(QueryPacker::class, $result->getPacker()); + } +} diff --git a/tests/Plugin/Jsb/VerifySignaturePluginTest.php b/tests/Plugin/Jsb/VerifySignaturePluginTest.php new file mode 100644 index 000000000..8fcc89c57 --- /dev/null +++ b/tests/Plugin/Jsb/VerifySignaturePluginTest.php @@ -0,0 +1,86 @@ +plugin = new VerifySignaturePlugin(); + } + + public function testSignNormal() + { + $body = 'errCode=&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + public function testMulconnectorSign() + { + $body = 'partnerId=6a13eab71c4f4b0aa4757eda6fc59710&field1=&field2=&field3=&respBizDate=20240618&respCode=000000&respMsg=交易成功&totalRows=17&pageSize=20¤tPage=1&totalPages=1&hasNext=0&hasPrevious=0&signType=RSA&sign=alEzu2R3ZquH9ff0bI4b9Cl4MDDnEM3vPtMGwfwAVYJuYuecCFCf36glEiHu+KHxG/kzyRnDpakmeSPoGfs2GLsSxzxU6p3pNdBesbkZvU8j2WVUdwq1DZ6Z6SDqS1ZEMiRBGymePOGbBows+/DY8RrTplx4j0TvKqCUrfJun4o=&-&transDate=20240617&transTime=20240617151229&orderNo=20240617151229401202589&outTradeNo=RK-YC202406170004&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240617&transTime=20240617150911&orderNo=20240617150911400947909&outTradeNo=YC202406170004&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=元仓充值&extfld3=20240617150917&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613164332&orderNo=20240613164332401166829&outTradeNo=RC240613164222066849&amount=0.03&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=YC002充值&extfld3=20240613164337&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613164134&orderNo=20240613164134400800219&outTradeNo=RC240613164110030316&amount=0.02&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=YC002充值&extfld3=20240613164139&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613161540&orderNo=20240613161540418461859&outTradeNo=RC240613161530097918&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=YC002充值&extfld3=20240613161551&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613153839&orderNo=20240613153839416172119&outTradeNo=RC240613153823029572&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=YC002充值&extfld3=20240613153847&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613145133&orderNo=20240613145133412731519&outTradeNo=YC202406132022&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=test充值&extfld3=20240613145138&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613144454&orderNo=20240613144454412182359&outTradeNo=TK-YC202406132021&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613144333&orderNo=20240613144333411961589&outTradeNo=YC202406132021&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=test充值&extfld3=20240613144349&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613144016&orderNo=20240613144016411383399&outTradeNo=TK-RC240613142104066624&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613144004&orderNo=20240613144004411355039&outTradeNo=TK-RC240613142846088651&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613143945&orderNo=20240613143945411253709&outTradeNo=TK-RC240613143227055276&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613143317&orderNo=20240613143317410648749&outTradeNo=RC240613143227055276&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=YC002充值&extfld3=20240613143338&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613142859&orderNo=20240613142859410207589&outTradeNo=RC240613142846088651&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=YC002充值&extfld3=20240613142905&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613142143&orderNo=20240613142143409559519&outTradeNo=RC240613142104066624&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=YC002充值&extfld3=20240613142149&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613105059&orderNo=20240613105059400587329&outTradeNo=RT-YC202406130102&amount=0.01&orderStatus=1&orderType=2&checkStatus=&tradeType=2&extfld1=0.0|0.0&extfld2=&extfld3=&deviceNo=1234567890&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8&-&transDate=20240613&transTime=20240613101235&orderNo=20240613101235476739849&outTradeNo=YC202406130102&amount=0.01&orderStatus=1&orderType=1&checkStatus=&tradeType=2&extfld1=0.0|0.01&extfld2=元仓充值&extfld3=20240613101243&deviceNo=&operatorId=&payId=oUpF8uLdyMJiT0t792_LFbuv1Lz8'; + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + + public function testEmptySign() + { + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_EMPTY); + + $body = 'errCode=1&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2'; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + public function testSignWrong() + { + self::expectException(InvalidSignException::class); + self::expectExceptionCode(Exception::SIGN_ERROR); + $body = 'errCode=1&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + + public function testMissingEpayPublicCertPath() + { + $body = 'errCode=1&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + + $rocket = (new Rocket())->setDestinationOrigin(new Response(200, [], $body)); + Pay::set(ConfigInterface::class, new Config()); + self::expectException(InvalidConfigException::class); + self::expectExceptionCode(Exception::CONFIG_JSB_INVALID); + self::expectExceptionMessage('配置异常: 缺少配置参数 -- [jsb_public_cert_path]'); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertSame($rocket, $result); + } + +} diff --git a/tests/Provider/JsbTest.php b/tests/Provider/JsbTest.php new file mode 100644 index 000000000..40d495828 --- /dev/null +++ b/tests/Provider/JsbTest.php @@ -0,0 +1,213 @@ +foo(); + } + + public function testMergeCommonPlugins() + { + Pay::config([]); + $plugins = [FooPluginStub::class]; + + self::assertEquals(array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ), Pay::jsb()->mergeCommonPlugins($plugins)); + } + + public function testScan() + { + $response = 'errCode=&field1=&field2=&field3=&orderNo=20240617144526400259379&orderStatus=1&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&payUrl=http://weixintest.jsbchina.cn/epcs/qr/login.htm?qrCode=2018060611052793473720240617144526688568&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&validTime=2&signType=RSA&sign=jN3Ha6J9UUIe9M0L/XeexEdaRL9GB6nMV12wNC7LQvTS6V4nKHj4Qzw6M8cNsA9L0Tb3QFT83B0qO3FJnruDrcHKqBLZb4FkoKKN/WiDBuA2UZQjG4+CBejoGJWfpkWSsei9tXUk36TB27lc2ZlYXSEwuuDwM7M9yvlYysc3fjg='; + //// + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn(new Response(200, [], $response)); + Pay::set(HttpClientInterface::class, $http); + + $result = Pay::jsb()->scan([ + 'outTradeNo' => 'YC202406170003', + 'totalFee' => 0.01, + 'proInfo' => '充值', + ]); + self::assertArrayHasKey('payUrl', $result->all()); + } + + public function testQuery() + { + $response = 'deviceNo=1234567890&errCode=&errMsg=&field1=2&field2=&field3=&orderNo=20240617144526400259379&orderStatus=2&outTradeNo=YC202406170003&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&respBizDate=20240617&respCode=000000&respMsg=交易成功&totalFee=0.01&signType=RSA&sign=fRMelfVITd0+aV+I5MT9SyTwRjWB+vOVyES9s3l+eKFV9bXwtQLpaORFpr1emepm2mZjCAgK9AORaYYn9vhk+0x+b2jk2QiyQ3aXYrrYx0+foK/OqN9dcJjSTIIpUUitGYk/6CJwe0OFPsDWgqDiLb9A298VFXg++czErz0stcM='; + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn(new Response(200, [], $response)); + Pay::set(HttpClientInterface::class, $http); + + $outTradeNo = 'YC202406170003'; + $result = Pay::jsb()->query([ + 'outTradeNo' => $outTradeNo, + ]); + + self::assertArrayHasKey('orderNo', $result->all()); + self::assertArrayHasKey('orderStatus', $result->all()); + self::assertEquals($outTradeNo, $result->get('outTradeNo')); + } + + public function testRefundErrorRefundAmt() + { + //单元测试检测异常是否正确 + $this->expectException(InvalidResponseException::class); + $response = 'field1=&field2=&field3=&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&respBizDate=20240617&respCode=027111&respMsg=退款金额错误&signType=RSA&sign=hzreuJgx+iYz71W8HKyQEH4+XqE7c2Ad6NLa8lSJcVEmjc2nZPZl2s+mBVmZX3PYSlysqq5rlXysGMzQxf4CWkuoUK9wGsCDUIssCBOPcRjdsC2/uFaLavs/jagkKE/tLt45D6h4kibgaHIZabN5NgUkP0p0TAFHISsPPdqjKLY='; + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn(new Response(200, [], $response)); + Pay::set(HttpClientInterface::class, $http); + $outTradeNo = 'YC202406170003'; + Pay::jsb()->refund([ + 'outTradeNo' => $outTradeNo, + 'refundAmt' => 0.02, + ]); + } + + public function testRefund() + { + //单元测试检测异常是否正确 + $response = 'fee=0&field1=2&field2=&field3=&orderStatus=3&outRefundNo=RK-YC202406170004&partnerId=6a13eab71c4f4b0aa4757eda6fc59710&refundAmt=0.01&refundNo=20240617151229401202589&respBizDate=20240617&respCode=000000&respMsg=交易成功&signType=RSA&sign=VJi8vD3ZkcPZXkgkJ3RX9oREKxeNoUAi9+SZoiBHNlNc87QN0NRngmLthHbzJUV6Fz8hQX5jumZQnhTpEqlTgEZsHRCIm7ZsqhingBNVKItq/sAqzlpIeogU/jVE4zueqInYIMbVOj6+3AQyZ1+Tblz6d0JrGals3exmBUt/03U='; + $http = Mockery::mock(Client::class); + $http->shouldReceive('sendRequest')->andReturn(new Response(200, [], $response)); + Pay::set(HttpClientInterface::class, $http); + + $outRefundNo = 'RK-YC202406170004'; + $result = Pay::jsb()->refund([ + 'outTradeNo' => 'YC202406170004', + 'refundAmt' => 0.01, + 'outRefundNo' => $outRefundNo, + ]); + + self::assertArrayHasKey('orderStatus', $result->all()); + self::assertEquals($outRefundNo, $result->get('outRefundNo')); + } + + + public function testCancel() + { + $this->expectException(InvalidParamsException::class); + + Pay::jsb()->cancel([ + 'outTradeNo' => 'YC202406170003', + ]); + } + + public function testClose() + { + $this->expectException(InvalidParamsException::class); + + Pay::jsb()->close([ + 'outTradeNo' => 'YC202406170003', + ]); + } + + public function testCallback() + { + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $result = Pay::jsb()->callback($payload); + self::assertNotEmpty($result->all()); + self::assertArrayHasKey('outTradeNo', $result->all()); + self::assertArrayHasKey('orderNo', $result->all()); + self::assertArrayHasKey('orderStatus', $result->all()); + self::assertArrayHasKey('field3', $result->all()); + + } + + public function testCallbackByFromGlobals() + { + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $_POST = array_merge($_POST, $payload); + $result = Pay::jsb()->callback(); + self::assertNotEmpty($result->all()); + self::assertArrayHasKey('outTradeNo', $result->all()); + self::assertArrayHasKey('orderNo', $result->all()); + self::assertArrayHasKey('orderStatus', $result->all()); + self::assertArrayHasKey('field3', $result->all()); + + } + + public function testCallbackByServerRequest() + { + $payload = [ + 'partnerId'=> '6a13eab71c4f4b0aa4757eda6fc59710', + 'orderStatus'=> '1', + 'totalFee'=> '0.02', + 'outTradeNo'=> 'RC240613164110030316', + 'orderNo'=> '20240613164114400729509', + 'field1'=> '2', + 'field2'=> '', + 'field3'=> '20240613164139|20240613164134400800219', + 'signType'=> 'RSA', + 'sign'=> 'DPKX4mZAVd/LwMDOt1OJgryBuPeH78y7B78smze+m+vvzae5MBf0O3BoTvVJQHD/RPVftHVvnYHeKvIjCC2bCrxoY9Sv2N8Hbr5HfjIikk0a2qaIQp6TTvecMP9JitzSuZP+sih+uxMkRM5Nrg8weGbePaQ6nODNWiSGDhV+Jq0=' + ]; + $serverRequest = new ServerRequest('POST', 'http://localhost', [], null); + $serverRequest = $serverRequest->withParsedBody($payload); + $result = Pay::jsb()->callback($serverRequest); + self::assertNotEmpty($result->all()); + self::assertArrayHasKey('outTradeNo', $result->all()); + self::assertArrayHasKey('orderNo', $result->all()); + self::assertArrayHasKey('orderStatus', $result->all()); + self::assertArrayHasKey('field3', $result->all()); + + } + + public function testSuccess() + { + $result = Pay::jsb()->success(); + + self::assertInstanceOf(ResponseInterface::class, $result); + self::assertStringContainsString('success', (string) $result->getBody()); + } +} diff --git a/tests/Shortcut/Jsb/QueryShortcutTest.php b/tests/Shortcut/Jsb/QueryShortcutTest.php new file mode 100644 index 000000000..7c00dd7cc --- /dev/null +++ b/tests/Shortcut/Jsb/QueryShortcutTest.php @@ -0,0 +1,39 @@ +plugin = new QueryShortcut(); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + QueryPlugin::class, + AddPayloadSignPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } +} diff --git a/tests/Shortcut/Jsb/RefundShortcutTest.php b/tests/Shortcut/Jsb/RefundShortcutTest.php new file mode 100644 index 000000000..ad42e9eda --- /dev/null +++ b/tests/Shortcut/Jsb/RefundShortcutTest.php @@ -0,0 +1,38 @@ +plugin = new RefundShortcut(); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + RefundPlugin::class, + AddPayloadSignPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } +} diff --git a/tests/Shortcut/Jsb/ScanShortcutTest.php b/tests/Shortcut/Jsb/ScanShortcutTest.php new file mode 100644 index 000000000..f00d732dc --- /dev/null +++ b/tests/Shortcut/Jsb/ScanShortcutTest.php @@ -0,0 +1,38 @@ +plugin = new ScanShortcut(); + } + + public function testDefault() + { + self::assertEquals([ + StartPlugin::class, + PayPlugin::class, + AddPayloadSignPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ], $this->plugin->getPlugins([])); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index cd9be3c15..147e4bd35 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,11 +1,18 @@ '9021000122682882', 'app_secret_cert' => 'MIIEpAIBAAKCAQEApSA9oxvcqfbgpgkxXvyCpnxaR6TPaEMh/ij+PhF8180zL82ic4whkrRlcu1Y179AKEZNar71Ugi37fKcXWLerjPOeb8WHnZgNG19gkAcOIqZPRPpJ1eRtwKEclIzt+j3H/wgXWkD7BTr61RjuAcviyvDVbAJ/TPlMqXdJFIuJwZblN2WblIv+4Dm1iPOB+fVCU3rsgg4eajf3HrZ7sq6fBhQhO5krDmIIYGsFZ+fohEgnLkBaF0gqNUb5Yb4PBfaEcu8Hcwq+XyBSMOVOIABRPQVDedW2sE/2NsLkR62DaEe/Ri9VUDJe0pE39P+X22DicJ3E3yrxvdioMnLtDqEuwIDAQABAoIBAQCSHZ1tH9J7c8IGKkxNyROzToZ0rxn5IK6LwKp5MfBO5X1N56DArldnAcpjkDL1dn7HJK6Mrr1WAfD/1ZcX680wSReEE9r2ybkHq3tMLn7KaZp/uYavEYYXc1rP7n1lV/iVjPz2q16VIU5Bx0MWLQWdGPSYdlXggHNoBe1RnobIcCGOVe9HlzCBtWzGpCZvMlqRbCuWAdp14aCkaJqpRxG4PY9Kd/NzELvhnCd9k8e7G2qcwx6gAoXN8OXO8jmZg/6fOvFnrGl6CBp8sioe5F3R023fDum546IqS8EZdCl5T0gW/boTbSV8luitab65xBO3PmUI+V2OEFCL6WcJxawBAoGBAOZoft6/LatdoXzr8vh+rKzacUHw246fpacbgx0B5DDymM7hbhXbY/NoCWPgBJtV3XI3DtMJ5yvlEVDQvPfbSHRPx2XQknwrM7ly2SLbaC+tuhcvoG6F1RLWFx+y/583seSlVNuWC9KdpLTKzo8wl8Z4/kheLTBxTxL20NZu79XBAoGBALd3fNoXk5V+T16hnSinPtt2NEsZpn+4w07DikzcpdyjCL5PYjp/BppmX3xly96fCZh3MO3Vkuya1xgauMzxVKQlR/aD5yVmsqK7wxNTY1ZQM74B44/4Mks/8MG2r7o3DElA4/qIeMP4CwkWmYcuij7npm2bgIqFzS+4aGZfDRF7AoGAKMO2Jpy2bMo9BwgLzdFDpbVkMmF1xu8R9NXWRayO/eX+CSQzQOS281qlxqjcx8rSSiHZmpb28notrRmxRTzjvchbo/TZ5eQS262pIxSkg0L+WJnRjZxaDWIZZz9ZIIdPDv/9WnhakSHZAS+cihLz12aSvqUC4744WkeWvUmVX0ECgYAGLDoCKHrps7c96tgbzwy5W4/E2xcUAwZnNwMHNQFLnBymMouOhkmVlk4uJEqosdcjzxbRWbc4yLjl8bg4BQKhBzQVojh7tKnb+c9Fbi/QbqBfCzc519LxXzRdgCUHceSy7kD9Y+wUQ9szMhR2TOWP2kFqPKolfvz5Vw4EK7yH0wKBgQDerq9Pthbii7lNt528/q0cH9vOMn9z76o6jMMea9EibclVHtdcQBWLOn8Yw97k+WSXYGuUrQUWWQbyabZqWkkS4cEjJf5/DiwOuYdNVXg7FK56ucTczBA7lR4dnunPW6U1HbSWf0Cn4Y/cl/z7B5QBSQt0W38IYHSaf6/sqsV6SA==', 'app_auth_token' => '', - 'app_public_cert_path' => __DIR__ . '/Cert/alipayAppPublicCert.crt', - 'alipay_public_cert_path' => __DIR__ . '/Cert/alipayPublicCert.crt', - 'alipay_root_cert_path' => __DIR__ . '/Cert/alipayRootCert.crt', + 'app_public_cert_path' => __DIR__.'/Cert/alipayAppPublicCert.crt', + 'alipay_public_cert_path' => __DIR__.'/Cert/alipayPublicCert.crt', + 'alipay_root_cert_path' => __DIR__.'/Cert/alipayRootCert.crt', 'notify_url' => 'https://pay.yansongda.cn', 'return_url' => 'https://pay.yansongda.cn', ], @@ -144,7 +151,26 @@ protected function setUp(): void 'mch_secret_key' => '979da4cfccbae7923641daa5dd7047c2', 'mode' => Pay::MODE_SANDBOX, ], - ] + ], + 'jsb' => [ + 'default' => [ + // 服务代码 + 'svr_code' => '', + // 必填-合作商ID + 'partner_id' => '6a13eab71c4f4b0aa4757eda6fc59710', + // 必填-公私钥对编号 + 'public_key_code' => '00', + // 必填-商户私钥(加密签名) + 'mch_secret_cert_path' => __DIR__.'/Cert/EpayKey.pem', + // 必填-商户公钥证书路径(提供江苏银行进行验证签名用) + 'mch_public_cert_path' => __DIR__.'/Cert/EpayCert.cer', + // 必填-江苏银行的公钥(用于解密江苏银行返回的数据) + 'jsb_public_cert_path' => __DIR__.'/Cert/jschina.cer', + 'notify_url' => 'http://127.0.0.1:8000/epay/return', + // 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:test环境, + 'mode' => Pay::MODE_SANDBOX, + ], + ], ]; // hyperf 单测时,未在 hyperf 框架内,所以 sdk 没有 container, 手动设置一个 diff --git a/web/.vitepress/sidebar/v3.js b/web/.vitepress/sidebar/v3.js index 72f0dd991..faefb64fe 100644 --- a/web/.vitepress/sidebar/v3.js +++ b/web/.vitepress/sidebar/v3.js @@ -20,6 +20,7 @@ export default [ { text: '支付宝', link: '/docs/v3/quick-start/alipay' }, { text: '微信', link: '/docs/v3/quick-start/wechat' }, { text: '银联', link: '/docs/v3/quick-start/unipay' }, + { text: '江苏银行', link: '/docs/v3/quick-start/jsb' }, { text: '返回格式', link: '/docs/v3/quick-start/return-format' } ] }, @@ -65,6 +66,18 @@ export default [ { text: '所有内置插件', link: '/docs/v3/unipay/all' } ] }, + { + text: '江苏银行', + collapsed: true, + items: [ + { text: '支付', link: '/docs/v3/jsb/pay' }, + { text: '查询', link: '/docs/v3/jsb/query' }, + { text: '退款', link: '/docs/v3/jsb/refund' }, + { text: '接收回调', link: '/docs/v3/jsb/callback' }, + { text: '确认回调', link: '/docs/v3/jsb/response' }, + { text: '所有内置插件', link: '/docs/v3/jsb/all' } + ] + }, { text: '核心架构', collapsed: false, diff --git a/web/docs/v3/jsb/all.md b/web/docs/v3/jsb/all.md new file mode 100644 index 000000000..5e3481a7a --- /dev/null +++ b/web/docs/v3/jsb/all.md @@ -0,0 +1,39 @@ +# 江苏银行e融支付更多方便的插件 + +得益于 yansongda/pay 的基础架构和良好的插件机制, +您可以自有的使用任何内置插件和自定义插件调用支付宝的任何 API。 + +诸如签名、API调用、解密、验签、解包等基础插件已经内置在 Pay 中, +您可以使用 `Pay::jsb()->mergeCommonPlugins(array $plugins)` 来获取调用 API 所必须的常用插件 + +首先,查找你想使用的插件,然后 + +```php +Pay::config($config); + +$params = [ + 'outTradeNo' => '1514027114', +]; + +$allPlugins = Pay::epay()->mergeCommonPlugins([QueryPlugin::class]); + +$result = Pay::epay()->pay($allPlugins, $params); +``` + +关于插件的详细介绍,如果您感兴趣,可以参考 [yansongda/artful](https://artful.yansongda.cn/) + +## 支付 + +### 扫码支付(聚合支付, 支持支付宝、微信、银联、江苏银行e融支付) + +- 交易预创建 + + `\Yansongda\Pay\Plugin\Jsb\Pay\Scan\PayPlugin` + +- 交易退款接口 + + `\Yansongda\Pay\Plugin\Jsb\Pay\Scan\RefundPlugin` + +- 交易/退款结果查询 + + `\Yansongda\Pay\Plugin\Jsb\Pay\Scan\QueryPlugin` diff --git a/web/docs/v3/jsb/callback.md b/web/docs/v3/jsb/callback.md new file mode 100644 index 000000000..ca9092454 --- /dev/null +++ b/web/docs/v3/jsb/callback.md @@ -0,0 +1,40 @@ +# 接收江苏银行e融支付回调 + +| 方法名 | 参数 | 返回值 | +|:--------:|:------------------------------:|:----------:| +| callback | 无/array/ServerRequestInterface | Collection | + +## 例子 + +```php +Pay::config($this->config); + +// 是的,你没有看错,就是这么简单! +$result = Pay::jsb()->callback(); +``` + +## 参数 + +### 第一个参数 + +#### `null` + +如果您没有传参,或传 `null` 则 `yansongda/pay` 会自动识别支付宝的回调请求并处理,通过 `Collection` 实例返回支付宝的处理参数 + +:::warning +建议仅在 php-fpm 下使用,swoole 方式请使用 `ServerRequestInterface` 参数传递方式 +::: + +#### `ServerRequestInterface` + +推荐在 swoole 环境下传递此参数,传递此参数后, yansongda/pay 会自动进行后续处理 + +#### `array` + +也可以自行解析请求参数,传递一个 array 会自动进行后续处理 + +### 第二个参数 + +第二个参数主要是传递相关自定义变量的,类似于 `web()` 中的 `_config` / `_method` 等参数。 + +例如,如果你想在回调的时候使用非默认配置,则可以 `Pay::alipay()->callback(null, ['_config' => 'yansongda'])` 切换为 `yansongda` 这个租户的配置信息。 diff --git a/web/docs/v3/jsb/pay.md b/web/docs/v3/jsb/pay.md new file mode 100644 index 000000000..a2df55b8b --- /dev/null +++ b/web/docs/v3/jsb/pay.md @@ -0,0 +1,31 @@ +# 江苏银行e融支付 + +江苏银行e融支付目前直接内置支持以下快捷方式支付方法,对应的支付 method 如下: + +| method | 说明 | 参数 | 返回值 | +|:--------:|:------:|:------------:|:----------:| +| scan | 扫码支付 | array $order | Collection | + +更多接口调用请参考后续文档 + +## 网页支付 + +### 例子 + +```php +Pay::config($this->config); + +$result = Pay::jsb()->scan([ + 'outTradeNo' => 'YC202406170003', + 'totalFee' => 0.01, + 'proInfo' => '充值' +]); + +//可通过$result->payUrl参数生成二维码支付链接 +``` + +### 订单配置参数 + +**所有订单配置中,客观参数均不用配置,扩展包已经为大家自动处理了** + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考支付文档 diff --git a/web/docs/v3/jsb/query.md b/web/docs/v3/jsb/query.md new file mode 100644 index 000000000..06607ba70 --- /dev/null +++ b/web/docs/v3/jsb/query.md @@ -0,0 +1,34 @@ +# 江苏银行e融支付查询订单(退款订单 or 交易支付订单) + +| 方法名 | 参数 | 返回值 | +|:-----:|:------------:|:----------:| +| query | array $order | Collection | + +## 查询交易支付订单 + +```php +Pay::config($this->config); + +//查询交易支付订单 +$order = [ + 'outTradeNo' => '1514027114', +]; +$result = Pay::jsb()->query($order); +``` + +## 查询退款订单 + +```php +Pay::config($this->config); + +//查询退款单号查询退款订单 +$order = [ + 'outTradeNo' => 'RK-1514027114', +]; + +$result = Pay::jsb()->query($order); +``` + +### 配置参数 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考文档。 diff --git a/web/docs/v3/jsb/refund.md b/web/docs/v3/jsb/refund.md new file mode 100644 index 000000000..3db2d17d9 --- /dev/null +++ b/web/docs/v3/jsb/refund.md @@ -0,0 +1,25 @@ +# 江苏银行e融支付退款 + +| 方法名 | 参数 | 返回值 | +|:------:|:------------:|:----------:| +| refund | array $order | Collection | + +:::warning 注意 +更多功能请查看源码,文档里仅仅列举了一些常用的功能。 +::: + +## 普通退款操作 + +```php +Pay::config($this->config); + +$result = Pay::jsb()->refund([ + 'outTradeNo' => 'YC202406170004', + 'refundAmt' => 0.01, + 'outRefundNo' => 'TK-YC202406170004', +]); +``` + +### 配置参数 + +所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考支付文档。 diff --git a/web/docs/v3/jsb/response.md b/web/docs/v3/jsb/response.md new file mode 100644 index 000000000..82f17f649 --- /dev/null +++ b/web/docs/v3/jsb/response.md @@ -0,0 +1,17 @@ +# 江苏银行e融支付确认回调 + +| 方法名 | 参数 | 返回值 | +|:-------:|:---:|:--------:| +| success | 无 | Response | + +## 例子 + +```php +Pay::config($config); + +return Pay::jsb()->success(); +``` + +## 配置参数 + +无 diff --git a/web/docs/v3/overview/contribute.md b/web/docs/v3/overview/contribute.md index 042742c86..adbb1ea29 100644 --- a/web/docs/v3/overview/contribute.md +++ b/web/docs/v3/overview/contribute.md @@ -1,6 +1,6 @@ # 参与开发 -由于测试及使用环境的限制,本项目中只开发了「支付宝」和「微信支付」的相关支付网关。 +由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「银联」、「江苏银行」的相关支付网关。 如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** diff --git a/web/docs/v3/quick-start/init.md b/web/docs/v3/quick-start/init.md index 6381290a0..239035f8b 100644 --- a/web/docs/v3/quick-start/init.md +++ b/web/docs/v3/quick-start/init.md @@ -96,6 +96,26 @@ $config = [ 'mode' => Pay::MODE_NORMAL, ], ], + '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' => [ 'enable' => false, 'file' => './logs/pay.log', diff --git a/web/docs/v3/quick-start/jsb.md b/web/docs/v3/quick-start/jsb.md new file mode 100644 index 000000000..661e8af3b --- /dev/null +++ b/web/docs/v3/quick-start/jsb.md @@ -0,0 +1,58 @@ +# 江苏银行e融支付快速入门 + +在初始化完毕后,就可以直接方便的享受 `yansongda/pay` 带来的便利了。 + +## 扫码支付 + +```php +Pay::config($this->config); + +$result = Pay::jsb()->scan([ + 'outTradeNo' => 'YC202406170003', + 'totalFee' => 0.01, + 'proInfo' => '充值' +]); + +return $result->payUrl; // 二维码 url +``` + +## 退款 + +```php +Pay::config($this->config); + +$result = Pay::jsb()->refund([ + 'outTradeNo' => 'YC202406170004', + 'refundAmt' => 0.01, + 'outRefundNo' => 'TK-YC202406170004', +]); +``` + +## 查询订单 + +```php +Pay::config($this->config); + +//查询交易支付订单 +$order = [ + 'outTradeNo' => '1514027114', +]; + +$result = Pay::jsb()->query($order); +``` + +## 回调处理 + +```php +Pay::config($this->config); + +$result = Pay::jsb()->callback(); +``` + +## 响应回调 + +```php +Pay::config($this->config); + +return Pay::jsb()->success(); +```