商城c端前端
This commit is contained in:
@@ -256,14 +256,14 @@ func parseFeatures(raw json.RawMessage) Features {
|
||||
return Features{}
|
||||
}
|
||||
return Features{
|
||||
TaskEnabled: findBool(config, "task_enabled", "taskEnabled", "TASK"),
|
||||
RechargeEnabled: findBool(config, "recharge_enabled", "rechargeEnabled", "RECHARGE"),
|
||||
WithdrawalEnabled: findBool(config, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL"),
|
||||
ManualMallEnabled: findBool(config, "manual_mall_enabled", "manualMallEnabled", "SHOP_STATUS"),
|
||||
TaskEnabled: findBool(config, true, "task_enabled", "taskEnabled", "TASK"),
|
||||
RechargeEnabled: findBool(config, true, "recharge_enabled", "rechargeEnabled", "RECHARGE"),
|
||||
WithdrawalEnabled: findBool(config, true, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL"),
|
||||
ManualMallEnabled: findBool(config, true, "manual_mall_enabled", "manualMallEnabled", "SHOP_STATUS"),
|
||||
}
|
||||
}
|
||||
|
||||
func findBool(config map[string]any, keys ...string) bool {
|
||||
func findBool(config map[string]any, fallback bool, keys ...string) bool {
|
||||
for _, key := range keys {
|
||||
if value, ok := config[key]; ok {
|
||||
return boolValue(value)
|
||||
@@ -276,7 +276,7 @@ func findBool(config map[string]any, keys ...string) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return fallback
|
||||
}
|
||||
|
||||
func boolValue(value any) bool {
|
||||
|
||||
@@ -61,6 +61,13 @@ func TestParseFeaturesSupportsNestedAndLegacyKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeaturesDefaultsToEnabled(t *testing.T) {
|
||||
features := parseFeatures(json.RawMessage(`{"siteName":"demo"}`))
|
||||
if !features.TaskEnabled || !features.RechargeEnabled || !features.WithdrawalEnabled || !features.ManualMallEnabled {
|
||||
t.Fatalf("unexpected feature defaults: %+v", features)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBannersSanitizeTargets(t *testing.T) {
|
||||
service := New(fakeRepository{banners: []store.Banner{
|
||||
{ID: 1, TargetType: 2, TargetValue: "/receive"},
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
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
|
||||
}
|
||||
|
||||
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='workbench' 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,1,?,?,?)`, input.TenantID, input.RechargeNo, input.MemberID,
|
||||
input.ChannelID, input.RequestAmount, input.Currency, vouchers, 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: 1, 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`
|
||||
}
|
||||
Reference in New Issue
Block a user