panghu 7 bulan lalu
induk
melakukan
f9d2ba64c3

+ 29 - 1
api/shop_order.go

@@ -9,6 +9,7 @@ import (
 	"go_zh/pkg/validator"
 	"go_zh/request/api"
 	"go_zh/server"
+	"go_zh/server/chinaums"
 )
 
 type ShopOrderController struct {
@@ -16,6 +17,7 @@ type ShopOrderController struct {
 }
 
 func (shopOrder ShopOrderController) GetOrderSn(c *gin.Context) {
+
 	mId := c.GetInt("mId")
 
 	//数据验证
@@ -117,5 +119,31 @@ func (shopOrder ShopOrderController) GetOrderSn(c *gin.Context) {
 		shopOrder.responseError(c, 400, "订单创建失败")
 		return
 	}
-	shopOrder.responseSuccess(c, shopOrderModel.ID)
+
+	chinaumsServer := chinaums.GetOrderSnServerInstance()
+	var payUrl string
+	if data.PayType == 1 { //微信支付
+		//查询当前用户openid
+		memberOpenIdModel := model.XMemberOpenID{}
+		var memberOpenIdInfo = model.XMemberOpenID{}
+		memberOpenIdInfo, err = memberOpenIdModel.GetMemberOpenInfo(mId, 1)
+		if err != nil {
+			shopOrder.responseError(c, 409, "会员信息错误")
+			return
+		}
+		if len(memberOpenIdInfo.Openid) <= 0 {
+			shopOrder.responseError(c, 409, "会员信息错误")
+			return
+		}
+		memberOpenId := memberOpenIdInfo.Openid
+		if isBank == 1 {
+			payUrl = chinaumsServer.ShopOrderWxH5Pay(shopMchidInfo, shopOrderModel.RsSn, shopOrderModel.PayMoney, "", memberOpenId)
+		}
+
+	} else { //支付宝支付
+		if isBank == 1 {
+			payUrl = chinaumsServer.ShopOrderAliH5Pay(shopMchidInfo, shopOrderModel.RsSn, shopOrderModel.PayMoney, "")
+		}
+	}
+	shopOrder.responseSuccess(c, payUrl)
 }

+ 0 - 2
main.go

@@ -44,8 +44,6 @@ func main() {
 	engine.Use(middleware.GinLoggerMiddleware())   //日志中间件
 	engine.Use(middleware.GinRecoveryMiddleware()) //recover中间件
 
-	engine.Use(middleware.ApiMiddleware()) //api请求中间件
-
 	// 设置路由
 	router.SetupRoutes(engine)
 

+ 38 - 0
model/x_member_open_ids.gen.go

@@ -0,0 +1,38 @@
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+
+package model
+
+import (
+	"go_zh/config"
+	"time"
+)
+
+const TableNameXMemberOpenID = "x_member_open_ids"
+
+// XMemberOpenID mapped from table <x_member_open_ids>
+type XMemberOpenID struct {
+	ID        int     `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
+	MID       int     `gorm:"column:m_id;not null;comment:会员ID" json:"m_id"`                            // 会员ID
+	Openid    string    `gorm:"column:openid;not null;comment:公众号id" json:"openid"`                       // 公众号id
+	Unopenid  string    `gorm:"column:unopenid;not null;comment:公众号唯一ID" json:"unopenid"`                 // 公众号唯一ID
+	Type      int     `gorm:"column:type;not null;default:1;comment:平台类型:1:微信,2:小程序,3:支付宝" json:"type"` // 平台类型:1:微信,2:小程序,3:支付宝
+	CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
+	UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
+}
+
+// TableName XMemberOpenID's table name
+func (*XMemberOpenID) TableName() string {
+	return TableNameXMemberOpenID
+}
+
+//获取商户银联信息
+func (*XMemberOpenID)  GetMemberOpenInfo(mId int,openType int) (XMemberOpenID, error)  {
+	info := XMemberOpenID{}
+	model := config.Mdb.Model(info).Where("m_id = ? AND type = ?",mId,openType)
+	if err := model.Find(&info).Error; err != nil {
+		return info,err
+	}
+	return info, nil
+}

+ 18 - 19
pkg/utils/utils.go

@@ -1,38 +1,24 @@
 package utils
 
 import (
+	"crypto/md5"
 	"crypto/rand"
+	"fmt"
 	"math/big"
+	"net/url"
 	"regexp"
 	"strconv"
-	"sync"
 )
 
-// Utils 工具单例
-type Utils struct{}
-
-var (
-	instance *Utils
-	once     sync.Once
-)
-
-// GetUtils  获取单例实例
-func GetUtils() *Utils {
-	once.Do(func() {
-		instance = &Utils{}
-	})
-	return instance
-}
-
 // VerifyPhone 验证手机号是否正确
-func (u *Utils) VerifyPhone(phone string) bool {
+func VerifyPhone(phone string) bool {
 	// 示例:中国大陆手机号(1开头,11位)
 	reg := regexp.MustCompile(`^1[3-9]\d{9}$`)
 	return reg.MatchString(phone)
 }
 
 // GetFourRandomDigits 获取4位随机数字
-func (u *Utils) GetFourRandomDigits() string {
+func GetFourRandomDigits() string {
 
 	// 生成 [0, 8999] 的安全随机数
 	n, err := rand.Int(rand.Reader, big.NewInt(9000))
@@ -44,3 +30,16 @@ func (u *Utils) GetFourRandomDigits() string {
 	randomNum := n.Int64() + 1000
 	return strconv.FormatInt(randomNum, 10)
 }
+
+// Md5Sign 使用MD5加密
+func Md5Sign(str string) string {
+	data := []byte(str) //切片
+	has := md5.Sum(data)
+	md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
+	return md5str
+}
+
+// UrlEncode
+func UrlEncode(str string) string {
+	return url.QueryEscape(str)
+}

+ 1 - 1
request/api/shop_order.go

@@ -3,6 +3,6 @@ package api
 type CreateOrderRequest struct {
 	ShopId        int    `binding:"required,gt=0" form:"shop_id" json:"shop_id" label:"商家id"`
 	PayType       int    `binding:"required,gt=0,lt=3" form:"pay_type" json:"pay_type" label:"支付方式"`
-	PayMoney      string `binding:"required,gt=0" form:"pay_money" json:"pay_money" label:"支付金额"`
+	PayMoney      string `binding:"required,gt=0,lte=1000000" form:"pay_money" json:"pay_money" label:"支付金额"`
 	PayCodeSource int    `binding:"omitempty" form:"pay_code_source" json:"pay_code_source" label:"收款码来源"`
 }

+ 2 - 0
router/api_ceshi.go

@@ -3,11 +3,13 @@ package router
 import (
 	"github.com/gin-gonic/gin"
 	"go_zh/api"
+	"go_zh/middleware"
 )
 
 func init() {
 	RegisterRoute(func(engine *gin.Engine) {
 		v1 := engine.Group("/api")
+		v1.Use(middleware.ApiMiddleware())
 		// 用户路由组
 		ceshiGroup := v1.Group("/ceshi")
 		{

+ 2 - 0
router/api_shop_order.go

@@ -3,11 +3,13 @@ package router
 import (
 	"github.com/gin-gonic/gin"
 	"go_zh/api"
+	"go_zh/middleware"
 )
 
 func init() {
 	RegisterRoute(func(engine *gin.Engine) {
 		v1 := engine.Group("/api")
+		v1.Use(middleware.ApiMiddleware())
 		// 商家订单路由组
 		shopOrderGroup := v1.Group("/shopOrder")
 		{

+ 165 - 0
server/chinaums/chinaumsServer.go

@@ -0,0 +1,165 @@
+package chinaums
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/spf13/viper"
+	"go_zh/model"
+	"go_zh/pkg/utils"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+)
+
+type ChinaumsServer struct {
+	appId    string
+	appKey   string
+	appletId string //小程序appid
+	wxAppId  string //微信公众号appid
+	signKey  string //解密使用密文
+}
+
+var (
+	chinaumsServerInstance *ChinaumsServer
+	chinaumsServerOnce     sync.Once
+)
+
+// GetOrderSnServerInstance 获取单例实例
+func GetOrderSnServerInstance() *ChinaumsServer {
+	chinaumsServerOnce.Do(func() {
+		chinaumsServerInstance = &ChinaumsServer{
+			appId:    viper.GetString("chinaums.appid"),
+			appKey:   viper.GetString("chinaums.appkey"),
+			appletId: viper.GetString("chinaums.appletid"),
+			wxAppId:  viper.GetString("chinaums.wxappid"),
+			signKey:  viper.GetString("chinaums.signkey"),
+		}
+	})
+	return chinaumsServerInstance
+}
+
+// ShopOrderWxH5Pay 微信公众号店铺收款码支付
+func (cs *ChinaumsServer) ShopOrderWxH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string, wxOpenid string) string {
+
+	url := "https://api-mop.chinaums.com/v1/netpay/webpay/pay"
+
+	result := make(map[string]interface{})
+	result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05")
+	result["mid"] = shopMchInfo.SubMchid
+	result["tid"] = shopMchInfo.Tid
+	result["instMid"] = "YUEDANDEFAULT" // 业务类型 YUEDANDEFAULT
+	result["merOrderId"] = orderSn
+	result["notifyUrl"] = notifyUrl
+	result["returnUrl"] = "https://shoppay.jhcs888.com/#/?order_sn=" + orderSn // 网页跳转地址
+	result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart()
+
+	result["subAppId"] = cs.wxAppId // 微信必填
+	result["subOpenId"] = wxOpenid
+
+	body, _ := json.Marshal(result)
+	strUrl := cs.curlget(url, orderSn, string(body))
+	return strUrl
+}
+
+// ShopOrderAliH5Pay 银联发起支付宝H5支付
+func (cs *ChinaumsServer) ShopOrderAliH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string) string {
+
+	url := "https://api-mop.chinaums.com/v1/netpay/trade/h5-pay"
+
+	result := make(map[string]interface{})
+	result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05")
+	result["mid"] = shopMchInfo.SubMchid
+	result["tid"] = shopMchInfo.Tid
+	result["merOrderId"] = orderSn
+	result["instMid"] = "H5DEFAULT"
+	result["notifyUrl"] = notifyUrl
+	result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart()
+
+	body, _ := json.Marshal(result)
+	strUrl := cs.curlget(url, orderSn, string(body))
+	return strUrl
+}
+
+// curlget 拼接请求地址
+func (cs *ChinaumsServer) curlget(url, orderSn, body string) string {
+	timestamp := time.Now().Format("20060102150405")
+	nonce := utils.Md5Sign(orderSn)
+	signature := utils.UrlEncode(cs.getSignature(timestamp, nonce, body))
+	reqUrl := fmt.Sprintf("%s?authorization=OPEN-FORM-PARAM&appId=%s&timestamp=%s&nonce=%s&content=%s&signature=%s", url, cs.appId, timestamp, nonce, utils.UrlEncode(body), signature)
+	return reqUrl
+}
+
+// buildSignStr 生成签名原始字符串,规则如下:
+// 1. 排除键名为 "sign" 的项;
+// 2. 排除值为 nil 的项;
+// 3. 剩余参数按键名升序排序;
+// 4. 值如果是 map 或 slice,则进行 JSON 编码;否则直接转换为字符串;
+// 5. 拼接成 key=value 形式,用 & 连接,末尾不加 &。
+func buildSignStr(params map[string]interface{}) string {
+	// 收集所有非 "sign" 且值不为 nil 的键
+	keys := make([]string, 0, len(params))
+	for k, v := range params {
+		if k == "sign" || v == nil {
+			continue
+		}
+		keys = append(keys, k)
+	}
+
+	// 键名排序
+	sort.Strings(keys)
+
+	var builder strings.Builder
+	for i, key := range keys {
+		val := params[key]
+
+		// 将值转换为字符串
+		var valStr string
+		switch v := val.(type) {
+		case map[string]interface{}: // 关联数组 -> JSON 对象
+			b, _ := json.Marshal(v)
+			valStr = string(b)
+		case []interface{}: // 索引数组 -> JSON 数组
+			b, _ := json.Marshal(v)
+			valStr = string(b)
+		default:
+			// 基本类型(string, int, float, bool 等)使用默认字符串表示
+			valStr = fmt.Sprint(v)
+		}
+
+		builder.WriteString(key)
+		builder.WriteString("=")
+		builder.WriteString(valStr)
+
+		// 除最后一个元素外,添加 & 分隔符
+		if i < len(keys)-1 {
+			builder.WriteString("&")
+		}
+	}
+
+	return builder.String()
+}
+
+// getSignature 生成签名,
+// 参数 timestamp, nonce, body 均为字符串类型
+func (cs *ChinaumsServer) getSignature(timestamp, nonce, body string) string {
+	// 1. 计算 body 的 SHA256 哈希(原始二进制)并转换为十六进制字符串
+	bodyHash := sha256.Sum256([]byte(body))
+	bodyHex := hex.EncodeToString(bodyHash[:])
+
+	// 2. 拼接 appid + timestamp + nonce + bodyHex
+	message := cs.appId + timestamp + nonce + bodyHex
+
+	// 3. 使用 HMAC-SHA256 对消息进行签名,密钥为 appkey
+	h := hmac.New(sha256.New, []byte(cs.appKey))
+	h.Write([]byte(message))
+	signature := h.Sum(nil) // 原始二进制签名
+
+	// 4. 对签名结果进行 Base64 编码
+	return base64.StdEncoding.EncodeToString(signature)
+}

+ 6 - 6
server/orderSnServer.go

@@ -6,7 +6,7 @@ import (
 	"time"
 )
 
-type orderSnServer struct {
+type OrderSnServer struct {
 	machineID int
 	lastTime  int64 // 上次时间(毫秒)
 	sequence  int64
@@ -14,14 +14,14 @@ type orderSnServer struct {
 }
 
 var (
-	orderSnServerInstance *orderSnServer
+	orderSnServerInstance *OrderSnServer
 	orderSnServerOnce     sync.Once
 )
 
 // GetOrderSnServerInstance 获取单例实例
-func GetOrderSnServerInstance(machineID int) *orderSnServer {
+func GetOrderSnServerInstance(machineID int) *OrderSnServer {
 	orderSnServerOnce.Do(func() {
-		orderSnServerInstance = &orderSnServer{
+		orderSnServerInstance = &OrderSnServer{
 			machineID: machineID,
 			lastTime:  time.Now().UnixMilli(),
 			sequence:  0,
@@ -30,7 +30,7 @@ func GetOrderSnServerInstance(machineID int) *orderSnServer {
 	return orderSnServerInstance
 }
 
-func (g *orderSnServer) Generate() string {
+func (g *OrderSnServer) Generate() string {
 	g.mu.Lock()
 	defer g.mu.Unlock()
 
@@ -55,7 +55,7 @@ func (g *orderSnServer) Generate() string {
 }
 
 // GenerateString 生成字符串格式订单号
-func (g *orderSnServer) GenerateString(prefix string) string {
+func (g *OrderSnServer) GenerateString(prefix string) string {
 	// 调用无锁Generate获取ID
 	id := g.Generate()
 

+ 6 - 6
server/redisServer.go

@@ -8,27 +8,27 @@ import (
 	"time"
 )
 
-type redisServer struct {
+type RedisServer struct {
 	rdb *redis.Client
 }
 
 var (
-	redisInstance *redisServer
+	redisInstance *RedisServer
 	redisOnce     sync.Once
 )
 var ctx = context.Background()
 
 // GetRedisServerInstance 获取单例实例
-func GetRedisServerInstance() *redisServer {
+func GetRedisServerInstance() *RedisServer {
 	redisOnce.Do(func() {
-		redisInstance = &redisServer{
+		redisInstance = &RedisServer{
 			rdb: config.Rdb,
 		}
 	})
 	return redisInstance
 }
 
-func (r *redisServer) SetStr(key string, val string, timeout int) bool {
+func (r *RedisServer) SetStr(key string, val string, timeout int) bool {
 	err := r.rdb.Set(ctx, key, val, time.Duration(timeout)*time.Second).Err()
 	if err != nil {
 		return false
@@ -36,7 +36,7 @@ func (r *redisServer) SetStr(key string, val string, timeout int) bool {
 	return true
 }
 
-func (r *redisServer) GetStr(key string) string {
+func (r *RedisServer) GetStr(key string) string {
 
 	if key == "" {
 		return ""