Browse Source

海通支付

kobe6258 2 days ago
parent
commit
9fda2368db

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

@@ -64,6 +64,10 @@ type RedisCache struct {
 	redisTemplate redis.UniversalClient
 }
 
+func (r *RedisCache) Keys(pattern string) (keys []string, err error) {
+	keys, err = r.redisTemplate.Keys(context.Background(), pattern).Result()
+	return
+}
 func (r *RedisCache) GetHSet(key string) map[string]string {
 	return r.GetHSetWithContext(context.Background(), key)
 }
@@ -83,6 +87,16 @@ func (r *RedisCache) SetHSetWithContext(ctx context.Context, key string, timeout
 	return r.redisTemplate.Expire(ctx, key, timeout).Err()
 }
 
+// UpdateHSetNoExpire 仅更新 HSet 的值,不更改其过期时间
+func (r *RedisCache) UpdateHSetNoExpire(key string, val ...interface{}) error {
+	return r.UpdateHSetNoExpireWithContext(context.Background(), key, val...)
+}
+
+// UpdateHSetNoExpireWithContext 仅更新 HSet 的值,不更改其过期时间(支持上下文)
+func (r *RedisCache) UpdateHSetNoExpireWithContext(ctx context.Context, key string, val ...interface{}) error {
+	return r.redisTemplate.HSet(ctx, key, val...).Err()
+}
+
 // GetString  获取一个值
 func (r *RedisCache) GetString(key string) string {
 	return r.GetStringWithContext(context.Background(), key)

+ 6 - 0
common/exception/exc_enums.go

@@ -148,12 +148,14 @@ const (
 	GetCustomerRiskInfoFailed
 	GenerateRiskTestTokenFailed
 	SyncAccountStatusError
+	SyncIdInfoError
 )
 const (
 	OrderErrorCode int = iota + 90000
 	IllegalOrderStatus
 	OrderPayTimeoutError
 	PayTradeOrderFailed
+	IllegalWechatToken
 )
 
 const (
@@ -168,6 +170,7 @@ const (
 
 	CreatePaymentOrderFailed
 	PaymentProcessingError
+	GetPaymentTokenFailed
 	PaymentDoneError
 	RefundDealFail
 )
@@ -283,6 +286,7 @@ var ErrorMap = map[int]string{
 	//webhook
 	SyncRiskError:               "同步风险等级失败",
 	SyncAccountStatusError:      "同步用户开户状态失败",
+	SyncIdInfoError:             "更新证件有效期失败",
 	GetCapTokenFailed:           "获取cap token失败",
 	GenerateRiskTestTokenFailed: "生成风险测评token失败",
 	GetCustomerRiskInfoFailed:   "查询客户风险信息失败",
@@ -290,6 +294,7 @@ var ErrorMap = map[int]string{
 	IllegalOrderStatus:   "非法的订单状态",
 	OrderPayTimeoutError: "订单支付超时",
 	PayTradeOrderFailed:  "订单支付失败",
+	IllegalWechatToken:   "无效的token",
 	//product
 	ProductOffSale:   "商品已下架",
 	ProductTypeError: "非法的产品类型",
@@ -298,6 +303,7 @@ var ErrorMap = map[int]string{
 	//支付
 	CreatePaymentOrderFailed: "创建支付订单失败",
 	PaymentProcessingError:   "支付订单处理中",
+	GetPaymentTokenFailed:    "获取微信订单参数失败",
 	PaymentDoneError:         "订单已完成支付",
 	RefundDealFail:           "处理退款应答失败",
 

+ 109 - 0
common/utils/payment/payment_utils.go

@@ -0,0 +1,109 @@
+package payment
+
+import (
+	"encoding/json"
+	"eta/eta_mini_ht_api/common/component/cache"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/utils/redis"
+	"fmt"
+	"github.com/google/uuid"
+	"github.com/mitchellh/mapstructure"
+	"strings"
+	"time"
+)
+
+const (
+	PENDING = "pending"
+	READY   = "ready"
+	USED    = "used"
+	EXPIRED = "expired"
+)
+
+var (
+	rdb = cache.GetInstance()
+)
+
+// PaymentToken Redis存储结构
+type PaymentToken struct {
+	PaymentParams *PaymentParam `json:"payment_params"`
+	ExpireTime    int64         `json:"expire_time"`
+}
+
+// PaymentParam 支付参数结构
+type PaymentParam struct {
+	ProductOrderNo string `json:"product_order_no" mapStruct:"ProductOrderNo"`
+	PayOrderNo     string `json:"pay_order_no" mapStruct:"PayOrderNo"`
+	Package        string `json:"package" mapStruct:"Package"`
+	Timestamp      string `json:"timestamp" mapStruct:"Timestamp"`
+	NonceStr       string `json:"nonce_str" mapStruct:"NonceStr"`
+	PaySign        string `json:"pay_sign" mapStruct:"PaySign"`
+	SignType       string `json:"sign_type" mapStruct:"SignType"`
+	Status         string `json:"status"  mapStruct:"Status"`
+}
+
+// SavePaymentToken Redis操作辅助函数
+func SavePaymentToken(pt *PaymentToken) (token string, err error) {
+	//需要过期所有的对应产品订单的历史支付订单
+	keys, _ := rdb.Keys("payment:token:*")
+	for _, key := range keys {
+		payParam, _ := GetPaymentParam(strings.TrimPrefix(key, "payment:token:"))
+		if payParam.PayOrderNo != "" && payParam.ProductOrderNo != "" && payParam.Status != "" {
+			payParam.Status = EXPIRED
+			jsonData, _ := json.Marshal(pt)
+			_ = rdb.UpdateHSetNoExpire(key, jsonData)
+		}
+	}
+	token = generateWechatOrderToken()
+	paramMap, err := StructToMap(pt.PaymentParams)
+	if err != nil {
+		return
+	}
+	err = rdb.SetHSet(redis.GenerateWechatOrderToken(token), time.Until(time.Unix(pt.ExpireTime, 0)), paramMap)
+	return
+}
+func StructToMap(v interface{}) (mapResult map[string]string, err error) {
+	mapResult = make(map[string]string)
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		Result:  &mapResult,
+		TagName: "mapStruct",
+	})
+	if err != nil {
+		return nil, err
+	}
+	err = decoder.Decode(v)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+func MapToStruct(mapResult map[string]string, v interface{}) (err error) {
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		Result:  v,
+		TagName: "mapStruct",
+	})
+	if err != nil {
+		return
+	}
+	err = decoder.Decode(mapResult)
+	if err != nil {
+		return
+	}
+	return
+}
+func GetPaymentParam(token string) (paymentParam PaymentParam, err error) {
+	val := rdb.GetHSet("payment:token:" + token)
+	err = MapToStruct(val, &paymentParam)
+	if err != nil {
+		logger.Error("生成微信订单token失败,%v", err)
+		return
+	}
+	if paymentParam.ProductOrderNo == "" || paymentParam.PayOrderNo == "" {
+		err = fmt.Errorf("订单信息不存在:" + token)
+	}
+	return
+}
+
+// GenerateWechatOrderToken 生成token
+func generateWechatOrderToken() (token string) {
+	return strings.ReplaceAll(uuid.New().String(), "-", "")
+}

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

@@ -7,6 +7,7 @@ const (
 	PaymentKeyPrefix  = "payment:"
 	LoginTokenPrefix  = "login:token:"
 	CAPAccessTokenKey = "cap:access_token"
+	WechatOrderKey    = "payment:token:"
 )
 
 const (
@@ -37,3 +38,6 @@ func GeneratePaymentKey(orderNo string) string {
 func GenerateMerchantKey() string {
 	return MerchantKey
 }
+func GenerateWechatOrderToken(token string) string {
+	return fmt.Sprintf("%s%s", WechatOrderKey, token)
+}

+ 1 - 1
controllers/order/order_controller.go

@@ -43,7 +43,7 @@ func (o *OrderController) PreviewProductOrder(productId int) {
 	})
 }
 
-// OrderDetail  获取订单详情
+// GetOrderDetail  获取订单详情
 // @Summary  获取订单详情
 // @Description  获取订单详情
 // @Success 200 {object} controllers.BaseResponse

+ 48 - 7
controllers/payment/payment_controller.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"eta/eta_mini_ht_api/common/exception"
 	"eta/eta_mini_ht_api/common/utils/lock"
+	payUtils "eta/eta_mini_ht_api/common/utils/payment"
 	"eta/eta_mini_ht_api/common/utils/redis"
 	"eta/eta_mini_ht_api/controllers"
 	orderDomian "eta/eta_mini_ht_api/domian/order"
@@ -25,6 +26,7 @@ type PayRequest struct {
 type PayResponse struct {
 	ProductOrderNo string `json:"productOrderNo"`
 	TradeOrderNo   string `json:"tradeOrderNo"`
+	PaymentToken   string `json:"paymentToken"`
 }
 
 // PayOrder  支付订单
@@ -95,17 +97,14 @@ func (pc *PaymentController) PayOrder() {
 			if err != nil {
 				pc.FailedResult("支付失败,正式用户信息不存在", result)
 			}
-			var tradeOrderNo string
-			tradeOrderNo, err = facade.PayOrder(officialUser.ID, userInfo.Id, productOrder)
+			var payOrderDTO facade.PayOrderDTO
+			payOrderDTO, 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)
+			pc.SuccessResult("支付订单创建成功", payOrderDTO, result)
 			return
 		} else {
 			pc.FailedResult("商品订单处理中,请稍后再试", result)
@@ -115,6 +114,48 @@ func (pc *PaymentController) PayOrder() {
 	})
 }
 
-//退款
+// PayOrderStatus 获取微信订单状态
+// @Summary 获取微信订单状态
+// @Description 获取微信订单状态
+// @Success 200 {object} controllers.BaseResponse
+// @router /payOrderStatus [get]
+func (pc *PaymentController) PayOrderStatus(token string) {
+	controllers.Wrap(&pc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = pc.InitWrapData("获取微信订单信息失败")
+		if token == "" {
+			pc.FailedResult("token", result)
+			err = exception.New(exception.IllegalWechatToken)
+			return
+		}
+		paymentParams, err := payUtils.GetPaymentParam(token)
+		if err != nil {
+			pc.FailedResult("微信订单信息不存在", result)
+			err = exception.NewWithException(exception.GetPaymentTokenFailed, err.Error())
+			return
+		}
+
+		if paymentParams.Status == payUtils.EXPIRED {
+			pc.FailedResult("订单超时,请重新支付", result)
+			err = exception.New(exception.GetPaymentTokenFailed)
+			return
+		}
+		if paymentParams.Status == payUtils.USED {
+			pc.FailedResult("请勿重复发起微信支付", result)
+			err = exception.New(exception.GetPaymentTokenFailed)
+			return
+		}
+		if paymentParams.Status == payUtils.PENDING {
+			pc.FailedResult("微信支付发起中,请耐心等待", result)
+			err = exception.New(exception.GetPaymentTokenFailed)
+			return
+		}
+		if paymentParams.Status == payUtils.READY {
+			//查询到就绪之后将token状态重置为已用
+			pc.SuccessResult("支付订单创建成功", paymentParams, result)
+			return
+		}
+		return
+	})
+}
 
 //

+ 10 - 10
controllers/web_hook/htfutures_account_controller.go

@@ -235,7 +235,7 @@ func (h *HTFuturesAccountController) SyncCustomerRiskLevel() {
 // @router /v1/syncAccountInfo/ [post]
 func (h *HTFuturesAccountController) SyncCustomerAccountInfo() {
 	controllers.WrapWebhook(&h.WebHookController, func() (result *controllers.WrapData, err error) {
-		result = h.InitWrapData("同步开户信息")
+		result = h.InitWrapData("同步开户信息失败")
 		syncCustomerRiskLevelReq := new(AccountOpenInfoReq)
 		h.GetPostParams(syncCustomerRiskLevelReq)
 		if ThirdRateLimitFilter(syncCustomerRiskLevelReq.IdNo) != 200 {
@@ -319,7 +319,7 @@ func (h *HTFuturesAccountController) SyncCustomerAccountInfo() {
 // @router /v1/syncIDInfo/ [post]
 func (h *HTFuturesAccountController) SyncCustomerIDInfo() {
 	controllers.WrapWebhook(&h.WebHookController, func() (result *controllers.WrapData, err error) {
-		result = h.InitWrapData("证件信息信息")
+		result = h.InitWrapData("证件有效期更新失败")
 		syncCustomerRiskLevelReq := new(IDInfoReq)
 		h.GetPostParams(syncCustomerRiskLevelReq)
 		if ThirdRateLimitFilter(syncCustomerRiskLevelReq.IdNo) != 200 {
@@ -329,12 +329,12 @@ func (h *HTFuturesAccountController) SyncCustomerIDInfo() {
 		}
 
 		if syncCustomerRiskLevelReq.MobileTel == "" {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			h.FailedResult("手机号码不能为空", result)
 			return
 		}
 		if _, ok := idKindMap[syncCustomerRiskLevelReq.IdKind]; !ok {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			validIdKind := make([]string, 0)
 			for _, v := range idKindMap {
 				validIdKind = append(validIdKind, string(v))
@@ -343,24 +343,24 @@ func (h *HTFuturesAccountController) SyncCustomerIDInfo() {
 			return
 		}
 		if syncCustomerRiskLevelReq.IdNo == "" {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			h.FailedResult("证号号码不能为空", result)
 			return
 		}
 		idBeginDate, parseErr := time.Parse(time.DateOnly, syncCustomerRiskLevelReq.IdBeginDate)
 		if parseErr != nil {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			h.FailedResult("身份证有效开始时间不合法["+syncCustomerRiskLevelReq.IdBeginDate+"]", result)
 			return
 		}
 		idEndDate, parseErr := time.Parse(time.DateOnly, syncCustomerRiskLevelReq.IdEndDate)
 		if parseErr != nil {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			h.FailedResult("身份证有效结束时间不合法["+syncCustomerRiskLevelReq.IdEndDate+"]", result)
 			return
 		}
 		if idEndDate.Before(idBeginDate) {
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			h.FailedResult("身份证有效结束时间不合法,开始日期不能大于结束日期", result)
 			return
 		}
@@ -374,10 +374,10 @@ func (h *HTFuturesAccountController) SyncCustomerIDInfo() {
 		if err != nil {
 			logger.ErrorWithTraceId(h.Ctx, err.Error())
 			h.FailedResult(err.Error(), result)
-			err = exception.New(exception.SyncAccountStatusError)
+			err = exception.New(exception.SyncIdInfoError)
 			return
 		}
-		result = h.InitWrapData("同步开户信息成功")
+		result = h.InitWrapData("同步证件有效期信息成功")
 		h.SuccessResult("success", syncCustomerRiskLevelReq, result)
 		return
 	})

+ 2 - 0
domian/order/product_order.go

@@ -32,6 +32,7 @@ type ProductOrderDTO struct {
 	PaymentTimeRemain  int64
 	Status             string
 	StatusCN           string
+	ExpiredTime        time.Time
 	CreatedTime        string
 	UpdatedTime        string
 }
@@ -118,6 +119,7 @@ func ConvertProductOrderDTO(order orderDao.ProductOrder) (orderDTO ProductOrderD
 		TotalAmount:    order.TotalAmount,
 		PaymentWay:     string(order.PaymentWay),
 		Status:         string(order.Status),
+		ExpiredTime:    order.ExpiredTime,
 		StatusCN:       transProductOrderStatusMap[order.Status],
 		CreatedTime:    order.CreatedTime.Format(time.DateTime),
 	}

+ 11 - 0
routers/commentsRouter.go

@@ -153,6 +153,17 @@ func init() {
             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: "PayOrderStatus",
+            Router: `/payOrderStatus`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("token"),
+			),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"],
         beego.ControllerComments{
             Method: "ProductList",

+ 30 - 14
service/facade/ht_trade_service.go

@@ -3,36 +3,52 @@ package facade
 import (
 	"eta/eta_mini_ht_api/api"
 	logger "eta/eta_mini_ht_api/common/component/log"
+	payUtils "eta/eta_mini_ht_api/common/utils/payment"
 	"eta/eta_mini_ht_api/domian/order"
 	"eta/eta_mini_ht_api/service/payment"
+	"time"
 )
 
 const (
-	channelWeChat  = "7"
-	timeoutSeconds = "900"
+	channelWeChat    = "7"
+	timeoutSeconds   = "900"
+	BeforeOrderClose = 1 * time.Second
 )
 
 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,
+type PayOrderDTO struct {
+	ProductOrderNo string
+	TradeOrderNo   string
+	PaymentToken   string
+}
+
+func PayOrder(userId, templateUserId int, productOrder order.ProductOrderDTO) (payOrderDTO PayOrderDTO, err error) {
+	//在redis中生成token,前端通过token获取订单状态
+	expiredTime := productOrder.ExpiredTime.Sub(time.Now().Add(BeforeOrderClose))
+	tradeOrderNo := order.GenerateTradeOrderNo()
+	var paymentParams = &payUtils.PaymentToken{
+		PaymentParams: &payUtils.PaymentParam{
+			ProductOrderNo: productOrder.OrderID,
+			PayOrderNo:     tradeOrderNo,
+			Status:         payUtils.PENDING,
+		},
+		ExpireTime: int64(expiredTime),
+	}
+	token, err := payUtils.SavePaymentToken(paymentParams)
+	if err != nil {
+		logger.Error("创建支付订单失败:%v", err)
+		return
 	}
-	tradeOrderNo, err = htTradeApi.CreatePaymentOrder(req)
-	tradeOrderNo = order.GenerateTradeOrderNo()
 	err = payment.CreatePaymentOrder(userId, templateUserId, productOrder.OrderID, tradeOrderNo)
 	if err != nil {
 		logger.Error("创建支付订单失败:%v", err)
 		return
 	}
+	payOrderDTO.TradeOrderNo = tradeOrderNo
+	payOrderDTO.PaymentToken = token
+	payOrderDTO.ProductOrderNo = productOrder.OrderID
 	return
 }