feat(product): 支持查询 FTAI 产品

- 新增 findFtaiProduct 方法,用于查询 FTAI 产品信息
- 修改 RechargeProductsDto 以支持 FTAI 产品的特殊逻辑
- 更新 ProductItem 实体,允许 effect 属性为 null
- 调整产品价格的处理方式,确保精度正确
-增加 FTAI 产品查询的单元测试
This commit is contained in:
李东云
2025-09-01 17:18:01 +08:00
parent 25612823a5
commit 48f44f4c4c
4 changed files with 104 additions and 26 deletions

View File

@@ -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;
}
}

View File

@@ -22,6 +22,6 @@ final class ProductItem
public string $description,
public Money $unitPrice,
public ProductType $productType,
public RechargeEffect $effect,
public ?RechargeEffect $effect = null,
) {}
}

View File

@@ -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'],
),

View File

@@ -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);
});