Files
junhong_cmp_fiber/internal/service/exchange/audit.go
break 5e552d99bc 收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
2026-08-05 14:30:54 +08:00

495 lines
23 KiB
Go

package exchange
import (
"context"
"strconv"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/model"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
type cardExchangeAuditBefore struct {
Wallets map[uint]model.AssetWallet
DeviceBindings []*model.PersonalCustomerDevice
ICCIDBindings []*model.PersonalCustomerICCID
}
// SetAccessAudit 注入卡与设备换货完整用例的统一审计 Writer。
func (s *Service) SetAccessAudit(writer *audit.Writer) {
s.auditWriter = writer
}
func (s *Service) appendCardExchangeAudit(
ctx context.Context,
tx *gorm.DB,
actionCode, summary, result string,
order *model.ExchangeOrder,
oldCard, newCard *model.IotCard,
orderBefore, orderAfter map[string]any,
oldCardBefore, oldCardAfter map[string]any,
newCardBefore, newCardAfter map[string]any,
extra []audit.ResourceInput,
businessErr error,
) error {
if order == nil || order.OldAssetType != constants.ExchangeAssetTypeIotCard {
return nil
}
if s.auditWriter == nil {
return errors.New(errors.CodeInvalidStatus, "卡换货统一审计接缝未配置")
}
internalOnly := actionCode == constants.AuditActionCardExchangeRenewed
resources := []audit.ResourceInput{cardExchangeOrderAuditResource(order, summary, internalOnly, orderBefore, orderAfter)}
if oldCard != nil {
resources = append(resources, cardExchangeCardAuditResource(oldCard, constants.AuditResourceRoleCardExchangeOldCard, summary, internalOnly, oldCardBefore, oldCardAfter))
}
if newCard != nil {
resources = append(resources, cardExchangeCardAuditResource(newCard, constants.AuditResourceRoleCardExchangeNewCard, summary, internalOnly, newCardBefore, newCardAfter))
}
shopResource, err := loadCardExchangeShopAuditResource(ctx, tx, order.ShopID)
if err != nil {
return err
}
if shopResource != nil {
resources = append(resources, *shopResource)
}
resources = append(resources, extra...)
errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr)
scopeType, scopeID := constants.AuditScopePlatform, ""
if actionCode == constants.AuditActionCardExchangeShippingInfoSubmitted {
scopeType = constants.AuditScopePersonalCustomer
scopeID = auditcontext.From(ctx).ActorID
}
return s.auditWriter.Append(ctx, tx, audit.AppendInput{
ActionCode: actionCode, Summary: summary, ScopeType: scopeType, ScopeID: scopeID,
Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary,
Metadata: map[string]any{"flow_type": effectiveExchangeFlowType(order.FlowType), "migrate_data": order.MigrateData},
Resources: resources,
})
}
func cardExchangeOrderAuditResource(order *model.ExchangeOrder, summary string, internalOnly bool, beforeData, afterData map[string]any) audit.ResourceInput {
key := order.ExchangeNo
if key == "" {
key = "iot_card:" + strconv.FormatUint(uint64(order.OldAssetID), 10) + ":exchange"
}
var id *string
if order.ID > 0 {
value := strconv.FormatUint(uint64(order.ID), 10)
id = &value
}
resource := audit.ResourceInput{
Type: constants.AuditResourceExchangeOrder, ID: id, Key: key, DisplayName: key,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleCardExchangeOrder,
IdentitySnapshot: map[string]any{
"id": order.ID, "exchange_no": order.ExchangeNo, "flow_type": effectiveExchangeFlowType(order.FlowType),
"old_asset_type": order.OldAssetType, "old_asset_id": order.OldAssetID, "old_asset_identifier": order.OldAssetIdentifier,
"new_asset_type": order.NewAssetType, "new_asset_id": order.NewAssetID, "new_asset_identifier": order.NewAssetIdentifier,
"shop_id": order.ShopID, "status": order.Status,
},
BeforeData: beforeData, AfterData: afterData,
}
if internalOnly {
resource.SubjectVisibility = constants.AuditSubjectInternalOnly
} else {
resource.SubjectVisibility = constants.AuditSubjectResult
resource.SubjectSummary = summary
}
return resource
}
func cardExchangeCardAuditResource(card *model.IotCard, role, summary string, internalOnly bool, beforeData, afterData map[string]any) audit.ResourceInput {
id := strconv.FormatUint(uint64(card.ID), 10)
relation := constants.AuditResourceRelationReference
if len(beforeData) > 0 || len(afterData) > 0 {
relation = constants.AuditResourceRelationAffected
}
resource := audit.ResourceInput{
Type: constants.AuditResourceIotCard, ID: &id, Key: audit.IotCardResourceKey(card), DisplayName: card.ICCID,
Relation: relation, Role: role, IdentitySnapshot: audit.IotCardIdentitySnapshot(card),
BeforeData: beforeData, AfterData: afterData,
}
if internalOnly {
resource.SubjectVisibility = constants.AuditSubjectInternalOnly
} else {
resource.SubjectVisibility = constants.AuditSubjectResult
resource.SubjectSummary = summary
}
return resource
}
func loadCardExchangeShopAuditResource(ctx context.Context, tx *gorm.DB, shopID *uint) (*audit.ResourceInput, error) {
if shopID == nil || *shopID == 0 {
return nil, nil
}
var shop model.Shop
if err := tx.WithContext(ctx).Unscoped().Where("id = ?", *shopID).First(&shop).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡换货所属店铺失败")
}
id := strconv.FormatUint(uint64(shop.ID), 10)
return &audit.ResourceInput{
Type: constants.AuditResourceShop, ID: &id, Key: id, DisplayName: shop.ShopName,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleCardExchangeShop,
IdentitySnapshot: map[string]any{"id": shop.ID, "shop_code": shop.ShopCode, "shop_name": shop.ShopName, "parent_id": shop.ParentID, "level": shop.Level},
SubjectVisibility: constants.AuditSubjectInternalOnly,
}, nil
}
func (s *Service) captureCardExchangeAuditBefore(ctx context.Context, tx *gorm.DB, oldCard, newCard *model.IotCard) (*cardExchangeAuditBefore, error) {
state := &cardExchangeAuditBefore{Wallets: make(map[uint]model.AssetWallet)}
cardIDs := make([]uint, 0, 2)
if oldCard != nil {
cardIDs = append(cardIDs, oldCard.ID)
}
if newCard != nil {
cardIDs = append(cardIDs, newCard.ID)
}
if len(cardIDs) > 0 {
var wallets []model.AssetWallet
if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id IN ?", constants.ExchangeAssetTypeIotCard, cardIDs).Find(&wallets).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡换货钱包审计快照失败")
}
for _, wallet := range wallets {
state.Wallets[wallet.ResourceID] = wallet
}
}
devices, iccids, err := loadCardExchangeBindings(ctx, tx, oldCard)
if err != nil {
return nil, err
}
state.DeviceBindings, state.ICCIDBindings = devices, iccids
return state, nil
}
func loadCardExchangeBindings(ctx context.Context, tx *gorm.DB, card *model.IotCard) ([]*model.PersonalCustomerDevice, []*model.PersonalCustomerICCID, error) {
if card == nil {
return nil, nil, nil
}
if card.VirtualNo != "" {
var rows []*model.PersonalCustomerDevice
if err := tx.WithContext(ctx).Where("virtual_no = ? AND status = ?", card.VirtualNo, constants.StatusEnabled).Find(&rows).Error; err != nil {
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡换货客户绑定失败")
}
return rows, nil, nil
}
var rows []*model.PersonalCustomerICCID
if err := tx.WithContext(ctx).Where("iccid = ? AND status = ?", card.ICCID, constants.StatusEnabled).Find(&rows).Error; err != nil {
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡换货 ICCID 绑定失败")
}
return nil, rows, nil
}
func (s *Service) buildCardExchangeCompletionResources(
ctx context.Context,
tx *gorm.DB,
order *model.ExchangeOrder,
oldCard, newCard *model.IotCard,
before *cardExchangeAuditBefore,
migration *exchangeMigrationResult,
) ([]audit.ResourceInput, error) {
resources := cardExchangeOldBindingResources(before)
devices, iccids, err := loadCardExchangeBindings(ctx, tx, newCard)
if err != nil {
return nil, err
}
resources = append(resources, cardExchangeNewBindingResources(devices, iccids)...)
beforeWallets := map[uint]model.AssetWallet(nil)
if before != nil {
beforeWallets = before.Wallets
}
walletResources, err := loadCardExchangeWalletResources(ctx, tx, oldCard.ID, newCard.ID, beforeWallets)
if err != nil {
return nil, err
}
resources = append(resources, walletResources...)
if migration == nil {
return resources, nil
}
transactionResources, err := loadCardExchangeTransactionResources(ctx, tx, order.ExchangeNo)
if err != nil {
return nil, err
}
resources = append(resources, transactionResources...)
usageResources, err := loadCardExchangePackageUsageResources(ctx, tx, migration.PackageUsageIDs, oldCard.ID, newCard.ID)
if err != nil {
return nil, err
}
return append(resources, usageResources...), nil
}
func cardExchangeOldBindingResources(before *cardExchangeAuditBefore) []audit.ResourceInput {
if before == nil {
return nil
}
resources := make([]audit.ResourceInput, 0, len(before.DeviceBindings)+len(before.ICCIDBindings))
for _, row := range before.DeviceBindings {
if row == nil {
continue
}
id := strconv.FormatUint(uint64(row.ID), 10)
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourcePersonalCustomerDevice, ID: &id, Key: id, DisplayName: row.VirtualNo,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
IdentitySnapshot: map[string]any{"id": row.ID, "customer_id": row.CustomerID, "virtual_no": row.VirtualNo, "bind_at": row.BindAt, "last_used_at": row.LastUsedAt, "status": row.Status},
BeforeData: map[string]any{"virtual_no": row.VirtualNo, "status": row.Status}, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
for _, row := range before.ICCIDBindings {
if row == nil {
continue
}
id := strconv.FormatUint(uint64(row.ID), 10)
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourcePersonalCustomerICCID, ID: &id, Key: id, DisplayName: row.ICCID,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
IdentitySnapshot: map[string]any{"id": row.ID, "customer_id": row.CustomerID, "iccid": row.ICCID, "iccid_19": row.ICCID19, "bind_at": row.BindAt, "last_used_at": row.LastUsedAt, "status": row.Status},
BeforeData: map[string]any{"iccid": row.ICCID, "status": row.Status}, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
return resources
}
func cardExchangeNewBindingResources(devices []*model.PersonalCustomerDevice, iccids []*model.PersonalCustomerICCID) []audit.ResourceInput {
resources := make([]audit.ResourceInput, 0, len(devices)+len(iccids))
for _, row := range devices {
if row == nil {
continue
}
id := strconv.FormatUint(uint64(row.ID), 10)
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourcePersonalCustomerDevice, ID: &id, Key: id, DisplayName: row.VirtualNo,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
IdentitySnapshot: map[string]any{"id": row.ID, "customer_id": row.CustomerID, "virtual_no": row.VirtualNo, "bind_at": row.BindAt, "last_used_at": row.LastUsedAt, "status": row.Status},
AfterData: map[string]any{"virtual_no": row.VirtualNo, "status": row.Status}, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
for _, row := range iccids {
if row == nil {
continue
}
id := strconv.FormatUint(uint64(row.ID), 10)
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourcePersonalCustomerICCID, ID: &id, Key: id, DisplayName: row.ICCID,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
IdentitySnapshot: map[string]any{"id": row.ID, "customer_id": row.CustomerID, "iccid": row.ICCID, "iccid_19": row.ICCID19, "bind_at": row.BindAt, "last_used_at": row.LastUsedAt, "status": row.Status},
AfterData: map[string]any{"iccid": row.ICCID, "status": row.Status}, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
return resources
}
func loadCardExchangeWalletResources(ctx context.Context, tx *gorm.DB, oldCardID, newCardID uint, before map[uint]model.AssetWallet) ([]audit.ResourceInput, error) {
return loadExchangeWalletResources(ctx, tx, constants.ExchangeAssetTypeIotCard, oldCardID, newCardID, before,
constants.AuditResourceRoleCardExchangeOldWallet, constants.AuditResourceRoleCardExchangeNewWallet, "卡")
}
func loadExchangeWalletResources(ctx context.Context, tx *gorm.DB, assetType string, oldAssetID, newAssetID uint, before map[uint]model.AssetWallet, oldRole, newRole, assetName string) ([]audit.ResourceInput, error) {
var wallets []model.AssetWallet
if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id IN ?", assetType, []uint{oldAssetID, newAssetID}).Find(&wallets).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询"+assetName+"换货迁移后钱包失败")
}
resources := make([]audit.ResourceInput, 0, len(wallets))
for _, wallet := range wallets {
id := strconv.FormatUint(uint64(wallet.ID), 10)
role := newRole
if wallet.ResourceID == oldAssetID {
role = oldRole
}
relation := constants.AuditResourceRelationAffected
beforeData, afterData := map[string]any{"exists": false}, cardExchangeWalletData(wallet)
if previous, ok := before[wallet.ResourceID]; ok {
if !cardExchangeWalletChanged(previous, wallet) {
relation, beforeData, afterData = constants.AuditResourceRelationReference, nil, nil
} else {
beforeData = cardExchangeWalletData(previous)
}
}
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourceAssetWallet, ID: &id, Key: id, DisplayName: id,
Relation: relation, Role: role,
IdentitySnapshot: map[string]any{"id": wallet.ID, "resource_type": wallet.ResourceType, "resource_id": wallet.ResourceID, "currency": wallet.Currency, "shop_id_tag": wallet.ShopIDTag, "enterprise_id_tag": wallet.EnterpriseIDTag},
BeforeData: beforeData, AfterData: afterData, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
return resources, nil
}
func cardExchangeWalletData(wallet model.AssetWallet) map[string]any {
return map[string]any{"balance": wallet.Balance, "frozen_balance": wallet.FrozenBalance, "status": wallet.Status, "version": wallet.Version, "shop_id_tag": wallet.ShopIDTag, "enterprise_id_tag": wallet.EnterpriseIDTag}
}
func cardExchangeWalletChanged(before, after model.AssetWallet) bool {
return before.Balance != after.Balance || before.FrozenBalance != after.FrozenBalance || before.Status != after.Status ||
before.Version != after.Version || before.ShopIDTag != after.ShopIDTag || !sameOptionalUint(before.EnterpriseIDTag, after.EnterpriseIDTag)
}
func sameOptionalUint(left, right *uint) bool {
return left == nil && right == nil || left != nil && right != nil && *left == *right
}
func loadCardExchangeRenewWalletResource(ctx context.Context, tx *gorm.DB, cardID uint, before map[uint]model.AssetWallet) (*audit.ResourceInput, error) {
return loadExchangeRenewWalletResource(ctx, tx, constants.ExchangeAssetTypeIotCard, cardID, before, constants.AuditResourceRoleCardExchangeOldWallet, "卡")
}
func loadExchangeRenewWalletResource(ctx context.Context, tx *gorm.DB, assetType string, assetID uint, before map[uint]model.AssetWallet, role, assetName string) (*audit.ResourceInput, error) {
var wallet model.AssetWallet
if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id = ?", assetType, assetID).First(&wallet).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询旧"+assetName+"转新钱包失败")
}
id := strconv.FormatUint(uint64(wallet.ID), 10)
beforeData := map[string]any{"exists": false}
if previous, ok := before[assetID]; ok {
beforeData = cardExchangeWalletData(previous)
}
return &audit.ResourceInput{
Type: constants.AuditResourceAssetWallet, ID: &id, Key: id, DisplayName: id,
Relation: constants.AuditResourceRelationAffected, Role: role,
IdentitySnapshot: map[string]any{"id": wallet.ID, "resource_type": wallet.ResourceType, "resource_id": wallet.ResourceID, "currency": wallet.Currency, "shop_id_tag": wallet.ShopIDTag, "enterprise_id_tag": wallet.EnterpriseIDTag},
BeforeData: beforeData, AfterData: cardExchangeWalletData(wallet), SubjectVisibility: constants.AuditSubjectInternalOnly,
}, nil
}
func loadCardExchangeTransactionResources(ctx context.Context, tx *gorm.DB, exchangeNo string) ([]audit.ResourceInput, error) {
return loadExchangeTransactionResources(ctx, tx, exchangeNo, constants.AuditResourceRoleCardExchangeWalletTransaction, "卡")
}
func loadExchangeTransactionResources(ctx context.Context, tx *gorm.DB, exchangeNo, role, assetName string) ([]audit.ResourceInput, error) {
var rows []model.AssetWalletTransaction
if err := tx.WithContext(ctx).Where("transaction_type = ? AND reference_type = ? AND reference_no = ?", constants.AssetTransactionTypeExchange, constants.ReferenceTypeExchange, exchangeNo).Find(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询"+assetName+"换货钱包流水失败")
}
resources := make([]audit.ResourceInput, 0, len(rows))
for _, row := range rows {
id := strconv.FormatUint(uint64(row.ID), 10)
resources = append(resources, audit.ResourceInput{
Type: constants.AuditResourceAssetWalletTransaction, ID: &id, Key: id, DisplayName: exchangeNo,
Relation: constants.AuditResourceRelationAffected, Role: role,
IdentitySnapshot: map[string]any{"id": row.ID, "asset_wallet_id": row.AssetWalletID, "resource_type": row.ResourceType, "resource_id": row.ResourceID, "transaction_type": row.TransactionType, "reference_type": row.ReferenceType, "reference_no": row.ReferenceNo, "status": row.Status},
AfterData: map[string]any{"amount": row.Amount, "balance_before": row.BalanceBefore, "balance_after": row.BalanceAfter}, SubjectVisibility: constants.AuditSubjectInternalOnly,
})
}
return resources, nil
}
func loadCardExchangePackageUsageResources(ctx context.Context, tx *gorm.DB, ids []uint, oldCardID, newCardID uint) ([]audit.ResourceInput, error) {
return loadExchangePackageUsageResources(ctx, tx, ids, "iot_card_id", oldCardID, newCardID, constants.AuditResourceRoleCardExchangePackageUsage, "卡")
}
func loadExchangePackageUsageResources(ctx context.Context, tx *gorm.DB, ids []uint, assetIDField string, oldAssetID, newAssetID uint, role, assetName string) ([]audit.ResourceInput, error) {
if len(ids) == 0 {
return nil, nil
}
var rows []model.PackageUsage
if err := tx.WithContext(ctx).Where("id IN ?", ids).Order("id ASC").Find(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询"+assetName+"换货套餐权益失败")
}
orderIDs := make(map[uint]struct{}, len(rows))
packageIDs := make(map[uint]struct{}, len(rows))
resources := make([]audit.ResourceInput, 0, len(rows)*3)
for _, row := range rows {
resource := audit.PackageUsageResource(&row, constants.AuditResourceRelationAffected, role,
map[string]any{assetIDField: oldAssetID}, map[string]any{assetIDField: newAssetID})
resource.SubjectVisibility = constants.AuditSubjectInternalOnly
resources = append(resources, resource)
orderIDs[row.OrderID] = struct{}{}
packageIDs[row.PackageID] = struct{}{}
}
var orders []model.Order
if err := tx.WithContext(ctx).Where("id IN ?", exchangeUintKeys(orderIDs)).Order("id ASC").Find(&orders).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询"+assetName+"换货套餐权益关联订单失败")
}
for i := range orders {
resources = append(resources, audit.OrderResource(&orders[i], constants.AuditResourceRelationReference, constants.AuditResourceRolePackageUsageOrder))
}
var packages []model.Package
if err := tx.WithContext(ctx).Where("id IN ?", exchangeUintKeys(packageIDs)).Order("id ASC").Find(&packages).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询"+assetName+"换货套餐权益关联套餐失败")
}
for i := range packages {
resources = append(resources, audit.PackageResource(&packages[i], constants.AuditResourceRelationReference, constants.AuditResourceRolePackageUsagePackage, nil, nil))
}
return resources, nil
}
func exchangeUintKeys(values map[uint]struct{}) []uint {
result := make([]uint, 0, len(values))
for value := range values {
if value > 0 {
result = append(result, value)
}
}
return result
}
func (s *Service) recordCardExchangeFailure(ctx context.Context, actionCode, summary, result string, order *model.ExchangeOrder, oldCard, newCard *model.IotCard, businessErr error) {
if order == nil || order.OldAssetType != constants.ExchangeAssetTypeIotCard {
return
}
if s.db == nil || s.auditWriter == nil {
recordCardExchangeAuditSecondaryFailure(ctx, actionCode, order.ExchangeNo, businessErr, errors.New(errors.CodeInvalidStatus, "卡换货统一审计接缝未配置"))
return
}
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return s.appendCardExchangeAudit(ctx, tx, actionCode, summary, result, order, oldCard, newCard,
nil, nil, nil, nil, nil, nil, nil, businessErr)
}); err != nil {
recordCardExchangeAuditSecondaryFailure(ctx, actionCode, order.ExchangeNo, businessErr, err)
}
}
func recordCardExchangeAuditSecondaryFailure(ctx context.Context, actionCode, exchangeNo string, businessErr, auditErr error) {
errorCode, _ := assetAuditSvc.BuildErrorInfo(businessErr)
linkage := auditcontext.From(ctx)
auditfailure.RecordSecondaryWriteFailure(actionCode, exchangeNo, linkage.RequestID, linkage.CorrelationID, errorCode, auditErr)
}
func (s *Service) recordCardExchangeOrderFailure(ctx context.Context, actionCode, summary string, order *model.ExchangeOrder, businessErr error) {
if order == nil || order.OldAssetType != constants.ExchangeAssetTypeIotCard {
return
}
oldCard, newCard := s.loadCardExchangeAuditCards(ctx, order)
s.recordCardExchangeFailure(ctx, actionCode, summary, cardExchangeFailureResult(businessErr), order, oldCard, newCard, businessErr)
}
func (s *Service) loadCardExchangeAuditCards(ctx context.Context, order *model.ExchangeOrder) (*model.IotCard, *model.IotCard) {
if order == nil || order.OldAssetType != constants.ExchangeAssetTypeIotCard {
return nil, nil
}
var oldCard *model.IotCard
if s.iotCardStore != nil {
oldCard, _ = s.iotCardStore.GetByID(ctx, order.OldAssetID)
}
if oldCard == nil {
oldCard = &model.IotCard{Model: gorm.Model{ID: order.OldAssetID}, ICCID: order.OldAssetIdentifier, ShopID: order.ShopID}
}
var newCard *model.IotCard
if order.NewAssetID != nil && *order.NewAssetID > 0 {
if s.iotCardStore != nil {
newCard, _ = s.iotCardStore.GetByID(ctx, *order.NewAssetID)
}
if newCard == nil {
newCard = &model.IotCard{Model: gorm.Model{ID: *order.NewAssetID}, ICCID: order.NewAssetIdentifier, ShopID: order.ShopID}
}
}
return oldCard, newCard
}
func cardExchangeFailureResult(err error) string {
appErr, ok := err.(*errors.AppError)
if !ok {
return constants.AuditResultFailed
}
switch appErr.Code {
case errors.CodeDatabaseError, errors.CodeInternalError, errors.CodeExchangeMigrationFailed:
return constants.AuditResultFailed
default:
return constants.AuditResultDenied
}
}