Java程序员_编程开发学习笔记_网站安全运维教程_渗透技术教程

Gin框架对接彩虹易支付接口完整实现方案

阿贵
7月24日发布 /正在检测是否收录...
温馨提示:
本文最后更新于2025年07月24日,已超过8天没有更新,若内容或图片失效,请留言反馈。

Gin框架对接彩虹易支付接口完整实现方案

一、基础配置

首先创建支付配置结构体,用于存储商户信息:

type PayConfig struct {
    Pid       int    // 商户ID
    Key       string // 商户密钥
    NotifyUrl string // 异步通知地址
    ReturnUrl string // 跳转通知地址
    ApiUrl    string // 接口地址
}

go.jpg

二、支付请求实现

1. 页面跳转支付实现

func PageJumpPay(c *gin.Context) {
    var req struct {
        PayType string  `json:"pay_type"` // alipay/wxpay/qqpay
        Amount  float64 `json:"amount" binding:"required"`
        OrderNo string  `json:"order_no" binding:"required"`
        Goods   string  `json:"goods" binding:"required"`
    }
    
    if err := c.ShouldBind(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    params := map[string]interface{}{
        "pid":         config.PayCfg.Pid,
        "type":        req.PayType,
        "out_trade_no": req.OrderNo,
        "notify_url":  config.PayCfg.NotifyUrl,
        "return_url":  config.PayCfg.ReturnUrl,
        "name":        req.Goods,
        "money":       fmt.Sprintf("%.2f", req.Amount),
    }
    
    sign := GenerateSign(params)
    params["sign"] = sign
    params["sign_type"] = "MD5"
    
    // 构建form表单自动提交
    html := `<html><body><form id="payForm" action="https://pay.javait.cn/submit.php" method="post">`
    for k, v := range params {
        html += fmt.Sprintf(`<input type="hidden" name="%s" value="%v" />`, k, v)
    }
    html += `</form><script>document.getElementById('payForm').submit();</script></body></html>`
    
    c.Data(200, "text/html; charset=utf-8", []byte(html))
}

2. API接口支付实现

func ApiPay(c *gin.Context) {
    var req struct {
        PayType string  `json:"pay_type" binding:"required"`
        Amount  float64 `json:"amount" binding:"required"`
        OrderNo string  `json:"order_no" binding:"required"`
        Goods   string  `json:"goods" binding:"required"`
        ClientIP string `json:"client_ip" binding:"required"`
        Device  string  `json:"device"`
    }
    
    if err := c.ShouldBind(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    params := map[string]interface{}{
        "pid":         config.PayCfg.Pid,
        "type":        req.PayType,
        "out_trade_no": req.OrderNo,
        "notify_url":  config.PayCfg.NotifyUrl,
        "name":        req.Goods,
        "money":       fmt.Sprintf("%.2f", req.Amount),
        "clientip":    req.ClientIP,
        "device":      req.Device,
    }
    
    sign := GenerateSign(params)
    params["sign"] = sign
    params["sign_type"] = "MD5"
    
    client := resty.New()
    resp, err := client.R().
        SetHeader("Content-Type", "application/x-www-form-urlencoded").
        SetFormData(convertParams(params)).
        Post("https://pay.javait.cn/mapi.php")
    
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    var result map[string]interface{}
    if err := json.Unmarshal(resp.Body(), &result); err != nil {
        c.JSON(500, gin.H{"error": "parse response error"})
        return
    }
    
    // 验证返回签名
    if !VerifyResponseSign(result) {
        c.JSON(500, gin.H{"error": "sign verify failed"})
        return
    }
    
    c.JSON(200, result)
}

三、签名与验证实现

1. 签名生成函数

func GenerateSign(params map[string]interface{}) string {
    // 过滤空值和签名相关字段
    filtered := make(map[string]string)
    for k, v := range params {
        if k == "sign" || k == "sign_type" {
            continue
        }
        if val, ok := v.(string); ok && val == "" {
            continue
        }
        filtered[k] = fmt.Sprintf("%v", v)
    }
    
    // 按key排序
    keys := make([]string, 0, len(filtered))
    for k := range filtered {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    // 拼接字符串
    var signStr string
    for i, k := range keys {
        if i > 0 {
            signStr += "&"
        }
        signStr += k + "=" + filtered[k]
    }
    signStr += config.PayCfg.Key
    
    // MD5加密
    h := md5.New()
    h.Write([]byte(signStr))
    return hex.EncodeToString(h.Sum(nil))
}

2. 响应签名验证

func VerifyResponseSign(data map[string]interface{}) bool {
    sign, ok := data["sign"].(string)
    if !ok {
        return false
    }
    
    // 复制一份数据并移除sign字段
    params := make(map[string]interface{})
    for k, v := range data {
        if k != "sign" {
            params[k] = v
        }
    }
    
    // 重新生成签名
    calculatedSign := GenerateSign(params)
    return calculatedSign == sign
}

四、支付结果通知处理

1. 异步通知处理

func NotifyHandler(c *gin.Context) {
    params := make(map[string]interface{})
    if err := c.ShouldBind(¶ms); err != nil {
        c.String(400, "fail")
        return
    }
    
    // 验证签名
    if !VerifyResponseSign(params) {
        c.String(400, "fail")
        return
    }
    
    // 验证支付状态
    status, ok := params["trade_status"].(string)
    if !ok || status != "TRADE_SUCCESS" {
        c.String(400, "fail")
        return
    }
    
    // 处理业务逻辑
    orderNo := params["out_trade_no"].(string)
    amount := params["money"].(string)
    // TODO: 更新订单状态
    
    c.String(200, "success")
}

2. 跳转通知处理

func ReturnHandler(c *gin.Context) {
    params := make(map[string]interface{})
    if err := c.ShouldBind(¶ms); err != nil {
        c.Redirect(302, "/error?msg=参数错误")
        return
    }
    
    // 验证签名
    if !VerifyResponseSign(params) {
        c.Redirect(302, "/error?msg=签名验证失败")
        return
    }
    
    // 验证支付状态
    status, ok := params["trade_status"].(string)
    if !ok || status != "TRADE_SUCCESS" {
        c.Redirect(302, "/error?msg=支付未成功")
        return
    }
    
    // 跳转到支付成功页面
    orderNo := params["out_trade_no"].(string)
    c.Redirect(302, "/pay/success?order_no="+orderNo)
}

五、辅助函数

// 转换参数为字符串map
func convertParams(params map[string]interface{}) map[string]string {
    result := make(map[string]string)
    for k, v := range params {
        result[k] = fmt.Sprintf("%v", v)
    }
    return result
}

// 获取客户端IP
func GetClientIP(c *gin.Context) string {
    ip := c.Request.Header.Get("X-Forwarded-For")
    if ip == "" {
        ip = c.Request.RemoteAddr
    }
    return strings.Split(ip, ":")
}

六、路由配置

func SetupPayRoutes(r *gin.Engine) {
    payGroup := r.Group("/pay")
    {
        payGroup.POST("/page", PageJumpPay)    // 页面跳转支付
        payGroup.POST("/api", ApiPay)         // API接口支付
        payGroup.POST("/notify", NotifyHandler) // 异步通知
        payGroup.GET("/return", ReturnHandler)  // 跳转通知
    }
}

七、使用示例

1. 发起页面跳转支付

// 前端调用示例
POST /pay/page
Content-Type: application/json

{
    "pay_type": "alipay",
    "amount": 100.00,
    "order_no": "ORDER123456",
    "goods": "VIP会员"
}

2. 发起API支付

// 前端调用示例
POST /pay/api
Content-Type: application/json

{
    "pay_type": "wxpay",
    "amount": 100.00,
    "order_no": "ORDER123456",
    "goods": "VIP会员",
    "client_ip": "192.168.1.100",
    "device": "mobile"
}

3. 处理支付结果

支付平台会异步通知到配置的notify_url,并同步跳转到return_url,这两个URL分别对应我们实现的/pay/notify/pay/return接口。

八、注意事项

  1. 安全性:确保商户密钥(Key)妥善保管,不要硬编码在代码中
  2. 幂等性:支付结果通知处理要实现幂等性,防止重复处理
  3. 日志记录:记录所有支付请求和通知的原始数据,便于排查问题
  4. 金额验证:处理通知时要验证金额与订单金额是否一致
  5. 超时设置:HTTP请求设置合理的超时时间
  6. 异常处理:做好各种异常情况的处理,如网络超时、签名错误等

以上实现完整覆盖了支付接口对接的所有环节,包括发起支付、签名验证、结果通知处理等关键功能,可以直接集成到您的Gin项目中。

喜欢就支持一下吧
点赞 1 分享 收藏
评论 抢沙发
OωO
取消 登录评论