package wallet import ( "context" "strings" "hyapp/pkg/appcode" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" ) // FreezeSalaryWithdrawal 把工资从可用余额冻结到提现审核池;提交申请前必须先由 gateway 完成身份校验。 func (s *Service) FreezeSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) { if err := normalizeSalaryWithdrawalCommand(&command, false); err != nil { return ledger.SalaryWithdrawalReceipt{}, err } if command.Reason == "" { command.Reason = "salary withdrawal freeze" } if s.repository == nil { return ledger.SalaryWithdrawalReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.FreezeSalaryWithdrawal(ctx, command) } // SettleSalaryWithdrawal 在财务审核通过后扣减冻结金额;它只动 frozen,不再次扣 available。 func (s *Service) SettleSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) { if err := normalizeSalaryWithdrawalCommand(&command, true); err != nil { return ledger.SalaryWithdrawalReceipt{}, err } if command.Reason == "" { command.Reason = "salary withdrawal approved" } if s.repository == nil { return ledger.SalaryWithdrawalReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.SettleSalaryWithdrawal(ctx, command) } // ReleaseSalaryWithdrawal 在审核拒绝或申请创建失败时把冻结工资返还到可用余额。 func (s *Service) ReleaseSalaryWithdrawal(ctx context.Context, command ledger.SalaryWithdrawalCommand) (ledger.SalaryWithdrawalReceipt, error) { if err := normalizeSalaryWithdrawalCommand(&command, false); err != nil { return ledger.SalaryWithdrawalReceipt{}, err } if command.Reason == "" { command.Reason = "salary withdrawal released" } if s.repository == nil { return ledger.SalaryWithdrawalReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.ReleaseSalaryWithdrawal(ctx, command) } func normalizeSalaryWithdrawalCommand(command *ledger.SalaryWithdrawalCommand, requireOperator bool) error { if command == nil || command.CommandID == "" || command.UserID <= 0 || command.SalaryUSDMinor <= 0 { return xerr.New(xerr.InvalidArgument, "salary withdrawal command is incomplete") } if requireOperator && command.OperatorUserID <= 0 { return xerr.New(xerr.InvalidArgument, "operator_user_id is required") } command.SalaryAssetType = strings.ToUpper(strings.TrimSpace(command.SalaryAssetType)) if !ledger.ValidSalaryAssetType(command.SalaryAssetType) { return xerr.New(xerr.InvalidArgument, "salary_asset_type is invalid") } command.CommandID = strings.TrimSpace(command.CommandID) command.Reason = strings.TrimSpace(command.Reason) command.WithdrawalRef = strings.TrimSpace(command.WithdrawalRef) command.WithdrawalApplicationID = strings.TrimSpace(command.WithdrawalApplicationID) if len(command.CommandID) > 128 || len(command.Reason) > 512 || len(command.WithdrawalRef) > 128 || len(command.WithdrawalApplicationID) > 64 { return xerr.New(xerr.InvalidArgument, "salary withdrawal text fields are too long") } return nil }