|
|
|
@@ -3,7 +3,10 @@ package portal
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"math"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/perfect-panel/server/internal/model/log"
|
|
|
|
@@ -26,6 +29,7 @@ import (
|
|
|
|
|
"github.com/perfect-panel/server/pkg/payment/alipay"
|
|
|
|
|
"github.com/perfect-panel/server/pkg/payment/epay"
|
|
|
|
|
"github.com/perfect-panel/server/pkg/payment/stripe"
|
|
|
|
|
"github.com/perfect-panel/server/pkg/tool"
|
|
|
|
|
"github.com/perfect-panel/server/pkg/xerr"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
)
|
|
|
|
@@ -73,6 +77,13 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest
|
|
|
|
|
}
|
|
|
|
|
// Route to appropriate payment handler based on payment platform
|
|
|
|
|
switch paymentPlatform.ParsePlatform(orderInfo.Method) {
|
|
|
|
|
case paymentPlatform.AppleIAP:
|
|
|
|
|
productId := fmt.Sprintf("merchant.hifastvpn.day%d", orderInfo.Quantity)
|
|
|
|
|
resp = &types.CheckoutOrderResponse{
|
|
|
|
|
Type: "apple_iap",
|
|
|
|
|
ProductIds: []string{productId},
|
|
|
|
|
}
|
|
|
|
|
return resp, nil
|
|
|
|
|
case paymentPlatform.EPay:
|
|
|
|
|
// Process EPay payment - generates payment URL for redirect
|
|
|
|
|
url, err := l.epayPayment(paymentConfig, orderInfo, req.ReturnUrl)
|
|
|
|
@@ -186,13 +197,20 @@ func (l *PurchaseCheckoutLogic) alipayF2fPayment(pay *payment.Payment, info *ord
|
|
|
|
|
l.Errorw("[PurchaseCheckout] queryExchangeRate error", logger.Field("error", err.Error()))
|
|
|
|
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
convertAmount := int64(amount * 100) // Convert to cents for API
|
|
|
|
|
convertAmount := int64(math.Round(amount * 100))
|
|
|
|
|
l.Infow("alipay amount",
|
|
|
|
|
logger.Field("src_cents", info.Amount),
|
|
|
|
|
logger.Field("decimal", amount),
|
|
|
|
|
logger.Field("cents", convertAmount),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Create pre-payment trade and generate QR code
|
|
|
|
|
QRCode, err := client.PreCreateTrade(l.ctx, alipay.Order{
|
|
|
|
|
o := alipay.Order{
|
|
|
|
|
OrderNo: info.OrderNo,
|
|
|
|
|
Amount: convertAmount,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
l.Infow("alipay request", logger.Field("order", o))
|
|
|
|
|
QRCode, err := client.PreCreateTrade(l.ctx, o)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] PreCreateTrade error", logger.Field("error", err.Error()))
|
|
|
|
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "PreCreateTrade error: %s", err.Error())
|
|
|
|
@@ -218,25 +236,50 @@ func (l *PurchaseCheckoutLogic) stripePayment(config string, info *order.Order,
|
|
|
|
|
WebhookSecret: stripeConfig.WebhookSecret,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Convert order amount to CNY using current exchange rate
|
|
|
|
|
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
|
|
|
|
currency := "USD"
|
|
|
|
|
sysCurrency, _ := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
|
|
|
|
if sysCurrency != nil {
|
|
|
|
|
configs := struct {
|
|
|
|
|
CurrencyUnit string
|
|
|
|
|
CurrencySymbol string
|
|
|
|
|
AccessKey string
|
|
|
|
|
}{}
|
|
|
|
|
tool.SystemConfigSliceReflectToStruct(sysCurrency, &configs)
|
|
|
|
|
if configs.CurrencyUnit != "" {
|
|
|
|
|
currency = configs.CurrencyUnit
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert order amount to configured currency using current exchange rate
|
|
|
|
|
amount, err := l.queryExchangeRate(strings.ToUpper(currency), info.Amount)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] queryExchangeRate error", logger.Field("error", err.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "queryExchangeRate error: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
convertAmount := int64(amount * 100) // Convert to cents for Stripe API
|
|
|
|
|
convertAmount := int64(math.Round(amount * 100))
|
|
|
|
|
l.Infow("stripe amount",
|
|
|
|
|
logger.Field("src_cents", info.Amount),
|
|
|
|
|
logger.Field("decimal", amount),
|
|
|
|
|
logger.Field("cents", convertAmount),
|
|
|
|
|
logger.Field("currency", currency),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Create Stripe payment sheet for client-side processing
|
|
|
|
|
result, err := client.CreatePaymentSheet(&stripe.Order{
|
|
|
|
|
// Map apple_pay to card for Stripe API, but keep apple_pay in config/response
|
|
|
|
|
paymentMethod := stripeConfig.Payment
|
|
|
|
|
if paymentMethod == "apple_pay" {
|
|
|
|
|
paymentMethod = "card"
|
|
|
|
|
}
|
|
|
|
|
ord := &stripe.Order{
|
|
|
|
|
OrderNo: info.OrderNo,
|
|
|
|
|
Subscribe: strconv.FormatInt(info.SubscribeId, 10),
|
|
|
|
|
Amount: convertAmount,
|
|
|
|
|
Currency: "cny",
|
|
|
|
|
Payment: stripeConfig.Payment,
|
|
|
|
|
},
|
|
|
|
|
&stripe.User{
|
|
|
|
|
Email: identifier,
|
|
|
|
|
})
|
|
|
|
|
Currency: strings.ToLower(currency),
|
|
|
|
|
Payment: paymentMethod,
|
|
|
|
|
}
|
|
|
|
|
usr := &stripe.User{Email: identifier}
|
|
|
|
|
l.Infow("stripe request", logger.Field("order", ord), logger.Field("user", usr))
|
|
|
|
|
result, err := client.CreatePaymentSheet(ord, usr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Errorw("[PurchaseCheckout] CreatePaymentSheet error", logger.Field("error", err.Error()))
|
|
|
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "CreatePaymentSheet error: %s", err.Error())
|
|
|
|
@@ -282,6 +325,11 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
|
|
|
|
} else {
|
|
|
|
|
amount = float64(info.Amount) / float64(100)
|
|
|
|
|
}
|
|
|
|
|
amount = math.Round(amount*100) / 100
|
|
|
|
|
l.Infow("epay amount",
|
|
|
|
|
logger.Field("src_cents", info.Amount),
|
|
|
|
|
logger.Field("decimal", amount),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// gateway mod
|
|
|
|
|
isGatewayMod := report.IsGatewayMode()
|
|
|
|
@@ -342,6 +390,11 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
|
|
|
|
} else {
|
|
|
|
|
amount = float64(info.Amount) / float64(100)
|
|
|
|
|
}
|
|
|
|
|
amount = math.Round(amount*100) / 100
|
|
|
|
|
l.Infow("crypto amount",
|
|
|
|
|
logger.Field("src_cents", info.Amount),
|
|
|
|
|
logger.Field("decimal", amount),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// gateway mod
|
|
|
|
|
isGatewayMod := report.IsGatewayMode()
|
|
|
|
@@ -395,18 +448,53 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
|
|
|
|
return amount, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retrieve system currency configuration from DB for FixedRate fallback
|
|
|
|
|
currency, dbErr := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
|
|
|
|
var fixedRate string
|
|
|
|
|
if dbErr == nil && currency != nil {
|
|
|
|
|
configs := struct {
|
|
|
|
|
CurrencyUnit string
|
|
|
|
|
CurrencySymbol string
|
|
|
|
|
AccessKey string
|
|
|
|
|
FixedRate string
|
|
|
|
|
}{}
|
|
|
|
|
tool.SystemConfigSliceReflectToStruct(currency, &configs)
|
|
|
|
|
fixedRate = strings.TrimSpace(configs.FixedRate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip conversion if no exchange rate API key configured
|
|
|
|
|
if l.svcCtx.Config.Currency.AccessKey == "" {
|
|
|
|
|
if to == "CNY" && strings.TrimSpace(l.svcCtx.Config.Currency.Unit) == "USD" && fixedRate != "" {
|
|
|
|
|
r := tool.FormatStringToFloat(fixedRate)
|
|
|
|
|
if r > 0 {
|
|
|
|
|
l.Infow("exchangeRate.fixed", logger.Field("rate", r))
|
|
|
|
|
return amount * r, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
l.Infof("[PurchaseCheckout] AccessKey is empty, skip conversion")
|
|
|
|
|
return amount, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert currency if system currency differs from target currency
|
|
|
|
|
result, err := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, l.svcCtx.Config.Currency.AccessKey, 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Logger.Error("[PurchaseCheckout] QueryExchangeRate error", logger.Field("error", err.Error()))
|
|
|
|
|
return 0, err
|
|
|
|
|
if to == "CNY" && strings.TrimSpace(l.svcCtx.Config.Currency.Unit) == "USD" && fixedRate != "" {
|
|
|
|
|
r := tool.FormatStringToFloat(fixedRate)
|
|
|
|
|
if r > 0 {
|
|
|
|
|
l.Infow("exchangeRate.fixed.fallback", logger.Field("rate", r), logger.Field("error", err.Error()))
|
|
|
|
|
return amount * r, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// fallback: try without access key
|
|
|
|
|
result2, err2 := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, "", 1)
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
l.Logger.Error("[PurchaseCheckout] QueryExchangeRate error", logger.Field("error", err.Error()))
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
result = result2
|
|
|
|
|
}
|
|
|
|
|
l.svcCtx.ExchangeRate = result
|
|
|
|
|
l.Infow("exchangeRate", logger.Field("from", l.svcCtx.Config.Currency.Unit), logger.Field("to", to), logger.Field("rate", result))
|
|
|
|
|
return result * amount, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|