package app import ( "context" "fmt" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" ) type appBalanceRechargeAccount struct { AccountType string AccountId int64 CustomerType int CustomerAgentId int64 EndUserId int64 AgentId int64 CreatedDept int64 } // BalanceRechargeSubmit 创建余额充值待支付订单,并返回前端可使用的支付信息。 // 余额入账和余额流水只在支付回调或模拟支付确认成功后写入。 func (s *sAppCard) BalanceRechargeSubmit(ctx context.Context, param *model.AppBalanceRechargeSubmitParam) (res *model.AppBalanceRechargeSubmitModel, err error) { if !appBalanceRechargeEnabled(ctx) { return nil, gerror.New("充值功能暂未开放") } if param == nil { param = &model.AppBalanceRechargeSubmitParam{} } amount := appRoundAmount(param.Amount) if amount <= 0 { return nil, gerror.New("充值金额必须大于0") } user, err := appCurrentUser(ctx) if err != nil { return nil, err } now := gtime.Now() orderSn := appOrderNo("SO", user.UserId, now) payProvider := strings.ToLower(strings.TrimSpace(param.PayProvider)) if payProvider == "" { payProvider = "wechat" } switch payProvider { case "mock": if !appMockPaymentEnabled(ctx) { return nil, gerror.New("模拟支付未启用") } case "wechat": if !appWechatEnabled(ctx) { return nil, gerror.New("微信支付通道未配置") } default: return nil, gerror.New("暂不支持的支付方式") } var orderId int64 err = dao.TOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { account, err := s.ensureAppBalanceRechargeAccount(ctx, tx, user, now) if err != nil { return err } orderId, err = s.insertAppBalanceRechargeOrder(ctx, tx, user, account, orderSn, strings.TrimSpace(param.BuyerNote), amount, now) if err != nil { return err } return s.insertAppBalanceRechargeOrderItem(ctx, tx, user, account, orderId, orderSn, amount, now) }) if err != nil { return nil, err } var payRequest *appWechatPayRequest if payProvider == "wechat" { if appNormalizeWechatPayScene(param.PayScene) == "mini" { if err = service.AppAuth().BindWechatOpenIdByLoginCode(ctx, user, param.WxCode); err != nil { return nil, err } } payRequest, err = appBuildWechatPayRequest(ctx, &entity.TOrder{ Id: orderId, OrderSn: orderSn, OrderType: consts.OrderTypeBalanceRecharge, PayStatus: consts.PayStatusUnpaid, TotalAmount: amount, CreatedAt: now, }, param.PayScene, user) if err != nil { return nil, err } } paymentExpireAt, paymentRemainingSeconds, paymentExpired := appBalanceRechargePaymentTiming(ctx, &entity.TOrder{ OrderType: consts.OrderTypeBalanceRecharge, PayStatus: consts.PayStatusUnpaid, CreatedAt: now, }) return &model.AppBalanceRechargeSubmitModel{ OrderId: orderId, OrderSn: orderSn, TotalAmount: amount, PriceText: appFormatMoney(amount), OrderStatus: consts.OrderStatusPendingPay, PayStatus: consts.PayStatusUnpaid, Status: "pending_pay", PaymentExpireAt: paymentExpireAt, PaymentRemainingSeconds: paymentRemainingSeconds, PaymentExpired: paymentExpired, PayProvider: payProvider, PayUrl: appWechatPayUrl(payRequest), PayMethod: appWechatPayMethod(payRequest), PayGateway: "", PayParams: appWechatPayParams(payRequest), }, nil } func (s *sAppCard) ensureAppBalanceRechargeAccount(ctx context.Context, tx gdb.TX, user *entity.SysUser, now *gtime.Time) (*appBalanceRechargeAccount, error) { if user == nil { return nil, gerror.New("当前用户不存在") } if s.isTerminalUser(ctx, user) { if err := syncTerminalUserProfile(ctx, tx, user, &model.AppUpdateUserInfoParam{ NickName: user.NickName, TerminalName: user.NickName, Phone: user.Phonenumber, Email: user.Email, Sex: user.Sex, }, now); err != nil { return nil, gerror.Wrap(err, "初始化终端余额账户失败") } var terminal *entity.TTerminal if err := dao.TTerminal.Ctx(ctx).TX(tx). Where(dao.TTerminal.Columns().TenantId, user.TenantId). Where(dao.TTerminal.Columns().TerminalId, user.UserId). Limit(1). Scan(&terminal); err != nil { return nil, gerror.Wrap(err, "查询终端余额账户失败") } if terminal == nil || terminal.TerminalId == 0 { return nil, gerror.New("终端余额账户不存在,无法充值") } if strings.TrimSpace(terminal.Status) != "" && strings.TrimSpace(terminal.Status) != consts.SysUserStatusNormal { return nil, gerror.New("终端账户已停用,无法充值") } return &appBalanceRechargeAccount{ AccountType: "terminal", AccountId: user.UserId, CustomerType: consts.CustomerTypeTerminal, CustomerAgentId: appUserDeptOrDefault(user), EndUserId: user.UserId, AgentId: 0, CreatedDept: appUserDeptOrDefault(user), }, nil } if user.DeptId <= 0 { return nil, gerror.New("当前用户未绑定机构,无法充值") } var agentBalance *entity.TAgentBalance if err := dao.TAgentBalance.Ctx(ctx).TX(tx). Where(dao.TAgentBalance.Columns().TenantId, user.TenantId). Where(dao.TAgentBalance.Columns().DeptId, user.DeptId). Limit(1). Scan(&agentBalance); err != nil { return nil, gerror.Wrap(err, "查询企业余额账户失败") } if agentBalance == nil || agentBalance.Id == 0 { createdBalance, createErr := s.ensureAppAgentBalance(ctx, tx, user, now) if createErr != nil { return nil, createErr } agentBalance = createdBalance } if agentBalance.Status != 0 { return nil, gerror.New("企业账户已停用,无法充值") } sellerDeptId, err := s.renewSellerDeptId(ctx, user) if err != nil { return nil, err } if sellerDeptId <= 0 { sellerDeptId = user.DeptId } return &appBalanceRechargeAccount{ AccountType: "agent", AccountId: user.DeptId, CustomerType: consts.CustomerTypeAgent, CustomerAgentId: user.DeptId, EndUserId: 0, AgentId: sellerDeptId, CreatedDept: user.DeptId, }, nil } func (s *sAppCard) insertAppBalanceRechargeOrder(ctx context.Context, tx gdb.TX, user *entity.SysUser, account *appBalanceRechargeAccount, orderSn string, buyerNote string, amount float64, now *gtime.Time) (int64, error) { orderId, err := dao.TOrder.Ctx(ctx).TX(tx).Data(do.TOrder{ OrderSn: orderSn, ParentOrderId: 0, ParentOrderSn: "", CustomerType: account.CustomerType, CustomerAgentId: account.CustomerAgentId, EndUserId: account.EndUserId, AgentId: account.AgentId, OrderType: consts.OrderTypeBalanceRecharge, OrderStatus: consts.OrderStatusPendingPay, ShippingStatus: 0, ReturnStatus: 0, Consignee: "", Mobile: "", Email: "", Address: "", AreaCode: "", BuyerNote: strings.TrimSpace(buyerNote), AdminNote: "", Remark: "小程序余额充值待支付订单", ShippingMethod: consts.ShippingMethodNone, LogisticsId: 0, LogisticsName: "", ShippingTypeId: 0, ShippingTypeName: "", TrackingNo: "", PayType: consts.PayTypeOnline, PayStatus: consts.PayStatusUnpaid, PayOrderSn: "", TotalAmount: amount, ProductAmount: amount, ShippingFee: 0, ServiceFee: 0, InvoiceFee: 0, CouponAmount: 0, DiscountAmount: 0, Balance: 0, PaidAmount: 0, OnlinePaidAmount: 0, OfflinePaidAmount: 0, UnpaidAmount: amount, UnrefundAmount: 0, IsNeedCommission: 0, DistributionStatus: 0, TenantId: user.TenantId, CreatedDept: account.CreatedDept, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).InsertAndGetId() if err != nil { return 0, gerror.Wrap(err, "创建余额充值订单失败") } return orderId, nil } func (s *sAppCard) insertAppBalanceRechargeOrderItem(ctx context.Context, tx gdb.TX, user *entity.SysUser, account *appBalanceRechargeAccount, orderId int64, orderSn string, amount float64, now *gtime.Time) error { skuData := appRenewMarshalJSON([]map[string]any{ {"skuName": "业务类型", "skuValue": "余额充值"}, {"skuName": "充值金额", "skuValue": appFormatMoney(amount), "amount": amount}, }) extraSkuData := appRenewMarshalJSON(map[string]any{ "business": "miniapp_balance_recharge", "accountType": account.AccountType, "accountId": account.AccountId, "amount": amount, "payChannel": "pending", }) if _, err := dao.TOrderItem.Ctx(ctx).TX(tx).Data(do.TOrderItem{ OrderId: orderId, OrderSn: orderSn, CustomerType: account.CustomerType, CustomerAgentId: account.CustomerAgentId, EndUserId: account.EndUserId, Price: amount, Quantity: 1, ProductId: 0, ProductName: "余额充值", ProductSn: "BALANCE_RECHARGE", PicThumb: "", SkuId: 0, SkuData: skuData, DeliveryQuantity: 0, ProductType: consts.ProductTypeBalance, IsGift: 0, AgentId: account.AgentId, PrepayPrice: 0, Commission: "", OriginPrice: amount, PromotionData: "", ExtraSkuData: extraSkuData, PayMode: 0, TenantId: user.TenantId, CreatedDept: account.CreatedDept, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).Insert(); err != nil { return gerror.Wrap(err, "创建余额充值订单项失败") } return nil } func appFormatMoney(value float64) string { return fmt.Sprintf("¥%.2f", appRoundAmount(value)) } func appBalanceRechargeEnabled(ctx context.Context) bool { return g.Cfg().MustGet(ctx, "payment.balanceRecharge.enabled", false).Bool() }