diff --git a/internal/handler/notify/paymentNotifyHandler.go b/internal/handler/notify/paymentNotifyHandler.go index d2efcb1..cd7d8b9 100644 --- a/internal/handler/notify/paymentNotifyHandler.go +++ b/internal/handler/notify/paymentNotifyHandler.go @@ -26,7 +26,7 @@ func PaymentNotifyHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { } switch payment.ParsePlatform(platform) { - case payment.EPay: + case payment.EPay, payment.CryptoSaaS: req := &types.EPayNotifyRequest{} if err := c.ShouldBind(req); err != nil { result.HttpResult(c, nil, err) diff --git a/internal/handler/public/portal/purchaseCheckoutHandler.go b/internal/handler/public/portal/purchaseCheckoutHandler.go index 21c6737..0e34d38 100644 --- a/internal/handler/public/portal/purchaseCheckoutHandler.go +++ b/internal/handler/public/portal/purchaseCheckoutHandler.go @@ -8,7 +8,7 @@ import ( "github.com/perfect-panel/server/pkg/result" ) -// Purchase Checkout +// PurchaseCheckoutHandler Purchase Checkout func PurchaseCheckoutHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { return func(c *gin.Context) { var req types.CheckoutOrderRequest diff --git a/internal/logic/admin/payment/createPaymentMethodLogic.go b/internal/logic/admin/payment/createPaymentMethodLogic.go index 014f595..23cd48d 100644 --- a/internal/logic/admin/payment/createPaymentMethodLogic.go +++ b/internal/logic/admin/payment/createPaymentMethodLogic.go @@ -55,10 +55,9 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM Token: random.KeyNew(8, 1), } err = l.svcCtx.PaymentModel.Transaction(l.ctx, func(tx *gorm.DB) error { - if req.Platform == "Stripe" { var cfg paymentModel.StripeConfig - if err := cfg.Unmarshal(paymentMethod.Config); err != nil { + if err = cfg.Unmarshal([]byte(paymentMethod.Config)); err != nil { l.Errorf("[CreatePaymentMethod] unmarshal stripe config error: %s", err.Error()) return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal stripe config error: %s", err.Error()) } @@ -79,7 +78,8 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "create stripe webhook endpoint error: %s", err.Error()) } cfg.WebhookSecret = endpoint.Secret - paymentMethod.Config = cfg.Marshal() + content, _ := cfg.Marshal() + paymentMethod.Config = string(content) } if err = tx.Model(&paymentModel.Payment{}).Create(paymentMethod).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert payment method error: %s", err.Error()) @@ -101,27 +101,36 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM func parsePaymentPlatformConfig(ctx context.Context, platform payment.Platform, config interface{}) string { data, err := json.Marshal(config) if err != nil { - logger.WithContext(ctx).Errorw("parse payment platform config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error())) + logger.WithContext(ctx).Errorw("marshal config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error())) + return "" } + + // 通用处理函数 + handleConfig := func(name string, target interface { + Unmarshal([]byte) error + Marshal() ([]byte, error) + }) string { + if err = target.Unmarshal(data); err != nil { + logger.WithContext(ctx).Errorw("parse "+name+" config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) + return "" + } + content, err := target.Marshal() + if err != nil { + logger.WithContext(ctx).Errorw("marshal "+name+" config error", logger.Field("error", err.Error())) + return "" + } + return string(content) + } + switch platform { case payment.Stripe: - stripe := &paymentModel.StripeConfig{} - if err := stripe.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse stripe config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return stripe.Marshal() + return handleConfig("Stripe", &paymentModel.StripeConfig{}) case payment.AlipayF2F: - alipay := &paymentModel.AlipayF2FConfig{} - if err := alipay.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse alipay config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return alipay.Marshal() + return handleConfig("Alipay", &paymentModel.AlipayF2FConfig{}) case payment.EPay: - epay := &paymentModel.EPayConfig{} - if err := epay.Unmarshal(string(data)); err != nil { - logger.WithContext(ctx).Errorw("parse epay config error", logger.Field("config", string(data)), logger.Field("error", err.Error())) - } - return epay.Marshal() + return handleConfig("Epay", &paymentModel.EPayConfig{}) + case payment.CryptoSaaS: + return handleConfig("CryptoSaaS", &paymentModel.CryptoSaaSConfig{}) default: return "" } diff --git a/internal/logic/admin/payment/updatePaymentMethodLogic.go b/internal/logic/admin/payment/updatePaymentMethodLogic.go index 87b4fd7..7c2dda2 100644 --- a/internal/logic/admin/payment/updatePaymentMethodLogic.go +++ b/internal/logic/admin/payment/updatePaymentMethodLogic.go @@ -19,7 +19,7 @@ type UpdatePaymentMethodLogic struct { svcCtx *svc.ServiceContext } -// Update Payment Method +// NewUpdatePaymentMethodLogic Update Payment Method func NewUpdatePaymentMethodLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePaymentMethodLogic { return &UpdatePaymentMethodLogic{ Logger: logger.WithContext(ctx), diff --git a/internal/logic/public/portal/purchaseCheckoutLogic.go b/internal/logic/public/portal/purchaseCheckoutLogic.go index b0c4994..f2efd71 100644 --- a/internal/logic/public/portal/purchaseCheckoutLogic.go +++ b/internal/logic/public/portal/purchaseCheckoutLogic.go @@ -106,6 +106,17 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest CheckoutUrl: url, } + case paymentPlatform.CryptoSaaS: + // Process EPay payment - generates payment URL for redirect + url, err := l.epayPayment(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 { @@ -140,8 +151,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()) } @@ -191,8 +202,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()) } @@ -249,12 +261,11 @@ 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) @@ -288,6 +299,48 @@ 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) + + // Convert order amount to CNY using current exchange rate + amount, err := l.queryExchangeRate("CNY", info.Amount) + if err != nil { + return "", err + } + + // Build notification URL for payment status callbacks + notifyUrl := "" + if config.Domain != "" { + notifyUrl = config.Domain + "/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 + } + + // 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) { diff --git a/internal/model/payment/payment.go b/internal/model/payment/payment.go index b76a61f..46ee0a0 100644 --- a/internal/model/payment/payment.go +++ b/internal/model/payment/payment.go @@ -46,13 +46,19 @@ type StripeConfig struct { Payment string `json:"payment"` } -func (l *StripeConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *StripeConfig) Marshal() ([]byte, error) { + type Alias StripeConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *StripeConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *StripeConfig) Unmarshal(data []byte) error { + type Alias StripeConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } type AlipayF2FConfig struct { @@ -63,13 +69,19 @@ type AlipayF2FConfig struct { Sandbox bool `json:"sandbox"` } -func (l *AlipayF2FConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *AlipayF2FConfig) Marshal() ([]byte, error) { + type Alias AlipayF2FConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *AlipayF2FConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *AlipayF2FConfig) Unmarshal(data []byte) error { + type Alias AlipayF2FConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } type EPayConfig struct { @@ -78,11 +90,38 @@ type EPayConfig struct { Key string `json:"key"` } -func (l *EPayConfig) Marshal() string { - b, _ := json.Marshal(l) - return string(b) +func (l *EPayConfig) Marshal() ([]byte, error) { + type Alias EPayConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) } -func (l *EPayConfig) Unmarshal(s string) error { - return json.Unmarshal([]byte(s), l) +func (l *EPayConfig) Unmarshal(data []byte) error { + type Alias EPayConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) +} + +type CryptoSaaSConfig struct { + Endpoint string `json:"endpoint"` + AccountID string `json:"account_id"` + SecretKey string `json:"secret_key"` +} + +func (l *CryptoSaaSConfig) Marshal() ([]byte, error) { + type Alias CryptoSaaSConfig + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(l), + }) +} + +func (l *CryptoSaaSConfig) Unmarshal(data []byte) error { + type Alias CryptoSaaSConfig + aux := (*Alias)(l) + return json.Unmarshal(data, &aux) } diff --git a/pkg/payment/platform.go b/pkg/payment/platform.go index 2955391..42b8815 100644 --- a/pkg/payment/platform.go +++ b/pkg/payment/platform.go @@ -9,10 +9,12 @@ const ( AlipayF2F EPay Balance - UNSUPPORTED + CryptoSaaS + UNSUPPORTED Platform = -1 ) var platformNames = map[string]Platform{ + "CryptoSaaS": CryptoSaaS, "Stripe": Stripe, "AlipayF2F": AlipayF2F, "EPay": EPay, @@ -68,5 +70,14 @@ func GetSupportedPlatforms() []types.PlatformInfo { "key": "Key", }, }, + { + Platform: CryptoSaaS.String(), + PlatformUrl: "https://t.me/CryptoSaaSBot", + PlatformFieldDescription: map[string]string{ + "endpoint": "API Endpoint", + "account_id": "Account ID", + "secret_key": "Secret Key", + }, + }, } }