feat(payment): 发送支付成功通知邮件
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
55b13cd7b4
commit
903ef7b592
@ -264,6 +264,7 @@ func (h *PaymentHandler) CreateOrder(c *gin.Context) {
|
||||
PaymentSource: req.PaymentSource,
|
||||
OrderType: req.OrderType,
|
||||
PlanID: req.PlanID,
|
||||
Locale: c.GetHeader("Accept-Language"),
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
|
||||
@ -310,9 +310,87 @@ func (s *PaymentService) markCompleted(ctx context.Context, o *dbent.PaymentOrde
|
||||
"creditedAmount": o.Amount,
|
||||
"payAmount": o.PayAmount,
|
||||
})
|
||||
s.dispatchPaymentFulfillmentNotification(o, auditAction)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PaymentService) dispatchPaymentFulfillmentNotification(o *dbent.PaymentOrder, auditAction string) {
|
||||
if s == nil || s.notificationEmailService == nil || o == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), emailSendTimeout)
|
||||
defer cancel()
|
||||
var err error
|
||||
switch auditAction {
|
||||
case "RECHARGE_SUCCESS":
|
||||
err = s.sendBalanceRechargeSuccessNotification(ctx, o)
|
||||
case "SUBSCRIPTION_SUCCESS":
|
||||
err = s.sendSubscriptionPurchaseSuccessNotification(ctx, o)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
slog.Warn("payment fulfillment notification email failed", "order_id", o.ID, "action", auditAction, "err", err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *PaymentService) sendBalanceRechargeSuccessNotification(ctx context.Context, o *dbent.PaymentOrder) error {
|
||||
currentBalance := ""
|
||||
if s.userRepo != nil {
|
||||
if user, err := s.userRepo.GetByID(ctx, o.UserID); err == nil && user != nil {
|
||||
currentBalance = fmt.Sprintf("%.2f", user.Balance)
|
||||
}
|
||||
}
|
||||
return s.notificationEmailService.Send(ctx, NotificationEmailSendInput{
|
||||
Event: NotificationEmailEventBalanceRechargeSuccess,
|
||||
RecipientEmail: o.UserEmail,
|
||||
RecipientName: firstNonEmpty(o.UserName, o.UserEmail),
|
||||
UserID: o.UserID,
|
||||
SourceType: "payment_order",
|
||||
SourceID: strconv.FormatInt(o.ID, 10),
|
||||
Variables: map[string]string{
|
||||
"recharge_amount": fmt.Sprintf("%.2f", o.Amount),
|
||||
"current_balance": currentBalance,
|
||||
"order_id": strconv.FormatInt(o.ID, 10),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PaymentService) sendSubscriptionPurchaseSuccessNotification(ctx context.Context, o *dbent.PaymentOrder) error {
|
||||
variables := map[string]string{
|
||||
"subscription_group": "Subscription",
|
||||
"subscription_days": "",
|
||||
"expiry_time": "",
|
||||
"order_id": strconv.FormatInt(o.ID, 10),
|
||||
}
|
||||
if o.SubscriptionDays != nil {
|
||||
variables["subscription_days"] = strconv.Itoa(*o.SubscriptionDays)
|
||||
}
|
||||
if o.SubscriptionGroupID != nil {
|
||||
if s.groupRepo != nil {
|
||||
if group, err := s.groupRepo.GetByID(ctx, *o.SubscriptionGroupID); err == nil && group != nil && strings.TrimSpace(group.Name) != "" {
|
||||
variables["subscription_group"] = group.Name
|
||||
}
|
||||
}
|
||||
if s.subscriptionSvc != nil {
|
||||
if sub, err := s.subscriptionSvc.GetActiveSubscription(ctx, o.UserID, *o.SubscriptionGroupID); err == nil && sub != nil {
|
||||
variables["expiry_time"] = sub.ExpiresAt.Format("2006-01-02 15:04")
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.notificationEmailService.Send(ctx, NotificationEmailSendInput{
|
||||
Event: NotificationEmailEventSubscriptionPurchaseSuccess,
|
||||
RecipientEmail: o.UserEmail,
|
||||
RecipientName: firstNonEmpty(o.UserName, o.UserEmail),
|
||||
UserID: o.UserID,
|
||||
SourceType: "payment_order",
|
||||
SourceID: strconv.FormatInt(o.ID, 10),
|
||||
Variables: variables,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PaymentService) ExecuteSubscriptionFulfillment(ctx context.Context, oid int64) error {
|
||||
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
|
||||
if err != nil {
|
||||
|
||||
@ -48,6 +48,9 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest
|
||||
if user.Status != payment.EntityStatusActive {
|
||||
return nil, infraerrors.Forbidden("USER_INACTIVE", "user account is disabled")
|
||||
}
|
||||
if s.notificationEmailService != nil {
|
||||
s.notificationEmailService.RememberRecipientLocale(ctx, req.UserID, user.Email, req.Locale)
|
||||
}
|
||||
orderAmount := req.Amount
|
||||
limitAmount := req.Amount
|
||||
if plan != nil {
|
||||
|
||||
@ -83,6 +83,7 @@ type CreateOrderRequest struct {
|
||||
PaymentSource string
|
||||
OrderType string
|
||||
PlanID int64
|
||||
Locale string
|
||||
}
|
||||
|
||||
type CreateOrderResponse struct {
|
||||
@ -174,18 +175,19 @@ type TopUserStat struct {
|
||||
// --- Service ---
|
||||
|
||||
type PaymentService struct {
|
||||
providerMu sync.Mutex
|
||||
providersLoaded bool
|
||||
entClient *dbent.Client
|
||||
registry *payment.Registry
|
||||
loadBalancer payment.LoadBalancer
|
||||
redeemService *RedeemService
|
||||
subscriptionSvc *SubscriptionService
|
||||
configService *PaymentConfigService
|
||||
userRepo UserRepository
|
||||
groupRepo GroupRepository
|
||||
resumeService *PaymentResumeService
|
||||
affiliateService *AffiliateService
|
||||
providerMu sync.Mutex
|
||||
providersLoaded bool
|
||||
entClient *dbent.Client
|
||||
registry *payment.Registry
|
||||
loadBalancer payment.LoadBalancer
|
||||
redeemService *RedeemService
|
||||
subscriptionSvc *SubscriptionService
|
||||
configService *PaymentConfigService
|
||||
userRepo UserRepository
|
||||
groupRepo GroupRepository
|
||||
resumeService *PaymentResumeService
|
||||
affiliateService *AffiliateService
|
||||
notificationEmailService *NotificationEmailService
|
||||
}
|
||||
|
||||
func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, loadBalancer payment.LoadBalancer, redeemService *RedeemService, subscriptionSvc *SubscriptionService, configService *PaymentConfigService, userRepo UserRepository, groupRepo GroupRepository, affiliateService *AffiliateService) *PaymentService {
|
||||
@ -194,6 +196,10 @@ func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, load
|
||||
return svc
|
||||
}
|
||||
|
||||
func (s *PaymentService) SetNotificationEmailService(notificationEmailService *NotificationEmailService) {
|
||||
s.notificationEmailService = notificationEmailService
|
||||
}
|
||||
|
||||
// --- Provider Registry ---
|
||||
|
||||
// EnsureProviders lazily initializes the provider registry on first call.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user