Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ var TelegramBotName = ""
var QuotaForNewUser = 0
var QuotaForInviter = 0
var QuotaForInvitee = 0

// Referral commission settings (payment-based referral)
var ReferralCommissionEnabled = false // Enable commission when referred user recharges
var ReferralCommissionPercent = 10.0 // Percentage of recharge amount (0-100)
var ReferralCommissionMaxRecharges = 0 // Max recharges to give commission (0 = unlimited)
var ChannelDisableThreshold = 5.0
var AutomaticDisableChannelEnabled = false
var AutomaticEnableChannelEnabled = false
Expand Down
4 changes: 4 additions & 0 deletions controller/topup.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ func EpayNotify(c *gin.Context) {
}
log.Printf("易支付回调更新用户成功 %v", topUp)
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money))
// Credit referral commission to inviter (if enabled)
if err := model.CreditReferralCommission(topUp.UserId, topUp.Money); err != nil {
log.Printf("用户 %d 返佣失败: %v", topUp.UserId, err)
}
}
} else {
log.Printf("易支付异常回调: %v", verifyInfo)
Expand Down
9 changes: 9 additions & 0 deletions model/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ func InitOptionMap() {
common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser)
common.OptionMap["QuotaForInviter"] = strconv.Itoa(common.QuotaForInviter)
common.OptionMap["QuotaForInvitee"] = strconv.Itoa(common.QuotaForInvitee)
common.OptionMap["ReferralCommissionEnabled"] = strconv.FormatBool(common.ReferralCommissionEnabled)
common.OptionMap["ReferralCommissionPercent"] = strconv.FormatFloat(common.ReferralCommissionPercent, 'f', -1, 64)
common.OptionMap["ReferralCommissionMaxRecharges"] = strconv.Itoa(common.ReferralCommissionMaxRecharges)
common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
common.OptionMap["PreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
common.OptionMap["ModelRequestRateLimitCount"] = strconv.Itoa(setting.ModelRequestRateLimitCount)
Expand Down Expand Up @@ -296,6 +299,8 @@ func updateOptionMap(key string, value string) (err error) {
setting.DefaultUseAutoGroup = boolValue
case "ExposeRatioEnabled":
ratio_setting.SetExposeRatioEnabled(boolValue)
case "ReferralCommissionEnabled":
common.ReferralCommissionEnabled = boolValue
}
}
switch key {
Expand Down Expand Up @@ -394,6 +399,10 @@ func updateOptionMap(key string, value string) (err error) {
common.QuotaForInviter, _ = strconv.Atoi(value)
case "QuotaForInvitee":
common.QuotaForInvitee, _ = strconv.Atoi(value)
case "ReferralCommissionPercent":
common.ReferralCommissionPercent, _ = strconv.ParseFloat(value, 64)
case "ReferralCommissionMaxRecharges":
common.ReferralCommissionMaxRecharges, _ = strconv.Atoi(value)
case "QuotaRemindThreshold":
common.QuotaRemindThreshold, _ = strconv.Atoi(value)
case "PreConsumedQuota":
Expand Down
17 changes: 17 additions & 0 deletions model/topup.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ func Recharge(referenceId string, customerId string) (err error) {

RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%d", logger.FormatQuota(int(quota)), topUp.Amount))

// Credit referral commission to inviter (if enabled)
if err := CreditReferralCommission(topUp.UserId, topUp.Money); err != nil {
common.SysLog(fmt.Sprintf("用户 %d 返佣失败: %v", topUp.UserId, err))
}

return nil
}

Expand Down Expand Up @@ -303,8 +308,15 @@ func ManualCompleteTopUp(tradeNo string) error {

// 事务外记录日志,避免阻塞
RecordLog(userId, LogTypeTopup, fmt.Sprintf("管理员补单成功,充值金额: %v,支付金额:%f", logger.FormatQuota(quotaToAdd), payMoney))

// Credit referral commission to inviter (if enabled)
if err := CreditReferralCommission(userId, payMoney); err != nil {
common.SysLog(fmt.Sprintf("用户 %d 返佣失败: %v", userId, err))
}

return nil
}

func RechargeCreem(referenceId string, customerEmail string, customerName string) (err error) {
if referenceId == "" {
return errors.New("未提供支付单号")
Expand Down Expand Up @@ -372,5 +384,10 @@ func RechargeCreem(referenceId string, customerEmail string, customerName string

RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", quota, topUp.Money))

// Credit referral commission to inviter (if enabled)
if err := CreditReferralCommission(topUp.UserId, topUp.Money); err != nil {
common.SysLog(fmt.Sprintf("用户 %d 返佣失败: %v", topUp.UserId, err))
}

return nil
}
59 changes: 59 additions & 0 deletions model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,65 @@ func inviteUser(inviterId int) (err error) {
return DB.Save(user).Error
}

// CreditReferralCommission credits the inviter with a commission when the referred user recharges
// This implements payment-based referral rewards instead of instant registration bonuses
func CreditReferralCommission(userId int, rechargeAmount float64) error {
if !common.ReferralCommissionEnabled {
return nil // Commission feature disabled
}

if rechargeAmount <= 0 {
return nil // No recharge amount
}

// Get the user who recharged
user, err := GetUserById(userId, true)
if err != nil {
return err
}

// Check if user has an inviter
if user.InviterId == 0 {
return nil // No inviter, no commission
}

// Check max recharges limit (if configured)
if common.ReferralCommissionMaxRecharges > 0 {
// Count successful recharges for this user
var rechargeCount int64
DB.Model(&TopUp{}).Where("user_id = ? AND status = ?", userId, common.TopUpStatusSuccess).Count(&rechargeCount)
if int(rechargeCount) > common.ReferralCommissionMaxRecharges {
return nil // Max recharges reached, no more commission
}
}

// Calculate commission
commissionPercent := common.ReferralCommissionPercent
if commissionPercent <= 0 || commissionPercent > 100 {
return nil // Invalid percentage
}

// Commission = rechargeAmount * (percent/100) * QuotaPerUnit
commission := int(rechargeAmount * (commissionPercent / 100) * common.QuotaPerUnit)
if commission <= 0 {
return nil // Commission too small
}

// Atomically update inviter's AffQuota to prevent race conditions under concurrent recharges
err = DB.Model(&User{}).Where("id = ?", user.InviterId).Updates(map[string]interface{}{
"aff_quota": gorm.Expr("aff_quota + ?", commission),
"aff_history": gorm.Expr("aff_history + ?", commission),
}).Error
if err != nil {
return err
}

// Log the commission
RecordLog(user.InviterId, LogTypeSystem, fmt.Sprintf("邀请用户充值返佣 %s (%.1f%% of $%.2f)", logger.LogQuota(commission), commissionPercent, rechargeAmount))

return nil
}

func (user *User) TransferAffQuotaToQuota(quota int) error {
// 检查quota是否小于最小额度
if float64(quota) < common.QuotaPerUnit {
Expand Down
9 changes: 9 additions & 0 deletions web/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,15 @@
"邀请获得额度": "Invitation quota",
"邀请链接": "Invitation link",
"邀请链接已复制到剪切板": "Invitation link has been copied to clipboard",
"邀请充值返佣设置": "Referral Commission Settings",
"启用充值返佣": "Enable Referral Commission",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "When enabled, the inviter receives a percentage of the amount when the invited user recharges",
"返佣比例": "Commission Percentage",
"被邀请用户充值时,邀请人获得的返佣比例": "The percentage of recharge amount the inviter receives as commission",
"最大返佣次数": "Max Commission Recharges",
"被邀请用户前N次充值给予返佣,0表示不限次数": "Give commission for the first N recharges of invited users, 0 means unlimited",
"保存返佣设置": "Save Commission Settings",
"邀请用户充值返佣 %s (%.1f%% of $%.2f)": "Referral commission %s (%.1f%% of $%.2f)",
"邮件通知": "Email notification",
"邮箱": "Email",
"邮箱地址": "Email address",
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,14 @@
"邀请获得额度": "Quota d'invitation",
"邀请链接": "Lien d'invitation",
"邀请链接已复制到剪切板": "Le lien d'invitation a été copié dans le presse-papiers",
"邀请充值返佣设置": "Paramètres de commission de parrainage",
"启用充值返佣": "Activer la commission sur recharge",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "Une fois activé, le parrain reçoit un pourcentage des recharges effectuées par ses filleuls",
"返佣比例": "Pourcentage de commission",
"被邀请用户充值时,邀请人获得的返佣比例": "Pourcentage du montant rechargé versé au parrain",
"最大返佣次数": "Nombre maximum de recharges commissionnées",
"被邀请用户前N次充值给予返佣,0表示不限次数": "Commission sur les N premières recharges du filleul, 0 = illimité",
"保存返佣设置": "Enregistrer les paramètres de commission",
"邮件通知": "Notification par e-mail",
"邮箱": "E-mail",
"邮箱地址": "Adresse e-mail",
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -2387,6 +2387,14 @@
"邀请获得额度": "招待特典クォータ",
"邀请链接": "招待リンク",
"邀请链接已复制到剪切板": "招待リンクがクリップボードにコピーされました",
"邀请充值返佣设置": "招待コミッション設定",
"启用充值返佣": "チャージコミッションを有効化",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "有効にすると、招待されたユーザーがチャージした際、招待者は金額の一定割合をコミッションとして受け取れます",
"返佣比例": "コミッション率",
"被邀请用户充值时,邀请人获得的返佣比例": "招待されたユーザーのチャージ金額に対する招待者のコミッション率",
"最大返佣次数": "最大コミッション回数",
"被邀请用户前N次充值给予返佣,0表示不限次数": "招待されたユーザーの最初のN回のチャージにコミッションを付与、0は無制限",
"保存返佣设置": "コミッション設定を保存",
"邮件通知": "メール通知",
"邮箱": "メールアドレス",
"邮箱地址": "メールアドレス",
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -2417,6 +2417,14 @@
"邀请获得额度": "Получить лимит через приглашение",
"邀请链接": "Ссылка приглашения",
"邀请链接已复制到剪切板": "Ссылка приглашения скопирована в буфер обмена",
"邀请充值返佣设置": "Настройки комиссии за приглашение",
"启用充值返佣": "Включить комиссию за пополнение",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "После включения, когда приглашённый пользователь пополняет счёт, приглашающий получает процент от суммы",
"返佣比例": "Процент комиссии",
"被邀请用户充值时,邀请人获得的返佣比例": "Процент от суммы пополнения, который получает приглашающий",
"最大返佣次数": "Максимум пополнений для комиссии",
"被邀请用户前N次充值给予返佣,0表示不限次数": "Комиссия за первые N пополнений приглашённого, 0 = без лимита",
"保存返佣设置": "Сохранить настройки комиссии",
"邮件通知": "Email-уведомления",
"邮箱": "Электронная почта",
"邮箱地址": "Адрес электронной почты",
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2914,6 +2914,14 @@
"邀请获得额度": "Hạn ngạch nhận được từ lời mời",
"邀请链接": "Liên kết mời",
"邀请链接已复制到剪切板": "Liên kết mời đã được sao chép vào khay nhớ tạm",
"邀请充值返佣设置": "Cài đặt hoa hồng giới thiệu",
"启用充值返佣": "Bật hoa hồng nạp tiền",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "Khi bật, người giới thiệu sẽ nhận được phần trăm hoa hồng khi người được mời nạp tiền",
"返佣比例": "Tỷ lệ hoa hồng",
"被邀请用户充值时,邀请人获得的返佣比例": "Tỷ lệ phần trăm hoa hồng người giới thiệu nhận được khi người được mời nạp tiền",
"最大返佣次数": "Số lần nạp tiền tối đa được hoa hồng",
"被邀请用户前N次充值给予返佣,0表示不限次数": "Hoa hồng cho N lần nạp tiền đầu tiên của người được mời, 0 = không giới hạn",
"保存返佣设置": "Lưu cài đặt hoa hồng",
"邀请链接已复制到剪贴板": "Liên kết mời đã được sao chép vào khay nhớ tạm",
"邮件通知": "Thông báo email",
"邮箱": "Email",
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2387,6 +2387,14 @@
"邀请获得额度": "邀请获得额度",
"邀请链接": "邀请链接",
"邀请链接已复制到剪切板": "邀请链接已复制到剪切板",
"邀请充值返佣设置": "邀请充值返佣设置",
"启用充值返佣": "启用充值返佣",
"开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣": "开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣",
"返佣比例": "返佣比例",
"被邀请用户充值时,邀请人获得的返佣比例": "被邀请用户充值时,邀请人获得的返佣比例",
"最大返佣次数": "最大返佣次数",
"被邀请用户前N次充值给予返佣,0表示不限次数": "被邀请用户前N次充值给予返佣,0表示不限次数",
"保存返佣设置": "保存返佣设置",
"邮件通知": "邮件通知",
"邮箱": "邮箱",
"邮箱地址": "邮箱地址",
Expand Down
63 changes: 63 additions & 0 deletions web/src/pages/Setting/Operation/SettingsCreditLimit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default function SettingsCreditLimit(props) {
QuotaForInviter: '',
QuotaForInvitee: '',
'quota_setting.enable_free_model_pre_consume': true,
ReferralCommissionEnabled: false,
ReferralCommissionPercent: '10',
ReferralCommissionMaxRecharges: '0',
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
Expand Down Expand Up @@ -189,6 +192,66 @@ export default function SettingsCreditLimit(props) {
</Button>
</Row>
</Form.Section>

<Form.Section text={t('邀请充值返佣设置')}>
<Row gutter={16}>
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
<Form.Switch
label={t('启用充值返佣')}
field={'ReferralCommissionEnabled'}
extraText={t('开启后,被邀请用户充值时,邀请人可获得充值金额的一定比例作为返佣')}
onChange={(value) =>
setInputs({
...inputs,
ReferralCommissionEnabled: value,
})
}
/>
</Col>
</Row>
<Row gutter={16}>
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
<Form.InputNumber
label={t('返佣比例')}
field={'ReferralCommissionPercent'}
step={0.1}
min={0}
max={100}
suffix={'%'}
extraText={t('被邀请用户充值时,邀请人获得的返佣比例')}
placeholder={t('例如:10')}
onChange={(value) =>
setInputs({
...inputs,
ReferralCommissionPercent: String(value),
})
}
/>
</Col>
<Col xs={24} sm={12} md={8} lg={8} xl={8}>
<Form.InputNumber
label={t('最大返佣次数')}
field={'ReferralCommissionMaxRecharges'}
step={1}
min={0}
suffix={t('次')}
extraText={t('被邀请用户前N次充值给予返佣,0表示不限次数')}
placeholder={t('例如:3')}
onChange={(value) =>
setInputs({
...inputs,
ReferralCommissionMaxRecharges: String(value),
})
}
/>
</Col>
</Row>
<Row>
<Button size='default' onClick={onSubmit}>
{t('保存返佣设置')}
</Button>
</Row>
</Form.Section>
</Form>
</Spin>
</>
Expand Down