Compare commits

...

16 Commits

Author SHA1 Message Date
李东云
245f13b26d chore(release): 1.2.0 2025-08-20 13:50:37 +08:00
李东云
fc5cf453ae test: 更新 LuxPay 回调测试用例
移除了 LuxPayCallbackTest 中冗余的 only() 方法调用,简化了测试用例的编写。
2025-08-20 13:50:05 +08:00
李东云
8f71cbeb65 feat(transaction): 添加交易相关实体和回调服务
- 新增 CardTransaction、PointTransaction、Item、Transaction 和 TransactionRecord 类
- 实现 CallbackDomainSvc 类的 callback 方法,用于处理回调请求
- 添加 LuxPayCallbackTest 测试类,验证回调解析功能
2025-08-20 13:48:44 +08:00
李东云
9517b5bd62 chore(release): 1.1.0 2025-08-18 20:00:54 +08:00
李东云
c8ee3bb23b feat(pay): 新增消费命令和通用订单 DTO
- 添加 ConsumeCmd 类用于消费操作
- 重构 RechargeCmd 类,使其返回自身以便链式调用
- 将 RechargeDto 重命名为 OrderDto,作为通用订单数据传输对象
- 更新 OrderRepoInterface 接口,使用新的命令和 DTO 类
- 修改 AccountBalanceRepo、OrderRepo 和 ProductRepo,移除不必要的选项参数
- 新增 CreateConsumptionOrderTest 测试用例
- 更新 CreateRechargeOrderTest 测试用例以使用新的命令和 DTO 类
2025-08-18 19:58:42 +08:00
李东云
80dc1a3706 feat(pay): 添加充值功能
- 新增 RechargeCmd 类用于处理充值命令
- 新增 RechargeDto 类用于表示充值交易数据
- 新增 OrderAction、OrderStatus 和 PayType 枚举类
- 新增 OrderRepoInterface接口和 OrderRepo 实现类,用于处理订单相关操作
- 更新 ConfigProvider,添加新依赖项
- 新增 CreateRechargeOrderTest 测试用例
2025-08-18 19:12:07 +08:00
李东云
ca6000a5c8 chore(release): 1.0.0 2025-08-18 18:32:55 +08:00
李东云
1814adc30e feat(product): 增加 EMA 产品查询功能
- 移除了未使用的 ProductItem 引用
- 修改了 findEmaProduct 方法的参数,从 currentVersion 改为 uid
- 更新了 findEmaProduct 方法的实现,使用 uid 参数进行查询
- 重构了 RechargeProduct 对象的构建方式,提高了代码可读性
- 更新了单元测试,增加了 EMA 产品查询的测试用例
2025-08-18 16:57:14 +08:00
李东云
06dc4a2e65 feat(product): 新增充值产品相关数据结构和接口
- 添加 RechargeProduct、ProductItem、RechargeEffect 等实体和值对象
- 实现充值产品相关的数据传输对象 (DTO)
- 定义充值产品仓库接口并提供具体实现
- 增加相关单元测试
2025-08-18 16:41:37 +08:00
李东云
a2fc4cecf8 feat(pay): 添加产品类型枚举和兑换率接口
- 新增 ProductType 枚举类,用于定义产品类型
- 添加 ExchangeRepoInterface 接口,用于获取兑换率
- 实现 QueryPointRateTest 测试用例,验证兑换率查询功能
2025-08-18 15:33:13 +08:00
李东云
1bb8666b5e refactor(pay): 调整请求头构建逻辑
- 将 RequestHeaderBuilder trait 移至 AbstractRepo 中
- 删除 AccountBalanceRepo 中的重复 trait 引用
- 此改动简化了代码结构,避免了重复代码
2025-08-18 14:42:42 +08:00
李东云
5559f2c2a5 refactor(pay): 重构账户余额查询功能
- 新增 AbstractRepo 抽象类,用于统一封装请求服务
- 修正 Infrastructure 目录名称拼写错误
- 更新命名空间和类名引用
- 简化 AccountBalanceRepo 类的构造方法
2025-08-18 14:41:06 +08:00
李东云
4f0d402538 refactor(pay): 重构账户余额查询接口并添加单元测试
- 创建 RequestHeaderBuilder Trait 以简化请求头构建逻辑
- 在 AccountBalanceRepo 中使用 RequestHeaderBuilder Trait
- 添加 QueryPointBalanceTest 单元测试用例
2025-08-18 14:23:14 +08:00
李东云
336cb3a9b9 test(pay): 添加账户信息查询功能测试
- 新增 QueryAccountInformationTest 以测试账户信息查询功能
- 实现 getAccount 方法的单元测试
- 添加测试引导文件和 HTTP 测试用例基类
- 更新 composer.json 和 composer.lock 文件
2025-08-18 14:08:30 +08:00
李东云
3d91f76dc0 feat(domain): 新增账户余额和积分相关实体及接口
- 添加了 PointsBalance、AccountBalance、PointType 等实体类
- 新增了 AccountRepoInterface 接口及其实现类 AccountBalanceRepo
- 引入了 Carbon库用于处理时间相关的逻辑
- 删除了未使用的 WechatService 类
- 更新了 ConfigProvider 以注册新的 Repository
- 修改了 composer.json 和 composer.lock 文件,添加了新的依赖
2025-08-18 09:49:21 +08:00
李东云
7f7fe100e7 build(deps): 更新 Hyperf 和 Symfony 组件
- 更新 Hyperf constants 从 v3.1.42 到 v3.1.57
- 更新 Hyperf exception-handler 从 v3.1.42 到 v3.1.57
- 更新 Hyperf guzzle 从 v3.1.42 到 v3.1.58
- 更新 Hyperf http-message 从 v3.1.48 到 v3.1.57
- 更新 Hyperf http-server 从 v3.1.55 到 v3.1.59
- 更新 singularity/hdk-core从 1.0.0-beta.11 到 1.0.0-beta.14- 更新 Symfony 组件到最新版本
- 更新 fidry/cpu-core-counter 从 1.1.0 到 1.2.0
- 更新 myclabs/deep-copy 从 1.13.1 到 1.13.3
2025-08-17 09:16:38 +08:00
52 changed files with 2515 additions and 111 deletions

View File

@@ -1,4 +1,51 @@
# 版本更新日志
## [1.2.0](http://124.126.16.154:8888/singularity/hdk-pay/compare/v1.1.0...v1.2.0) (2025-08-20)
### ✨ Features | 新功能
* **transaction:** 添加交易相关实体和回调服务 ([8f71cbe](http://124.126.16.154:8888/singularity/hdk-pay/commit/8f71cbeb653d0722f231c300c2a1fabe79764580))
### ✅ Tests | 测试
* 更新 LuxPay 回调测试用例 ([fc5cf45](http://124.126.16.154:8888/singularity/hdk-pay/commit/fc5cf453aed133552514e1b60699fe63d2178543))
## [1.1.0](http://124.126.16.154:8888/singularity/hdk-pay/compare/v1.0.0...v1.1.0) (2025-08-18)
### ✨ Features | 新功能
* **pay:** 新增消费命令和通用订单 DTO ([c8ee3bb](http://124.126.16.154:8888/singularity/hdk-pay/commit/c8ee3bb23bf41f39a64ca08db4a005d2893c1554))
* **pay:** 添加充值功能 ([80dc1a3](http://124.126.16.154:8888/singularity/hdk-pay/commit/80dc1a3706fa973f3360c70f1db34d403eb741df))
## [1.0.0](http://124.126.16.154:8888/singularity/hdk-pay/compare/v1.0.0-beta.5...v1.0.0) (2025-08-18)
### 📦‍ Build System | 打包构建
* **deps:** 更新 Hyperf 和 Symfony 组件 ([7f7fe10](http://124.126.16.154:8888/singularity/hdk-pay/commit/7f7fe100e71e98557de0399702ef2e3cb17d3b42))
### ✅ Tests | 测试
* **pay:** 添加账户信息查询功能测试 ([336cb3a](http://124.126.16.154:8888/singularity/hdk-pay/commit/336cb3a9b9baa0b9e47ca7cf5bb5a6bc04d48c90))
### ♻️ Code Refactoring | 代码重构
* **pay:** 调整请求头构建逻辑 ([1bb8666](http://124.126.16.154:8888/singularity/hdk-pay/commit/1bb8666b5e4432d720d9b8256baa06d4f570718a))
* **pay:** 重构账户余额查询功能 ([5559f2c](http://124.126.16.154:8888/singularity/hdk-pay/commit/5559f2c2a59326a8aa209ffb00693523d39fc319))
* **pay:** 重构账户余额查询接口并添加单元测试 ([4f0d402](http://124.126.16.154:8888/singularity/hdk-pay/commit/4f0d402538410020c89910b48bb418f2d306b1a0))
### ✨ Features | 新功能
* **domain:** 新增账户余额和积分相关实体及接口 ([3d91f76](http://124.126.16.154:8888/singularity/hdk-pay/commit/3d91f76dc0657bd24643ee18a3192ad631001530))
* **pay:** 添加产品类型枚举和兑换率接口 ([a2fc4ce](http://124.126.16.154:8888/singularity/hdk-pay/commit/a2fc4cecf8105616b440add0bfaab93b91abce09))
* **product:** 增加 EMA 产品查询功能 ([1814adc](http://124.126.16.154:8888/singularity/hdk-pay/commit/1814adc30e0c3801c11bfb270a666ebd9348d115))
* **product:** 新增充值产品相关数据结构和接口 ([06dc4a2](http://124.126.16.154:8888/singularity/hdk-pay/commit/06dc4a2e6555f92aab09271a3f8cd09fc8960e95))
## [1.0.0-beta.5](http://124.126.16.154:8888/singularity/hdk-pay/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2025-07-17)

View File

@@ -8,6 +8,11 @@
"Singularity\\HDK\\Pay\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Singularity\\HDK\\Test\\Pay\\": "tests/"
}
},
"extra": {
"hyperf": {
"config": "Singularity\\HDK\\Pay\\ConfigProvider"
@@ -15,21 +20,24 @@
},
"require": {
"php": ">=8.2",
"ext-bcmath": "*",
"composer/composer": ">=2.7.7",
"hyperf/config": "~3.1.0",
"hyperf/constants": "3.1.*",
"hyperf/contract": "~3.1.0",
"hyperf/di": "~3.1.0",
"hyperf/guzzle": "3.1.*",
"hyperf/http-server": "3.1.*",
"hyperf/http-server": "^3.1",
"moneyphp/money": "^4.1",
"nesbot/carbon": "*",
"singularity/hdk-core": "^1.0.0"
},
"require-dev": {
"firebase/php-jwt": "^6.8.0",
"hyperf/testing": "^3.1",
"friendsofhyperf/pest-plugin-hyperf": "3.1.*",
"swoole/ide-helper": "^4.8.13"
"swoole/ide-helper": "^4.8.13",
"nesbot/carbon": "^2.0"
},
"minimum-stability": "beta",
"prefer-stable": true,
@@ -62,5 +70,5 @@
"url": "https://mirrors.aliyun.com/composer/"
}
},
"version": "1.0.0-beta.5"
"version": "1.2.0"
}

574
composer.lock generated
View File

@@ -4,8 +4,83 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7706df233cf3930599f3126c864d2309",
"content-hash": "1502e30908a8b64e02da6b75783f3598",
"packages": [
{
"name": "carbonphp/carbon-doctrine-types",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^8.1"
},
"conflict": {
"doctrine/dbal": "<4.0.0 || >=5.0.0"
},
"require-dev": {
"doctrine/dbal": "^4.0.0",
"nesbot/carbon": "^2.71.0 || ^3.0.0",
"phpunit/phpunit": "^10.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "KyleKatarn",
"email": "kylekatarnls@gmail.com"
}
],
"description": "Types to use Carbon in Doctrine",
"keywords": [
"carbon",
"date",
"datetime",
"doctrine",
"time"
],
"support": {
"issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
"source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
},
"funding": [
{
"url": "https://github.com/kylekatarnls",
"type": "github"
},
{
"url": "https://opencollective.com/Carbon",
"type": "open_collective"
},
{
"url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
"type": "tidelift"
}
],
"time": "2024-02-09T16:56:22+00:00"
},
{
"name": "composer/ca-bundle",
"version": "1.5.6",
@@ -1825,16 +1900,16 @@
},
{
"name": "hyperf/constants",
"version": "v3.1.42",
"version": "v3.1.57",
"source": {
"type": "git",
"url": "https://github.com/hyperf/constants.git",
"reference": "e1e1184779cd163f9603ce234e1ecccb6fe382ae"
"reference": "0b7f77ae9f54175163084096f60577627ffa55c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyperf/constants/zipball/e1e1184779cd163f9603ce234e1ecccb6fe382ae",
"reference": "e1e1184779cd163f9603ce234e1ecccb6fe382ae",
"url": "https://api.github.com/repos/hyperf/constants/zipball/0b7f77ae9f54175163084096f60577627ffa55c4",
"reference": "0b7f77ae9f54175163084096f60577627ffa55c4",
"shasum": "",
"mirrors": [
{
@@ -1844,9 +1919,11 @@
]
},
"require": {
"hyperf/context": "~3.1.0",
"hyperf/contract": "~3.1.0",
"hyperf/di": "~3.1.0",
"hyperf/stringable": "~3.1.0",
"hyperf/support": "~3.1.0",
"hyperf/utils": "~3.1.0",
"php": ">=8.1"
},
"suggest": {
@@ -1893,7 +1970,7 @@
"type": "open_collective"
}
],
"time": "2024-09-25T02:54:12+00:00"
"time": "2025-06-06T02:41:30+00:00"
},
{
"name": "hyperf/context",
@@ -2558,16 +2635,16 @@
},
{
"name": "hyperf/exception-handler",
"version": "v3.1.42",
"version": "v3.1.57",
"source": {
"type": "git",
"url": "https://github.com/hyperf/exception-handler.git",
"reference": "df2135fb0ffe0bb61032911038aea6488077cdef"
"reference": "c2240b8a455b7305b470576dd846078d4aca44f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyperf/exception-handler/zipball/df2135fb0ffe0bb61032911038aea6488077cdef",
"reference": "df2135fb0ffe0bb61032911038aea6488077cdef",
"url": "https://api.github.com/repos/hyperf/exception-handler/zipball/c2240b8a455b7305b470576dd846078d4aca44f0",
"reference": "c2240b8a455b7305b470576dd846078d4aca44f0",
"shasum": "",
"mirrors": [
{
@@ -2582,6 +2659,7 @@
"hyperf/dispatcher": "~3.1.0",
"hyperf/http-message": "~3.1.0",
"hyperf/stdlib": "~3.1.0",
"hyperf/stringable": "~3.1.0",
"hyperf/support": "~3.1.0",
"php": ">=8.1",
"psr/container": "^1.0 || ^2.0",
@@ -2635,7 +2713,7 @@
"type": "open_collective"
}
],
"time": "2024-09-25T02:54:12+00:00"
"time": "2025-06-06T02:41:30+00:00"
},
{
"name": "hyperf/framework",
@@ -2722,16 +2800,16 @@
},
{
"name": "hyperf/guzzle",
"version": "v3.1.42",
"version": "v3.1.58",
"source": {
"type": "git",
"url": "https://github.com/hyperf/guzzle.git",
"reference": "fe838557530bf7b2d39dc604563c3a3ff8d5618f"
"reference": "d6de1e1cc6175137b8f91b79f308c73ce426717e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyperf/guzzle/zipball/fe838557530bf7b2d39dc604563c3a3ff8d5618f",
"reference": "fe838557530bf7b2d39dc604563c3a3ff8d5618f",
"url": "https://api.github.com/repos/hyperf/guzzle/zipball/d6de1e1cc6175137b8f91b79f308c73ce426717e",
"reference": "d6de1e1cc6175137b8f91b79f308c73ce426717e",
"shasum": "",
"mirrors": [
{
@@ -2742,6 +2820,9 @@
},
"require": {
"guzzlehttp/guzzle": "^6.3 || ^7.0",
"hyperf/context": "~3.1.0",
"hyperf/coroutine": "~3.1.0",
"hyperf/engine": "^2.0",
"php": ">=8.1",
"psr/container": "^1.0 || ^2.0",
"psr/http-message": "^1.0 || ^2.0"
@@ -2777,7 +2858,7 @@
],
"support": {
"issues": "https://github.com/hyperf/guzzle/issues",
"source": "https://github.com/hyperf/guzzle/tree/v3.1.42"
"source": "https://github.com/hyperf/guzzle/tree/v3.1.58"
},
"funding": [
{
@@ -2789,20 +2870,20 @@
"type": "open_collective"
}
],
"time": "2024-09-25T02:54:12+00:00"
"time": "2025-06-23T03:55:31+00:00"
},
{
"name": "hyperf/http-message",
"version": "v3.1.48",
"version": "v3.1.57",
"source": {
"type": "git",
"url": "https://github.com/hyperf/http-message.git",
"reference": "534ce81af0feaa0c4a9e132af1c6a9c5527a8d85"
"reference": "26201c732b9ceb6fa14af3bfa381463d3e1a4e6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyperf/http-message/zipball/534ce81af0feaa0c4a9e132af1c6a9c5527a8d85",
"reference": "534ce81af0feaa0c4a9e132af1c6a9c5527a8d85",
"url": "https://api.github.com/repos/hyperf/http-message/zipball/26201c732b9ceb6fa14af3bfa381463d3e1a4e6a",
"reference": "26201c732b9ceb6fa14af3bfa381463d3e1a4e6a",
"shasum": "",
"mirrors": [
{
@@ -2813,6 +2894,9 @@
},
"require": {
"hyperf/codec": "~3.1.0",
"hyperf/context": "~3.1.0",
"hyperf/contract": "~3.1.0",
"hyperf/coroutine": "~3.1.0",
"hyperf/engine": "^2.11",
"hyperf/support": "~3.1.0",
"laminas/laminas-mime": "^2.7",
@@ -2850,7 +2934,7 @@
],
"support": {
"issues": "https://github.com/hyperf/http-message/issues",
"source": "https://github.com/hyperf/http-message/tree/v3.1.48"
"source": "https://github.com/hyperf/http-message/tree/v3.1.57"
},
"funding": [
{
@@ -2862,20 +2946,20 @@
"type": "open_collective"
}
],
"time": "2024-12-05T02:41:08+00:00"
"time": "2025-06-06T02:41:30+00:00"
},
{
"name": "hyperf/http-server",
"version": "v3.1.55",
"version": "v3.1.59",
"source": {
"type": "git",
"url": "https://github.com/hyperf/http-server.git",
"reference": "96a1d80cd9482942899d7068af0e6ecb78d382ce"
"reference": "b5f8dbd03578adb101fbf98ab7407510d553130f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyperf/http-server/zipball/96a1d80cd9482942899d7068af0e6ecb78d382ce",
"reference": "96a1d80cd9482942899d7068af0e6ecb78d382ce",
"url": "https://api.github.com/repos/hyperf/http-server/zipball/b5f8dbd03578adb101fbf98ab7407510d553130f",
"reference": "b5f8dbd03578adb101fbf98ab7407510d553130f",
"shasum": "",
"mirrors": [
{
@@ -2889,8 +2973,10 @@
"hyperf/collection": "~3.1.0",
"hyperf/context": "~3.1.0",
"hyperf/contract": "~3.1.0",
"hyperf/coordinator": "~3.1.0",
"hyperf/coroutine": "~3.1.0",
"hyperf/dispatcher": "~3.1.0",
"hyperf/engine": "^2.0",
"hyperf/event": "~3.1.0",
"hyperf/exception-handler": "~3.1.0",
"hyperf/http-message": "~3.1.0",
@@ -2898,6 +2984,7 @@
"hyperf/serializer": "~3.1.0",
"hyperf/server": "~3.1.0",
"hyperf/stdlib": "~3.1.0",
"hyperf/stringable": "~3.1.0",
"hyperf/support": "~3.1.0",
"nikic/fast-route": "^1.3",
"php": ">=8.1",
@@ -2950,7 +3037,7 @@
"type": "open_collective"
}
],
"time": "2025-05-14T07:44:25+00:00"
"time": "2025-06-30T01:39:01+00:00"
},
{
"name": "hyperf/logger",
@@ -4619,6 +4706,119 @@
],
"time": "2022-08-04T09:53:51+00:00"
},
{
"name": "nesbot/carbon",
"version": "2.73.0",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075",
"reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"carbonphp/carbon-doctrine-types": "*",
"ext-json": "*",
"php": "^7.1.8 || ^8.0",
"psr/clock": "^1.0",
"symfony/polyfill-mbstring": "^1.0",
"symfony/polyfill-php80": "^1.16",
"symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0",
"doctrine/orm": "^2.7 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.0",
"kylekatarnls/multi-tester": "^2.0",
"ondrejmirtes/better-reflection": "<6",
"phpmd/phpmd": "^2.9",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12.99 || ^1.7.14",
"phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
"phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
"squizlabs/php_codesniffer": "^3.4"
},
"bin": [
"bin/carbon"
],
"type": "library",
"extra": {
"laravel": {
"providers": [
"Carbon\\Laravel\\ServiceProvider"
]
},
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-2.x": "2.x-dev",
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Carbon\\": "src/Carbon/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "https://markido.com"
},
{
"name": "kylekatarnls",
"homepage": "https://github.com/kylekatarnls"
}
],
"description": "An API extension for DateTime that supports 281 different languages.",
"homepage": "https://carbon.nesbot.com",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
"docs": "https://carbon.nesbot.com/docs",
"issues": "https://github.com/briannesbitt/Carbon/issues",
"source": "https://github.com/briannesbitt/Carbon"
},
"funding": [
{
"url": "https://github.com/sponsors/kylekatarnls",
"type": "github"
},
{
"url": "https://opencollective.com/Carbon#sponsor",
"type": "opencollective"
},
{
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
"type": "tidelift"
}
],
"time": "2025-01-08T20:10:23+00:00"
},
{
"name": "nikic/fast-route",
"version": "v1.3.0",
@@ -4930,6 +5130,60 @@
],
"time": "2025-04-16T13:01:53+00:00"
},
{
"name": "psr/clock",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/clock.git",
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.0 || ^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Psr\\Clock\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for reading the clock.",
"homepage": "https://github.com/php-fig/clock",
"keywords": [
"clock",
"now",
"psr",
"psr-20",
"time"
],
"support": {
"issues": "https://github.com/php-fig/clock/issues",
"source": "https://github.com/php-fig/clock/tree/1.0.0"
},
"time": "2022-11-25T14:36:26+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
@@ -5848,16 +6102,16 @@
},
{
"name": "singularity/hdk-core",
"version": "1.0.0-beta.11",
"version": "1.0.0-beta.14",
"source": {
"type": "git",
"url": "https://nest.doylee.cn/HDK/hdk-core",
"reference": "1.0.0-beta.11"
"reference": "1.0.0-beta.14"
},
"dist": {
"type": "zip",
"url": "https://nest.doylee.cn/api/packages/HDK/composer/files/singularity%2Fhdk-core/1.0.0-beta.11/singularity-hdk-core.1.0.0-beta.11.zip",
"shasum": "2a0273945e71fe0802891311b4ec9428ec1fa6fc"
"url": "https://nest.doylee.cn/api/packages/HDK/composer/files/singularity%2Fhdk-core/1.0.0-beta.14/singularity-hdk-core.1.0.0-beta.14.zip",
"shasum": "dc0a07b1c83088d3ba2b694de7d11dae9ca8b115"
},
"require": {
"composer/composer": ">=2.0",
@@ -5950,7 +6204,7 @@
}
],
"description": "Common Hyperf Development Kit",
"time": "2025-07-11T16:45:09+08:00"
"time": "2025-08-12T15:05:30+08:00"
},
{
"name": "swow/psr7-plus",
@@ -6183,7 +6437,7 @@
},
{
"name": "symfony/filesystem",
"version": "v7.2.0",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
@@ -6235,7 +6489,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.2.0"
"source": "https://github.com/symfony/filesystem/tree/v7.3.0"
},
"funding": [
{
@@ -6255,16 +6509,16 @@
},
{
"name": "symfony/finder",
"version": "v7.2.2",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "87a71856f2f56e4100373e92529eed3171695cfb"
"reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
"reference": "87a71856f2f56e4100373e92529eed3171695cfb",
"url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d",
"reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d",
"shasum": "",
"mirrors": [
{
@@ -6305,7 +6559,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.2.2"
"source": "https://github.com/symfony/finder/tree/v7.3.0"
},
"funding": [
{
@@ -6321,7 +6575,7 @@
"type": "tidelift"
}
],
"time": "2024-12-30T19:00:17+00:00"
"time": "2024-12-30T19:00:26+00:00"
},
{
"name": "symfony/http-foundation",
@@ -7246,16 +7500,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.5",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
"url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
"shasum": "",
"mirrors": [
{
@@ -7293,7 +7547,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.5"
"source": "https://github.com/symfony/process/tree/v7.3.0"
},
"funding": [
{
@@ -7309,7 +7563,7 @@
"type": "tidelift"
}
],
"time": "2025-03-13T12:21:46+00:00"
"time": "2025-04-17T09:11:12+00:00"
},
{
"name": "symfony/service-contracts",
@@ -7493,6 +7747,191 @@
],
"time": "2025-04-20T20:18:16+00:00"
},
{
"name": "symfony/translation",
"version": "v6.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/de8afa521e04a5220e9e58a1dc99971ab7cac643",
"reference": "de8afa521e04a5220e9e58a1dc99971ab7cac643",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.5|^3.0"
},
"conflict": {
"symfony/config": "<5.4",
"symfony/console": "<5.4",
"symfony/dependency-injection": "<5.4",
"symfony/http-client-contracts": "<2.5",
"symfony/http-kernel": "<5.4",
"symfony/service-contracts": "<2.5",
"symfony/twig-bundle": "<5.4",
"symfony/yaml": "<5.4"
},
"provide": {
"symfony/translation-implementation": "2.3|3.0"
},
"require-dev": {
"nikic/php-parser": "^4.18|^5.0",
"psr/log": "^1|^2|^3",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/console": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/finder": "^5.4|^6.0|^7.0",
"symfony/http-client-contracts": "^2.5|^3.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/intl": "^5.4|^6.0|^7.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/routing": "^5.4|^6.0|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^5.4|^6.0|^7.0"
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\Translation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v6.4.23"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-06-26T21:24:02+00:00"
},
{
"name": "symfony/translation-contracts",
"version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "4667ff3bd513750603a09c8dedbea942487fb07c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c",
"reference": "4667ff3bd513750603a09c8dedbea942487fb07c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.5-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Translation\\": ""
},
"exclude-from-classmap": [
"/Test/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to translation",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/translation-contracts/tree/v3.5.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "teapot/status-code",
"version": "v1.1.2",
@@ -7791,16 +8230,16 @@
},
{
"name": "fidry/cpu-core-counter",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/theofidry/cpu-core-counter.git",
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42"
"reference": "8520451a140d3f46ac33042715115e290cf5785f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42",
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42",
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f",
"reference": "8520451a140d3f46ac33042715115e290cf5785f",
"shasum": "",
"mirrors": [
{
@@ -7846,7 +8285,7 @@
],
"support": {
"issues": "https://github.com/theofidry/cpu-core-counter/issues",
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0"
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0"
},
"funding": [
{
@@ -7854,7 +8293,7 @@
"type": "github"
}
],
"time": "2024-02-07T09:43:46+00:00"
"time": "2024-08-06T10:04:20+00:00"
},
{
"name": "filp/whoops",
@@ -8236,16 +8675,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.1",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": "",
"mirrors": [
{
@@ -8290,7 +8729,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
@@ -8298,7 +8737,7 @@
"type": "tidelift"
}
],
"time": "2025-04-29T12:36:36+00:00"
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "nunomaduro/collision",
@@ -8947,16 +9386,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.6.1",
"version": "5.6.2",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8"
"reference": "92dde6a5919e34835c506ac8c523ef095a95ed62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
"reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62",
"reference": "92dde6a5919e34835c506ac8c523ef095a95ed62",
"shasum": "",
"mirrors": [
{
@@ -9011,9 +9450,9 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1"
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2"
},
"time": "2024-12-07T09:39:29+00:00"
"time": "2025-04-13T19:20:35+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@@ -10826,7 +11265,8 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=8.2"
"php": ">=8.2",
"ext-bcmath": "*"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"

View File

@@ -0,0 +1,41 @@
<?php
/**
* ConsumeCmd.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Application\Command;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Enum\PaymentMethod;
final class ConsumeCmd
{
public array $items = [];
public function __construct(
public string $uid,
public PayType $type,
public PaymentMethod|PointType $method,
public array $external,
public ?string $externalID = null,
public ?string $remark = null,
) {}
public function addItem(string $name, float $unitPrice, int $quantity = 1): ConsumeCmd
{
$this->items[] = [
'name' => $name,
'quantity' => $quantity,
'unit_price' => $unitPrice,
];
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* RechargeCmd.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Application\Command;
final class RechargeCmd {
public array $items = [];
public function __construct(public string $uid) {}
public function addItem(int $productId, int $quantity = 1): RechargeCmd
{
$this->items[] = [
'product_id' => $productId,
'quantity' => $quantity,
];
return $this;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* AbstractDto.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Application\Dto;
use Hyperf\Codec\Json;
use Hyperf\Contract\Jsonable;
use Swoole\ArrayObject;
abstract class AbstractDto extends ArrayObject implements Jsonable
{
public function __toString(): string
{
return $this->toJson();
}
public function toJson(): string
{
return Json::encode($this->toArray());
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* RechargeProductsDto.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Application\Dto\Product;
use Singularity\HDK\Pay\Application\Dto\AbstractDto;
use Singularity\HDK\Pay\Domain\Product\Aggregate\Entities\ProductItem;
use Singularity\HDK\Pay\Domain\Product\Aggregate\RechargeProduct;
final class RechargeProductsDto extends AbstractDto
{
public function __construct(RechargeProduct $product)
{
parent::__construct(array_filter([
'one_time' => $this->parseProductItem($product->getOneTime()),
'renew' => $this->parseProductItem($product->getRenew()),
'plan' => array_map(
fn(ProductItem $item) => $this->parseProductItem($item),
$product->getPlans(),
),
'package' => array_map(
fn(ProductItem $item) => $this->parseProductItem($item),
$product->getPackages(),
),
]));
}
private function parseProductItem(?ProductItem $product): array
{
if (is_null($product)) {
return [];
}
$effect = $product->effect;
$price = $product->unitPrice;
return [
'id' => $product->id,
'name' => $product->description,
'currency' => $price->getCurrency()->getCode(),
'price' => (float)$price->getAmount(),
'total_points' => $effect->getPointTotal(),
'bonus_rate_pct' => (float)(bcmul(
num1: $effect->getPointTotal() == 0
? '0'
: bcdiv(
(string)$effect->pointBonus,
(string)$effect->getPointTotal(),
3,
),
num2: '100',
scale: 0,
)),
'point' => [
'total' => $effect->getPointTotal(),
'number' => $effect->pointBasic,
'bonus' => $effect->pointBonus,
],
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* RechargeDto.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Application\Dto\Transaction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderAction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
final readonly class OrderDto
{
public function __construct(
private string $orderNo,
private OrderAction $action,
private PayType $payType,
private OrderStatus $status,
) {}
public function getOrderNo(): string
{
return $this->orderNo;
}
public function getAction(): OrderAction
{
return $this->action;
}
public function getPayType(): PayType
{
return $this->payType;
}
public function getStatus(): OrderStatus
{
return $this->status;
}
}

View File

@@ -6,6 +6,13 @@ namespace Singularity\HDK\Pay;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Framework\Logger\StdoutLogger;
use Singularity\HDK\Pay\Domain\Account\Repository\AccountRepoInterface;
use Singularity\HDK\Pay\Domain\Product\Repository\ExchangeRepoInterface;
use Singularity\HDK\Pay\Domain\Product\Repository\RechargeProductRepoInterface;
use Singularity\HDK\Pay\Domain\Transaction\Repository\OrderRepoInterface;
use Singularity\HDK\Pay\Infrastructure\Repository\AccountBalanceRepo;
use Singularity\HDK\Pay\Infrastructure\Repository\OrderRepo;
use Singularity\HDK\Pay\Infrastructure\Repository\ProductRepo;
/**
* ConfigProvider.php@HyperfAuth
@@ -18,11 +25,16 @@ class ConfigProvider
{
public function __invoke(): array
{
/** @noinspection PhpUndefinedConstantInspection */
return [
// 合并到 config/autoload/dependencies.php 文件
'dependencies' => [
StdoutLoggerInterface::class => StdoutLogger::class,
// Command
AccountRepoInterface::class => AccountBalanceRepo::class,
ExchangeRepoInterface::class => ProductRepo::class,
RechargeProductRepoInterface::class => ProductRepo::class,
OrderRepoInterface::class => OrderRepo::class,
],
// 合并到 config/autoload/annotations.php 文件
'annotations' => [

View File

@@ -0,0 +1,46 @@
<?php
/**
* AccountBalance.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/17
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Account\Aggregate;
use Singularity\HDK\Pay\Domain\Account\Aggregate\ValueObject\PointsBalance;
/**
* Singularity\HDK\Pay\Domain\Account\Aggregate\AccountBalance@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/17
*
* @template TPointType of string
* @template TPointsBalances of array<TPointType, PointsBalance>
*/
final readonly class AccountBalance
{
/**
* @param string $uid
* @param TPointsBalances $pointsBalances
*/
public function __construct(
private string $uid,
private array $pointsBalances = [],
) {}
public function getUid(): string
{
return $this->uid;
}
public function getPointsBalances(): array
{
return $this->pointsBalances;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* PointsBalance.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Account\Aggregate\ValueObject;
use Carbon\Carbon;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
final readonly class PointsBalance
{
public function __construct(
private PointType $type,
private float $total,
private float $cost,
public float $amount,
private string|null $version,
private Carbon|null $expiredAt,
) {
}
public function getType(): PointType
{
return $this->type;
}
public function getTotal(): float
{
return $this->total;
}
public function getCost(): float
{
return $this->cost;
}
public function getVersion(): ?string
{
return $this->version;
}
public function getExpiredAt(): ?Carbon
{
return $this->expiredAt;
}
public function getAmount(): float
{
return $this->amount;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* PointType.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
namespace Singularity\HDK\Pay\Domain\Account\Enum;
enum PointType: string
{
case LuxPoint = 'lux-point';
case EMA = 'ema';
case FtaiAligner = 'aligner';
case FtaiRetainer = 'retainer';
case Aligner4D = 'aligner-4d';
case NightguardAi = 'nightguard-ai';
public static function values(): array
{
return [
self::LuxPoint->value,
self::EMA->value,
self::FtaiAligner->value,
self::FtaiRetainer->value,
self::Aligner4D->value,
self::NightguardAi->value,
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* AccountBalanceRpc.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/17
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Account\Repository;
use Singularity\HDK\Pay\Domain\Account\Aggregate\AccountBalance;
use Singularity\HDK\Pay\Domain\Account\Aggregate\ValueObject\PointsBalance;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
interface AccountRepoInterface
{
public function getAccount(string $uid): AccountBalance;
public function getPointBalance(string $uid, PointType $pointType): PointsBalance;
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* AggregateRoot.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/31
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain;
abstract class AggregateRoot implements AggregateRootInterface {
/**
* @var array{}|DomainEvent[]
*/
protected array $domainEvents = [];
public function recordEvent(DomainEvent $event): void
{
$this->domainEvents[] = $event;
}
public function flushAllEvent(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* AbstractAggregateRoot.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/25
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain;
interface AggregateRootInterface {}

View File

@@ -0,0 +1,12 @@
<?php
/**
* DomainEvent.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/31
*/
namespace Singularity\HDK\Pay\Domain;
interface DomainEvent {}

View File

@@ -0,0 +1,14 @@
<?php
/**
* DomainException.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/8
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain;
class DomainException extends \RuntimeException {}

View File

@@ -0,0 +1,27 @@
<?php
/**
* RechargeProduct.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Product\Aggregate\Entities;
use Money\Money;
use Singularity\HDK\Pay\Domain\Product\Aggregate\ValueObject\RechargeEffect;
use Singularity\HDK\Pay\Domain\Product\Enum\ProductType;
final class ProductItem
{
public function __construct(
public int $id,
public string $description,
public Money $unitPrice,
public ProductType $productType,
public RechargeEffect $effect,
) {}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* RechargeProduct.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Product\Aggregate;
use Singularity\HDK\Pay\Domain\AggregateRoot;
use Singularity\HDK\Pay\Domain\Product\Aggregate\Entities\ProductItem;
/**
* App\Domain\Product\Aggregate\RechargeProduct@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*
* @property-read ProductItem[] $plans
* @property-read ProductItem[] $packages
* @property-read ProductItem $oneTime
*/
final class RechargeProduct extends AggregateRoot
{
public function __construct(
private readonly ?ProductItem $oneTime,
private readonly ?ProductItem $renew = null,
private readonly array $plans = [],
private readonly array $packages = [],
) {}
/**
* @param array $idList
* @return array{'id': int, 'quantity': int, 'detail': ProductItem}
*/
public function selectItemList(array $idList): array
{
$result = [];
/** @var ProductItem $item */
foreach ([$this->oneTime, $this->renew, ...$this->plans, ...$this->packages] as $item) {
if (in_array($item->id, $idList)) {
$result[] = $item;
}
}
return $result;
}
/**
* @return ProductItem[]
*/
public function getPlans(): array
{
return $this->plans;
}
public function getRenew(): ?ProductItem
{
return $this->renew;
}
/**
* @return ProductItem[]
*/
public function getPackages(): array
{
return $this->packages;
}
public function getOneTime(): ?ProductItem
{
return $this->oneTime;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* RechargeEffect.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Product\Aggregate\ValueObject;
use Carbon\Carbon;
use Hyperf\Framework\Exception\NotImplementedException;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
final class RechargeEffect
{
private float $pointTotal;
public function __construct(
public PointType $pointType,
public float $pointBasic,
public float $pointBonus,
public ?Carbon $expiredAt = null,
public ?string $version = null,
) {
$this->pointTotal = (float)bcadd((string)$pointBasic, (string)$pointBonus, 4);
}
public function getPointTotal(): float
{
return $this->pointTotal;
}
/**
* @template TPoints of array{'total': float, 'basic': float, 'bonus': float}
* @return array{'points'?: TPoints, 'version'?: string, 'expired_at'?: Carbon}
*/
public function toArray(): array
{
return array_filter([
'points' => $this->pointTotal == 0
? null
: [
'total' => $this->pointTotal,
'basic' => $this->pointBasic,
'bonus' => $this->pointBonus,
],
'version' => $this->version,
'expired_at' => $this->expiredAt,
]);
}
/**
* @param PointType $pointType
* @param string $range
* @return Carbon
*/
private static function parseExpiredAt(PointType $pointType, string $range): Carbon
{
if ($pointType !== PointType::EMA) {
throw new NotImplementedException();
}
$now = Carbon::now();
preg_match('/^(\d+)(\w)$/', $range, $matches);
$quantity = $matches[1]; // 数字部分
$unit = $matches[2]; // 单位部分
return match ($unit) {
'Y' => $now->addYears($quantity),
'm' => $now->addMonths($quantity),
'd' => $now->addDays($quantity),
'H' => $now->addHours($quantity),
'i' => $now->addMinutes($quantity),
's' => $now->addSeconds($quantity),
default => $now
};
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* ProductType.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
namespace Singularity\HDK\Pay\Domain\Product\Enum;
enum ProductType {
case plan;
case pack;
case oneTime;
case renew;
public static function tryFrom(string $type): ?ProductType
{
return match ($type) {
'plan' => self::plan,
'pack' => self::pack,
'oneTime' => self::oneTime,
'renew' => self::renew,
default => null,
};
}
public static function names(): array
{
$result = [];
foreach (self::cases() as $case) {
$result[] = $case->name;
}
return $result;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* ExchangeRepoInterface.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/29
*/
namespace Singularity\HDK\Pay\Domain\Product\Repository;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
interface ExchangeRepoInterface
{
/**
* @param PointType $source
* @param PointType $target
* @return float
*/
public function getRate(PointType $source, PointType $target): float;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* RechargeProductRepoInterface.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/5
*/
namespace Singularity\HDK\Pay\Domain\Product\Repository;
use Singularity\HDK\Pay\Domain\Product\Aggregate\RechargeProduct;
interface RechargeProductRepoInterface
{
/**
* @return RechargeProduct
*/
public function findLuxPointProduct(): RechargeProduct;
/**
* @param string $uid
* @return RechargeProduct
*/
public function findEmaProduct(string $uid): RechargeProduct;
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* TransactionRecord.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Aggregate;
use Carbon\Carbon;
use Singularity\HDK\Pay\Domain\AggregateRoot;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Item;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction\CardTransaction;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction\PointTransaction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderAction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
final class TransactionRecord extends AggregateRoot
{
/**
* @param string $orderNo
* @param string $uid
* @param PayType $payType
* @param OrderAction $action
* @param OrderStatus $status
* @param string $source
* @param Item[] $items
* @param array<PointTransaction|CardTransaction> $transactions
* @param array $refunds
* @param array|null $external
* @param string|null $externalId
* @param Carbon|null $createdAt
* @param Carbon|null $occurredAt
* @param Carbon|null $lastRefundedAt
*/
public function __construct(
private readonly string $orderNo,
private readonly string $uid,
private readonly PayType $payType,
private readonly OrderAction $action,
private readonly OrderStatus $status,
private readonly string $source,
private readonly array $items,
private readonly array $transactions,
private readonly array $refunds,
private readonly ?array $external,
private readonly ?string $externalId,
private readonly ?Carbon $createdAt,
private readonly ?Carbon $occurredAt,
private readonly ?Carbon $lastRefundedAt,
) {}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Item.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject;
use Swoole\ArrayObject;
final class Item extends ArrayObject
{
public function __construct(
private readonly string $name,
private readonly int $quantity,
private readonly float $price,
) {
parent::__construct([
'name' => $name,
'quantity' => $quantity,
'price' => $price,
]);
}
public function getName(): string
{
return $this->name;
}
public function getQuantity(): int
{
return $this->quantity;
}
public function getPrice(): float
{
return $this->price;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Transaction.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Enum\PaymentMethod;
use Swoole\ArrayObject;
/**
* Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
abstract class Transaction extends ArrayObject
{
public function __construct(
private PayType $payType,
private PaymentMethod|PointType $type,
private float $amount,
private OrderStatus $status,
) {
parent::__construct(
[
'pay_type' => $payType,
'type' => $type,
'amount' => $amount,
'status' => $status,
],
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* CardTransaction.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Enum\PaymentMethod;
final class CardTransaction extends Transaction
{
public function __construct(
PaymentMethod $method,
float $amount,
OrderStatus $status,
) {
parent::__construct(
payType: PayType::Card,
type: $method,
amount: $amount,
status: $status,
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* PointTransaction.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
final class PointTransaction extends Transaction
{
public function __construct(PointType $type, float $amount, OrderStatus $status)
{
parent::__construct(
payType: PayType::Point,
type: $type,
amount: $amount,
status: $status,
);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* OrderAction.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/30
*/
namespace Singularity\HDK\Pay\Domain\Transaction\Enum;
enum OrderAction: string {
case Recharge = 'recharge';
case Consumption = 'consumption';
case Refund = 'refund';
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* OrderStatus.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2024/11/26
*/
namespace Singularity\HDK\Pay\Domain\Transaction\Enum;
enum OrderStatus
{
case created;
case paid;
case completed;
case refunded;
case cancelled;
case failed;
case closed;
public static function tryFrom(mixed $status): OrderStatus
{
$status = strtolower($status);
return match ($status) {
'paid' => self::paid,
'refunded' => self::refunded,
'cancelled' => self::cancelled,
'completed' => self::completed,
'failed' => self::failed,
'closed' => self::closed,
default => self::created,
};
}
public static function all(): array
{
return [
self::created,
self::paid,
self::completed,
self::refunded,
self::cancelled,
self::closed,
];
}
public static function values(): array
{
return array_column(self::all(), 'value');
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* PayType.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/25
*/
namespace Singularity\HDK\Pay\Domain\Transaction\Enum;
Enum PayType: string {
case Point = 'point';
case Card = 'card';
case Hybrid = 'hybrid';
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* OrderRepoInterface.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
namespace Singularity\HDK\Pay\Domain\Transaction\Repository;
use Singularity\HDK\Pay\Application\Command\ConsumeCmd;
use Singularity\HDK\Pay\Application\Command\RechargeCmd;
use Singularity\HDK\Pay\Application\Dto\Transaction\OrderDto;
interface OrderRepoInterface
{
public function recharge(RechargeCmd $cmd): OrderDto;
public function consume(ConsumeCmd $cmd): OrderDto;
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* CallbackDomainSvc.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain\Transaction\Service;
use Carbon\Carbon;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\TransactionRecord;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Item;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction\CardTransaction;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\ValueObject\Transaction\PointTransaction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderAction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Enum\PaymentMethod;
/**
* Singularity\HDK\Pay\Domain\Transaction\Service\CallbackDomainSvc@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
final class CallbackDomainSvc
{
/**
* @param array $order
* @return TransactionRecord
*/
public function callback(array $order): TransactionRecord
{
$payType = PayType::tryFrom($order['pay_type']);
return new TransactionRecord(
orderNo: $order['order_no'],
uid: $order['uid'],
payType: $payType,
action: OrderAction::tryFrom($order['action']),
status: OrderStatus::tryFrom($order['status']),
source: $order['source'],
items: array_map(
callback: fn(array $item) => new Item(
name: $item['name'],
quantity: $item['quantity'],
price: $item['unit_price'],
),
array: $order['items'],
),
transactions: array_map(
callback: fn(array $transaction)
=> $transaction['pay_type'] === PayType::Card->value
? new CardTransaction(
method: PaymentMethod::tryFrom($transaction['type']),
amount: $transaction['amount'],
status: OrderStatus::tryFrom($transaction['status']),
)
: new PointTransaction(
type: PointType::tryFrom($transaction['type']),
amount: $transaction['amount'],
status: OrderStatus::tryFrom($transaction['status']),
),
array: $order['transactions'],
),
refunds: $order['refunds'],
external: $order['external'],
externalId: $order['external_id'],
createdAt: isset($order['created_at']) ? new Carbon($order['created_at']) : null,
occurredAt: isset($order['occurred_at']) ? new Carbon($order['occurred_at']) : null,
lastRefundedAt: isset($order['last_refunded_at']) ? new Carbon($order['last_refunded_at']) : null,
);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* ValueObject.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/12
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Domain;
use Swoole\ArrayObject;
abstract class ValueObject extends ArrayObject {}

17
src/Enum/Environment.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
/**
* Environment.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/8
*/
namespace Singularity\HDK\Pay\Enum;
enum Environment: string
{
case Dev = 'development';
case Prod = 'production';
}

17
src/Enum/Site.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
/**
* Site.php@LuxPay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/8
*/
namespace Singularity\HDK\Pay\Enum;
enum Site: string
{
case ChinaMainland = 'CN';
case NorthAmerica = 'NA';
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* AbstractRepo.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Infrastructure\Repository;
use GuzzleHttp\RequestOptions;
use Singularity\HDK\Core\Http\RequestService;
use Singularity\HDK\Core\Http\RequestServiceFactory;
use Singularity\HDK\Pay\Trait\RequestHeaderBuilder;
use function Hyperf\Config\config;
abstract class AbstractRepo
{
use RequestHeaderBuilder;
protected RequestService $requestService;
public function __construct(?string $baseUrl = null)
{
$this->requestService = RequestServiceFactory::make([
'base_uri' => $baseUrl ?? config('payment.base_uri'),
RequestOptions::ALLOW_REDIRECTS => true,
'headers' => $this->headerBuilder(),
]);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* AccountBalanceRpc.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/17
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Infrastructure\Repository;
use Hyperf\Codec\Json;
use Singularity\HDK\Pay\Domain\Account\Aggregate\AccountBalance;
use Singularity\HDK\Pay\Domain\Account\Aggregate\ValueObject\PointsBalance;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Account\Repository\AccountRepoInterface;
final class AccountBalanceRepo extends AbstractRepo implements AccountRepoInterface
{
public function getAccount(string $uid): AccountBalance
{
$response = $this->requestService->requestGet(
url: "/rpc/v2/account/$uid/balance",
params: [
'type' => join(',', PointType::values()),
],
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new AccountBalance(
uid: $result['uid'],
pointsBalances: array_map(
callback: fn($pointBalance)
=> new PointsBalance(
type: PointType::from($pointBalance['name']),
total: $pointBalance['total'],
cost: $pointBalance['cost'],
amount: $pointBalance['amount'],
version: $pointBalance['version'],
expiredAt: $pointBalance['expired_at'],
),
array: $result['point_balance'],
),
);
}
public function getPointBalance(string $uid, PointType $pointType): PointsBalance
{
$response = $this->requestService->requestGet(
url: "/rpc/v2/account/$uid/balance/$pointType->value",
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new PointsBalance(
type: PointType::from($result['name']),
total: $result['total'],
cost: $result['cost'],
amount: $result['amount'],
version: $result['version'],
expiredAt: $result['expired_at'],
);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* OrderRepo.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Infrastructure\Repository;
use GuzzleHttp\Exception\GuzzleException;
use Hyperf\Codec\Json;
use Singularity\HDK\Pay\Application\Command\ConsumeCmd;
use Singularity\HDK\Pay\Application\Command\RechargeCmd;
use Singularity\HDK\Pay\Application\Dto\Transaction\OrderDto;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderAction;
use Singularity\HDK\Pay\Domain\Transaction\Enum\OrderStatus;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Domain\Transaction\Repository\OrderRepoInterface;
final class OrderRepo extends AbstractRepo implements OrderRepoInterface
{
/**
* @param RechargeCmd $cmd
* @return OrderDto
* @throws GuzzleException
*/
public function recharge(RechargeCmd $cmd): OrderDto
{
$response = $this->requestService->requestPost(
url: '/rpc/v2/transaction/orders/recharge',
data: [
'uid' => $cmd->uid,
'items' => $cmd->items,
],
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new OrderDto(
orderNo: $result['order_no'],
action: OrderAction::tryFrom($result['action']),
payType: PayType::tryFrom($result['pay_type']),
status: OrderStatus::tryFrom($result['status']),
);
}
public function consume(ConsumeCmd $cmd): OrderDto
{
$response = $this->requestService->requestPost(
url: '/rpc/v2/transaction/orders/consumption',
data: [
'uid' => $cmd->uid,
'type' => $cmd->type->value,
'method' => $cmd->method->value,
'items' => $cmd->items,
'external' => $cmd->external,
'external_id' => $cmd->externalID ?? '',
'remark' => $cmd->remark,
],
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new OrderDto(
orderNo: $result['order_no'],
action: OrderAction::tryFrom($result['action']),
payType: PayType::tryFrom($result['pay_type']),
status: OrderStatus::tryFrom($result['status']),
);
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* ProductRepo.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
declare(strict_types=1);
namespace Singularity\HDK\Pay\Infrastructure\Repository;
use GuzzleHttp\Exception\GuzzleException;
use Hyperf\Codec\Json;
use Money\Currency;
use Money\Money;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Product\Aggregate\Entities\ProductItem;
use Singularity\HDK\Pay\Domain\Product\Aggregate\RechargeProduct;
use Singularity\HDK\Pay\Domain\Product\Aggregate\ValueObject\RechargeEffect;
use Singularity\HDK\Pay\Domain\Product\Enum\ProductType;
use Singularity\HDK\Pay\Domain\Product\Repository\ExchangeRepoInterface;
use Singularity\HDK\Pay\Domain\Product\Repository\RechargeProductRepoInterface;
final class ProductRepo extends AbstractRepo implements RechargeProductRepoInterface, ExchangeRepoInterface
{
/**
* @inheritDoc
*/
public function findLuxPointProduct(): RechargeProduct
{
$response = $this->requestService->requestGet(
url: '/rpc/v2/products/lux-point',
options: [
'headers' => $this->headerBuilder(),
],
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new RechargeProduct(
oneTime: new ProductItem(
id: $result['one_time']['id'],
description: $result['one_time']['name'],
unitPrice: new Money(
amount: $result['one_time']['price'],
currency: new Currency($result['one_time']['currency']),
),
productType: ProductType::oneTime,
effect: new RechargeEffect(
pointType: PointType::LuxPoint,
pointBasic: $result['one_time']['point']['number'],
pointBonus: $result['one_time']['point']['bonus'],
),
),
packages: array_map(fn(array $item) => new ProductItem(
id: $item['id'],
description: $item['name'],
unitPrice: new Money(
amount: $item['price'],
currency: new Currency($item['currency']),
),
productType: ProductType::pack,
effect: new RechargeEffect(
pointType: PointType::LuxPoint,
pointBasic: $item['point']['number'],
pointBonus: $item['point']['bonus'],
),
), $result['package']),
);
}
/**
* @inheritDoc
*/
public function findEmaProduct(string $uid): RechargeProduct
{
$response = $this->requestService->requestGet(
url: '/rpc/v2/products/ema',
params: ['uid' => $uid],
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return new RechargeProduct(
oneTime: isset($result['one_time'])
? new ProductItem(
id: $result['one_time']['id'],
description: $result['one_time']['name'],
unitPrice: new Money(
amount: $result['one_time']['price'],
currency: new Currency($result['one_time']['currency']),
),
productType: ProductType::oneTime,
effect: new RechargeEffect(
pointType: PointType::EMA,
pointBasic: $result['one_time']['point']['number'],
pointBonus: $result['one_time']['point']['bonus'],
),
)
: null,
renew: isset($result['renew'])
? new ProductItem(
id: $result['renew']['id'],
description: $result['renew']['name'],
unitPrice: new Money(
amount: $result['renew']['price'],
currency: new Currency($result['renew']['currency']),
),
productType: ProductType::renew,
effect: new RechargeEffect(
pointType: PointType::EMA,
pointBasic: $result['renew']['point']['number'],
pointBonus: $result['renew']['point']['bonus'],
),
)
: null,
plans: array_map(fn(array $item) => new ProductItem(
id: $item['id'],
description: $item['name'],
unitPrice: new Money(
amount: $item['price'],
currency: new Currency($item['currency']),
),
productType: ProductType::plan,
effect: new RechargeEffect(
pointType: PointType::LuxPoint,
pointBasic: $item['point']['number'],
pointBonus: $item['point']['bonus'],
),
), $result['plan']),
);
}
/**
* @param PointType $source
* @param PointType $target
* @return float
* @throws GuzzleException
*/
public function getRate(PointType $source, PointType $target): float
{
$response = $this->requestService->requestGet(
url: "/rpc/v2/products/$target->value/exchange-rate/$source->value",
);
$content = $response->getBody()->getContents();
$result = Json::decode($content);
return $result['rate'];
}
}

View File

@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
/**
* WechatService.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/7/17
*/
namespace Singularity\HDK\Pay\Service;
/**
* Singularity\HDK\Pay\Service\WechatService@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/7/17
*/
final class WechatService
{
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* RequestHeaderBuilder.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
namespace Singularity\HDK\Pay\Trait;
use Lmc\HttpConstants\Header;
use Singularity\HDK\Core\I18n\Enum\Languages;
use Singularity\HDK\Pay\Enum\Environment;
use Singularity\HDK\Pay\Enum\Site;
use function Hyperf\Config\config;
trait RequestHeaderBuilder {
public function headerBuilder(): array
{
return [
Header::CONTENT_TYPE => 'application/json',
Header::ACCEPT_LANGUAGE => $locale?->value ?? config('translation.locale', Languages::EN->value),
'X-SP-ID' => config('app_name'),
'X-SITE' => (match (strtoupper(config('site', Site::NorthAmerica->value))) {
'CN' => Site::ChinaMainland,
default => Site::NorthAmerica,
})->value,
'X-ENV' => (match (strtolower(config('app_env', Environment::Dev->value))) {
'prod', 'production' => Environment::Prod,
default => Environment::Dev,
})->value,
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* QueryAccountInformationTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Singularity\HDK\Pay\Domain\Account\Aggregate\AccountBalance;
use Singularity\HDK\Pay\Infrastructure\Repository\AccountBalanceRepo;
use function Hyperf\Support\make;
it('should be able to query account information', function () {
$uid = '123456';
$repo = make(AccountBalanceRepo::class);
$result = $repo->getAccount($uid);
expect($result)
->toBeInstanceOf(AccountBalance::class)
->and($result->getUid())->tobe($uid);
});

View File

@@ -0,0 +1,24 @@
<?php
/**
* QueryPointBalanceTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Singularity\HDK\Pay\Domain\Account\Aggregate\ValueObject\PointsBalance;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Infrastructure\Repository\AccountBalanceRepo;
use function Hyperf\Support\make;
it('should query point balance', function () {
$uid = 'cn3221';
$repo = make(AccountBalanceRepo::class);
$point_balance = $repo->getPointBalance($uid, PointType::EMA);
expect($point_balance)
->toBeInstanceOf(PointsBalance::class)
->and($point_balance->getType())->toBe(PointType::EMA);
});

View File

@@ -0,0 +1,24 @@
<?php
/**
* QueryPointRateTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Infrastructure\Repository\ProductRepo;
use function Hyperf\Support\make;
it('can query point rate', function () {
$repo = make(ProductRepo::class);
$from = PointType::FtaiAligner;
$to = PointType::LuxPoint;
$rate = $repo->getRate($from, $to);
expect($rate)->toBeFloat();
});

View File

@@ -0,0 +1,46 @@
<?php
/**
* QueryProductsTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Pest\Expectation;
use Singularity\HDK\Pay\Domain\Product\Aggregate\Entities\ProductItem;
use Singularity\HDK\Pay\Domain\Product\Aggregate\RechargeProduct;
use Singularity\HDK\Pay\Infrastructure\Repository\ProductRepo;
use function Hyperf\Support\make;
it('should can query LuxPoint products', function () {
$repo = make(ProductRepo::class);
$products = $repo->findLuxPointProduct();
expect($products)
->toBeInstanceOf(RechargeProduct::class)
->and($products->getOneTime())->toBeInstanceOf(ProductItem::class)
->and($products->getPackages())->each->toBeInstanceOf(ProductItem::class);
});
it('should can query EMA products', function () {
$repo = make(ProductRepo::class);
$uid = 'cn3321';
$products = $repo->findEmaProduct($uid);
expect($products)
->toBeInstanceOf(RechargeProduct::class)
->when(
$products->getOneTime() !== null,
fn(Expectation $expectation) => $expectation
->and($products->getOneTime())->toBeInstanceOf(ProductItem::class),
)
->when(
$products->getRenew() !== null,
fn(Expectation $expectation) => $expectation
->and($products->getRenew())->toBeInstanceOf(ProductItem::class),
)
->and($products->getPlans())->each->toBeInstanceOf(ProductItem::class);
});

View File

@@ -0,0 +1,34 @@
<?php
/**
* CreateConsumptionOrderTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Singularity\HDK\Pay\Application\Command\ConsumeCmd;
use Singularity\HDK\Pay\Application\Dto\Transaction\OrderDto;
use Singularity\HDK\Pay\Domain\Account\Enum\PointType;
use Singularity\HDK\Pay\Domain\Transaction\Enum\PayType;
use Singularity\HDK\Pay\Infrastructure\Repository\OrderRepo;
use function Hyperf\Support\make;
it('should can create consumption order', function () {
$uid = 'cn3321';
$cmd = (new ConsumeCmd(
uid: $uid,
type: PayType::Point,
method: PointType::FtaiAligner,
external: [],
externalID: null,
remark: '',
))->addItem('Design FTAI Aligner', 1, 5);
$repo = make(OrderRepo::class);
$result = $repo->consume($cmd);
expect($result)->toBeInstanceOf(OrderDto::class);
});

View File

@@ -0,0 +1,23 @@
<?php
/**
* CreateRechargeOrderTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/18
*/
use Singularity\HDK\Pay\Application\Command\RechargeCmd;
use Singularity\HDK\Pay\Application\Dto\Transaction\OrderDto;
use Singularity\HDK\Pay\Infrastructure\Repository\OrderRepo;
use function Hyperf\Support\make;
it('should can create recharge order', function () {
$cmd = (new RechargeCmd('cn3321'))->addItem(1, 2);
$repo = make(OrderRepo::class);
$result = $repo->recharge($cmd);
expect($result)->toBeInstanceOf(OrderDto::class);
});

View File

@@ -0,0 +1,99 @@
<?php
/**
* LuxPayCallbackTest.php@Pay
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/20
*/
use Hyperf\Codec\Json;
use Singularity\HDK\Pay\Domain\Transaction\Aggregate\TransactionRecord;
use Singularity\HDK\Pay\Domain\Transaction\Service\CallbackDomainSvc;
use function Hyperf\Support\make;
dataset('cases', [
'recharge' => [
<<<JSON
{
"order_no": "202508191929138659DTB",
"uid": "cn3321",
"pay_type": "card",
"action": "recharge",
"status": "created",
"source": "LuxDesign",
"items": [
{
"name": "Starter Pack",
"unit_price": 399,
"quantity": 1
}
],
"transactions": [
{
"pay_type": "card",
"type": "stripe.checkout",
"amount": 399,
"status": "created"
}
],
"refunds": [],
"external": [],
"external_id": "",
"created_at": "2025-08-20 10:44:46",
"occurred_at": null,
"last_refunded_at": null
}
JSON,
],
'consumption' => [
<<<JSON
{
"order_no": "202508190441101116DTB",
"uid": "cn3321",
"pay_type": "hybrid",
"action": "consumption",
"status": "created",
"source": "LuxDesign",
"items": [
{
"name": "Design - retainer",
"quantity": 1,
"unit_price": 3
}
],
"transactions": [
{
"pay_type": "card",
"type": "stripe.checkout",
"amount": 3,
"status": "created"
},
{
"pay_type": "point",
"type": "lux-point",
"amount": 3.05,
"status": "created"
}
],
"refunds": [],
"external": [],
"external_id": "",
"created_at": "2025-08-20 10:44:46",
"occurred_at": null,
"last_refunded_at": null
}
JSON,
]
]);
it(
'should parse request recharge body to TransactionRecord Entity',
function ($data) {
$data = Json::decode($data);
$entity = make(CallbackDomainSvc::class)->callback($data);
expect($entity)->toBeInstanceOf(TransactionRecord::class);
},
)->with('cases');

53
tests/HttpTestCase.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Singularity\HDK\Test\Pay;
use Hyperf\Testing\Client;
use PHPUnit\Framework\TestCase;
use function Hyperf\Support\make;
/**
* Class HttpTestCase.
* @method get($uri, $data = [], $headers = [])
* @method post($uri, $data = [], $headers = [])
* @method json($uri, $data = [], $headers = [])
* @method file($uri, $data = [], $headers = [])
* @method request($method, $path, $options = [])
*/
abstract class HttpTestCase extends TestCase
{
/**
* @var Client
*/
protected Client $client;
/**
* @inheritDoc
*/
public function __construct(?string $name = null)
{
parent::__construct($name);
$this->client = make(Client::class);
}
/**
* @param string $name
* @param array<string, mixed> $arguments
* @return array<string, mixed>
*/
public function __call(string $name, array $arguments): array
{
return $this->client->{$name}(...$arguments);
}
}

32
tests/Pest.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
use Singularity\HDK\Test\Pay\HttpTestCase;
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your tests functions is always bound to a specific PHPUnit tests
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
// uses(HttpTestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your tests files.
|
*/
/*function something()
{
// ..
}*/

View File

@@ -2,19 +2,16 @@
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\Config\Config;
use Hyperf\Context\ApplicationContext;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\ClassLoader;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSource;
use Psr\Container\ContainerInterface;
use Swoole\Runtime;
use function Hyperf\Support\env;
use function Hyperf\Support\make;
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
@@ -22,16 +19,26 @@ ini_set('display_startup_errors', 'on');
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
!defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
Swoole\Runtime::enableCoroutine(true);
Runtime::enableCoroutine(SWOOLE_HOOK_FLAGS);
require BASE_PATH . '/vendor/autoload.php';
Hyperf\Di\ClassLoader::init();
ClassLoader::init();
$container = new Container(new DefinitionSource([]));
if (!$container instanceof ContainerInterface) {
throw new RuntimeException('The dependency injection container is invalid.');
}
$container = ApplicationContext::setContainer($container);
$container = ApplicationContext::setContainer($container);
/** @var Config $config */
$config = make(Config::class, [
[
'app_name' => 'LuxDesign',
'dependencies' => [],
'payment' => [
'base_uri' => env('LUX_PAY_BASE_URI', 'http://test-pay.luxcreo.com'),
],
],
]);
ApplicationContext::getContainer()->set(ConfigInterface::class, $config);