583 lines
17 KiB
Go
583 lines
17 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"license-manager/internal/models"
|
|
"license-manager/internal/repository"
|
|
"license-manager/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type PublicRenewalHandler struct {
|
|
licenseRepo repository.LicenseRepository
|
|
customerRepo repository.CustomerRepository
|
|
cuUserRepo repository.CuUserRepository
|
|
packageService service.PackageService
|
|
cuOrderService service.CuOrderService
|
|
paymentService service.PaymentService
|
|
}
|
|
|
|
func NewPublicRenewalHandler(
|
|
licenseRepo repository.LicenseRepository,
|
|
customerRepo repository.CustomerRepository,
|
|
cuUserRepo repository.CuUserRepository,
|
|
packageService service.PackageService,
|
|
cuOrderService service.CuOrderService,
|
|
paymentService service.PaymentService,
|
|
) *PublicRenewalHandler {
|
|
return &PublicRenewalHandler{
|
|
licenseRepo: licenseRepo,
|
|
customerRepo: customerRepo,
|
|
cuUserRepo: cuUserRepo,
|
|
packageService: packageService,
|
|
cuOrderService: cuOrderService,
|
|
paymentService: paymentService,
|
|
}
|
|
}
|
|
|
|
type publicAuthCodeQueryRequest struct {
|
|
AuthorizationCode string `json:"authorization_code" binding:"required"`
|
|
}
|
|
|
|
type publicRenewalOrderRequest struct {
|
|
AuthorizationCode string `json:"authorization_code" binding:"required"`
|
|
PackageID string `json:"package_id" binding:"required"`
|
|
LicenseCount int `json:"license_count" binding:"required,min=1,max=1000"`
|
|
RenewDays int `json:"renew_days" binding:"required,min=1,max=3650"`
|
|
PaymentMethod string `json:"payment_method"`
|
|
ContactName string `json:"contact_name" binding:"required,max=100"`
|
|
ContactPhone string `json:"contact_phone" binding:"required,min=7,max=20"`
|
|
ContactEmail string `json:"contact_email" binding:"omitempty,email,max=255"`
|
|
CompanyName string `json:"company_name" binding:"omitempty,max=200"`
|
|
}
|
|
|
|
type publicPurchaseOrderRequest struct {
|
|
PackageID string `json:"package_id" binding:"required"`
|
|
LicenseCount int `json:"license_count" binding:"required,min=1,max=1000"`
|
|
PaymentMethod string `json:"payment_method"`
|
|
ContactName string `json:"contact_name" binding:"required,max=100"`
|
|
ContactPhone string `json:"contact_phone" binding:"required,min=7,max=20"`
|
|
ContactEmail string `json:"contact_email" binding:"omitempty,email,max=255"`
|
|
CompanyName string `json:"company_name" binding:"omitempty,max=200"`
|
|
CustomerType string `json:"customer_type" binding:"omitempty,oneof=individual enterprise government education"`
|
|
Description string `json:"description" binding:"omitempty,max=500"`
|
|
}
|
|
|
|
// QueryAuthorizationCode 公开查询授权码信息
|
|
func (h *PublicRenewalHandler) QueryAuthorizationCode(c *gin.Context) {
|
|
var req publicAuthCodeQueryRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "900001",
|
|
Message: "请求参数错误",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
code := strings.TrimSpace(req.AuthorizationCode)
|
|
authCode, err := h.licenseRepo.GetAuthorizationCodeByCode(c.Request.Context(), code)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{
|
|
Code: "300001",
|
|
Message: "授权码不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
customerName := ""
|
|
contactPerson := ""
|
|
contactPhone := ""
|
|
contactEmail := ""
|
|
|
|
customer, err := h.customerRepo.GetCustomerByID(c.Request.Context(), authCode.CustomerID)
|
|
if err == nil {
|
|
customerName = customer.CustomerName
|
|
contactPerson = customer.ContactPerson
|
|
if customer.Phone != nil {
|
|
contactPhone = *customer.Phone
|
|
}
|
|
if customer.Email != nil {
|
|
contactEmail = *customer.Email
|
|
}
|
|
}
|
|
|
|
activeCount, _ := h.licenseRepo.GetActiveLicenseCount(c.Request.Context(), authCode.ID)
|
|
|
|
status := "normal"
|
|
now := time.Now()
|
|
if authCode.IsLocked {
|
|
status = "locked"
|
|
} else if now.After(authCode.EndDate) {
|
|
status = "expired"
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.APIResponse{
|
|
Code: "000000",
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"authorization_code_id": authCode.ID,
|
|
"authorization_code": authCode.Code,
|
|
"customer_id": authCode.CustomerID,
|
|
"customer_name": customerName,
|
|
"contact_person": contactPerson,
|
|
"contact_phone": contactPhone,
|
|
"contact_email": contactEmail,
|
|
"status": status,
|
|
"is_locked": authCode.IsLocked,
|
|
"start_date": authCode.StartDate.Format(time.RFC3339),
|
|
"end_date": authCode.EndDate.Format(time.RFC3339),
|
|
"max_activations": authCode.MaxActivations,
|
|
"active_licenses": activeCount,
|
|
},
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
}
|
|
|
|
// CreateRenewalOrder 公开创建续期支付单
|
|
func (h *PublicRenewalHandler) CreateRenewalOrder(c *gin.Context) {
|
|
var req publicRenewalOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "900001",
|
|
Message: "请求参数错误",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
authCode, err := h.licenseRepo.GetAuthorizationCodeByCode(c.Request.Context(), strings.TrimSpace(req.AuthorizationCode))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{
|
|
Code: "300001",
|
|
Message: "授权码不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
if authCode.IsLocked {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "300003",
|
|
Message: "授权码已锁定,无法续期",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
_, err = h.packageService.GetPackageDetail(c.Request.Context(), req.PackageID)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "800002",
|
|
Message: "套餐不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
priceResult, err := h.cuOrderService.CalculatePrice(c.Request.Context(), req.PackageID, req.LicenseCount)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "601004",
|
|
Message: "价格计算失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
customer, err := h.customerRepo.GetCustomerByID(c.Request.Context(), authCode.CustomerID)
|
|
if err == nil {
|
|
if strings.TrimSpace(req.CompanyName) != "" {
|
|
customer.CustomerName = strings.TrimSpace(req.CompanyName)
|
|
}
|
|
customer.ContactPerson = strings.TrimSpace(req.ContactName)
|
|
phone := strings.TrimSpace(req.ContactPhone)
|
|
if phone != "" {
|
|
customer.Phone = &phone
|
|
}
|
|
email := strings.TrimSpace(req.ContactEmail)
|
|
if email != "" {
|
|
customer.Email = &email
|
|
}
|
|
_ = h.customerRepo.UpdateCustomer(c.Request.Context(), customer)
|
|
}
|
|
|
|
cuUserID, err := h.ensureCuUser(c.Request.Context(), authCode.CustomerID, req.ContactPhone, req.ContactName, req.ContactEmail)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
Code: "900004",
|
|
Message: "创建客户用户失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
renewalExtra := map[string]interface{}{
|
|
"authorization_code_id": authCode.ID,
|
|
"renew_days": req.RenewDays,
|
|
}
|
|
extraDataBytes, _ := json.Marshal(renewalExtra)
|
|
extraData := string(extraDataBytes)
|
|
|
|
paymentMethod := strings.TrimSpace(req.PaymentMethod)
|
|
if paymentMethod == "" {
|
|
paymentMethod = models.PaymentMethodAlipay
|
|
}
|
|
|
|
businessID := authCode.ID
|
|
payment, err := h.paymentService.CreatePayment(c.Request.Context(), &models.PaymentCreateRequest{
|
|
BusinessType: models.BusinessTypeLicenseExtend,
|
|
BusinessID: &businessID,
|
|
CustomerID: authCode.CustomerID,
|
|
CuUserID: cuUserID,
|
|
Amount: priceResult.TotalAmount,
|
|
Currency: "CNY",
|
|
PaymentMethod: paymentMethod,
|
|
ExtraData: &extraData,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
Code: "602001",
|
|
Message: "创建支付单失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.APIResponse{
|
|
Code: "000000",
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"payment_no": payment.PaymentNo,
|
|
"payment_url": payment.PaymentURL,
|
|
"total_amount": payment.Amount,
|
|
"expire_time": payment.ExpireTime.Format(time.RFC3339),
|
|
"renew_days": req.RenewDays,
|
|
},
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
}
|
|
|
|
// CreatePurchaseOrder 公开创建新购支付单
|
|
func (h *PublicRenewalHandler) CreatePurchaseOrder(c *gin.Context) {
|
|
var req publicPurchaseOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "900001",
|
|
Message: "请求参数错误",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
pkg, err := h.packageService.GetPackageDetail(c.Request.Context(), req.PackageID)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "800002",
|
|
Message: "套餐不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
priceResult, err := h.cuOrderService.CalculatePrice(c.Request.Context(), req.PackageID, req.LicenseCount)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "601004",
|
|
Message: "价格计算失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
customerID, cuUserID, err := h.ensureCustomerAndUser(
|
|
c.Request.Context(),
|
|
strings.TrimSpace(req.CompanyName),
|
|
strings.TrimSpace(req.ContactName),
|
|
strings.TrimSpace(req.ContactPhone),
|
|
strings.TrimSpace(req.ContactEmail),
|
|
strings.TrimSpace(req.CustomerType),
|
|
strings.TrimSpace(req.Description),
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
Code: "900004",
|
|
Message: "客户信息写入失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
orderReq := &models.CuOrderCreateRequest{
|
|
PackageID: req.PackageID,
|
|
LicenseCount: req.LicenseCount,
|
|
PaymentMethod: strings.TrimSpace(req.PaymentMethod),
|
|
}
|
|
if orderReq.PaymentMethod == "" {
|
|
orderReq.PaymentMethod = models.PaymentMethodAlipay
|
|
}
|
|
|
|
if priceResult.TotalAmount == 0 {
|
|
order, createErr := h.cuOrderService.CreateOrder(c.Request.Context(), cuUserID, customerID, orderReq)
|
|
if createErr != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "601001",
|
|
Message: "订单创建失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.APIResponse{
|
|
Code: "000000",
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"order_id": order.ID,
|
|
"order_no": order.OrderNo,
|
|
"package_name": pkg.Name,
|
|
"total_amount": 0,
|
|
"license_count": req.LicenseCount,
|
|
"authorization_code": order.AuthorizationCode,
|
|
},
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
order, err := h.cuOrderService.CreatePendingOrder(c.Request.Context(), cuUserID, customerID, orderReq)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "601001",
|
|
Message: "订单创建失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
payment, err := h.paymentService.CreatePayment(c.Request.Context(), &models.PaymentCreateRequest{
|
|
BusinessType: models.BusinessTypePackageOrder,
|
|
BusinessID: &order.ID,
|
|
CustomerID: customerID,
|
|
CuUserID: cuUserID,
|
|
Amount: priceResult.TotalAmount,
|
|
Currency: "CNY",
|
|
PaymentMethod: orderReq.PaymentMethod,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
|
|
Code: "602001",
|
|
Message: "支付单创建失败",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.APIResponse{
|
|
Code: "000000",
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"order_id": order.ID,
|
|
"order_no": order.OrderNo,
|
|
"package_name": pkg.Name,
|
|
"payment_no": payment.PaymentNo,
|
|
"payment_url": payment.PaymentURL,
|
|
"total_amount": payment.Amount,
|
|
"expire_time": payment.ExpireTime.Format(time.RFC3339),
|
|
"license_count": req.LicenseCount,
|
|
},
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
}
|
|
|
|
// GetPublicPaymentStatus 公开支付状态查询
|
|
func (h *PublicRenewalHandler) GetPublicPaymentStatus(c *gin.Context) {
|
|
paymentNo := strings.TrimSpace(c.Param("payment_no"))
|
|
if paymentNo == "" {
|
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
Code: "900001",
|
|
Message: "支付单号不能为空",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
statusResp, err := h.paymentService.GetPaymentStatus(c.Request.Context(), paymentNo)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{
|
|
Code: "602002",
|
|
Message: "支付单不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
payment, err := h.paymentService.GetPayment(c.Request.Context(), paymentNo)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{
|
|
Code: "602002",
|
|
Message: "支付单不存在",
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
return
|
|
}
|
|
|
|
authCodeID := ""
|
|
endDate := ""
|
|
if payment.ExtraData != nil && strings.TrimSpace(*payment.ExtraData) != "" {
|
|
var renewalData struct {
|
|
AuthorizationCodeID string `json:"authorization_code_id"`
|
|
}
|
|
if err := json.Unmarshal([]byte(*payment.ExtraData), &renewalData); err == nil {
|
|
authCodeID = renewalData.AuthorizationCodeID
|
|
if strings.TrimSpace(authCodeID) != "" {
|
|
authCode, err := h.licenseRepo.GetAuthorizationCodeByID(c.Request.Context(), authCodeID)
|
|
if err == nil {
|
|
endDate = authCode.EndDate.Format(time.RFC3339)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.APIResponse{
|
|
Code: "000000",
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"payment_no": statusResp.PaymentNo,
|
|
"status": statusResp.Status,
|
|
"amount": statusResp.Amount,
|
|
"payment_time": statusResp.PaymentTime,
|
|
"trade_no": statusResp.TradeNo,
|
|
"authorization_code_id": authCodeID,
|
|
"authorization_end_date": endDate,
|
|
"business_order": statusResp.BusinessOrder,
|
|
},
|
|
Timestamp: getCurrentTimestamp(),
|
|
})
|
|
}
|
|
|
|
func (h *PublicRenewalHandler) ensureCuUser(ctx context.Context, customerID, phone, contactName, contactEmail string) (string, error) {
|
|
countryCode := "+86"
|
|
trimmedPhone := strings.TrimSpace(phone)
|
|
user, err := h.cuUserRepo.GetByPhone(trimmedPhone, countryCode)
|
|
if err == nil {
|
|
return user.ID, nil
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", err
|
|
}
|
|
|
|
var realName *string
|
|
name := strings.TrimSpace(contactName)
|
|
if name != "" {
|
|
realName = &name
|
|
}
|
|
var email *string
|
|
trimmedEmail := strings.TrimSpace(contactEmail)
|
|
if trimmedEmail != "" {
|
|
email = &trimmedEmail
|
|
}
|
|
|
|
newUser := &models.CuUser{
|
|
CustomerID: customerID,
|
|
Phone: trimmedPhone,
|
|
PhoneCountryCode: countryCode,
|
|
UserRole: "member",
|
|
RealName: realName,
|
|
Email: email,
|
|
Status: "active",
|
|
PhoneVerified: true,
|
|
EmailVerified: false,
|
|
Language: "zh-CN",
|
|
Timezone: "Asia/Shanghai",
|
|
}
|
|
if err := h.cuUserRepo.Create(newUser); err != nil {
|
|
return "", err
|
|
}
|
|
return newUser.ID, nil
|
|
}
|
|
|
|
func (h *PublicRenewalHandler) ensureCustomerAndUser(
|
|
ctx context.Context,
|
|
companyName, contactName, phone, email, customerType, description string,
|
|
) (string, string, error) {
|
|
countryCode := "+86"
|
|
trimmedPhone := strings.TrimSpace(phone)
|
|
existingUser, err := h.cuUserRepo.GetByPhone(trimmedPhone, countryCode)
|
|
if err == nil {
|
|
customer, customerErr := h.customerRepo.GetCustomerByID(ctx, existingUser.CustomerID)
|
|
if customerErr == nil {
|
|
if companyName != "" {
|
|
customer.CustomerName = companyName
|
|
}
|
|
if contactName != "" {
|
|
customer.ContactPerson = contactName
|
|
}
|
|
if trimmedPhone != "" {
|
|
customer.Phone = &trimmedPhone
|
|
}
|
|
if email != "" {
|
|
customer.Email = &email
|
|
}
|
|
if description != "" {
|
|
customer.Description = &description
|
|
}
|
|
_ = h.customerRepo.UpdateCustomer(ctx, customer)
|
|
}
|
|
return existingUser.CustomerID, existingUser.ID, nil
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", "", err
|
|
}
|
|
|
|
ct := customerType
|
|
if ct == "" {
|
|
ct = "enterprise"
|
|
}
|
|
customerName := companyName
|
|
if customerName == "" {
|
|
customerName = contactName
|
|
}
|
|
if customerName == "" {
|
|
customerName = trimmedPhone
|
|
}
|
|
contactPerson := contactName
|
|
if contactPerson == "" {
|
|
contactPerson = trimmedPhone
|
|
}
|
|
|
|
customer := &models.Customer{
|
|
CustomerName: customerName,
|
|
CustomerType: ct,
|
|
ContactPerson: contactPerson,
|
|
CustomerLevel: "basic",
|
|
Status: "active",
|
|
CreatedBy: "public-order",
|
|
}
|
|
if trimmedPhone != "" {
|
|
customer.Phone = &trimmedPhone
|
|
}
|
|
if email != "" {
|
|
customer.Email = &email
|
|
}
|
|
if description != "" {
|
|
customer.Description = &description
|
|
}
|
|
if err := h.customerRepo.CreateCustomer(ctx, customer); err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
cuUserID, err := h.ensureCuUser(ctx, customer.ID, trimmedPhone, contactName, email)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return customer.ID, cuUserID, nil
|
|
}
|