From 48f44f4c4ceef95e7315ce52924ea3729f8214f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=B8=9C=E4=BA=91?= Date: Mon, 1 Sep 2025 17:18:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(product):=20=E6=94=AF=E6=8C=81=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=20FTAI=20=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 findFtaiProduct 方法,用于查询 FTAI 产品信息 - 修改 RechargeProductsDto 以支持 FTAI 产品的特殊逻辑 - 更新 ProductItem 实体,允许 effect 属性为 null - 调整产品价格的处理方式,确保精度正确 -增加 FTAI 产品查询的单元测试 --- .../Dto/Product/RechargeProductsDto.php | 46 +++++++------ .../Aggregate/Entities/ProductItem.php | 2 +- src/Infrastructure/Repository/ProductRepo.php | 65 +++++++++++++++++-- tests/Feature/Product/QueryProductsTest.php | 17 +++++ 4 files changed, 104 insertions(+), 26 deletions(-) diff --git a/src/Application/Dto/Product/RechargeProductsDto.php b/src/Application/Dto/Product/RechargeProductsDto.php index 95044c3..287826d 100644 --- a/src/Application/Dto/Product/RechargeProductsDto.php +++ b/src/Application/Dto/Product/RechargeProductsDto.php @@ -43,28 +43,36 @@ final class RechargeProductsDto extends AbstractDto $effect = $product->effect; $price = $product->unitPrice; - return [ + $result = [ '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, - ], + 'price' => (float)bcdiv($price->getAmount(), '100', 2), ]; + + $is_ftai = empty($effect); + if (!$is_ftai) { + $result += [ + '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, + ], + ]; + } + + return $result; } } \ No newline at end of file diff --git a/src/Domain/Product/Aggregate/Entities/ProductItem.php b/src/Domain/Product/Aggregate/Entities/ProductItem.php index a2584f7..912642b 100644 --- a/src/Domain/Product/Aggregate/Entities/ProductItem.php +++ b/src/Domain/Product/Aggregate/Entities/ProductItem.php @@ -22,6 +22,6 @@ final class ProductItem public string $description, public Money $unitPrice, public ProductType $productType, - public RechargeEffect $effect, + public ?RechargeEffect $effect = null, ) {} } \ No newline at end of file diff --git a/src/Infrastructure/Repository/ProductRepo.php b/src/Infrastructure/Repository/ProductRepo.php index 35b31c2..85d9c84 100644 --- a/src/Infrastructure/Repository/ProductRepo.php +++ b/src/Infrastructure/Repository/ProductRepo.php @@ -45,7 +45,7 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter id: $result['one_time']['id'], description: $result['one_time']['name'], unitPrice: new Money( - amount: $result['one_time']['price'], + amount:bcmul((string)$result['one_time']['price'], '100'), currency: new Currency($result['one_time']['currency']), ), productType: ProductType::oneTime, @@ -59,7 +59,7 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter id: $item['id'], description: $item['name'], unitPrice: new Money( - amount: $item['price'], + amount: bcmul((string)$item['price'], '100'), currency: new Currency($item['currency']), ), productType: ProductType::pack, @@ -72,6 +72,59 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter ); } + public function findFtaiProduct(string $uid, PointType $pointType): RechargeProduct + { + $response = $this->requestService->requestGet( + url: '/rpc/v2/products/ftai', + params: ['uid' => $uid, 'type' => $pointType->value], + ); + + $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: bcmul((string)$result['one_time']['price'], '100'), + currency: new Currency($result['one_time']['currency']), + ), + productType: ProductType::oneTime, + effect: new RechargeEffect( + pointType: $pointType, + pointBasic: $result['one_time']['point']['number'], + pointBonus: $result['one_time']['point']['bonus'], + ), + ) + : null, + plans: array_map(fn(array $item) => new ProductItem( + id: $item['id'], + description: $item['name'], + unitPrice: new Money( + amount: bcmul((string)$item['price'], '100'), + currency: new Currency($item['currency']), + ), + productType: ProductType::plan, + ), $result['plan']), + packages: array_map(fn(array $item) => new ProductItem( + id: $item['id'], + description: $item['name'], + unitPrice: new Money( + amount: bcmul((string)$item['price'], '100'), + currency: new Currency($item['currency']), + ), + productType: ProductType::plan, + effect: new RechargeEffect( + pointType: $pointType, + pointBasic: $item['point']['number'], + pointBonus: $item['point']['bonus'], + ), + ), $result['package']), + ); + } + /** * @inheritDoc */ @@ -91,7 +144,7 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter id: $result['one_time']['id'], description: $result['one_time']['name'], unitPrice: new Money( - amount: $result['one_time']['price'], + amount:bcmul((string)$result['one_time']['price'], '100'), currency: new Currency($result['one_time']['currency']), ), productType: ProductType::oneTime, @@ -107,7 +160,7 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter id: $result['renew']['id'], description: $result['renew']['name'], unitPrice: new Money( - amount: $result['renew']['price'], + amount: bcmul((string)$result['renew']['price'], '100'), currency: new Currency($result['renew']['currency']), ), productType: ProductType::renew, @@ -122,12 +175,12 @@ final class ProductRepo extends AbstractRepo implements RechargeProductRepoInter id: $item['id'], description: $item['name'], unitPrice: new Money( - amount: $item['price'], + amount: bcmul((string)$item['price'], '100'), currency: new Currency($item['currency']), ), productType: ProductType::plan, effect: new RechargeEffect( - pointType: PointType::LuxPoint, + pointType: PointType::EMA, pointBasic: $item['point']['number'], pointBonus: $item['point']['bonus'], ), diff --git a/tests/Feature/Product/QueryProductsTest.php b/tests/Feature/Product/QueryProductsTest.php index 2f1aa16..495d9a0 100644 --- a/tests/Feature/Product/QueryProductsTest.php +++ b/tests/Feature/Product/QueryProductsTest.php @@ -9,6 +9,7 @@ */ use Pest\Expectation; +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\Infrastructure\Repository\ProductRepo; @@ -44,3 +45,19 @@ it('should can query EMA products', function () { ) ->and($products->getPlans())->each->toBeInstanceOf(ProductItem::class); }); + +it('should can query FTAI products', function () { + $repo = make(ProductRepo::class); + + $uid = '61efabc21e5a2'; + $products = $repo->findFtaiProduct($uid, PointType::FtaiAligner); + expect($products) + ->toBeInstanceOf(RechargeProduct::class) + ->when( + $products->getOneTime() !== null, + fn(Expectation $expectation) => $expectation + ->and($products->getOneTime())->toBeInstanceOf(ProductItem::class), + ) + ->and($products->getPackages())->each->toBeInstanceOf(ProductItem::class) + ->and($products->getPlans())->each->toBeInstanceOf(ProductItem::class); +});