package store import ( "context" "database/sql" "encoding/json" "errors" ) type RechargeChannel struct { ID int64 `db:"id"` Code string `db:"channel_code"` Name string `db:"name"` ChannelType int `db:"channel_type"` MinAmount int64 `db:"min_amount"` MaxAmount int64 `db:"max_amount"` FeeType int `db:"fee_type"` FeeFixedAmount int64 `db:"fee_fixed_amount"` FeeRate int `db:"fee_rate"` } type RechargeCreation struct { TenantID int64 MemberID int64 RechargeNo string ChannelID int64 RequestAmount int64 Currency string VoucherAttachmentIDs []int64 Remark string } type RechargeOrder struct { ID int64 `db:"id"` RechargeNo string `db:"recharge_no"` ChannelID int64 `db:"channel_id"` ChannelName string `db:"channel_name"` RequestAmount int64 `db:"request_amount"` PaidAmount int64 `db:"paid_amount"` Currency string `db:"currency"` VoucherAttachmentIDs json.RawMessage `db:"voucher_attachment_ids"` Status int `db:"recharge_status"` Remark string `db:"remark"` ReviewReasonCode string `db:"review_reason_code"` ReviewReason string `db:"review_reason"` ExternalNo string `db:"external_no"` PaidAt string `db:"paid_at"` ReviewedAt string `db:"reviewed_at"` CreditedAt string `db:"credited_at"` CreatedAt string `db:"created_at"` UpdatedAt string `db:"updated_at"` } type RechargePage struct { Items []RechargeOrder Total int64 } const rechargeStatusReview = 2 func (s *Store) RechargeChannels(ctx context.Context, tenantID int64) ([]RechargeChannel, error) { items := make([]RechargeChannel, 0) err := s.db.SelectContext(ctx, &items, `SELECT id,channel_code,name,channel_type,min_amount,max_amount, fee_type,fee_fixed_amount,fee_rate FROM hg_payment_channel WHERE tenant_id=? AND direction IN (1,3) AND status=1 AND deleted_at IS NULL ORDER BY sort,id`, tenantID) return items, err } func (s *Store) CreateRecharge(ctx context.Context, input RechargeCreation) (*RechargeOrder, error) { tx, err := s.db.BeginTxx(ctx, nil) if err != nil { return nil, err } defer tx.Rollback() var memberID int64 err = tx.GetContext(ctx, &memberID, `SELECT id FROM hg_app_member WHERE tenant_id=? AND id=? AND member_status=1 AND deleted_at IS NULL LIMIT 1 FOR UPDATE`, input.TenantID, input.MemberID) if errors.Is(err, sql.ErrNoRows) { return nil, ErrAccountRestricted } if err != nil { return nil, err } var channel RechargeChannel err = tx.GetContext(ctx, &channel, `SELECT id,channel_code,name,channel_type,min_amount,max_amount, fee_type,fee_fixed_amount,fee_rate FROM hg_payment_channel WHERE tenant_id=? AND id=? AND direction IN (1,3) AND status=1 AND deleted_at IS NULL LIMIT 1 FOR UPDATE`, input.TenantID, input.ChannelID) if errors.Is(err, sql.ErrNoRows) { return nil, ErrRechargeChannel } if err != nil { return nil, err } if input.RequestAmount < maxInt64(1, channel.MinAmount) || (channel.MaxAmount > 0 && input.RequestAmount > channel.MaxAmount) { return nil, ErrRechargeAmount } for _, attachmentID := range input.VoucherAttachmentIDs { var count int if err = tx.GetContext(ctx, &count, `SELECT COUNT(*) FROM hg_sys_attachment WHERE tenant_id=? AND id=? AND app_id='olons_server' AND member_id=? AND status=1`, input.TenantID, attachmentID, input.MemberID); err != nil { return nil, err } if count != 1 { return nil, ErrRechargeVoucher } } vouchers, err := json.Marshal(input.VoucherAttachmentIDs) if err != nil { return nil, err } result, err := tx.ExecContext(ctx, `INSERT INTO hg_recharge_order (tenant_id,recharge_no,member_id,channel_id,request_amount,paid_amount,currency, voucher_attachment_ids,recharge_type,recharge_status,remark,created_by,updated_by) VALUES(?,?,?,?,?,0,?,?,1,?,?,?,?)`, input.TenantID, input.RechargeNo, input.MemberID, input.ChannelID, input.RequestAmount, input.Currency, vouchers, rechargeStatusReview, input.Remark, input.MemberID, input.MemberID) if err != nil { return nil, err } id, err := result.LastInsertId() if err != nil { return nil, err } if err = tx.Commit(); err != nil { return nil, err } return &RechargeOrder{ID: id, RechargeNo: input.RechargeNo, ChannelID: channel.ID, ChannelName: channel.Name, RequestAmount: input.RequestAmount, Currency: input.Currency, VoucherAttachmentIDs: vouchers, Status: rechargeStatusReview, Remark: input.Remark}, nil } func (s *Store) RechargeOrders(ctx context.Context, tenantID, memberID int64, status, offset, limit int) (*RechargePage, error) { where := ` WHERE o.tenant_id=? AND o.member_id=?` args := []any{tenantID, memberID} if status > 0 { where += ` AND o.recharge_status=?` args = append(args, status) } var total int64 if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM hg_recharge_order o`+where, args...); err != nil { return nil, err } args = append(args, limit, offset) items := make([]RechargeOrder, 0) err := s.db.SelectContext(ctx, &items, rechargeSelectSQL()+where+ ` ORDER BY o.created_at DESC,o.id DESC LIMIT ? OFFSET ?`, args...) return &RechargePage{Items: items, Total: total}, err } func (s *Store) RechargeOrder(ctx context.Context, tenantID, memberID, orderID int64) (*RechargeOrder, error) { var row RechargeOrder err := s.db.GetContext(ctx, &row, rechargeSelectSQL()+ ` WHERE o.tenant_id=? AND o.member_id=? AND o.id=? LIMIT 1`, tenantID, memberID, orderID) if errors.Is(err, sql.ErrNoRows) { return nil, ErrRechargeNotFound } return &row, err } func rechargeSelectSQL() string { return `SELECT o.id,o.recharge_no,o.channel_id,COALESCE(c.name,'') AS channel_name,o.request_amount, o.paid_amount,o.currency,COALESCE(o.voucher_attachment_ids,JSON_ARRAY()) AS voucher_attachment_ids, o.recharge_status,o.remark,o.review_reason_code,o.review_reason,o.external_no, COALESCE(DATE_FORMAT(o.paid_at,'%Y-%m-%dT%H:%i:%sZ'),'') AS paid_at, COALESCE(DATE_FORMAT(o.reviewed_at,'%Y-%m-%dT%H:%i:%sZ'),'') AS reviewed_at, COALESCE(DATE_FORMAT(o.credited_at,'%Y-%m-%dT%H:%i:%sZ'),'') AS credited_at, DATE_FORMAT(o.created_at,'%Y-%m-%dT%H:%i:%sZ') AS created_at, DATE_FORMAT(o.updated_at,'%Y-%m-%dT%H:%i:%sZ') AS updated_at FROM hg_recharge_order o LEFT JOIN hg_payment_channel c ON c.tenant_id=o.tenant_id AND c.id=o.channel_id` }