Browse Source

三期支付

kobe6258 4 tháng trước cách đây
mục cha
commit
31d5d3294e

+ 15 - 14
api/ht_account_api.go → api/ht_api.go

@@ -25,21 +25,22 @@ const (
 var (
 	htFacadeOnce sync.Once
 
-	htFacade   *HTAccountApi
-	redisUtils = cache.GetInstance()
+	htFacade *HTApi
 )
 
-type HTAccountApi struct {
+type HTApi struct {
 	htConfig *config.HTBizConfig
 	// HTTP请求客户端
-	client *client.HttpClient
+	client     *client.HttpClient
+	redisUtils *cache.RedisCache
 }
 
-func GetInstance() *HTAccountApi {
+func GetInstance() *HTApi {
 	htFacadeOnce.Do(func() {
-		htFacade = &HTAccountApi{
-			htConfig: config.GetConfig(contants.HT).(*config.HTBizConfig),
-			client:   client.DefaultClient()}
+		htFacade = &HTApi{
+			htConfig:   config.GetConfig(contants.HT).(*config.HTBizConfig),
+			client:     client.DefaultClient(),
+			redisUtils: cache.GetInstance()}
 	})
 	return htFacade
 }
@@ -59,7 +60,7 @@ type CustomerRiskReq struct {
 	LoginType  string `json:"login_type"`
 }
 
-func (f *HTAccountApi) EnCodeData(req CustomerRiskReq) (token string, err error) {
+func (f *HTApi) EnCodeData(req CustomerRiskReq) (token string, err error) {
 	publicKey, err := auth.ParsePublicKey(f.htConfig.GetWebhookPublicKey())
 	if err != nil {
 		logger.Error("解析公钥失败:%v", err)
@@ -86,9 +87,9 @@ type EncodeReq struct {
 	Param string `json:"param"`
 }
 
-func (f *HTAccountApi) getToken() (token string, err error) {
+func (f *HTApi) getToken() (token string, err error) {
 	accessToken := redis.GenerateCAPAccessTokenKey()
-	token = redisUtils.GetString(accessToken)
+	token = f.redisUtils.GetString(accessToken)
 	var tokenInfo TokenInfo
 	if token == "" {
 		tokenInfo, err = f.GetAccessToken()
@@ -105,11 +106,11 @@ func (f *HTAccountApi) getToken() (token string, err error) {
 		}
 		duration := expireTime.Sub(time.Now()).Seconds()
 		token = tokenInfo.AccessToken
-		_ = redisUtils.SetString(accessToken, tokenInfo.AccessToken, int(duration)-5)
+		_ = f.redisUtils.SetString(accessToken, tokenInfo.AccessToken, int(duration)-5)
 	}
 	return
 }
-func (f *HTAccountApi) GetCustomerRiskLevelInfo(req ClientSuitInfoReq) (info CustomerAccountInfo, err error) {
+func (f *HTApi) GetCustomerRiskLevelInfo(req ClientSuitInfoReq) (info CustomerAccountInfo, err error) {
 	url := f.htConfig.GetAccountApiUrl() + clientSuitInfoUrl
 	token, err := f.getToken()
 	if err != nil {
@@ -144,7 +145,7 @@ type AccessTokenReq struct {
 	SecretKey string `json:"secret_key"`
 }
 
-func (f *HTAccountApi) GetAccessToken() (token TokenInfo, err error) {
+func (f *HTApi) GetAccessToken() (token TokenInfo, err error) {
 	url := f.htConfig.GetAccountApiUrl() + accessTokenUrl
 	req := AccessTokenReq{
 		AppId:     f.htConfig.GetAppId(),

+ 88 - 0
api/payment_api.go

@@ -0,0 +1,88 @@
+package api
+
+import (
+	"encoding/json"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"io"
+	"sync"
+)
+
+const (
+	queryPaymentUrl  = "/paygateway/query.json"
+	createPaymentUrl = "/paygateway/createpay.json"
+)
+
+var (
+	paymentFacadeOnce sync.Once
+	paymentFacade     *HTPaymentApi
+)
+
+type HTPaymentApi struct {
+	*HTApi
+}
+
+func GetPaymentInstance() *HTPaymentApi {
+	paymentFacadeOnce.Do(func() {
+		paymentFacade = &HTPaymentApi{
+			GetInstance(),
+		}
+	})
+	return paymentFacade
+}
+
+type PaymentOrderRequest struct {
+	AppId           string `json:"appId"`           // 必须 业务系统编号
+	Sign            string `json:"sign"`            // 必须 加密字符串
+	ChannelTypeCode string `json:"channelTypeCode"` // 必须 支付渠道编码(6:华为支付,7:微信小程序)
+	Subject         string `json:"subject"`         // 必须 商品标题
+	Body            string `json:"body"`            // 必须 商品描述
+	TotalFee        string `json:"totalFee"`        // 必须 订单价格
+	BillNo          string `json:"billNo"`          // 必须 商户系统内部的订单号
+	BillTypeCode    string `json:"billTypeCode"`    // 非必须 业务类型code
+	BillType        string `json:"billType"`        // 非必须 业务类型
+	TimeoutExpress  string `json:"timeoutExpress"`  // 必须 订单失效时间(秒)
+	Optional        string `json:"optional"`        // 必须 扩展类参数
+	Mobile          string `json:"mobile"`          // 非必须 手机号
+	IapProductId    string `json:"iapProductId"`    // 非必须 苹果内购产品ID
+}
+type PaymentOrderResponse struct {
+	Success    bool   `json:"success"`    // 必须 接口调用结果
+	ResultCode string `json:"resultCode"` // 必须 结果码
+	ResultDesc string `json:"resultDesc"` // 必须 结果描述
+	ResultView string `json:"resultView"` // 必须 错误结果显示
+	PrePayStr  string `json:"prePayStr"`  // 必须 预支付订单信息
+}
+
+func (f *HTPaymentApi) CreatePaymentOrder(req PaymentOrderRequest) (tradeNo string, err error) {
+	url := f.htConfig.GetPaymentApiUrl() + createPaymentUrl
+	req.AppId = f.htConfig.GetPaymentAppId()
+	req.Sign = f.htConfig.GetPaymentSign()
+	resp, err := f.client.Post(url, req)
+	if err != nil {
+		logger.Error("调用支付创单接口失败:[%v]", err)
+		return
+	}
+	defer func(Body io.ReadCloser) {
+		closeErr := Body.Close()
+		if closeErr != nil {
+			logger.Error("关闭Response失败:%v", closeErr)
+		}
+	}(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
+	var paymentOrderResponse PaymentOrderResponse
+	err = json.Unmarshal(body, &paymentOrderResponse)
+	if err != nil {
+		logger.Warn("[支付接口调用]解析支付创建订单接口应答失败:%v", err)
+		err = exception.New(exception.CreatePaymentOrderFailed)
+		return
+	}
+	if !paymentOrderResponse.Success {
+		logger.Warn("[支付接口调用] 创建订单失败:[code:%v, msg:%v]", paymentOrderResponse.ResultCode, paymentOrderResponse.ResultDesc)
+		err = exception.NewWithException(exception.CreatePaymentOrderFailed, paymentOrderResponse.ResultView)
+		return
+	}
+	logger.Info("[支付接口调用] 创建订单成功:[%v]", paymentOrderResponse.PrePayStr)
+	tradeNo = paymentOrderResponse.PrePayStr
+	return
+}

+ 8 - 0
common/component/cache/redis.go

@@ -35,6 +35,7 @@ func GetInstance() *RedisCache {
 				if err != nil {
 					log.Fatalf("redis 链接失败:%v", err)
 				}
+				logger.Info("redis地址:%v", redisConf.GetHost())
 			} else {
 				logger.Info("Redis configuration is incomplete: Host is missing")
 				return
@@ -101,6 +102,9 @@ func (r *RedisCache) SetString(key string, val string, expired int) error {
 
 // SetStringWithContext  设置一个值
 func (r *RedisCache) SetStringWithContext(ctx context.Context, key string, val string, expired int) error {
+	if expired <= 0 {
+		return r.redisTemplate.Set(ctx, key, val, time.Duration(expired)).Err()
+	}
 	return r.redisTemplate.SetEX(ctx, key, val, time.Duration(expired)*time.Second).Err()
 }
 
@@ -143,3 +147,7 @@ func (r *RedisCache) DoWithContext(ctx context.Context, commandName string, args
 	newArgs = append(newArgs, args...)
 	return r.redisTemplate.Do(ctx, newArgs...).Result()
 }
+
+func (r *RedisCache) RedisClient() redis.UniversalClient {
+	return r.redisTemplate
+}

+ 20 - 1
common/component/config/ht_biz_config.go

@@ -10,11 +10,15 @@ type HTOpts struct {
 	DesCode           string
 	Task              string
 	AccountApiUrl     string
+	PaymentApiUrl     string
 	WebhookPrivateKey string
 	WebhookPublicKey  string
 	AccountInfoUrl    string
 	CapAppId          string
 	CapSecretKey      string
+	PaymentAppId      string
+	PaymentSign       string
+	MerchantId        string
 }
 type HTBizConfig struct {
 	BaseConfig
@@ -55,7 +59,18 @@ func (e *HTBizConfig) GetWebhookPublicKey() string {
 func (e *HTBizConfig) GetAccountApiUrl() string {
 	return e.opts.AccountApiUrl
 }
-
+func (e *HTBizConfig) GetMerchantId() string {
+	return e.opts.MerchantId
+}
+func (e *HTBizConfig) GetPaymentApiUrl() string {
+	return e.opts.PaymentApiUrl
+}
+func (e *HTBizConfig) GetPaymentAppId() string {
+	return e.opts.PaymentAppId
+}
+func (e *HTBizConfig) GetPaymentSign() string {
+	return e.opts.PaymentSign
+}
 func (e *HTBizConfig) GetAppId() string {
 	return e.opts.CapAppId
 }
@@ -76,8 +91,12 @@ func (e *HTBizConfig) InitConfig() {
 		WebhookPrivateKey: e.GetString("webhook.private_key"),
 		WebhookPublicKey:  e.GetString("webhook.public_key"),
 		AccountInfoUrl:    e.GetString("api.account_url"),
+		PaymentApiUrl:     e.GetString("api.payment_url"),
 		CapAppId:          e.GetString("api.app_id"),
 		CapSecretKey:      e.GetString("api.secret_key"),
+		PaymentAppId:      e.GetString("api.payment_app_id"),
+		PaymentSign:       e.GetString("api.payment_secret_key"),
+		MerchantId:        e.GetString("merchant_id"),
 	}
 	e.opts = opts
 }

+ 1 - 0
common/component/es/es.go

@@ -51,6 +51,7 @@ func GetInstance() *ESClient {
 			logger.Info("初始化es")
 			// 这里可以添加初始化Redis的逻辑
 			esClient = newEs(esConf)
+			logger.Info("es地址:%v", esConf.GetUrl())
 		}
 	})
 	return esClient

+ 17 - 1
common/exception/exc_enums.go

@@ -82,6 +82,7 @@ const (
 	GenerateOrderNoFailed
 	IllegalOrderNo
 	GetOrderListFailed
+	GetOrderDetailFailed
 )
 
 // WechatErrCode 微信
@@ -121,6 +122,8 @@ const (
 	IllegalProductId
 
 	IllegalProductOrderId
+
+	MerchantInfoNotConfig
 )
 const (
 	WebhookErrCode int = iota + 80000
@@ -139,6 +142,13 @@ const (
 	ProductErrorCode int = iota + 100000
 	ProductOffSale
 )
+const (
+	PaymentErrCode int = iota + 110000
+
+	CreatePaymentOrderFailed
+	PaymentProcessingError
+	PaymentDoneError
+)
 
 // ErrorMap 用于存储错误码和错误信息的映射
 var ErrorMap = map[int]string{
@@ -195,8 +205,9 @@ var ErrorMap = map[int]string{
 	OfficialUserNotFound:               "用户未开户",
 	SubscribeFailed:                    "订阅失败",
 	GenerateOrderNoFailed:              "生成订单号",
-	IllegalOrderNo:                     "非法的品订单号",
+	IllegalOrderNo:                     "非法的品订单号",
 	GetOrderListFailed:                 "获取产品订单列表失败",
+	GetOrderDetailFailed:               "获取订单详情失败",
 	//微信
 	WeChatServerError:    "微信服务器发生错误",
 	WechatUserInfoFailed: "获取微信用户信息失败",
@@ -224,6 +235,7 @@ var ErrorMap = map[int]string{
 	ProductInfoError:      "获取商品信息失败",
 	IllegalProductId:      "非法的产品ID",
 	IllegalProductOrderId: "非法的产品订单ID",
+	MerchantInfoNotConfig: "商户信息未配置",
 	//webhook
 	SyncRiskError:               "同步风险等级失败",
 	GetCapTokenFailed:           "获取cap token失败",
@@ -234,6 +246,10 @@ var ErrorMap = map[int]string{
 	OrderPayTimeoutError: "订单支付超时",
 	//product
 	ProductOffSale: "商品已下架",
+	//支付
+	CreatePaymentOrderFailed: "创建支付订单失败",
+	PaymentProcessingError:   "支付订单处理中",
+	PaymentDoneError:         "订单已完成支付",
 }
 
 func Equals(code int, message string) bool {

+ 58 - 0
common/utils/lock/distrubtLock.go

@@ -0,0 +1,58 @@
+package lock
+
+import (
+	"context"
+	"eta/eta_mini_ht_api/common/component/cache"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+)
+
+const (
+	lockName = "lock:"
+)
+
+var (
+	ctx = context.Background()
+	rdb = cache.GetInstance()
+)
+
+func AcquireLock(key string, expiration int, Holder string) bool {
+	script := redis.NewScript(`local key = KEYS[1]
+			local clientId = ARGV[1]
+			local expiration = tonumber(ARGV[2])
+			if redis.call("EXISTS", key) == 0 then
+				redis.call("SET", key, clientId, "EX", expiration)
+				return 1
+			else
+				return 0
+			end`)
+
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	result, err := script.Run(ctx, rdb.RedisClient(), []string{lockey}, Holder, expiration).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}
+
+func ReleaseLock(key string, holder string) bool {
+	script := redis.NewScript(`
+	   if redis.call("get", KEYS[1]) == ARGV[1] then
+	       return redis.call("del", KEYS[1])
+	   else
+	       return 0
+	   end
+	`)
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	result, err := script.Run(ctx, rdb.RedisClient(), []string{lockey}, holder).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}

+ 9 - 0
common/utils/redis/key_generator.go

@@ -4,12 +4,14 @@ import "fmt"
 
 const (
 	SmsKeyPrefix      = "sms_code:"
+	PaymentKeyPrefix  = "payment:"
 	LoginTokenPrefix  = "login:token:"
 	CAPAccessTokenKey = "cap:access_token"
 )
 
 const (
 	ValidAreaCode = "areaCode:valid:list"
+	MerchantKey   = "merchant:id"
 )
 
 func GenerateSmsKey(mobile string) string {
@@ -26,3 +28,10 @@ func GenerateCAPAccessTokenKey() string {
 func GenerateReportRefreshKey(source string, id int, modifyTime int64) string {
 	return fmt.Sprintf("%s:%d:%d", source, id, modifyTime)
 }
+
+func GeneratePaymentKey(orderNo string) string {
+	return fmt.Sprint(PaymentKeyPrefix, orderNo)
+}
+func GenerateMerchantKey() string {
+	return MerchantKey
+}

+ 103 - 0
controllers/order/order_controller.go

@@ -0,0 +1,103 @@
+package order
+
+import (
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/page"
+	"eta/eta_mini_ht_api/controllers"
+	"eta/eta_mini_ht_api/service/order"
+	"eta/eta_mini_ht_api/service/user"
+)
+
+type OrderController struct {
+	controllers.BaseController
+}
+
+type PreviewOrderRequest struct {
+	ProductId int `json:"productId"`
+}
+
+// PreviewProductOrder  预览订单
+// @Summary 预览订单
+// @Description 预览订单
+// @Success 200 {object} controllers.BaseResponse
+// @router /previewProductOrder [get]
+func (o *OrderController) PreviewProductOrder(productId int) {
+	controllers.Wrap(&o.BaseController, func() (result *controllers.WrapData, err error) {
+		result = o.InitWrapData("预览订单创建失败")
+		var userInfo user.User
+		userInfo = o.Data["user"].(user.User)
+		if productId <= 0 {
+			err = exception.New(exception.IllegalProductId)
+			o.FailedResult("预览订单失败,产品编号非法", result)
+			return
+		}
+		//创单
+		//返回订单信息、商品信息
+		orderInfo, err := order.PreViewProductOrder(userInfo, productId)
+		if err != nil {
+			o.FailedResult("创建预览订单失败", result)
+			return
+		}
+		o.SuccessResult("预览订单创建成功", orderInfo, result)
+		return
+	})
+}
+
+// OrderDetail  获取订单详情
+// @Summary  获取订单详情
+// @Description  获取订单详情
+// @Success 200 {object} controllers.BaseResponse
+// @router /orderDetail [get]
+func (sc *SubscribeController) GetOrderDetail(orderId int) {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("获取订单号失败")
+		userInfo := sc.Data["user"].(user.User)
+		if orderId <= 0 {
+			err = exception.New(exception.IllegalProductOrderId)
+			sc.FailedResult("获取订单详情失败,产品编号非法", result)
+			return
+		}
+		orderDetail, err := order.GetOrderDetail(orderId, userInfo.Id)
+		if err != nil {
+			sc.FailedResult("获取订单详情失败", result)
+			err = exception.NewWithException(exception.GetOrderListFailed, err.Error())
+			return
+		}
+		sc.SuccessResult("获取订单详情成功", orderDetail, result)
+		return
+	})
+}
+
+// GetOrderList  获取用户订单号列表
+// @Summary   获取用户订单号列表
+// @Description   获取用户订单号列表
+// @Success 200 {object} controllers.BaseResponse
+// @router /orderList [get]
+func (sc *SubscribeController) GetOrderList(orderStatus string) {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("分页查询订单列表失败")
+		userInfo := sc.Data["user"].(user.User)
+		pageRes := page.Page{
+			Current:  sc.PageInfo.Current,
+			PageSize: sc.PageInfo.PageSize,
+		}
+		pageRes.Total, pageRes.LatestId = order.GetTotalPageCountByUserId(userInfo.Id, orderStatus)
+		if sc.PageInfo.LatestId == 0 {
+			sc.PageInfo.LatestId = pageRes.LatestId
+		} else {
+			pageRes.LatestId = sc.PageInfo.LatestId
+			pageRes.Total = sc.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		list, err := order.GetOrderPage(sc.PageInfo, userInfo.Id, orderStatus)
+		if err != nil {
+			sc.FailedResult("分页查询订单列表失败", result)
+			return
+		}
+		orders := new(page.PageResult)
+		orders.Data = list
+		orders.Page = pageRes
+		sc.SuccessResult("查询订单列表成功", orders, result)
+		return
+	})
+}

+ 3 - 62
controllers/user/subscribe_controller.go → controllers/order/subscribe_controller.go

@@ -1,8 +1,7 @@
-package user
+package order
 
 import (
 	"eta/eta_mini_ht_api/common/exception"
-	"eta/eta_mini_ht_api/common/utils/page"
 	"eta/eta_mini_ht_api/controllers"
 	"eta/eta_mini_ht_api/service/facade"
 	"eta/eta_mini_ht_api/service/order"
@@ -87,7 +86,7 @@ const (
 // @Summary  获取订单号
 // @Description  获取订单号
 // @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderNo [get]
+// @router /orderNo [get]
 func (sc *SubscribeController) GetOrderNo() {
 	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
 		result = sc.InitWrapData("获取订单号失败")
@@ -104,69 +103,11 @@ func (sc *SubscribeController) GetOrderNo() {
 	})
 }
 
-// OrderDetail  获取订单号
-// @Summary  获取订单号
-// @Description  获取订单号
-// @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderDetail [get]
-func (sc *SubscribeController) GetOrderDetail(orderId int) {
-	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
-		result = sc.InitWrapData("获取订单号失败")
-		userInfo := sc.Data["user"].(user.User)
-		if orderId <= 0 {
-			err = exception.New(exception.IllegalProductOrderId)
-			sc.FailedResult("获取订单详情失败,产品编号非法", result)
-			return
-		}
-		orderDetail, err := order.GetOrderDetail(orderId, userInfo.Id)
-		if err != nil {
-			sc.FailedResult("获取订单详情失败", result)
-		}
-		sc.SuccessResult("获取订单详情成功", orderDetail, result)
-		return
-	})
-}
-
-// GetOrderList  获取用户订单号列表
-// @Summary   获取用户订单号列表
-// @Description   获取用户订单号列表
-// @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderList [get]
-func (sc *SubscribeController) GetOrderList(orderStatus string) {
-	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
-		result = sc.InitWrapData("分页查询订单列表失败")
-		userInfo := sc.Data["user"].(user.User)
-		pageRes := page.Page{
-			Current:  sc.PageInfo.Current,
-			PageSize: sc.PageInfo.PageSize,
-		}
-		pageRes.Total, pageRes.LatestId = order.GetTotalPageCountByUserId(userInfo.Id, orderStatus)
-		if sc.PageInfo.LatestId == 0 {
-			sc.PageInfo.LatestId = pageRes.LatestId
-		} else {
-			pageRes.LatestId = sc.PageInfo.LatestId
-			pageRes.Total = sc.PageInfo.Total
-		}
-		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-
-		list, err := order.GetOrderPage(sc.PageInfo, userInfo.Id, orderStatus)
-		if err != nil {
-			sc.FailedResult("分页查询订单列表失败", result)
-			return
-		}
-		orders := new(page.PageResult)
-		orders.Data = list
-		orders.Page = pageRes
-		sc.SuccessResult("查询报告列表成功", orders, result)
-		return
-	})
-}
-
 // SubscribeProduct  订阅产品
 // @Summary 订阅产品
 // @Description 订阅产品
 // @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/createOrder [post]
+// @router /subscribe [post]
 func (sc *SubscribeController) SubscribeProduct() {
 	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
 		result = sc.InitWrapData("订阅产品失败")

+ 89 - 2
controllers/payment/payment_controller.go

@@ -1,12 +1,99 @@
 package payment
 
-import "eta/eta_mini_ht_api/controllers"
+import (
+	"errors"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/lock"
+	"eta/eta_mini_ht_api/common/utils/redis"
+	"eta/eta_mini_ht_api/controllers"
+	orderService "eta/eta_mini_ht_api/domian/order"
+	userService "eta/eta_mini_ht_api/domian/user"
+	"eta/eta_mini_ht_api/service/facade"
+	"eta/eta_mini_ht_api/service/order"
+	"gorm.io/gorm"
+)
+import "eta/eta_mini_ht_api/service/user"
 
 type PaymentController struct {
 	controllers.BaseController
 }
 
-//支付
+// 支付
+type PayRequest struct {
+	ProductOrderNo string `json:"productOrderNo"`
+}
+type PayResponse struct {
+	ProductOrderNo string `json:"productOrderNo"`
+	TradeOrderNo   string `json:"tradeOrderNo"`
+}
+
+// PayOrder  支付订单
+// @Summary 支付订单
+// @Description 支付订单
+// @Success 200 {object} controllers.BaseResponse
+// @router /payOrder [post]
+func (pc *PaymentController) PayOrder() {
+	controllers.Wrap(&pc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = pc.InitWrapData("创建支付订单失败")
+		payRequest := new(PayRequest)
+		pc.GetPostParams(payRequest)
+		if payRequest.ProductOrderNo == "" {
+			pc.FailedResult("订单编号不能为空", result)
+			err = exception.New(exception.IllegalOrderNo)
+			return
+		}
+		userInfo := pc.Data["user"].(user.User)
+		defer func() {
+			lock.ReleaseLock(redis.GeneratePaymentKey(payRequest.ProductOrderNo), pc.Data["threadId"].(string))
+		}()
+		if lock.AcquireLock(redis.GeneratePaymentKey(payRequest.ProductOrderNo), 10, pc.Data["threadId"].(string)) {
+			//校验商品订单状态
+			var productOrder orderService.ProductOrderDTO
+			productOrder, err = order.GetProductOrderByUser(userInfo.Id, payRequest.ProductOrderNo)
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					pc.FailedResult("支付失败,商品订单信息不存在", result)
+					err = exception.New(exception.IllegalOrderNo)
+				} else {
+					pc.FailedResult("支付失败,查询商品订单失败", result)
+					err = exception.New(exception.IllegalOrderNo)
+				}
+				return
+			}
+			if productOrder.Status != "pending" {
+				if productOrder.Status == "closed" {
+					pc.FailedResult("支付失败,商品订单已关闭", result)
+					err = exception.New(exception.IllegalOrderStatus)
+				} else {
+					pc.FailedResult("支付失败,商品订单状态异常", result)
+					err = exception.New(exception.IllegalOrderStatus)
+				}
+				return
+			}
+			var officialUser userService.OfficialUserDTO
+			officialUser, err = user.GetUserByTemplateUserId(userInfo.Id)
+			if err != nil {
+				pc.FailedResult("支付失败,正式用户信息不存在", result)
+			}
+			var tradeOrderNo string
+			tradeOrderNo, err = facade.PayOrder(officialUser.ID, userInfo.Id, productOrder)
+			if err != nil {
+				pc.FailedResult("支付订单创建失败", result)
+				err = exception.NewWithException(exception.CreatePaymentOrderFailed, err.Error())
+				return
+			}
+			pc.SuccessResult("支付订单创建成功", PayResponse{
+				ProductOrderNo: productOrder.OrderID,
+				TradeOrderNo:   tradeOrderNo,
+			}, result)
+			return
+		} else {
+			pc.FailedResult("商品订单处理中,请稍后再试", result)
+			err = exception.New(exception.PaymentProcessingError)
+			return
+		}
+	})
+}
 
 //退款
 

+ 0 - 43
controllers/user/order_controller.go

@@ -1,43 +0,0 @@
-package user
-
-import (
-	"eta/eta_mini_ht_api/common/exception"
-	"eta/eta_mini_ht_api/controllers"
-)
-
-type OrderController struct {
-	controllers.BaseController
-}
-
-type ProductOrderReq struct {
-	ProductId int `json:"productId"`
-}
-
-// CreateProductOrder  创建商品订单
-// @Summary 创建商品订单
-// @Description 创建商品订单
-// @Success 200 {object} controllers.BaseResponse
-// @router /createProductOrder [post]
-func (o *OrderController) CreateProductOrder() {
-	controllers.Wrap(&o.BaseController, func() (result *controllers.WrapData, err error) {
-		result = o.InitWrapData("获取商品详情失败")
-		err = exception.New(exception.IllegalProductId)
-		o.FailedResult("获取商品详情失败", result)
-		//客戶是否开户,是否有风险等级,是否风险等级过期
-
-		//报告的风险等级 套餐的风险等级
-		//创单
-		//facade.CheckUserRiskLevel()
-		//productInfo, err := productService.GetProductInfoById(productId)
-		//if err != nil {
-		//	o.FailedResult("获取商品详情失败", result)
-		//	return
-		//}
-		//o.SuccessResult("获取商品详情成功", productInfo, result)
-		return
-	})
-	//
-	//获取用户信息
-	//userInfo := o.Data["user"].(user.User)
-
-}

+ 89 - 39
domian/order/product_order.go

@@ -4,6 +4,7 @@ import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/exception"
 	"eta/eta_mini_ht_api/common/utils/page"
+	productDao "eta/eta_mini_ht_api/models/merchant"
 	orderDao "eta/eta_mini_ht_api/models/order"
 	"fmt"
 	"math/rand"
@@ -11,25 +12,30 @@ import (
 )
 
 type ProductOrderDTO struct {
-	ID                int
-	OrderID           string
-	UserID            int
-	ProductID         int
-	TotalAmount       string
-	ProductPrice      string
-	ProductName       string
-	PaymentWay        string
-	PaymentTimeRemain int64
-	Status            string
-	CreatedTime       string
+	ID                 int
+	OrderID            string
+	UserID             int
+	TemplateUserID     int
+	ProductID          int
+	TotalAmount        string
+	ProductPrice       string
+	ProductName        string
+	ProductDescription string
+	PaymentWay         string
+	PaymentTimeRemain  int64
+	Status             string
+	StatusCN           string
+	CreatedTime        string
+	UpdatedTime        string
 }
 
 type ProductOrderDetailDTO struct {
 	ProductOrderDTO
-	TransactionID int
-	PaymentTime   string
-	RefundStatus  string
-	Remark        string
+	TransactionID    int
+	PaymentTime      string
+	RefundFinishTime string
+	RefundStatus     string
+	Remark           string
 }
 
 var (
@@ -73,47 +79,70 @@ func CreateProductOrder(orderDTO ProductOrderDTO) (orderNo string, err error) {
 
 func convertProductOrderDTO(order orderDao.ProductOrder) (orderDTO ProductOrderDTO) {
 	orderDTO = ProductOrderDTO{
-		ID:          order.ID,
-		OrderID:     order.OrderID,
-		UserID:      order.UserID,
-		ProductID:   order.ProductID,
-		TotalAmount: order.TotalAmount,
-		PaymentWay:  string(order.PaymentWay),
-		Status:      transProductOrderStatusMap[order.Status],
-		CreatedTime: order.CreatedTime.Format(time.DateTime),
+		ID:             order.ID,
+		OrderID:        order.OrderID,
+		UserID:         order.UserID,
+		TemplateUserID: order.UserID,
+		ProductID:      order.ProductID,
+		TotalAmount:    order.TotalAmount,
+		PaymentWay:     string(order.PaymentWay),
+		Status:         string(order.Status),
+		StatusCN:       transProductOrderStatusMap[order.Status],
+		CreatedTime:    order.CreatedTime.Format(time.DateTime),
 	}
 	if order.Status == orderDao.OrderStatusRefund {
-		orderDTO.Status = transRefundStatusMap[order.RefundStatus]
+		orderDTO.Status = string(order.RefundStatus)
+		orderDTO.StatusCN = transRefundStatusMap[order.RefundStatus]
 	}
 	if order.Status == orderDao.OrderStatusPending {
 		duration := time.Now().Sub(order.CreatedTime)
 		timeout := duration - 15*time.Minute
 		if timeout > 0 {
 			logger.Info("订单已超时:%v", order.OrderID)
-			orderDTO.Status = transProductOrderStatusMap[orderDao.OrderStatusClosed]
+			orderDTO.Status = string(orderDao.OrderStatusClosed)
+			orderDTO.StatusCN = transProductOrderStatusMap[orderDao.OrderStatusClosed]
 		} else {
 			orderDTO.PaymentTimeRemain = int64(duration.Seconds())
 		}
 	}
 	return
 }
-func convertProductOrderDetailDTO(order orderDao.ProductOrder) ProductOrderDetailDTO {
-	return ProductOrderDetailDTO{
+func convertProductOrderDetailDTO(order orderDao.ProductOrder) (orderDetailDTO ProductOrderDetailDTO) {
+	orderDetailDTO = ProductOrderDetailDTO{
 		ProductOrderDTO: ProductOrderDTO{
-			ID:          order.ID,
-			OrderID:     order.OrderID,
-			UserID:      order.UserID,
-			ProductID:   order.ProductID,
-			TotalAmount: order.TotalAmount,
-			PaymentWay:  string(order.PaymentWay),
-			Status:      transProductOrderStatusMap[order.Status],
-			CreatedTime: order.CreatedTime.Format(time.DateTime),
+			ID:             order.ID,
+			OrderID:        order.OrderID,
+			UserID:         order.UserID,
+			TemplateUserID: order.TemplateUserID,
+			ProductID:      order.ProductID,
+			TotalAmount:    order.TotalAmount,
+			PaymentWay:     string(order.PaymentWay),
+			Status:         string(order.Status),
+			StatusCN:       transProductOrderStatusMap[order.Status],
+			CreatedTime:    order.CreatedTime.Format(time.DateTime),
 		},
-		TransactionID: order.TransactionID,
-		PaymentTime:   order.PaymentTime.Format(time.DateTime),
-		RefundStatus:  string(order.RefundStatus),
-		Remark:        order.Remark,
+		TransactionID:    order.TradeID,
+		PaymentTime:      order.PaymentTime.Format(time.DateTime),
+		RefundStatus:     string(order.RefundStatus),
+		RefundFinishTime: order.RefundFinishTime.Format(time.DateTime),
+		Remark:           order.Remark,
+	}
+	if order.Status == orderDao.OrderStatusRefund {
+		orderDetailDTO.Status = string(order.RefundStatus)
+		orderDetailDTO.StatusCN = transRefundStatusMap[order.RefundStatus]
 	}
+	if order.Status == orderDao.OrderStatusPending {
+		duration := time.Now().Sub(order.CreatedTime)
+		timeout := duration - 15*time.Minute
+		if timeout > 0 {
+			logger.Info("订单已超时:%v", order.OrderID)
+			orderDetailDTO.Status = string(orderDao.OrderStatusClosed)
+			orderDetailDTO.StatusCN = transProductOrderStatusMap[orderDao.OrderStatusClosed]
+		} else {
+			orderDetailDTO.PaymentTimeRemain = int64(duration.Seconds())
+		}
+	}
+	return
 }
 
 func convertProductOrder(order ProductOrderDTO) orderDao.ProductOrder {
@@ -165,7 +194,14 @@ func GetOrderDetail(orderId int, userId int) (orderDTO ProductOrderDetailDTO, er
 		logger.Error("查询订单详情失败:%v", err)
 		return
 	}
+	product, err := productDao.GetMerchantProductById(order.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
 	orderDTO = convertProductOrderDetailDTO(order)
+	orderDTO.ProductName = product.Title
+	orderDTO.ProductDescription = product.Description
 	return
 }
 
@@ -186,3 +222,17 @@ func BatchCloseOrder() (err error) {
 	//}
 	return nil
 }
+
+func GetOrderByUser(templateUserId int, orderNo string) (orderDTO ProductOrderDTO, err error) {
+	var order orderDao.ProductOrder
+	order, err = orderDao.GetOrderByUser(templateUserId, orderNo)
+	product, err := productDao.GetMerchantProductById(order.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
+	orderDTO = convertProductOrderDTO(order)
+	orderDTO.ProductName = product.Title
+	orderDTO.ProductDescription = product.Description
+	return
+}

+ 102 - 0
domian/order/trade_order.go

@@ -0,0 +1,102 @@
+package order
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/models"
+	"eta/eta_mini_ht_api/models/order"
+	"fmt"
+	"math/rand"
+	"time"
+)
+
+const (
+	RMB = "CNY"
+)
+
+type TradeOrderDTO struct {
+	ID             int       `gorm:"column:id;primaryKey"`
+	TransactionID  string    `gorm:"column:transaction_id;type:varchar(255);comment:第三方平台ID"`
+	ProductOrderID string    `gorm:"column:product_order_id;type:varchar(255);comment:商品订单号"`
+	PaymentAccount string    `gorm:"column:payment_account;type:varchar(255);comment:支付账号"`
+	PaymentWay     string    `gorm:"column:payment_way;type:enum('wechat');comment:支付渠道"`
+	Amount         string    `gorm:"column:amount;type:varchar(20);comment:支付金额"`
+	Currency       string    `gorm:"column:currency;type:varchar(255);comment:货币"`
+	MerchantID     string    `gorm:"column:merchant_id;type:int(11);comment:商户id"`
+	UserID         int       `gorm:"column:user_id;type:int(11);comment:用户id"`
+	TemplateUserID int       `gorm:"column:template_user_id;type:int(11);comment:临时用户id"`
+	PaymentType    string    `gorm:"column:payment_type;type:enum('pay','refund');comment:订单类型"`
+	PaymentStatus  string    `gorm:"column:payment_status;type:enum('pending','processing','done','failed');comment:支付状态"`
+	DealTime       time.Time `gorm:"column:deal_time;type:datetime;comment:完成时间"`
+}
+
+func GenerateTradeOrderNo() string {
+	timestamp := time.Now().UnixNano() / 1000000 // 毫秒级时间戳
+	// 生成随机数
+	rand.New(rand.NewSource(time.Now().UnixNano()))
+	randomPart := rand.Intn(999999)
+	// 格式化订单号
+	orderNumber := fmt.Sprintf("T%d%06d", timestamp, randomPart)
+	return orderNumber
+}
+
+func CreateTradeOrder(userId, templateUserId int, productOrderNo, tradeOrderNo, merchantNo string) (err error) {
+	db := models.Main()
+	productOrder, err := order.GetOrderByUser(templateUserId, productOrderNo)
+	if err != nil {
+		logger.Error("获取商品订单信息失败%v", err)
+		return
+	}
+	tx := db.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+			return
+		}
+		tx.Commit()
+	}()
+	tradeOrder := order.TradeOrder{
+		ProductOrderID: productOrderNo,
+		TransactionID:  tradeOrderNo,
+		Amount:         productOrder.TotalAmount,
+		Currency:       RMB,
+		MerchantID:     merchantNo,
+		UserID:         templateUserId,
+		TemplateUserID: templateUserId,
+		PaymentType:    order.PaymentTypePay,
+	}
+	err = tx.Create(&tradeOrder).Error
+	if err != nil {
+		logger.Error("创建支付订单失败%v", err)
+		return
+	}
+	productOrder.TradeID = tradeOrder.ID
+	productOrder.TradeNO = tradeOrder.TransactionID
+	productOrder.PaymentTime = time.Now()
+	err = tx.Save(productOrder).Error
+	return
+}
+func convertToDTO(tradeOrder order.TradeOrder) TradeOrderDTO {
+	return TradeOrderDTO{
+		ID:             tradeOrder.ID,
+		TransactionID:  tradeOrder.TransactionID,
+		ProductOrderID: tradeOrder.ProductOrderID,
+		PaymentAccount: tradeOrder.PaymentAccount,
+		PaymentWay:     tradeOrder.PaymentWay,
+		Amount:         tradeOrder.Amount,
+		Currency:       tradeOrder.Currency,
+		MerchantID:     tradeOrder.MerchantID,
+		UserID:         tradeOrder.UserID,
+		TemplateUserID: tradeOrder.UserID,
+		PaymentType:    string(tradeOrder.PaymentType),
+		PaymentStatus:  string(tradeOrder.PaymentStatus),
+		DealTime:       tradeOrder.DealTime,
+	}
+}
+func GetUnFailedTradeFlowByProductOrder(productOrderNo string) (dtoList []TradeOrderDTO, err error) {
+	var tradeOrderList []order.TradeOrder
+	tradeOrderList, err = order.GetUnFailedTradeFlowByProductOrder(productOrderNo)
+	for _, tradeOrder := range tradeOrderList {
+		dtoList = append(dtoList, convertToDTO(tradeOrder))
+	}
+	return
+}

+ 35 - 0
main.go

@@ -12,6 +12,7 @@ import (
 	"eta/eta_mini_ht_api/domian/report"
 	"eta/eta_mini_ht_api/models/eta"
 	"eta/eta_mini_ht_api/models/ht"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
 	_ "eta/eta_mini_ht_api/routers"
 	_ "eta/eta_mini_ht_api/task"
 	"github.com/beego/beego/v2/server/web"
@@ -41,6 +42,8 @@ func main() {
 		}
 		//初始化第三方AccessToken
 		initThirdPartyAccessToken()
+		//初始化商户信息
+		initMerchant(htConfig.GetMerchantId())
 	}()
 	logger.Info("初始化成功")
 	web.Run()
@@ -137,7 +140,39 @@ func initReport() {
 	wg.Wait()
 	logger.Info("初始化研报库完成")
 }
+func initMerchant(merchantId string) {
+	merchantList, err := merchantDao.GetEnablerMerchants()
+	if err != nil {
+		logger.Error("加载商户信息失败:%v", err)
+	}
+	if len(merchantList) == 0 {
+		logger.Warn("未配置商户信息,请配置商户信息")
+	}
+	if merchantId != "" {
+		find := false
+		for _, merchant := range merchantList {
+			if merchant.MerchantID == merchantId {
+				err = redisUtils.SetString(redis.GenerateMerchantKey(), merchant.MerchantID, 0)
+				if err != nil {
+					logger.Error("redis商户ID添加失败:%v", err)
+					return
+				}
+				find = true
+				break
+			}
+			if !find {
+				logger.Error("未找到启动的商户信息,请配置商户信息,商户ID:[" + merchantId + "]")
+			}
+		}
+	} else {
+		err = redisUtils.SetString(redis.GenerateMerchantKey(), merchantList[0].MerchantID, -1)
+		if err != nil {
+			logger.Error("redis商户ID添加失败:%v", err)
+			return
+		}
+	}
 
+}
 func getPermissionNameById(id int, currentName *string) (err error) {
 	plate, err := ht.GetPermissionNameById(id)
 	if err != nil {

+ 3 - 0
middleware/auth_middleware.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_mini_ht_api/service/user"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/context"
+	"github.com/google/uuid"
 	"strings"
 )
 
@@ -83,6 +84,8 @@ var privateRoutes = []string{
 
 func AuthMiddleware() web.FilterFunc {
 	return func(ctx *context.Context) {
+		threadId := strings.ReplaceAll(uuid.New().String(), "-", "")
+		ctx.Input.SetData("threadId", threadId)
 		path := ctx.Input.URL()
 		logger.Info("请求路径:%v", path)
 		if !allowed(path) {

+ 10 - 1
models/merchant/merchant.go

@@ -1,6 +1,9 @@
 package merchant
 
-import "time"
+import (
+	"eta/eta_mini_ht_api/models"
+	"time"
+)
 
 // Merchant 商户信息结构体
 type Merchant struct {
@@ -16,3 +19,9 @@ type Merchant struct {
 func (Merchant) TableName() string {
 	return "merchants"
 }
+
+func GetEnablerMerchants() (merchant []Merchant, err error) {
+	db := models.Main()
+	err = db.Select("*").Where("enabled=?", 1).Find(&merchant).Error
+	return
+}

+ 1 - 1
models/merchant/merchant_product.go

@@ -11,7 +11,7 @@ type SaleStatus string
 type MerchantProductType string
 
 const (
-	detailColumns  = "id,title,price,is_permanent,valid_days,type,risk_level,stale_status"
+	detailColumns  = "id,title,price,is_permanent,valid_days,type,risk_level,sale_status"
 	sourceIdColumn = "id,risk_level,source_id"
 )
 const (

+ 32 - 21
models/order/product_order.go

@@ -11,7 +11,7 @@ import (
 
 type OrderStatus string
 type RefundStatus string
-type PaymentStatus string
+type PaymentWay string
 
 const (
 	//订单状态
@@ -26,26 +26,31 @@ const (
 	RefundStatusFailed     RefundStatus = "failed"
 	RefundStatusProcessing RefundStatus = "processing"
 
-	WechatPayWay PaymentStatus = "wechat"
-	AliPayWay    PaymentStatus = "alipay"
+	WechatPayWay PaymentWay = "wechat"
+	AliPayWay    PaymentWay = "alipay"
+
+	detailColumn = "id,order_id,user_id,template_user_id,product_id,status,created_time,updated_time,total_amount"
 )
 
 type ProductOrder struct {
-	ID            int           `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
-	OrderID       string        `gorm:"column:order_id;size:255;comment:'订单编号'" json:"order_id"`
-	UserID        int           `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
-	ProductID     int           `gorm:"column:product_id;default:null;comment:'产品id'" json:"product_id"`
-	TotalAmount   string        `gorm:"column:total_amount;size:255;default:null;comment:'金额'" json:"total_amount"`
-	TransactionID int           `gorm:"column:transaction_id;default:null;comment:'支付订单'" json:"transaction_id"`
-	RefundAmount  string        `gorm:"column:refund_amount;size:255;default:null;comment:'退款金额'" json:"refund_amount"`
-	PaymentWay    PaymentStatus `gorm:"column:payment_way;enum('wechat','alipay');default:null;comment:'支付渠道'"`
-	PaymentTime   time.Time     `gorm:"column:payment_time;default:null;comment:'支付时间'" json:"payment_time"`
-	Status        OrderStatus   `gorm:"column:status;type:enum('pending','processing','paid','closed','refund');default:'pending';comment:'订单状态'" json:"status"`
-	RefundStatus  RefundStatus  `gorm:"column:refund_status;type:enum('pending','processing','failed','success');default:null;comment:'退款状态'" json:"refund_status"`
-	Remark        string        `gorm:"column:remark;size:255;default:null;comment:'备注'" json:"remark"`
-	IsDeleted     int           `gorm:"column:is_deleted;size:1;default:0;comment:'是否删除'" json:"is_deleted"`
-	CreatedTime   time.Time     `gorm:"column:created_time;default:null;comment:'创建时间'" json:"created_time"`
-	UpdateTime    time.Time     `gorm:"column:update_time;default:null;comment:'更新时间'" json:"update_time"`
+	ID               int          `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+	OrderID          string       `gorm:"column:order_id;size:255;comment:'订单编号'" json:"order_id"`
+	UserID           int          `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
+	TemplateUserID   int          `gorm:"column:template_user_id;default:null;comment:'临时用户id'" json:"template_user_id"`
+	ProductID        int          `gorm:"column:product_id;default:null;comment:'产品id'" json:"product_id"`
+	TotalAmount      string       `gorm:"column:total_amount;size:255;default:null;comment:'金额'" json:"total_amount"`
+	TradeID          int          `gorm:"column:trade_id;default:null;comment:'支付订单'" json:"trade_id"`
+	TradeNO          string       `gorm:"column:trade_no;default:null;comment:'支付订单'" json:"trade_no"`
+	RefundAmount     string       `gorm:"column:refund_amount;size:255;default:null;comment:'退款金额'" json:"refund_amount"`
+	PaymentWay       PaymentWay   `gorm:"column:payment_way;enum('wechat','alipay');default:null;comment:'支付渠道'"`
+	PaymentTime      time.Time    `gorm:"column:payment_time;default:null;comment:'支付时间'" json:"payment_time"`
+	Status           OrderStatus  `gorm:"column:status;type:enum('pending','processing','paid','closed','refund');default:'pending';comment:'订单状态'" json:"status"`
+	RefundStatus     RefundStatus `gorm:"column:refund_status;type:enum('pending','processing','failed','success');default:null;comment:'退款状态'" json:"refund_status"`
+	RefundFinishTime time.Time    `gorm:"column:refund_finish_time;default:null;comment:'退款完成时间'" json:"refund_finish_time"`
+	Remark           string       `gorm:"column:remark;size:255;default:null;comment:'备注'" json:"remark"`
+	IsDeleted        int          `gorm:"column:is_deleted;size:1;default:0;comment:'是否删除'" json:"is_deleted"`
+	CreatedTime      time.Time    `gorm:"column:created_time;default:null;comment:'创建时间'" json:"created_time"`
+	UpdatedTime      time.Time    `gorm:"column:updated_time;default:null;comment:'更新时间'" json:"updated_time"`
 }
 
 func (pr *ProductOrder) BeforeCreate(db *gorm.DB) (err error) {
@@ -76,7 +81,7 @@ func GetTotalPageCountByUserId(userId int, status OrderStatus) (total int64, lat
 		logger.Error("查询订单总数失败:%v", err)
 		return 0, 0
 	}
-	queryLatestId := db.Model(&ProductOrder{}).Select("MAX(id)").Where("user_id = ? and is_deleted=0", userId)
+	queryLatestId := db.Model(&ProductOrder{}).Select("MAX(id)").Where("template_user_id = ? and is_deleted=0", userId)
 	if string(status) != "" {
 		query.Where("  status=?", status)
 	}
@@ -94,7 +99,7 @@ func GetOrderPage(latestId int64, offset int, size int, userId int, status Order
 	if string(status) != "" {
 		query.Where("  status=?", status)
 	}
-	query.Where("id<=? and user_id = ? and is_deleted=0 ", latestId, userId)
+	query.Where("id<=? and template_user_id = ? and is_deleted=0 ", latestId, userId)
 	err = query.Order("created_time desc").Offset(offset).Limit(size).Find(&orderList).Error
 	if err != nil {
 		logger.Error("查询订单总数失败:%v", err)
@@ -104,7 +109,7 @@ func GetOrderPage(latestId int64, offset int, size int, userId int, status Order
 
 func GetOrderDetail(orderId int, userId int) (order ProductOrder, err error) {
 	db := models.Main()
-	err = db.Model(&ProductOrder{}).Select("*").Where("id = ? and user_id = ? and is_deleted=0 ", orderId, userId).First(&order).Error
+	err = db.Model(&ProductOrder{}).Select("*").Where("id = ? and template_user_id = ? and is_deleted=0 ", orderId, userId).First(&order).Error
 	return
 }
 
@@ -130,3 +135,9 @@ func batchCloseOrder(ids []int) error {
 	//}
 	return nil
 }
+
+func GetOrderByUser(templateUserId int, orderNo string) (order ProductOrder, err error) {
+	db := models.Main()
+	err = db.Model(&ProductOrder{}).Select(detailColumn).Where("template_user_id= ? and order_id=? and is_deleted=0 ", templateUserId, orderNo).First(&order).Error
+	return
+}

+ 54 - 0
models/order/trade_order.go

@@ -0,0 +1,54 @@
+package order
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm"
+	"time"
+)
+
+type PaymentStatus string
+type PaymentType string
+
+const (
+	PaymentStatusPending    PaymentStatus = "pending"
+	PaymentStatusProcessing PaymentStatus = "processing"
+	PaymentStatusDone       PaymentStatus = "done"
+	PaymentStatusFailed     PaymentStatus = "failed"
+
+	PaymentTypePay    PaymentType = "pay"
+	PaymentTypeRefund PaymentType = "refund"
+)
+
+type TradeOrder struct {
+	ID             int           `gorm:"column:id;primaryKey"`
+	TransactionID  string        `gorm:"column:transaction_id;type:varchar(255);comment:第三方平台ID"`
+	ProductOrderID string        `gorm:"column:product_order_id;type:varchar(255);comment:商品订单号"`
+	PaymentAccount string        `gorm:"column:payment_account;type:varchar(255);comment:支付账号"`
+	PaymentWay     string        `gorm:"column:payment_way;type:enum('wechat');comment:支付渠道"`
+	Amount         string        `gorm:"column:amount;type:varchar(20);comment:支付金额"`
+	Currency       string        `gorm:"column:currency;type:varchar(255);comment:货币"`
+	MerchantID     string        `gorm:"column:merchant_id;type:varchar(255);comment:商户id"`
+	UserID         int           `gorm:"column:user_id;type:int(11);comment:用户id"`
+	TemplateUserID int           `gorm:"column:template_user_id;type:int(11);comment:临时用户id"`
+	PaymentType    PaymentType   `gorm:"column:payment_type;type:enum('pay','refund');comment:订单类型"`
+	PaymentStatus  PaymentStatus `gorm:"column:payment_status;type:enum('pending','processing','done','failed');comment:支付状态"`
+	DealTime       time.Time     `gorm:"column:deal_time;type:datetime;comment:完成时间"`
+	CreatedTime    time.Time     `gorm:"column:created_time;type:datetime;comment:创建时间"`
+	UpdatedTime    time.Time     `gorm:"column:updated_time;type:datetime;comment:更新时间"`
+}
+
+func (to *TradeOrder) TableName() string {
+	return "trade_orders"
+}
+
+func (to *TradeOrder) BeforeCreate(_ *gorm.DB) (err error) {
+	to.CreatedTime = time.Now()
+	to.PaymentStatus = PaymentStatusPending
+	return
+}
+
+func GetUnFailedTradeFlowByProductOrder(no string) (list []TradeOrder, err error) {
+	db := models.Main()
+	err = db.Where("product_order_id=? and payment_status!=? order by updated_time,created_time desc", no, PaymentStatusFailed).Find(&list).Error
+	return
+}

+ 53 - 42
routers/commentsRouter.go

@@ -54,8 +54,59 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:OrderController"],
         beego.ControllerComments{
-            Method: "CreateProductOrder",
-            Router: `/createProductOrder`,
+            Method: "PreviewProductOrder",
+            Router: `/previewProductOrder`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("productId"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderDetail",
+            Router: `/orderDetail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("orderId"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderList",
+            Router: `/orderList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("orderStatus"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderNo",
+            Router: `/orderNo`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "SubscribeProduct",
+            Router: `/subscribe`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/payment:PaymentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/payment:PaymentController"],
+        beego.ControllerComments{
+            Method: "PayOrder",
+            Router: `/payOrder`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -285,46 +336,6 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "SubscribeProduct",
-            Router: `/subscribe/createOrder`,
-            AllowHTTPMethods: []string{"post"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderDetail",
-            Router: `/subscribe/orderDetail`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(
-				param.New("orderId"),
-			),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderList",
-            Router: `/subscribe/orderList`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(
-				param.New("orderStatus"),
-			),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderNo",
-            Router: `/subscribe/orderNo`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:UserController"],
         beego.ControllerComments{
             Method: "Get",

+ 9 - 2
routers/router.go

@@ -3,6 +3,8 @@ package routers
 import (
 	"eta/eta_mini_ht_api/controllers"
 	"eta/eta_mini_ht_api/controllers/media"
+	"eta/eta_mini_ht_api/controllers/order"
+	"eta/eta_mini_ht_api/controllers/payment"
 	"eta/eta_mini_ht_api/controllers/product"
 	"eta/eta_mini_ht_api/controllers/report"
 	"eta/eta_mini_ht_api/controllers/sys"
@@ -32,7 +34,6 @@ func init() {
 		web.NSNamespace("/user",
 			web.NSInclude(
 				&user.UserController{},
-				&user.SubscribeController{},
 			),
 		),
 		web.NSNamespace("/auth",
@@ -72,7 +73,8 @@ func init() {
 		),
 		web.NSNamespace("/order",
 			web.NSInclude(
-				&user.OrderController{},
+				&order.OrderController{},
+				&order.SubscribeController{},
 			),
 		),
 		web.NSNamespace("/ws",
@@ -85,6 +87,11 @@ func init() {
 				&sys.SysController{},
 			),
 		),
+		web.NSNamespace("/payment",
+			web.NSInclude(
+				&payment.PaymentController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 1 - 1
service/facade/ht_account_service.go

@@ -87,7 +87,7 @@ func CheckUserRiskLevel(templateUserId int, productId int, officialUser userServ
 	if mappingErr != nil {
 		logger.Error("查询产品风险等级映射失败:%v", mappingErr)
 		code = 200
-		err = exception.New(exception.IllegalProductId)
+		err = exception.NewWithException(exception.QueryRiskMappingError, err.Error())
 		return
 	}
 	return compareRisk(mapping.ProductRiskLevel, product.RiskLevel)

+ 38 - 0
service/facade/ht_trade_service.go

@@ -0,0 +1,38 @@
+package facade
+
+import (
+	"eta/eta_mini_ht_api/api"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/domian/order"
+	"eta/eta_mini_ht_api/service/payment"
+)
+
+const (
+	channelWeChat  = "7"
+	timeoutSeconds = "900"
+)
+
+var (
+	htTradeApi = api.GetPaymentInstance()
+)
+
+func PayOrder(userId, templateUserId int, productOrder order.ProductOrderDTO) (tradeOrderNo string, err error) {
+	//先获取支付对接平台的支付订单号
+	req := api.PaymentOrderRequest{
+		BillNo:          productOrder.OrderID,
+		Body:            productOrder.ProductDescription,
+		ChannelTypeCode: channelWeChat,
+		Optional:        "",
+		Subject:         productOrder.ProductName,
+		TimeoutExpress:  timeoutSeconds,
+		TotalFee:        productOrder.TotalAmount,
+	}
+	tradeOrderNo, err = htTradeApi.CreatePaymentOrder(req)
+	tradeOrderNo = order.GenerateTradeOrderNo()
+	err = payment.CreatePaymentOrder(userId, templateUserId, productOrder.OrderID, tradeOrderNo)
+	if err != nil {
+		logger.Error("创建支付订单失败:%v", err)
+		return
+	}
+	return
+}

+ 56 - 10
service/order/order_service.go

@@ -64,10 +64,11 @@ func CreateProductOrder(templateUser user.User, productId int, orderNo string) (
 		return
 	}
 	orderNo, err = orderService.CreateProductOrder(orderService.ProductOrderDTO{
-		OrderID:     orderNo,
-		UserID:      templateUser.Id,
-		ProductID:   productId,
-		TotalAmount: productInfo.Price.String(),
+		OrderID:        orderNo,
+		UserID:         officialUser.ID,
+		TemplateUserID: templateUser.Id,
+		ProductID:      productId,
+		TotalAmount:    productInfo.Price.String(),
 	})
 	if err != nil {
 		logger.Error("创建订单失败:%v", err)
@@ -105,6 +106,53 @@ func CreateProductOrder(templateUser user.User, productId int, orderNo string) (
 	return
 }
 
+func PreViewProductOrder(templateUser user.User, productId int) (orderInfo ProductOrderInfo, err error) {
+	if productId <= 0 {
+		err = exception.New(exception.IllegalProductId)
+	}
+	productInfo, err := productService.GetMerchantProductById(productId)
+	if err != nil {
+		err = exception.NewWithException(exception.ProductInfoError, err.Error())
+		return
+	}
+	if productInfo.SaleStatus == OffSale {
+		err = exception.New(exception.ProductOffSale)
+		return
+	}
+	officialUser, err := userService.GetUserByTemplateUserId(templateUser.Id)
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			err = exception.New(exception.OfficialUserNotFound)
+		} else {
+			err = exception.NewWithException(exception.OfficialUserFoundError, err.Error())
+		}
+		return
+	}
+	buyer := BuyerInfo{
+		Name:     officialUser.RealName,
+		Mobile:   templateUser.Mobile,
+		AreaCode: templateUser.AreaCode,
+	}
+	var duration string
+	if !productInfo.IsPermanent {
+		beginDate := time.Now().Format(time.DateOnly)
+		endDate := time.Now().Add(time.Duration(productInfo.ValidDays*24) * time.Hour).Format(time.DateOnly)
+		duration = fmt.Sprintf("%s~%s", beginDate, endDate)
+	} else {
+		duration = permanentlyValid
+	}
+	product := ProductInfo{
+		Name:          productInfo.Title,
+		Type:          productInfo.Type,
+		Price:         productInfo.Price.String(),
+		ValidDuration: duration,
+	}
+	orderInfo = ProductOrderInfo{
+		Buyer:       buyer,
+		ProductInfo: product,
+	}
+	return
+}
 func GetTotalPageCountByUserId(userId int, orderStatus string) (total int64, latestId int64) {
 	return orderService.GetTotalPageCountByUserId(userId, orderStatus)
 }
@@ -133,11 +181,9 @@ func GetOrderPage(pageInfo page.PageInfo, userId int, orderStatus string) (order
 
 func GetOrderDetail(productId int, userId int) (order orderService.ProductOrderDetailDTO, err error) {
 	order, err = orderService.GetOrderDetail(productId, userId)
-	productInfo, err := productService.GetMerchantProductById(order.ProductID)
-	if err != nil {
-		return
-	}
-	order.ProductName = productInfo.Title
-	order.ProductPrice = productInfo.Price.String()
 	return
 }
+
+func GetProductOrderByUser(templateUserId int, orderNo string) (order orderService.ProductOrderDTO, err error) {
+	return orderService.GetOrderByUser(templateUserId, orderNo)
+}

+ 41 - 0
service/payment/payment_service.go

@@ -0,0 +1,41 @@
+package payment
+
+import (
+	"eta/eta_mini_ht_api/common/component/cache"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/redis"
+	orderService "eta/eta_mini_ht_api/domian/order"
+)
+
+var (
+	redisUtils = cache.GetInstance()
+)
+
+func CreatePaymentOrder(userId, templateUserId int, OrderNo string, tradeNo string) (err error) {
+	//先获取当前商品订单的支付记录
+	tradeOrderList, err := orderService.GetUnFailedTradeFlowByProductOrder(OrderNo)
+	if len(tradeOrderList) > 0 {
+		for _, tradeOrder := range tradeOrderList {
+			if tradeOrder.PaymentStatus == "done" {
+				logger.Error("当前商品订单已支付,无法再次发起支付")
+				return exception.New(exception.PaymentDoneError)
+			}
+			if tradeOrder.PaymentStatus == "pending" {
+				logger.Error("当前商品订单正在支付,无法再次发起支付")
+				return exception.New(exception.PaymentProcessingError)
+			}
+		}
+	}
+	merchantId := redisUtils.GetString(redis.GenerateMerchantKey())
+	if merchantId == "" {
+		logger.Error("商户配置信息不存在,无法创建支付订单")
+		err = exception.New(exception.MerchantInfoNotConfig)
+		return
+	}
+	return orderService.CreateTradeOrder(userId, templateUserId, OrderNo, tradeNo, merchantId)
+}
+
+func GenerateProductOrderNo() string {
+	return orderService.GenerateTradeOrderNo()
+}