新增客户端资产、钱包、订单、实名、设备管理等核心业务 Handler 与 DTO: - 客户端资产信息查询、套餐列表、套餐历史、资产刷新 - 客户端钱包详情、流水、充值校验、充值订单、充值记录 - 客户端订单创建、列表、详情 - 客户端实名认证链接获取 - 客户端设备卡列表、重启、恢复出厂、WiFi配置、切卡 - 客户端订单服务(含微信/支付宝支付流程) - 强充自动代购异步任务处理 - 数据库迁移 000084:充值记录增加自动代购状态字段
702 lines
22 KiB
Go
702 lines
22 KiB
Go
// Package client_order 提供 C 端订单下单服务。
|
|
package client_order
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
asset "github.com/break/junhong_cmp_fiber/internal/service/asset"
|
|
"github.com/break/junhong_cmp_fiber/internal/service/purchase_validation"
|
|
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"github.com/break/junhong_cmp_fiber/pkg/wechat"
|
|
"github.com/bytedance/sonic"
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
clientPurchaseIdempotencyTTL = 5 * time.Minute
|
|
clientPurchaseLockTTL = 10 * time.Second
|
|
)
|
|
|
|
// WechatConfigServiceInterface 微信配置服务接口。
|
|
type WechatConfigServiceInterface interface {
|
|
GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
|
|
}
|
|
|
|
// ForceRechargeRequirement 强充要求。
|
|
type ForceRechargeRequirement struct {
|
|
NeedForceRecharge bool
|
|
ForceRechargeAmount int64
|
|
}
|
|
|
|
// Service 客户端订单服务。
|
|
type Service struct {
|
|
assetService *asset.Service
|
|
purchaseValidationService *purchase_validation.Service
|
|
orderStore *postgres.OrderStore
|
|
rechargeRecordStore *postgres.AssetRechargeStore
|
|
walletStore *postgres.AssetWalletStore
|
|
personalDeviceStore *postgres.PersonalCustomerDeviceStore
|
|
openIDStore *postgres.PersonalCustomerOpenIDStore
|
|
wechatConfigService WechatConfigServiceInterface
|
|
packageSeriesStore *postgres.PackageSeriesStore
|
|
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
|
redis *redis.Client
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// New 创建客户端订单服务。
|
|
func New(
|
|
assetService *asset.Service,
|
|
purchaseValidationService *purchase_validation.Service,
|
|
orderStore *postgres.OrderStore,
|
|
rechargeRecordStore *postgres.AssetRechargeStore,
|
|
walletStore *postgres.AssetWalletStore,
|
|
personalDeviceStore *postgres.PersonalCustomerDeviceStore,
|
|
openIDStore *postgres.PersonalCustomerOpenIDStore,
|
|
wechatConfigService WechatConfigServiceInterface,
|
|
packageSeriesStore *postgres.PackageSeriesStore,
|
|
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
|
|
redisClient *redis.Client,
|
|
logger *zap.Logger,
|
|
) *Service {
|
|
return &Service{
|
|
assetService: assetService,
|
|
purchaseValidationService: purchaseValidationService,
|
|
orderStore: orderStore,
|
|
rechargeRecordStore: rechargeRecordStore,
|
|
walletStore: walletStore,
|
|
personalDeviceStore: personalDeviceStore,
|
|
openIDStore: openIDStore,
|
|
wechatConfigService: wechatConfigService,
|
|
packageSeriesStore: packageSeriesStore,
|
|
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
|
redis: redisClient,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CreateOrder 创建客户端订单。
|
|
func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.ClientCreateOrderRequest) (*dto.ClientCreateOrderResponse, error) {
|
|
if req == nil {
|
|
return nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if s.redis == nil {
|
|
return nil, errors.New(errors.CodeInternalError, "Redis 服务未配置")
|
|
}
|
|
|
|
skipPermissionCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
|
assetInfo, err := s.assetService.Resolve(skipPermissionCtx, strings.TrimSpace(req.Identifier))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.checkAssetOwnership(skipPermissionCtx, customerID, assetInfo.VirtualNo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
validationResult, err := s.validatePurchase(skipPermissionCtx, assetInfo, req.PackageIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if packagesNeedRealname(validationResult.Packages) && assetInfo.RealNameStatus != 1 {
|
|
return nil, errors.New(errors.CodeNeedRealname)
|
|
}
|
|
|
|
activeConfig, appID, err := s.resolveWechatConfig(skipPermissionCtx, req.AppType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
openID, err := s.resolveCustomerOpenID(skipPermissionCtx, customerID, appID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
businessKey := buildClientPurchaseBusinessKey(customerID, assetInfo, req)
|
|
redisKey := constants.RedisClientPurchaseIdempotencyKey(businessKey)
|
|
lockKey := constants.RedisClientPurchaseLockKey(assetInfo.AssetType, assetInfo.AssetID)
|
|
|
|
lockAcquired, err := s.redis.SetNX(skipPermissionCtx, lockKey, time.Now().String(), clientPurchaseLockTTL).Result()
|
|
if err != nil {
|
|
s.logger.Warn("获取客户端购买分布式锁失败,继续尝试幂等标记",
|
|
zap.Error(err),
|
|
zap.String("lock_key", lockKey),
|
|
)
|
|
}
|
|
if err == nil && !lockAcquired {
|
|
return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中,请勿重复提交")
|
|
}
|
|
|
|
claimed, err := s.redis.SetNX(skipPermissionCtx, redisKey, "processing", clientPurchaseIdempotencyTTL).Result()
|
|
if err != nil {
|
|
if lockAcquired {
|
|
_ = s.redis.Del(skipPermissionCtx, lockKey).Err()
|
|
}
|
|
return nil, errors.Wrap(errors.CodeInternalError, err, "设置客户端购买幂等标记失败")
|
|
}
|
|
if !claimed {
|
|
if lockAcquired {
|
|
_ = s.redis.Del(skipPermissionCtx, lockKey).Err()
|
|
}
|
|
return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中,请勿重复提交")
|
|
}
|
|
|
|
created := false
|
|
defer func() {
|
|
if lockAcquired {
|
|
_ = s.redis.Del(skipPermissionCtx, lockKey).Err()
|
|
}
|
|
if !created {
|
|
_ = s.redis.Del(skipPermissionCtx, redisKey).Err()
|
|
}
|
|
}()
|
|
|
|
paymentService, err := s.newPaymentService(activeConfig, appID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
forceRecharge := s.checkForceRechargeRequirement(skipPermissionCtx, validationResult)
|
|
if forceRecharge.NeedForceRecharge {
|
|
return s.createForceRechargeOrder(skipPermissionCtx, customerID, appID, openID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, paymentService, &created)
|
|
}
|
|
|
|
return s.createPackageOrder(skipPermissionCtx, customerID, appID, openID, validationResult, activeConfig, redisKey, paymentService, &created)
|
|
}
|
|
|
|
func (s *Service) checkAssetOwnership(ctx context.Context, customerID uint, virtualNo string) error {
|
|
owned, err := s.personalDeviceStore.ExistsByCustomerAndDevice(ctx, customerID, virtualNo)
|
|
if err != nil {
|
|
return errors.Wrap(errors.CodeDatabaseError, err, "查询资产归属失败")
|
|
}
|
|
if owned {
|
|
return nil
|
|
}
|
|
|
|
records, err := s.personalDeviceStore.GetByCustomerID(ctx, customerID)
|
|
if err != nil {
|
|
return errors.Wrap(errors.CodeDatabaseError, err, "查询资产归属失败")
|
|
}
|
|
for _, record := range records {
|
|
if record == nil {
|
|
continue
|
|
}
|
|
if record.Status == constants.StatusEnabled && record.VirtualNo == virtualNo {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return errors.New(errors.CodeForbidden, "无权限操作该资产或资源不存在")
|
|
}
|
|
|
|
func (s *Service) validatePurchase(ctx context.Context, assetInfo *dto.AssetResolveResponse, packageIDs []uint) (*purchase_validation.PurchaseValidationResult, error) {
|
|
switch assetInfo.AssetType {
|
|
case "card":
|
|
return s.purchaseValidationService.ValidateCardPurchase(ctx, assetInfo.AssetID, packageIDs)
|
|
case constants.ResourceTypeDevice:
|
|
return s.purchaseValidationService.ValidateDevicePurchase(ctx, assetInfo.AssetID, packageIDs)
|
|
default:
|
|
return nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
}
|
|
|
|
func (s *Service) resolveWechatConfig(ctx context.Context, appType string) (*model.WechatConfig, string, error) {
|
|
activeConfig, err := s.wechatConfigService.GetActiveConfig(ctx)
|
|
if err != nil {
|
|
return nil, "", errors.Wrap(errors.CodeDatabaseError, err, "查询微信配置失败")
|
|
}
|
|
if activeConfig == nil {
|
|
return nil, "", errors.New(errors.CodeWechatPayFailed, "未找到生效的微信支付配置")
|
|
}
|
|
|
|
switch appType {
|
|
case "official_account":
|
|
if activeConfig.OaAppID == "" {
|
|
return nil, "", errors.New(errors.CodeWechatPayFailed, "公众号支付配置不完整")
|
|
}
|
|
return activeConfig, activeConfig.OaAppID, nil
|
|
case "miniapp":
|
|
if activeConfig.MiniappAppID == "" {
|
|
return nil, "", errors.New(errors.CodeWechatPayFailed, "小程序支付配置不完整")
|
|
}
|
|
return activeConfig, activeConfig.MiniappAppID, nil
|
|
default:
|
|
return nil, "", errors.New(errors.CodeInvalidParam)
|
|
}
|
|
}
|
|
|
|
func (s *Service) resolveCustomerOpenID(ctx context.Context, customerID uint, appID string) (string, error) {
|
|
records, err := s.openIDStore.ListByCustomerID(ctx, customerID)
|
|
if err != nil {
|
|
return "", errors.Wrap(errors.CodeDatabaseError, err, "查询微信授权信息失败")
|
|
}
|
|
|
|
for _, record := range records {
|
|
if record == nil {
|
|
continue
|
|
}
|
|
if record.AppID == appID && strings.TrimSpace(record.OpenID) != "" {
|
|
return record.OpenID, nil
|
|
}
|
|
}
|
|
|
|
return "", errors.New(errors.CodeNotFound, "未找到当前应用的微信授权信息")
|
|
}
|
|
|
|
func (s *Service) newPaymentService(wechatConfig *model.WechatConfig, appID string) (*wechat.PaymentService, error) {
|
|
cache := wechat.NewRedisCache(s.redis)
|
|
paymentApp, err := wechat.NewPaymentAppFromConfig(wechatConfig, appID, cache, s.logger)
|
|
if err != nil {
|
|
return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "创建微信支付应用失败")
|
|
}
|
|
return wechat.NewPaymentService(paymentApp, s.logger), nil
|
|
}
|
|
|
|
func (s *Service) createPackageOrder(
|
|
ctx context.Context,
|
|
customerID uint,
|
|
appID string,
|
|
openID string,
|
|
validationResult *purchase_validation.PurchaseValidationResult,
|
|
activeConfig *model.WechatConfig,
|
|
redisKey string,
|
|
paymentService *wechat.PaymentService,
|
|
created *bool,
|
|
) (*dto.ClientCreateOrderResponse, error) {
|
|
order, err := s.buildPendingOrder(customerID, validationResult, activeConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items, err := s.buildOrderItems(ctx, customerID, validationResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.orderStore.Create(ctx, order, items); err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建订单失败")
|
|
}
|
|
|
|
s.markClientPurchaseCreated(ctx, redisKey, order.OrderNo)
|
|
*created = true
|
|
|
|
description := "套餐购买"
|
|
if len(items) > 0 && items[0] != nil && items[0].PackageName != "" {
|
|
description = items[0].PackageName
|
|
}
|
|
|
|
payResult, err := paymentService.CreateJSAPIOrder(ctx, order.OrderNo, description, openID, int(order.TotalAmount))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &dto.ClientCreateOrderResponse{
|
|
OrderType: "package",
|
|
Order: &dto.ClientOrderInfo{
|
|
OrderID: order.ID,
|
|
OrderNo: order.OrderNo,
|
|
TotalAmount: order.TotalAmount,
|
|
PaymentStatus: orderStatusToClientStatus(order.PaymentStatus),
|
|
CreatedAt: formatClientServiceTime(order.CreatedAt),
|
|
},
|
|
PayConfig: buildClientPayConfig(appID, payResult.PayConfig),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) createForceRechargeOrder(
|
|
ctx context.Context,
|
|
customerID uint,
|
|
appID string,
|
|
openID string,
|
|
assetInfo *dto.AssetResolveResponse,
|
|
validationResult *purchase_validation.PurchaseValidationResult,
|
|
activeConfig *model.WechatConfig,
|
|
forceRecharge *ForceRechargeRequirement,
|
|
redisKey string,
|
|
paymentService *wechat.PaymentService,
|
|
created *bool,
|
|
) (*dto.ClientCreateOrderResponse, error) {
|
|
resourceType, resourceID, err := resolveWalletResource(validationResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, errors.New(errors.CodeWalletNotFound, "钱包不存在")
|
|
}
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
|
|
}
|
|
|
|
linkedPackageIDs, err := sonic.Marshal(extractPackageIDs(validationResult.Packages))
|
|
if err != nil {
|
|
return nil, errors.Wrap(errors.CodeInternalError, err, "序列化关联套餐失败")
|
|
}
|
|
|
|
carrierID := resourceID
|
|
recharge := &model.AssetRechargeRecord{
|
|
UserID: customerID,
|
|
AssetWalletID: wallet.ID,
|
|
ResourceType: resourceType,
|
|
ResourceID: resourceID,
|
|
RechargeNo: generateClientRechargeNo(),
|
|
Amount: forceRecharge.ForceRechargeAmount,
|
|
PaymentMethod: model.PaymentMethodWechat,
|
|
PaymentConfigID: &activeConfig.ID,
|
|
Status: 1,
|
|
ShopIDTag: wallet.ShopIDTag,
|
|
EnterpriseIDTag: wallet.EnterpriseIDTag,
|
|
OperatorType: "personal_customer",
|
|
Generation: resolveGeneration(validationResult),
|
|
LinkedPackageIDs: datatypes.JSON(linkedPackageIDs),
|
|
LinkedOrderType: resolveOrderType(validationResult),
|
|
LinkedCarrierType: assetInfo.AssetType,
|
|
LinkedCarrierID: &carrierID,
|
|
AutoPurchaseStatus: constants.AutoPurchaseStatusPending,
|
|
}
|
|
|
|
if err := s.rechargeRecordStore.Create(ctx, recharge); err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建充值记录失败")
|
|
}
|
|
|
|
s.markClientPurchaseCreated(ctx, redisKey, recharge.RechargeNo)
|
|
*created = true
|
|
|
|
payResult, err := paymentService.CreateJSAPIOrder(ctx, recharge.RechargeNo, "余额充值", openID, int(recharge.Amount))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &dto.ClientCreateOrderResponse{
|
|
OrderType: "recharge",
|
|
Recharge: &dto.ClientRechargeInfo{
|
|
RechargeID: recharge.ID,
|
|
RechargeNo: recharge.RechargeNo,
|
|
Amount: recharge.Amount,
|
|
Status: rechargeStatusToClientStatus(recharge.Status),
|
|
AutoPurchaseStatus: recharge.AutoPurchaseStatus,
|
|
},
|
|
PayConfig: buildClientPayConfig(appID, payResult.PayConfig),
|
|
LinkedPackageInfo: buildLinkedPackageInfo(validationResult, forceRecharge),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) buildPendingOrder(customerID uint, result *purchase_validation.PurchaseValidationResult, activeConfig *model.WechatConfig) (*model.Order, error) {
|
|
orderType := resolveOrderType(result)
|
|
if orderType == "" {
|
|
return nil, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
now := time.Now()
|
|
expiresAt := now.Add(constants.OrderExpireTimeout)
|
|
order := &model.Order{
|
|
BaseModel: model.BaseModel{
|
|
Creator: customerID,
|
|
Updater: customerID,
|
|
},
|
|
OrderNo: s.orderStore.GenerateOrderNo(),
|
|
OrderType: orderType,
|
|
BuyerType: model.BuyerTypePersonal,
|
|
BuyerID: customerID,
|
|
TotalAmount: result.TotalPrice,
|
|
PaymentMethod: model.PaymentMethodWechat,
|
|
PaymentStatus: model.PaymentStatusPending,
|
|
CommissionStatus: model.CommissionStatusPending,
|
|
CommissionConfigVersion: 0,
|
|
Source: constants.OrderSourceClient,
|
|
Generation: resolveGeneration(result),
|
|
ExpiresAt: &expiresAt,
|
|
PaymentConfigID: &activeConfig.ID,
|
|
}
|
|
|
|
if result.Card != nil {
|
|
order.IotCardID = &result.Card.ID
|
|
order.SeriesID = result.Card.SeriesID
|
|
order.SellerShopID = result.Card.ShopID
|
|
} else if result.Device != nil {
|
|
order.DeviceID = &result.Device.ID
|
|
order.SeriesID = result.Device.SeriesID
|
|
order.SellerShopID = result.Device.ShopID
|
|
}
|
|
|
|
return order, nil
|
|
}
|
|
|
|
func (s *Service) buildOrderItems(ctx context.Context, customerID uint, result *purchase_validation.PurchaseValidationResult) ([]*model.OrderItem, error) {
|
|
sellerShopID := resolveSellerShopID(result)
|
|
items := make([]*model.OrderItem, 0, len(result.Packages))
|
|
|
|
for _, pkg := range result.Packages {
|
|
if pkg == nil {
|
|
continue
|
|
}
|
|
|
|
unitPrice, err := s.purchaseValidationService.GetPurchasePrice(ctx, pkg, sellerShopID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items = append(items, &model.OrderItem{
|
|
BaseModel: model.BaseModel{
|
|
Creator: customerID,
|
|
Updater: customerID,
|
|
},
|
|
PackageID: pkg.ID,
|
|
PackageName: pkg.PackageName,
|
|
Quantity: 1,
|
|
UnitPrice: unitPrice,
|
|
Amount: unitPrice,
|
|
})
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (s *Service) checkForceRechargeRequirement(ctx context.Context, result *purchase_validation.PurchaseValidationResult) *ForceRechargeRequirement {
|
|
defaultResult := &ForceRechargeRequirement{NeedForceRecharge: false}
|
|
|
|
var seriesID *uint
|
|
var sellerShopID uint
|
|
if result.Card != nil {
|
|
seriesID = result.Card.SeriesID
|
|
if result.Card.ShopID != nil {
|
|
sellerShopID = *result.Card.ShopID
|
|
}
|
|
} else if result.Device != nil {
|
|
seriesID = result.Device.SeriesID
|
|
if result.Device.ShopID != nil {
|
|
sellerShopID = *result.Device.ShopID
|
|
}
|
|
}
|
|
|
|
if seriesID == nil || *seriesID == 0 {
|
|
return defaultResult
|
|
}
|
|
|
|
series, err := s.packageSeriesStore.GetByID(ctx, *seriesID)
|
|
if err != nil {
|
|
s.logger.Warn("查询套餐系列失败", zap.Uint("series_id", *seriesID), zap.Error(err))
|
|
return defaultResult
|
|
}
|
|
|
|
config, err := series.GetOneTimeCommissionConfig()
|
|
if err != nil || config == nil || !config.Enable {
|
|
return defaultResult
|
|
}
|
|
|
|
if config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge {
|
|
return &ForceRechargeRequirement{
|
|
NeedForceRecharge: true,
|
|
ForceRechargeAmount: config.Threshold,
|
|
}
|
|
}
|
|
|
|
if config.EnableForceRecharge {
|
|
amount := config.ForceAmount
|
|
if amount == 0 {
|
|
amount = config.Threshold
|
|
}
|
|
return &ForceRechargeRequirement{
|
|
NeedForceRecharge: true,
|
|
ForceRechargeAmount: amount,
|
|
}
|
|
}
|
|
|
|
if sellerShopID > 0 {
|
|
allocation, allocErr := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, sellerShopID, *seriesID)
|
|
if allocErr == nil && allocation.EnableForceRecharge {
|
|
amount := allocation.ForceRechargeAmount
|
|
if amount == 0 {
|
|
amount = config.Threshold
|
|
}
|
|
return &ForceRechargeRequirement{
|
|
NeedForceRecharge: true,
|
|
ForceRechargeAmount: amount,
|
|
}
|
|
}
|
|
}
|
|
|
|
return defaultResult
|
|
}
|
|
|
|
func (s *Service) markClientPurchaseCreated(ctx context.Context, redisKey string, value string) {
|
|
if err := s.redis.Set(ctx, redisKey, value, clientPurchaseIdempotencyTTL).Err(); err != nil {
|
|
s.logger.Warn("设置客户端购买幂等标记失败",
|
|
zap.String("redis_key", redisKey),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
func buildLinkedPackageInfo(result *purchase_validation.PurchaseValidationResult, forceRecharge *ForceRechargeRequirement) *dto.LinkedPackageInfo {
|
|
packageNames := make([]string, 0, len(result.Packages))
|
|
for _, pkg := range result.Packages {
|
|
if pkg == nil || pkg.PackageName == "" {
|
|
continue
|
|
}
|
|
packageNames = append(packageNames, pkg.PackageName)
|
|
}
|
|
|
|
return &dto.LinkedPackageInfo{
|
|
PackageNames: packageNames,
|
|
TotalPackageAmount: result.TotalPrice,
|
|
ForceRechargeAmount: forceRecharge.ForceRechargeAmount,
|
|
WalletCredit: forceRecharge.ForceRechargeAmount,
|
|
}
|
|
}
|
|
|
|
func buildClientPayConfig(appID string, payConfig any) *dto.ClientPayConfig {
|
|
configMap, _ := payConfig.(map[string]any)
|
|
if configMap == nil {
|
|
configMap = map[string]any{}
|
|
}
|
|
|
|
return &dto.ClientPayConfig{
|
|
AppID: firstNonEmpty(stringFromAny(configMap["appId"]), appID),
|
|
Timestamp: firstNonEmpty(stringFromAny(configMap["timeStamp"]), stringFromAny(configMap["timestamp"])),
|
|
NonceStr: stringFromAny(configMap["nonceStr"]),
|
|
PackageVal: stringFromAny(configMap["package"]),
|
|
SignType: stringFromAny(configMap["signType"]),
|
|
PaySign: stringFromAny(configMap["paySign"]),
|
|
}
|
|
}
|
|
|
|
func resolveWalletResource(result *purchase_validation.PurchaseValidationResult) (string, uint, error) {
|
|
if result.Card != nil {
|
|
return constants.AssetWalletResourceTypeIotCard, result.Card.ID, nil
|
|
}
|
|
if result.Device != nil {
|
|
return constants.AssetWalletResourceTypeDevice, result.Device.ID, nil
|
|
}
|
|
return "", 0, errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
func resolveOrderType(result *purchase_validation.PurchaseValidationResult) string {
|
|
if result.Card != nil {
|
|
return model.OrderTypeSingleCard
|
|
}
|
|
if result.Device != nil {
|
|
return model.OrderTypeDevice
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func resolveGeneration(result *purchase_validation.PurchaseValidationResult) int {
|
|
if result.Card != nil && result.Card.Generation > 0 {
|
|
return result.Card.Generation
|
|
}
|
|
if result.Device != nil && result.Device.Generation > 0 {
|
|
return result.Device.Generation
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func resolveSellerShopID(result *purchase_validation.PurchaseValidationResult) uint {
|
|
if result.Card != nil && result.Card.ShopID != nil {
|
|
return *result.Card.ShopID
|
|
}
|
|
if result.Device != nil && result.Device.ShopID != nil {
|
|
return *result.Device.ShopID
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func packagesNeedRealname(packages []*model.Package) bool {
|
|
for _, pkg := range packages {
|
|
if pkg != nil && pkg.EnableRealnameActivation {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func extractPackageIDs(packages []*model.Package) []uint {
|
|
ids := make([]uint, 0, len(packages))
|
|
for _, pkg := range packages {
|
|
if pkg == nil {
|
|
continue
|
|
}
|
|
ids = append(ids, pkg.ID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func buildClientPurchaseBusinessKey(customerID uint, assetInfo *dto.AssetResolveResponse, req *dto.ClientCreateOrderRequest) string {
|
|
packageIDs := make([]uint, 0, len(req.PackageIDs))
|
|
packageIDs = append(packageIDs, req.PackageIDs...)
|
|
slices.Sort(packageIDs)
|
|
|
|
parts := make([]string, 0, len(packageIDs))
|
|
for _, packageID := range packageIDs {
|
|
parts = append(parts, strconv.FormatUint(uint64(packageID), 10))
|
|
}
|
|
|
|
return fmt.Sprintf("%d:%s:%d:%s:%s", customerID, assetInfo.AssetType, assetInfo.AssetID, req.AppType, strings.Join(parts, ","))
|
|
}
|
|
|
|
func orderStatusToClientStatus(status int) int {
|
|
switch status {
|
|
case model.PaymentStatusPending:
|
|
return 0
|
|
case model.PaymentStatusPaid:
|
|
return 1
|
|
case model.PaymentStatusCancelled:
|
|
return 2
|
|
default:
|
|
return status
|
|
}
|
|
}
|
|
|
|
func rechargeStatusToClientStatus(status int) int {
|
|
switch status {
|
|
case 1:
|
|
return 0
|
|
case 2, 3:
|
|
return 1
|
|
default:
|
|
return 2
|
|
}
|
|
}
|
|
|
|
func formatClientServiceTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.Format(time.RFC3339)
|
|
}
|
|
|
|
func generateClientRechargeNo() string {
|
|
return fmt.Sprintf("CRCH%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
|
|
}
|
|
|
|
func stringFromAny(value any) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprint(value)
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|