|
|
|
@@ -4,7 +4,10 @@ import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/perfect-panel/server/internal/model/log"
|
|
|
|
|
"github.com/perfect-panel/server/internal/report"
|
|
|
|
|
"github.com/perfect-panel/server/pkg/constant"
|
|
|
|
|
|
|
|
|
|
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
|
|
|
@@ -104,6 +107,17 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
|
|
|
|
CheckoutUrl: url,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case paymentPlatform.CryptoSaaS:
|
|
|
|
|
// Process EPay payment - generates payment URL for redirect
|
|
|
|
|
url, err := l.CryptoSaaSPayment(paymentConfig, orderInfo, req.ReturnUrl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "epayPayment error: %v", err.Error())
|
|
|
|
|
}
|
|
|
|
|
resp = &types.CheckoutOrderResponse{
|
|
|
|
|
CheckoutUrl: url,
|
|
|
|
|
Type: "url", // Client should redirect to URL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case paymentPlatform.Balance:
|
|
|
|
|
// Process balance payment - validate user and process payment immediately
|
|
|
|
|
if orderInfo.UserId == 0 {
|
|
|
|
@@ -138,8 +152,8 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
|
|
|
|
// It handles currency conversion and creates a pre-payment trade for QR code scanning
|
|
|
|
|
func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *order.Order) (string, error) {
|
|
|
|
|
// Parse Alipay F2F configuration from payment settings
|
|
|
|
|
f2FConfig := payment.AlipayF2FConfig{}
|
|
|
|
|
if err := json.Unmarshal([]byte(pay.Config), &f2FConfig); err != nil {
|
|
|
|
|
f2FConfig := &payment.AlipayF2FConfig{}
|
|
|
|
|
if err := f2FConfig.Unmarshal([]byte(pay.Config)); err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] Unmarshal Alipay config error", logger.Field("error", err.Error()))
|
|
|
|
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
@@ -189,8 +203,9 @@ func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *ord
|
|
|
|
|
// It supports various payment methods including WeChat Pay and Alipay through Stripe
|
|
|
|
|
func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order, identifier string) (*types.StripePayment, error) {
|
|
|
|
|
// Parse Stripe configuration from payment settings
|
|
|
|
|
stripeConfig := payment.StripeConfig{}
|
|
|
|
|
if err := json.Unmarshal([]byte(config), &stripeConfig); err != nil {
|
|
|
|
|
stripeConfig := &payment.StripeConfig{}
|
|
|
|
|
|
|
|
|
|
if err := stripeConfig.Unmarshal([]byte(config)); err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] Unmarshal Stripe config error", logger.Field("error", err.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
@@ -247,14 +262,13 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order,
|
|
|
|
|
// It handles currency conversion and creates a payment URL for external payment processing
|
|
|
|
|
func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
|
|
|
|
// Parse EPay configuration from payment settings
|
|
|
|
|
epayConfig := payment.EPayConfig{}
|
|
|
|
|
if err := json.Unmarshal([]byte(config.Config), &epayConfig); err != nil {
|
|
|
|
|
epayConfig := &payment.EPayConfig{}
|
|
|
|
|
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] Unmarshal EPay config error", logger.Field("error", err.Error()))
|
|
|
|
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize EPay client with merchant credentials
|
|
|
|
|
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key)
|
|
|
|
|
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type)
|
|
|
|
|
|
|
|
|
|
// Convert order amount to CNY using current exchange rate
|
|
|
|
|
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
|
|
|
@@ -262,16 +276,29 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// gateway mod
|
|
|
|
|
|
|
|
|
|
isGatewayMod := report.IsGatewayMode()
|
|
|
|
|
|
|
|
|
|
// Build notification URL for payment status callbacks
|
|
|
|
|
notifyUrl := ""
|
|
|
|
|
if config.Domain != "" {
|
|
|
|
|
notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
notifyUrl = config.Domain
|
|
|
|
|
if isGatewayMod {
|
|
|
|
|
notifyUrl += "/api/"
|
|
|
|
|
}
|
|
|
|
|
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
} else {
|
|
|
|
|
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
host = l.svcCtx.Config.Host
|
|
|
|
|
}
|
|
|
|
|
notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
|
|
|
|
|
notifyUrl = "https://" + host
|
|
|
|
|
if isGatewayMod {
|
|
|
|
|
notifyUrl += "/api"
|
|
|
|
|
}
|
|
|
|
|
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create payment URL for user redirection
|
|
|
|
@@ -286,12 +313,70 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
|
|
|
|
return url, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CryptoSaaSPayment processes CryptoSaaSPayment payment by generating a payment URL for redirect
|
|
|
|
|
// It handles currency conversion and creates a payment URL for external payment processing
|
|
|
|
|
func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info *order.Order, returnUrl string) (string, error) {
|
|
|
|
|
// Parse EPay configuration from payment settings
|
|
|
|
|
epayConfig := &payment.CryptoSaaSConfig{}
|
|
|
|
|
if err := epayConfig.Unmarshal([]byte(config.Config)); err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] Unmarshal EPay config error", logger.Field("error", err.Error()))
|
|
|
|
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
// Initialize EPay client with merchant credentials
|
|
|
|
|
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type)
|
|
|
|
|
|
|
|
|
|
// Convert order amount to CNY using current exchange rate
|
|
|
|
|
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// gateway mod
|
|
|
|
|
isGatewayMod := report.IsGatewayMode()
|
|
|
|
|
|
|
|
|
|
// Build notification URL for payment status callbacks
|
|
|
|
|
notifyUrl := ""
|
|
|
|
|
if config.Domain != "" {
|
|
|
|
|
notifyUrl = config.Domain
|
|
|
|
|
if isGatewayMod {
|
|
|
|
|
notifyUrl += "/api/"
|
|
|
|
|
}
|
|
|
|
|
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
} else {
|
|
|
|
|
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
host = l.svcCtx.Config.Host
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
notifyUrl = "https://" + host
|
|
|
|
|
if isGatewayMod {
|
|
|
|
|
notifyUrl += "/api"
|
|
|
|
|
}
|
|
|
|
|
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
|
|
|
|
}
|
|
|
|
|
// Create payment URL for user redirection
|
|
|
|
|
url := client.CreatePayUrl(epay.Order{
|
|
|
|
|
Name: l.svcCtx.Config.Site.SiteName,
|
|
|
|
|
Amount: amount,
|
|
|
|
|
OrderNo: info.OrderNo,
|
|
|
|
|
SignType: "MD5",
|
|
|
|
|
NotifyUrl: notifyUrl,
|
|
|
|
|
ReturnUrl: returnUrl,
|
|
|
|
|
})
|
|
|
|
|
return url, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// queryExchangeRate converts the order amount from system currency to target currency
|
|
|
|
|
// It retrieves the current exchange rate and performs currency conversion if needed
|
|
|
|
|
func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount float64, err error) {
|
|
|
|
|
// Convert cents to decimal amount
|
|
|
|
|
amount = float64(src) / float64(100)
|
|
|
|
|
|
|
|
|
|
if l.svcCtx.ExchangeRate != 0 && to == "CNY" {
|
|
|
|
|
amount = amount * l.svcCtx.ExchangeRate
|
|
|
|
|
return amount, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retrieve system currency configuration
|
|
|
|
|
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -335,7 +420,7 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
|
|
|
|
logger.Field("orderNo", o.OrderNo),
|
|
|
|
|
logger.Field("userId", u.Id),
|
|
|
|
|
)
|
|
|
|
|
err := l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2)
|
|
|
|
|
err = l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, o.OrderNo, 2)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] Update order status error",
|
|
|
|
|
logger.Field("error", err.Error()),
|
|
|
|
@@ -386,16 +471,21 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
|
|
|
|
|
|
|
|
|
// Create gift amount log if gift amount was used
|
|
|
|
|
if giftUsed > 0 {
|
|
|
|
|
giftLog := &user.GiftAmountLog{
|
|
|
|
|
UserId: u.Id,
|
|
|
|
|
UserSubscribeId: 0, // Will be updated when subscription is created
|
|
|
|
|
OrderNo: o.OrderNo,
|
|
|
|
|
Type: 2, // Type 2 represents gift amount decrease/usage
|
|
|
|
|
Amount: giftUsed,
|
|
|
|
|
Balance: userInfo.GiftAmount,
|
|
|
|
|
Remark: "Purchase payment",
|
|
|
|
|
giftLog := &log.Gift{
|
|
|
|
|
OrderNo: o.OrderNo,
|
|
|
|
|
Type: log.GiftTypeReduce, // Type 2 represents gift amount decrease/usage
|
|
|
|
|
Amount: giftUsed,
|
|
|
|
|
Balance: userInfo.GiftAmount,
|
|
|
|
|
Remark: "Purchase payment",
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(giftLog).Error
|
|
|
|
|
content, _ := giftLog.Marshal()
|
|
|
|
|
|
|
|
|
|
err = db.Create(&log.SystemLog{
|
|
|
|
|
Type: log.TypeGift.Uint8(),
|
|
|
|
|
ObjectID: userInfo.Id,
|
|
|
|
|
Date: time.Now().Format(time.DateOnly),
|
|
|
|
|
Content: string(content),
|
|
|
|
|
}).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -403,14 +493,20 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
|
|
|
|
|
|
|
|
|
// Create balance log if regular balance was used
|
|
|
|
|
if balanceUsed > 0 {
|
|
|
|
|
balanceLog := &user.BalanceLog{
|
|
|
|
|
UserId: u.Id,
|
|
|
|
|
Amount: balanceUsed,
|
|
|
|
|
Type: 3, // Type 3 represents payment deduction
|
|
|
|
|
OrderId: o.Id,
|
|
|
|
|
Balance: userInfo.Balance,
|
|
|
|
|
balanceLog := &log.Balance{
|
|
|
|
|
Amount: balanceUsed,
|
|
|
|
|
Type: log.BalanceTypePayment, // Type 3 represents payment deduction
|
|
|
|
|
OrderNo: o.OrderNo,
|
|
|
|
|
Balance: userInfo.Balance,
|
|
|
|
|
Timestamp: time.Now().UnixMilli(),
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(balanceLog).Error
|
|
|
|
|
content, _ := balanceLog.Marshal()
|
|
|
|
|
err = db.Create(&log.SystemLog{
|
|
|
|
|
Type: log.TypeBalance.Uint8(),
|
|
|
|
|
ObjectID: userInfo.Id,
|
|
|
|
|
Date: time.Now().Format(time.DateOnly),
|
|
|
|
|
Content: string(content),
|
|
|
|
|
}).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -418,7 +514,7 @@ func (l *PurchaseCheckoutLogic) balancePayment(u *user.User, o *order.Order) err
|
|
|
|
|
|
|
|
|
|
// Store gift amount used in order for potential refund tracking
|
|
|
|
|
o.GiftAmount = giftUsed
|
|
|
|
|
err = l.svcCtx.OrderModel.Update(l.ctx, o)
|
|
|
|
|
err = l.svcCtx.OrderModel.Update(l.ctx, o, db)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|