Files
sealos/controllers/pkg/utils/email.go
T
Jiahui 8c2db08fd4 Support subscriptions, atom payments, credits, and optimize debt systems (#5545)
* add credits

* create table

* change name & add table name

* optimize interface

* print result err log

* fix

* add credits for debt

* creditsTransaction add region

* refactor debt status:
Normal\LowBalance\CriticalBalance\Debt\DebtDeletion\Final Period.

* refactor debt status:
Normal\LowBalance\CriticalBalance\Debt\DebtDeletion\Final Period.

* add retry transaction

* optimize determine status

* support card pay & subscription plan & operator service

* add pay notify & redirect url

* fix

* optimize

* add error test log

* fix req time

* fix

* fix

* add subscription payment api

* only processes the `CAPTURE_RESULT` notify type

* fix

* fix

* fixed notify structure parsing

* fix

* fix

* init a Free initial subscription plan √/ Initialize credits Create √

* init resourcequota wiht subscription

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix card type

* fix

* Subscribe controller: real-time monitoring subscriptionTransaction processing created/upgrade/downgrade

* fix

* fix

* fix

* determine current debt status with subscription

* optimize pay api resp

* process subscription auto renewal

* optimize log

* fix

* fix

* add GetAppResourceCosts api

* fix

* add GetLastSubscriptionTransaction api

* add GetSubscriptionUpgradeAmount api

* add /subscription/flush-quota api

* fix

* add remote flush subscription quota

* go mod tidy

* fix

* add retry flush subscription quota

* add custom debt status send email body

* fix

* fix

* fix

* fix

* fix

* add free process subscription

* fix

* add default pay expire time

* optimize

* support kyc & optimize

* optimiz

* add descributed lock with gorm db

* optimize debt reconcile

* optimize send email

* optimize split sms vms code map; handler failure reflush debt status.

* optimize

* add email username

* optimize

* skip zero user

* optimize flush svc

* add convert test func

* fix

* optimize log

* use the k8s client with cache

* fix golang ci lint
2025-05-06 16:56:25 +08:00

207 lines
5.2 KiB
Go

package utils
import (
"strconv"
"time"
"github.com/go-gomail/gomail"
"github.com/labring/sealos/controllers/pkg/types"
)
type SMTPConfig struct {
ServerHost string
ServerPort int
Username string
FromEmail string
Passwd string
EmailTitle string
}
func (c *SMTPConfig) SendEmail(emailBody, to string) error {
m := gomail.NewMessage()
m.SetHeader("To", to)
m.SetAddressHeader("From", c.FromEmail, c.EmailTitle)
m.SetHeader("Subject", c.EmailTitle)
m.SetBody("text/html", emailBody)
d := gomail.NewDialer(c.ServerHost, c.ServerPort, c.Username, c.Passwd)
return d.DialAndSend(m)
}
func (c *SMTPConfig) SendEmailWithSubject(subject, emailBody, to string) error {
m := gomail.NewMessage()
m.SetHeader("To", to)
m.SetAddressHeader("From", c.FromEmail, c.EmailTitle)
m.SetHeader("Subject", subject)
m.SetBody("text/html", emailBody)
d := gomail.NewDialer(c.ServerHost, c.ServerPort, c.Username, c.Passwd)
return d.DialAndSend(m)
}
const (
EnvSMTPHost = "SMTP_HOST"
EnvSMTPPort = "SMTP_PORT"
EnvSMTPFrom = "SMTP_FROM"
EnvSMTPUser = "SMTP_USER"
EnvSMTPPassword = "SMTP_PASSWORD"
EnvSMTPTitle = "SMTP_TITLE"
EnvPaySuccessEmailTmpl = "PAY_SUCCESS_EMAIL_TMPL"
EnvPayFailedEmailTmpl = "PAY_FAILED_EMAIL_TMPL"
EnvSubSuccessEmailTmpl = "SUB_SUCCESS_EMAIL_TMPL"
EnvSubFailedEmailTmpl = "SUB_FAILED_EMAIL_TMPL"
)
type EmailRenderBuilder interface {
Build() map[string]interface{}
GetType() string
SetUserInfo(userInfo *types.UserInfo)
GetSubject() string
}
type EmailPayRender struct {
Type string
userInfo *types.UserInfo
Domain string
TopUpAmount int64
AccountBalance int64
}
func (e *EmailPayRender) Build() map[string]interface{} {
return map[string]interface{}{
"FirstName": e.userInfo.FirstName,
"LastName": e.userInfo.LastName,
"Domain": e.Domain,
"TopUpAmount": strconv.FormatInt(e.TopUpAmount, 10),
"AccountBalance": strconv.FormatInt(e.AccountBalance, 10),
}
}
func (e *EmailPayRender) GetType() string {
return e.Type
}
func (e *EmailPayRender) GetSubject() string {
return "Top-Up Successful"
}
func (e *EmailPayRender) SetUserInfo(userInfo *types.UserInfo) {
e.userInfo = userInfo
}
func (e *EmailSubRender) Build() map[string]interface{} {
build := map[string]interface{}{
"FirstName": e.userInfo.FirstName,
"LastName": e.userInfo.LastName,
"Domain": e.Domain,
"SubscriptionPlanName": e.SubscriptionPlanName,
"StartDate": e.StartDate.Format("2006-01-02"),
"EndDate": e.EndDate.Format("2006-01-02"),
}
switch e.SubscriptionPlanName {
case "Hobby":
build["SubscriptionFeatures"] = []string{
"Includes $5 credits",
"16 vCPU / 32GiB RAM",
"Unlimited disk & traffic within plan",
"Multiple regions",
"3 workspaces / region",
"5 seats / workspace",
}
case "Pro":
build["SubscriptionFeatures"] = []string{
"Includes $20 credits",
"128 vCPU / 256GiB RAM",
"Unlimited disk & traffic within plan",
"Multiple regions",
"Multiple workspace / region",
"Multiple seat / workspace",
}
}
return build
}
type EmailSubRender struct {
Type string
Operator types.SubscriptionOperator
userInfo types.UserInfo
Domain string
SubscriptionPlanName string
StartDate time.Time
EndDate time.Time
}
func (e *EmailSubRender) GetType() string {
return e.Type
}
func (e *EmailSubRender) SetUserInfo(userInfo *types.UserInfo) {
e.userInfo = *userInfo
}
func (e *EmailSubRender) GetSubject() string {
switch e.Operator {
case types.SubscriptionTransactionTypeUpgraded:
return "Your Subscription Has Been Successfully Updated"
case types.SubscriptionTransactionTypeDowngraded:
return "Your Subscription Has Been Successfully Downgraded"
case types.SubscriptionTransactionTypeCanceled:
return "Your Subscription Has Been Successfully Canceled"
case types.SubscriptionTransactionTypeRenewed:
return "Your Subscription Has Been Successfully Renewed"
default:
return "Your Subscription Has Been Successfully Activated"
}
}
type EmailDebtRender struct {
Type string
CurrentStatus types.DebtStatusType
userInfo types.UserInfo
Domain string
GraceReason []string
}
type DebtGraceReason string
const (
GraceReasonNoBalance DebtGraceReason = "insufficient balance"
GraceReasonSubExpired DebtGraceReason = "subscription expired"
)
func (e *EmailDebtRender) GetType() string {
return e.Type
}
func (e *EmailDebtRender) SetUserInfo(userInfo *types.UserInfo) {
e.userInfo = *userInfo
}
func (e *EmailDebtRender) GetSubject() string {
if types.ContainDebtStatus(types.DebtStates, e.CurrentStatus) {
if e.CurrentStatus == types.FinalDeletionPeriod {
return "Important: Your Resources Had Expired"
}
return "Important: Your Account Has Entered Grace Period"
}
return "Low Account Balance Reminder"
}
func (e *EmailDebtRender) Build() map[string]interface{} {
build := map[string]interface{}{
"FirstName": e.userInfo.FirstName,
"LastName": e.userInfo.LastName,
"Domain": e.Domain,
"GraceReason": e.GraceReason,
}
if e.Type == "CriticalBalancePeriod" {
build["CreditsAvailable"] = "1"
}
if e.Type == "LowBalancePeriod" {
build["CreditsAvailable"] = "5"
}
return build
}