From cca902391afc9a3b7bb7d6195864e2a300b65625 Mon Sep 17 00:00:00 2001 From: dang_ya_ming Date: Mon, 26 Dec 2022 11:51:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=94=AF=E4=BB=98=E5=92=8C?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Kernel/Traits/HasAttributes.php | 96 +++++++++ src/Kernel/Traits/InteractWithHandlers.php | 215 +++++++++++++++++++++ src/MiniProgram/Application.php | 8 +- src/MiniProgram/Order/Client.php | 36 ++++ src/MiniProgram/Order/ServiceProvider.php | 21 ++ src/MiniProgram/Pay/Client.php | 109 +++++++++++ src/MiniProgram/Pay/Message.php | 30 +++ src/MiniProgram/Pay/OrderInfo.php | 15 ++ src/MiniProgram/Pay/Server.php | 121 ++++++++++++ src/MiniProgram/Pay/ServiceProvider.php | 24 +++ 10 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 src/Kernel/Traits/HasAttributes.php create mode 100644 src/Kernel/Traits/InteractWithHandlers.php create mode 100644 src/MiniProgram/Order/Client.php create mode 100644 src/MiniProgram/Order/ServiceProvider.php create mode 100644 src/MiniProgram/Pay/Client.php create mode 100644 src/MiniProgram/Pay/Message.php create mode 100644 src/MiniProgram/Pay/OrderInfo.php create mode 100644 src/MiniProgram/Pay/Server.php create mode 100644 src/MiniProgram/Pay/ServiceProvider.php diff --git a/src/Kernel/Traits/HasAttributes.php b/src/Kernel/Traits/HasAttributes.php new file mode 100644 index 0000000..1f2acd6 --- /dev/null +++ b/src/Kernel/Traits/HasAttributes.php @@ -0,0 +1,96 @@ + + */ + protected array $attributes = []; + + /** + * @param array $attributes + */ + public function __construct(array $attributes) + { + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->attributes; + } + + public function toJson(): string|false + { + return json_encode($this->attributes); + } + + public function has(string $key): bool + { + return array_key_exists($key, $this->attributes); + } + + /** + * @param array $attributes + */ + public function merge(array $attributes): self + { + $this->attributes = array_merge($this->attributes, $attributes); + + return $this; + } + + /** + * @return array $attributes + */ + public function jsonSerialize(): array + { + return $this->attributes; + } + + public function __set(string $attribute, $value): void + { + $this->attributes[$attribute] = $value; + } + + public function __get(string $attribute) + { + return $this->attributes[$attribute] ?? null; + } + + public function offsetExists($offset): bool + { + /** @phpstan-ignore-next-line */ + return array_key_exists($offset, $this->attributes); + } + + public function offsetGet($offset) + { + return $this->attributes[$offset]; + } + + public function offsetSet($offset, $value): void + { + if (null === $offset) { + $this->attributes[] = $value; + } else { + $this->attributes[$offset] = $value; + } + } + + public function offsetUnset($offset): void + { + unset($this->attributes[$offset]); + } +} diff --git a/src/Kernel/Traits/InteractWithHandlers.php b/src/Kernel/Traits/InteractWithHandlers.php new file mode 100644 index 0000000..26dccfc --- /dev/null +++ b/src/Kernel/Traits/InteractWithHandlers.php @@ -0,0 +1,215 @@ + + */ + protected array $handlers = []; + + /** + * @return array + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function with(callable|string $handler): static + { + return $this->withHandler($handler); + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function withHandler(callable|string $handler): static + { + $this->handlers[] = $this->createHandlerItem($handler); + + return $this; + } + + /** + * @param callable|string $handler + * @return array{hash: string, handler: callable} + * + * @throws InvalidArgumentException + */ + public function createHandlerItem(callable|string $handler): array + { + return [ + 'hash' => $this->getHandlerHash($handler), + 'handler' => $this->makeClosure($handler), + ]; + } + + /** + * @param callable|string $handler + * @return string + * @throws InvalidArgumentException + */ + protected function getHandlerHash(callable|string $handler): string + { + switch (true) { + case is_string($handler): + return $handler; + case is_array($handler): + return is_string($handler[0]) ? $handler[0].'::'.$handler[1] : get_class($handler[0]).$handler[1]; + case $handler instanceof Closure: + return spl_object_hash($handler); + default: + throw new InvalidArgumentException('Invalid handler: '.gettype($handler)); + } + } + + /** + * @param callable|string $handler + * @return callable + * @throws InvalidArgumentException + */ + protected function makeClosure(callable|string $handler): callable + { + if (is_callable($handler)) { + return $handler; + } + + if (class_exists($handler) && method_exists($handler, '__invoke')) { + /** + * @psalm-suppress InvalidFunctionCall + * @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/5867 + */ + return fn () => (new $handler())(...func_get_args()); + } + + throw new InvalidArgumentException(sprintf('Invalid handler: %s.', $handler)); + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function prepend(callable|string $handler): static + { + return $this->prependHandler($handler); + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function prependHandler(callable|string $handler): static + { + array_unshift($this->handlers, $this->createHandlerItem($handler)); + + return $this; + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function without(callable|string $handler): static + { + return $this->withoutHandler($handler); + } + + /** + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function withoutHandler(callable|string $handler): static + { + $index = $this->indexOf($handler); + + if ($index > -1) { + unset($this->handlers[$index]); + } + + return $this; + } + + /** + * @param callable|string $handler + * @return int + * @throws InvalidArgumentException + */ + public function indexOf(callable|string $handler): int + { + foreach ($this->handlers as $index => $item) { + if ($item['hash'] === $this->getHandlerHash($handler)) { + return $index; + } + } + + return -1; + } + + /** + * @param $value + * @param callable|string $handler + * @return InteractWithHandlers + * @throws InvalidArgumentException + */ + public function when($value, callable|string $handler): static + { + if (is_callable($value)) { + $value = call_user_func($value, $this); + } + + if ($value) { + return $this->withHandler($handler); + } + + return $this; + } + + public function handle($result, $payload = null) + { + $next = $result = is_callable($result) ? $result : fn ($p) => $result; + + foreach (array_reverse($this->handlers) as $item) { + $next = fn ($p) => $item['handler']($p, $next) ?? $result($p); + } + + return $next($payload); + } + + /** + * @param callable|string $handler + * @return bool + * @throws InvalidArgumentException + */ + public function has(callable|string $handler): bool + { + return $this->indexOf($handler) > -1; + } +} diff --git a/src/MiniProgram/Application.php b/src/MiniProgram/Application.php index abc22c6..d6b3acf 100644 --- a/src/MiniProgram/Application.php +++ b/src/MiniProgram/Application.php @@ -4,6 +4,7 @@ namespace EasyTiktok\MiniProgram; use EasyTiktok\Kernel\ServiceContainer; use EasyTiktok\Kernel\Traits\ResponseCastable; +use EasyTiktok\MiniProgram\Pay\Server as PayServer; /** * Class Application. @@ -12,6 +13,9 @@ use EasyTiktok\Kernel\Traits\ResponseCastable; * @property Auth\Client $auth * @property QrCode\Client $qr_code * @property Server\Encryptor $encryptor + * @property Pay\Client $pay + * @property PayServer $pay_server + * @property Order\Client $order * @author zhaoxiang */ class Application extends ServiceContainer { @@ -25,7 +29,9 @@ class Application extends ServiceContainer { Base\ServiceProvider::class, Auth\ServiceProvider::class, QrCode\ServiceProvider::class, - Server\ServiceProvider::class + Server\ServiceProvider::class, + Pay\ServiceProvider::class, + Order\ServiceProvider::class, ]; /** diff --git a/src/MiniProgram/Order/Client.php b/src/MiniProgram/Order/Client.php new file mode 100644 index 0000000..6528fcf --- /dev/null +++ b/src/MiniProgram/Order/Client.php @@ -0,0 +1,36 @@ +httpPostJson('apps/order/v2/push', $params); + } +} diff --git a/src/MiniProgram/Order/ServiceProvider.php b/src/MiniProgram/Order/ServiceProvider.php new file mode 100644 index 0000000..b755db7 --- /dev/null +++ b/src/MiniProgram/Order/ServiceProvider.php @@ -0,0 +1,21 @@ +app['config']['app_id']; + $params = compact('app_id', 'out_order_no', 'total_amount', 'subject', 'body', 'notify_url', 'valid_time', 'cp_extra'); + $params['sign'] = $this->sign($params); + return $this->httpPostJson('apps/ecpay/v1/create_order', $params); + } + + /** + * 发起退款 + * @param string $out_order_no + * @param string $out_refund_no + * @param int $refund_amount + * @param string $reason + * @param string $notify_url + * @param string $msg_page + * @param string $cp_extra + * @return array + * @throws GuzzleException + * @throws HttpException + * @throws InvalidConfigException + */ + public function refundPayOrder(string $out_order_no, string $out_refund_no, int $refund_amount, string $reason, string $notify_url = '', string $msg_page = '', string $cp_extra = ''): array + { + $app_id = $this->app['config']['app_id']; + $params = compact('app_id', 'out_order_no', 'out_refund_no', 'refund_amount', 'reason', 'notify_url', 'msg_page', 'cp_extra'); + $params['sign'] = $this->sign($params); + return $this->httpPostJson('apps/ecpay/v1/create_refund', $params); + } + + /** + * 申请结算 + * @param string $out_settle_no + * @param string $out_order_no + * @param string $notify_url + * @param string $settle_desc + * @param string $cp_extra + * @param string $finish + * @return array + * @throws GuzzleException + * @throws HttpException + * @throws InvalidConfigException + */ + public function settle(string $out_settle_no, string $out_order_no, string $notify_url = '', string $settle_desc = '主动结算', string $cp_extra = '', string $finish = 'true'): array + { + $app_id = $this->app['config']['app_id']; + $params = compact('app_id', 'out_order_no', 'out_settle_no', 'settle_desc', 'notify_url', 'finish', 'cp_extra'); + $params['sign'] = $this->sign($params); + return $this->httpPostJson('apps/ecpay/v1/settle', $params); + } + + /** + * 签名 + * @param array $params + * @return string + */ + protected function sign(array $params): string + { + $need_sign_params = []; + foreach ($params as $k => $v) { + $v = trim(strval($v)); + if (empty($v) || in_array($k, $this->no_need_sign_params)) { + continue; + } + $need_sign_params[] = $v; + } + $need_sign_params[] = $this->app['config']['pay']['salt']; + sort($need_sign_params, SORT_STRING); + return md5(implode('&', $need_sign_params)); + } +} diff --git a/src/MiniProgram/Pay/Message.php b/src/MiniProgram/Pay/Message.php new file mode 100644 index 0000000..1b5597d --- /dev/null +++ b/src/MiniProgram/Pay/Message.php @@ -0,0 +1,30 @@ +toArray()['type'] ?? ''; + if (empty($type)) { + throw new \RuntimeException('Invalid event type.'); + } + return $type; + } +} diff --git a/src/MiniProgram/Pay/OrderInfo.php b/src/MiniProgram/Pay/OrderInfo.php new file mode 100644 index 0000000..3244731 --- /dev/null +++ b/src/MiniProgram/Pay/OrderInfo.php @@ -0,0 +1,15 @@ +order_id = $order_id; + $this->order_token = $order_token; + } +} \ No newline at end of file diff --git a/src/MiniProgram/Pay/Server.php b/src/MiniProgram/Pay/Server.php new file mode 100644 index 0000000..961a579 --- /dev/null +++ b/src/MiniProgram/Pay/Server.php @@ -0,0 +1,121 @@ +app = $app; + } + + public function serve(): Response + { + $message = $this->getRequestMessage(); + try { + $default_response = new Response( + 200, + [], + strval(json_encode(['err_no' => 0, 'err_tips' => 'success'], JSON_UNESCAPED_UNICODE)) + ); + $response = $this->handle($default_response, $message); + + if (!($response instanceof ResponseInterface)) { + $response = $default_response; + } + + return $response; + } catch (\Exception $e) { + return new Response( + 200, + [], + strval(json_encode(['err_no' => 400, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE)) + ); + } + } + + /** + * @link https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml + * + * @throws InvalidArgumentException + */ + public function handlePaid(callable $handler): static + { + $this->with(function (Message $message, Closure $next) use ($handler) { + return $message->getType() === Message::TYPE_PAY + ? $handler($message, $next) : $next($message); + }); + + return $this; + } + + /** + * @link https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_11.shtml + * + * @throws InvalidArgumentException + */ + public function handleRefunded(callable $handler): static + { + $this->with(function (Message $message, Closure $next) use ($handler) { + return $message->getType() === Message::TYPE_REFUND + ? $handler($message, $next) : $next($message); + }); + + return $this; + } + + /** + * @link https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/ecpay/settlements/callback + * + * @throws InvalidArgumentException + */ + public function handleSettled(callable $handler): static + { + $this->with(function (Message $message, Closure $next) use ($handler) { + return $message->getType() === Message::TYPE_SETTLED + ? $handler($message, $next) : $next($message); + }); + + return $this; + } + + public function setRequest(ServerRequestInterface $request): ServerRequestInterface + { + return $this->request = $request; + } + + public function getRequestMessage(): Message + { + if (empty($this->request)) { + throw new RuntimeException('empty request.'); + } + + $request = $this->request->getBody(); + $attributes = json_decode($request, true); + if (! is_array($attributes)) { + throw new RuntimeException('Invalid request body.'); + } + + // todo验签 + $attributes['msg'] = is_array($attributes['msg']) ? $attributes['msg'] : json_decode($attributes['msg'] ?? '', true); + return new Message($attributes); + } +} diff --git a/src/MiniProgram/Pay/ServiceProvider.php b/src/MiniProgram/Pay/ServiceProvider.php new file mode 100644 index 0000000..259e96c --- /dev/null +++ b/src/MiniProgram/Pay/ServiceProvider.php @@ -0,0 +1,24 @@ +