feat: update appraisal ordering and payment flows
This commit is contained in:
450
server-api/app/support/AppraisalServicePricePackageService.php
Normal file
450
server-api/app/support/AppraisalServicePricePackageService.php
Normal file
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
namespace app\support;
|
||||
|
||||
use support\think\Db;
|
||||
|
||||
class AppraisalServicePricePackageService
|
||||
{
|
||||
private const TABLE = 'appraisal_service_price_packages';
|
||||
|
||||
private const PROVIDERS = [
|
||||
'anxinyan' => [
|
||||
'text' => '安心验鉴定',
|
||||
'sla_hours' => 48,
|
||||
'default_package_name' => '安心验基础套餐',
|
||||
'default_package_code' => 'anxinyan_basic',
|
||||
'default_price' => 99.00,
|
||||
],
|
||||
'zhongjian' => [
|
||||
'text' => '中检鉴定',
|
||||
'sla_hours' => 72,
|
||||
'default_package_name' => '中检基础套餐',
|
||||
'default_package_code' => 'zhongjian_basic',
|
||||
'default_price' => 199.00,
|
||||
],
|
||||
];
|
||||
|
||||
public function adminIndex(): array
|
||||
{
|
||||
return [
|
||||
'providers' => $this->providerOptions(),
|
||||
'list' => $this->list(false),
|
||||
];
|
||||
}
|
||||
|
||||
public function serviceOptions(): array
|
||||
{
|
||||
$packages = $this->list(true);
|
||||
$grouped = [];
|
||||
foreach ($packages as $package) {
|
||||
$grouped[$package['service_provider']][] = $package;
|
||||
}
|
||||
|
||||
return array_map(function (string $serviceProvider) use ($grouped) {
|
||||
$provider = self::PROVIDERS[$serviceProvider];
|
||||
$items = $grouped[$serviceProvider] ?? [];
|
||||
$defaultPackage = $this->defaultFromList($items);
|
||||
|
||||
return [
|
||||
'service_provider' => $serviceProvider,
|
||||
'service_provider_text' => $provider['text'],
|
||||
'price' => $defaultPackage ? (float)$defaultPackage['price'] : (float)$provider['default_price'],
|
||||
'sla_hours' => (int)$provider['sla_hours'],
|
||||
'default_package_id' => $defaultPackage ? (int)$defaultPackage['id'] : 0,
|
||||
'default_package' => $defaultPackage,
|
||||
'packages' => $items,
|
||||
];
|
||||
}, array_keys(self::PROVIDERS));
|
||||
}
|
||||
|
||||
public function list(bool $enabledOnly = false): array
|
||||
{
|
||||
$query = Db::name(self::TABLE);
|
||||
if ($enabledOnly) {
|
||||
$query->where('is_enabled', 1);
|
||||
}
|
||||
|
||||
$rows = $query
|
||||
->order('service_provider', 'asc')
|
||||
->order('sort_order', 'asc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return array_map(fn (array $row) => $this->formatPackage($row), $rows);
|
||||
}
|
||||
|
||||
public function enabledPackages(string $serviceProvider): array
|
||||
{
|
||||
$serviceProvider = $this->normalizeServiceProvider($serviceProvider);
|
||||
|
||||
$rows = Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('is_enabled', 1)
|
||||
->order('sort_order', 'asc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return array_map(fn (array $row) => $this->formatPackage($row), $rows);
|
||||
}
|
||||
|
||||
public function save(array $payload, int $id = 0): int
|
||||
{
|
||||
$serviceProvider = $this->normalizeServiceProvider((string)($payload['service_provider'] ?? ''));
|
||||
$packageName = $this->limitText(trim((string)($payload['package_name'] ?? '')), 128);
|
||||
$packageCode = $this->normalizePackageCode((string)($payload['package_code'] ?? ''));
|
||||
$price = $this->normalizePrice($payload['price'] ?? null);
|
||||
$description = $this->limitText(trim((string)($payload['description'] ?? '')), 500);
|
||||
$isEnabled = !empty($payload['is_enabled']) ? 1 : 0;
|
||||
$isDefault = !empty($payload['is_default']) ? 1 : 0;
|
||||
$sortOrder = (int)($payload['sort_order'] ?? 0);
|
||||
|
||||
if ($packageName === '') {
|
||||
throw new \RuntimeException('套餐名称不能为空');
|
||||
}
|
||||
if ($packageCode === '') {
|
||||
throw new \RuntimeException('套餐编码不能为空,只能使用字母、数字、下划线或短横线');
|
||||
}
|
||||
if ($isDefault && !$isEnabled) {
|
||||
throw new \RuntimeException('默认套餐必须保持启用');
|
||||
}
|
||||
|
||||
$exists = null;
|
||||
if ($id > 0) {
|
||||
$exists = Db::name(self::TABLE)->where('id', $id)->find();
|
||||
if (!$exists) {
|
||||
throw new \RuntimeException('价格套餐不存在');
|
||||
}
|
||||
if ((string)$exists['service_provider'] !== $serviceProvider) {
|
||||
throw new \RuntimeException('已创建套餐不支持切换服务方');
|
||||
}
|
||||
}
|
||||
|
||||
$duplicate = Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('package_code', $packageCode)
|
||||
->when($id > 0, fn ($query) => $query->where('id', '<>', $id))
|
||||
->find();
|
||||
if ($duplicate) {
|
||||
throw new \RuntimeException('同一服务方下套餐编码不能重复');
|
||||
}
|
||||
|
||||
if (!$isEnabled && $exists && (int)$exists['is_enabled'] === 1) {
|
||||
$enabledCount = (int)Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('is_enabled', 1)
|
||||
->where('id', '<>', $id)
|
||||
->count();
|
||||
if ($enabledCount === 0) {
|
||||
throw new \RuntimeException('每个服务方至少需要保留一个启用套餐');
|
||||
}
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$data = [
|
||||
'service_provider' => $serviceProvider,
|
||||
'package_name' => $packageName,
|
||||
'package_code' => $packageCode,
|
||||
'price' => $price,
|
||||
'description' => $description,
|
||||
'is_enabled' => $isEnabled,
|
||||
'is_default' => $isDefault,
|
||||
'sort_order' => $sortOrder,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
if ($id > 0) {
|
||||
Db::name(self::TABLE)->where('id', $id)->update($data);
|
||||
$packageId = $id;
|
||||
} else {
|
||||
$data['created_at'] = $now;
|
||||
$packageId = (int)Db::name(self::TABLE)->insertGetId($data);
|
||||
}
|
||||
|
||||
if ($isDefault) {
|
||||
$this->clearOtherDefaults($serviceProvider, $packageId);
|
||||
}
|
||||
$this->normalizeProviderDefault($serviceProvider);
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $packageId;
|
||||
}
|
||||
|
||||
public function setEnabled(int $id, bool $enabled): void
|
||||
{
|
||||
$package = Db::name(self::TABLE)->where('id', $id)->find();
|
||||
if (!$package) {
|
||||
throw new \RuntimeException('价格套餐不存在');
|
||||
}
|
||||
|
||||
$serviceProvider = (string)$package['service_provider'];
|
||||
if (!$enabled) {
|
||||
$enabledCount = (int)Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('is_enabled', 1)
|
||||
->where('id', '<>', $id)
|
||||
->count();
|
||||
if ($enabledCount === 0) {
|
||||
throw new \RuntimeException('每个服务方至少需要保留一个启用套餐');
|
||||
}
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
Db::name(self::TABLE)->where('id', $id)->update([
|
||||
'is_enabled' => $enabled ? 1 : 0,
|
||||
'is_default' => $enabled ? (int)$package['is_default'] : 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$this->normalizeProviderDefault($serviceProvider);
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function setDefault(int $id): void
|
||||
{
|
||||
$package = Db::name(self::TABLE)->where('id', $id)->find();
|
||||
if (!$package) {
|
||||
throw new \RuntimeException('价格套餐不存在');
|
||||
}
|
||||
if ((int)$package['is_enabled'] !== 1) {
|
||||
throw new \RuntimeException('停用套餐不能设为默认套餐');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
Db::name(self::TABLE)
|
||||
->where('service_provider', (string)$package['service_provider'])
|
||||
->update([
|
||||
'is_default' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
Db::name(self::TABLE)->where('id', $id)->update([
|
||||
'is_default' => 1,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveForOrder(string $serviceProvider, int $packageId = 0, string $packageCode = ''): array
|
||||
{
|
||||
$serviceProvider = $this->normalizeServiceProvider($serviceProvider);
|
||||
$packageCode = trim($packageCode);
|
||||
|
||||
$query = Db::name(self::TABLE)->where('service_provider', $serviceProvider);
|
||||
if ($packageId > 0) {
|
||||
$query->where('id', $packageId);
|
||||
} elseif ($packageCode !== '') {
|
||||
$query->where('package_code', $packageCode);
|
||||
} else {
|
||||
$query->where('is_enabled', 1)
|
||||
->order('is_default', 'desc')
|
||||
->order('sort_order', 'asc')
|
||||
->order('id', 'asc');
|
||||
}
|
||||
|
||||
$package = $query->find();
|
||||
if (!$package) {
|
||||
throw new \RuntimeException('当前服务暂无可用价格套餐');
|
||||
}
|
||||
if ((int)$package['is_enabled'] !== 1) {
|
||||
throw new \RuntimeException('所选价格套餐已停用,请重新选择');
|
||||
}
|
||||
|
||||
return $this->formatPackage($package);
|
||||
}
|
||||
|
||||
public function snapshotForOrder(string $serviceProvider, int $packageId = 0, string $packageCode = ''): array
|
||||
{
|
||||
$package = $this->resolveForOrder($serviceProvider, $packageId, $packageCode);
|
||||
|
||||
return [
|
||||
'price_package_id' => (int)$package['id'],
|
||||
'price_package_name' => (string)$package['package_name'],
|
||||
'price_package_code' => (string)$package['package_code'],
|
||||
'price_package_price' => (float)$package['price'],
|
||||
'pay_amount' => (float)$package['price'],
|
||||
'sla_hours' => (int)$package['sla_hours'],
|
||||
'service_provider_text' => (string)$package['service_provider_text'],
|
||||
];
|
||||
}
|
||||
|
||||
public function providerOptions(): array
|
||||
{
|
||||
return array_map(fn (string $serviceProvider) => [
|
||||
'service_provider' => $serviceProvider,
|
||||
'service_provider_text' => self::PROVIDERS[$serviceProvider]['text'],
|
||||
'sla_hours' => (int)self::PROVIDERS[$serviceProvider]['sla_hours'],
|
||||
], array_keys(self::PROVIDERS));
|
||||
}
|
||||
|
||||
public function serviceProviderText(string $serviceProvider): string
|
||||
{
|
||||
$serviceProvider = isset(self::PROVIDERS[$serviceProvider]) ? $serviceProvider : 'anxinyan';
|
||||
return self::PROVIDERS[$serviceProvider]['text'];
|
||||
}
|
||||
|
||||
public function defaultSeeds(): array
|
||||
{
|
||||
return array_map(function (string $serviceProvider) {
|
||||
$provider = self::PROVIDERS[$serviceProvider];
|
||||
return [
|
||||
'service_provider' => $serviceProvider,
|
||||
'package_name' => $provider['default_package_name'],
|
||||
'package_code' => $provider['default_package_code'],
|
||||
'price' => (float)$provider['default_price'],
|
||||
'description' => '默认服务价格套餐',
|
||||
'is_enabled' => 1,
|
||||
'is_default' => 1,
|
||||
'sort_order' => 1,
|
||||
];
|
||||
}, array_keys(self::PROVIDERS));
|
||||
}
|
||||
|
||||
private function formatPackage(array $row): array
|
||||
{
|
||||
$serviceProvider = isset(self::PROVIDERS[(string)($row['service_provider'] ?? '')])
|
||||
? (string)$row['service_provider']
|
||||
: 'anxinyan';
|
||||
$provider = self::PROVIDERS[$serviceProvider];
|
||||
|
||||
return [
|
||||
'id' => (int)($row['id'] ?? 0),
|
||||
'service_provider' => $serviceProvider,
|
||||
'service_provider_text' => $provider['text'],
|
||||
'package_name' => (string)($row['package_name'] ?? ''),
|
||||
'package_code' => (string)($row['package_code'] ?? ''),
|
||||
'price' => (float)($row['price'] ?? 0),
|
||||
'description' => (string)($row['description'] ?? ''),
|
||||
'is_enabled' => (bool)($row['is_enabled'] ?? false),
|
||||
'is_default' => (bool)($row['is_default'] ?? false),
|
||||
'sort_order' => (int)($row['sort_order'] ?? 0),
|
||||
'sla_hours' => (int)$provider['sla_hours'],
|
||||
'created_at' => (string)($row['created_at'] ?? ''),
|
||||
'updated_at' => (string)($row['updated_at'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function defaultFromList(array $packages): ?array
|
||||
{
|
||||
foreach ($packages as $package) {
|
||||
if (!empty($package['is_default'])) {
|
||||
return $package;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages[0] ?? null;
|
||||
}
|
||||
|
||||
private function normalizeServiceProvider(string $serviceProvider): string
|
||||
{
|
||||
$serviceProvider = trim($serviceProvider);
|
||||
if (!isset(self::PROVIDERS[$serviceProvider])) {
|
||||
throw new \RuntimeException('服务类型不正确');
|
||||
}
|
||||
|
||||
return $serviceProvider;
|
||||
}
|
||||
|
||||
private function normalizePackageCode(string $packageCode): string
|
||||
{
|
||||
$packageCode = strtolower(trim($packageCode));
|
||||
if (preg_match('/^[a-z0-9_-]{2,64}$/', $packageCode) !== 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $packageCode;
|
||||
}
|
||||
|
||||
private function normalizePrice(mixed $value): float
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
if ($value === '' || preg_match('/^\d+(\.\d{1,2})?$/', $value) !== 1) {
|
||||
throw new \RuntimeException('套餐价格需填写大于 0 的金额,最多保留 2 位小数');
|
||||
}
|
||||
|
||||
$price = round((float)$value, 2);
|
||||
if ($price <= 0 || $price > 999999.99) {
|
||||
throw new \RuntimeException('套餐价格需大于 0 且不超过 999999.99');
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
private function limitText(string $value, int $maxLength): string
|
||||
{
|
||||
if (function_exists('mb_substr')) {
|
||||
return mb_substr($value, 0, $maxLength, 'UTF-8');
|
||||
}
|
||||
|
||||
return substr($value, 0, $maxLength);
|
||||
}
|
||||
|
||||
private function clearOtherDefaults(string $serviceProvider, int $packageId): void
|
||||
{
|
||||
Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('id', '<>', $packageId)
|
||||
->update([
|
||||
'is_default' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeProviderDefault(string $serviceProvider): void
|
||||
{
|
||||
$candidate = Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('is_enabled', 1)
|
||||
->order('is_default', 'desc')
|
||||
->order('sort_order', 'asc')
|
||||
->order('id', 'asc')
|
||||
->find();
|
||||
if (!$candidate) {
|
||||
Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('is_default', 1)
|
||||
->update([
|
||||
'is_default' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$candidateId = (int)$candidate['id'];
|
||||
Db::name(self::TABLE)
|
||||
->where('service_provider', $serviceProvider)
|
||||
->where('id', '<>', $candidateId)
|
||||
->where('is_default', 1)
|
||||
->update([
|
||||
'is_default' => 0,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
if ((int)$candidate['is_default'] !== 1) {
|
||||
Db::name(self::TABLE)->where('id', $candidateId)->update([
|
||||
'is_default' => 1,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user