- 重新设计支付页面UI,采用毛玻璃效果和动态背景- 更新支付接口调用方式,从表单提交改为JSON请求 -优化支付签名验证逻辑,统一错误处理方式 - 修改京东支付接口参数和返回结构体 - 移除OpenTelemetry追踪相关代码 - 添加支付倒计时功能和本地存储支持 - 优化支付按钮加载状态和交互反馈 - 调整HTML结构和CSS样式,提升用户体验
398 lines
12 KiB
Go
398 lines
12 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"html/template"
|
||
"net/url"
|
||
"os"
|
||
"shop/internal/config"
|
||
"shop/internal/models"
|
||
"shop/internal/models/merchant_deploy"
|
||
"shop/internal/schema/request"
|
||
"shop/internal/schema/response"
|
||
"shop/internal/service"
|
||
"shop/internal/traceRouter"
|
||
"shop/internal/utils"
|
||
"shop/internal/utils/client"
|
||
"slices"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/beego/beego/v2/server/web"
|
||
"github.com/mitchellh/mapstructure"
|
||
"github.com/panjf2000/ants/v2"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
var p, _ = ants.NewPool(10000)
|
||
|
||
type HomeAction struct {
|
||
web.Controller
|
||
}
|
||
|
||
func (c *HomeAction) OrderConfirm() {
|
||
c.Data["amount"] = c.GetString("amount")
|
||
c.Data["orderId"] = c.GetString("orderId")
|
||
c.Data["returnUrl"] = c.GetString("returnUrl")
|
||
c.Data["payKey"] = c.GetString("payKey")
|
||
c.TplName = "order_confirm.html"
|
||
}
|
||
|
||
// ShowHome /*加载首页及数据*/
|
||
func (c *HomeAction) ShowHome() {
|
||
ctx, span := traceRouter.CreateSpan(c.Ctx.Request.Context(), "span", "首页")
|
||
defer span.End()
|
||
siteName, _ := web.AppConfig.String("siteName")
|
||
returnUrl := c.GetString("returnUrl")
|
||
productName := "支付功能(非商品消费)"
|
||
|
||
sign := c.GetString("sign")
|
||
if sign == "" {
|
||
flash := web.NewFlash()
|
||
flash.Error("交易签名不能为空")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
m := models.OrderParams{}
|
||
err := m.Decrypt(ctx, sign)
|
||
if err != nil {
|
||
traceRouter.Logger.WithContext(ctx).Error("交易签名解密错误!", zap.String("sign", sign), zap.String("error", err.Error()))
|
||
flash := web.NewFlash()
|
||
flash.Error("交易签名错误,请检查签名是否正确!")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
if m.PayKey == "" {
|
||
flash := web.NewFlash()
|
||
traceRouter.Logger.WithContext(ctx).Error("不存在paykey!", zap.String("sign", sign))
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
merchantInfo := service.GetMerchantByPasskey(ctx, m.PayKey)
|
||
if merchantInfo.Id == 0 {
|
||
flash := web.NewFlash()
|
||
flash.Error("商户不存在,请联系客服")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
c.Data["sign"] = sign
|
||
queryData := request.QueryOrder{
|
||
Appkey: m.PayKey,
|
||
OrderNo: m.OrderNo,
|
||
Timestamp: time.Now().Unix(),
|
||
}
|
||
queryData.Sign = utils.GetMD5SignMF(queryData.ToStrMap(), merchantInfo.MerchantSecret)
|
||
//判断订单状态
|
||
if resp, err2 := client.PostWithFormData(ctx, config.GetMerchantOrder(), map[string]string{}, queryData.ToMap()); err2 == nil {
|
||
respData := response.MerchantQueryResp{}
|
||
_ = json.Unmarshal([]byte(resp), &respData)
|
||
switch respData.Data.Status {
|
||
case "success":
|
||
str := "/ok.html" + "?orderId=" + respData.Data.OrderNo + "&amount=" + respData.Data.Amount +
|
||
"&returnUrl=" + returnUrl
|
||
c.Ctx.Output.Header("Location", str)
|
||
c.Redirect("/ok.html", 302)
|
||
return
|
||
case "fail":
|
||
str := "/error.html" + "?orderId=" + respData.Data.OrderNo + "&amount=" + respData.Data.Amount +
|
||
"&returnUrl=" + returnUrl + "&errorMsg=" + respData.Data.CardReturnData
|
||
c.Ctx.Output.Header("Location", str)
|
||
c.Redirect(str, 302)
|
||
case "wait":
|
||
str := "/order-confirm.html" + "?orderId=" + respData.Data.OrderNo + "&returnUrl=" + returnUrl +
|
||
"&payKey=" + m.PayKey + "&amount=" + respData.Data.Amount
|
||
c.Ctx.Output.Header("Location", str)
|
||
c.Redirect(str, 302)
|
||
}
|
||
}
|
||
resp, err := client.Get(ctx, config.GetMMValue(), map[string]string{
|
||
"productCode": m.ProductCode,
|
||
"showMMValue": fmt.Sprintf("%f", m.ShowMMValue),
|
||
"payKey": m.PayKey,
|
||
})
|
||
r, err := client.Unmarshal(resp)
|
||
if err != nil {
|
||
flash := web.NewFlash()
|
||
traceRouter.Logger.WithContext(ctx).Error("获取面额失败!", zap.Any("data", r.Data))
|
||
flash.Error("获取面额失败!")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
}
|
||
if r.Code == -1 {
|
||
flash := web.NewFlash()
|
||
flash.Error("%s", r.Msg)
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
if r.Data == nil {
|
||
flash := web.NewFlash()
|
||
traceRouter.Logger.WithContext(ctx).Error("获取面额失败!", zap.Any("data", r.Data))
|
||
flash.Error("获取价格失败")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
//解析页面详情数据
|
||
var profitMarginList []merchant_deploy.ProfitMargin
|
||
err = utils.MapToStruct(r.Data.([]interface{}), &profitMarginList)
|
||
if err != nil || len(profitMarginList) == 0 {
|
||
flash := web.NewFlash()
|
||
traceRouter.Logger.WithContext(ctx).Error("获取真实价格失败!", zap.Any("data", r.Data))
|
||
flash.Error("获取真实价格失败")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
sort.SliceStable(profitMarginList, func(i, j int) bool {
|
||
return profitMarginList[i].Sort < profitMarginList[j].Sort
|
||
})
|
||
|
||
// 处理linkID
|
||
for i, margin := range profitMarginList {
|
||
|
||
if strings.Contains(string(margin.LinkID), "?") && !margin.IsLinkSingle {
|
||
// 分离出?后面的内容
|
||
linkIDList := strings.Split(string(margin.LinkID), "?")
|
||
profitMarginList[i].LinkID = template.URL(linkIDList[0] + "?" + url.QueryEscape(strings.Join(linkIDList[1:], "?")))
|
||
}
|
||
|
||
profitMarginList[i].Color = "--bs-info"
|
||
|
||
if strings.Contains(margin.PlatformLabel, "淘宝") || strings.Contains(margin.PlatformLabel, "点击跳转购买") {
|
||
profitMarginList[i].Color = "--bs-orange"
|
||
if !margin.IsLinkSingle {
|
||
profitMarginList[i].LinkID = template.URL("tbopen://" + fmt.Sprintf("m.taobao.com/tbopen/index.html?h5Url=https%%3A%%2F%%2Fh5.m.taobao.com%%2Fawp%%2Fcore%%2Fdetail.htm%%3Fid%%3D%s", margin.LinkID))
|
||
}
|
||
}
|
||
|
||
if strings.Contains(margin.PlatformLabel, "拼多多") {
|
||
profitMarginList[i].Color = "--bs-red"
|
||
if !margin.IsLinkSingle {
|
||
profitMarginList[i].LinkID = template.URL(fmt.Sprintf("pinduoduo://com.xunmeng.pinduoduo/https_detail.html?goods_id=%s", margin.LinkID))
|
||
}
|
||
}
|
||
|
||
if strings.Contains(margin.PlatformLabel, "京东") {
|
||
profitMarginList[i].Color = "--bs-red"
|
||
if !margin.IsLinkSingle {
|
||
profitMarginList[i].LinkID = template.URL("openapp.jdmobile://" + fmt.Sprintf("virtual?params={\"des\":\"m\",\"url\":\"https://item.m.jd.com/product/%v.html?ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=CopyURL\",\"category\":\"jump\"}", margin.LinkID))
|
||
}
|
||
}
|
||
|
||
if strings.Contains(margin.PlatformLabel, "抖音") {
|
||
profitMarginList[i].Color = "--bs-black"
|
||
if !margin.IsLinkSingle {
|
||
profitMarginList[i].LinkID = template.URL("snssdk1128://" + fmt.Sprintf("ec_goods_detail?promotion_id=%s", margin.LinkID))
|
||
}
|
||
}
|
||
}
|
||
|
||
requestOrder := request.CreatedOrder{
|
||
PayKey: m.PayKey,
|
||
OrderNo: m.OrderNo,
|
||
OrderPrice: profitMarginList[0].FactLabel,
|
||
OrderPeriod: 24,
|
||
NotifyUrl: m.NotifyUrl,
|
||
Sign: "",
|
||
ProductCode: m.ProductCode,
|
||
Timestamp: time.Now().Unix(),
|
||
}
|
||
|
||
requestOrder.Sign = utils.GetMD5SignMF(requestOrder.ToStrMap(), merchantInfo.MerchantSecret)
|
||
//创建订单
|
||
response, err := client.Post(ctx, service.CreateOrderHost, map[string]string{}, requestOrder.ToMap())
|
||
if err != nil {
|
||
flash := web.NewFlash()
|
||
flash.Error("订单创建失败")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
res, err := client.Unmarshal(response)
|
||
if err != nil {
|
||
flash := web.NewFlash()
|
||
flash.Error("订单创建失败")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
if res.Code != 0 {
|
||
flash := web.NewFlash()
|
||
flash.Error("创建订单失败:%s", res.Msg)
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
}
|
||
|
||
//接口类型转map类型
|
||
createOrderParams := models.CreateOrderResponse{}
|
||
err = mapstructure.Decode(res.Data, &createOrderParams)
|
||
|
||
traceRouter.Logger.WithContext(ctx).Info("创建订单返回参数", zap.Any("data", createOrderParams))
|
||
if err != nil {
|
||
flash := web.NewFlash()
|
||
flash.Error("订单创建失败")
|
||
flash.Store(&c.Controller)
|
||
c.Redirect("/error.html", 302)
|
||
return
|
||
}
|
||
|
||
c.Data["dynamicUrl"] = config.GetPortalUrl()
|
||
c.Data["siteName"] = siteName
|
||
c.Data["pname"] = productName
|
||
c.Data["orderNo"] = m.OrderNo
|
||
c.Data["returnUrl"] = returnUrl
|
||
c.Data["mmValue"] = profitMarginList[0].FactLabel
|
||
c.Data["showMMValue"] = profitMarginList[0].ShowLabel
|
||
c.Data["notifyUrl"] = m.NotifyUrl
|
||
c.Data["productCode"] = m.ProductCode
|
||
c.Data["profitMarginList"] = profitMarginList
|
||
|
||
if slices.Contains([]string{"index-aolai.html", "百礼卡"}, createOrderParams.PaymentName) {
|
||
for i := range profitMarginList {
|
||
profitMarginList[i].Color = "--bs-orange"
|
||
}
|
||
}
|
||
|
||
//获取views目录下所有文件
|
||
views, _ := os.ReadDir("views")
|
||
for _, view := range views {
|
||
if view.IsDir() {
|
||
continue
|
||
}
|
||
if view.Name() == createOrderParams.PaymentName {
|
||
c.TplName = createOrderParams.PaymentName
|
||
return
|
||
}
|
||
}
|
||
c.Data["cardName"] = createOrderParams.PaymentName
|
||
c.TplName = "index-template.html"
|
||
}
|
||
|
||
// Notify /*通知*/
|
||
func (c *HomeAction) Notify() {
|
||
c.Ctx.WriteString("success")
|
||
}
|
||
|
||
// ShowOK /*加载首页及数据*/
|
||
func (c *HomeAction) ShowOK() {
|
||
//取值
|
||
c.Data["orderId"] = c.GetString("orderId")
|
||
c.Data["amount"] = c.GetString("amount")
|
||
c.Data["returnUrl"], _ = web.AppConfig.String("returnUrl")
|
||
c.TplName = "ok.html"
|
||
}
|
||
|
||
func (c *HomeAction) ErrorPage() {
|
||
c.Data["orderId"] = c.GetString("orderId")
|
||
c.Data["amount"] = c.GetString("amount")
|
||
c.Data["errorReason"] = c.GetString("errorMsg")
|
||
c.Data["returnUrl"], _ = web.AppConfig.String("returnUrl")
|
||
c.TplName = "error.html"
|
||
}
|
||
|
||
// QueryOrderStatus 查询订单状态
|
||
func (c *HomeAction) QueryOrderStatus() {
|
||
ctx, span := traceRouter.CreateSpan(c.Ctx.Request.Context(), "span", "查询订单状态")
|
||
defer span.End()
|
||
|
||
orderNo := c.GetString("orderNo")
|
||
payKey := c.GetString("payKey")
|
||
returnUrl := c.GetString("returnUrl")
|
||
|
||
if orderNo == "" || payKey == "" {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 400,
|
||
"msg": "订单编号或支付密钥不能为空",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
merchantInfo := service.GetMerchantByPasskey(ctx, payKey)
|
||
if merchantInfo.Id == 0 {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 400,
|
||
"msg": "商户不存在",
|
||
}
|
||
c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
queryData := request.QueryOrder{
|
||
Appkey: payKey,
|
||
OrderNo: orderNo,
|
||
Timestamp: time.Now().Unix(),
|
||
}
|
||
queryData.Sign = utils.GetMD5SignMF(queryData.ToStrMap(), merchantInfo.MerchantSecret)
|
||
|
||
// 判断订单状态
|
||
if resp, err2 := client.PostWithFormData(ctx, config.GetMerchantOrder(), map[string]string{}, queryData.ToMap()); err2 == nil {
|
||
respData := response.MerchantQueryResp{}
|
||
_ = json.Unmarshal([]byte(resp), &respData)
|
||
|
||
switch respData.Data.Status {
|
||
case "success":
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "支付成功",
|
||
"data": map[string]interface{}{
|
||
"status": "success",
|
||
"orderNo": respData.Data.OrderNo,
|
||
"amount": respData.Data.Amount,
|
||
"returnUrl": returnUrl,
|
||
},
|
||
}
|
||
case "fail":
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "支付失败",
|
||
"data": map[string]interface{}{
|
||
"status": "fail",
|
||
"orderNo": respData.Data.OrderNo,
|
||
"amount": respData.Data.Amount,
|
||
"errorReason": respData.Data.CardReturnData,
|
||
"returnUrl": returnUrl,
|
||
},
|
||
}
|
||
case "wait":
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "等待支付",
|
||
"data": map[string]interface{}{
|
||
"status": "wait",
|
||
},
|
||
}
|
||
default:
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "处理中",
|
||
"data": map[string]interface{}{
|
||
"status": "processing",
|
||
},
|
||
}
|
||
}
|
||
} else {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 500,
|
||
"msg": "查询订单状态失败",
|
||
}
|
||
}
|
||
|
||
c.ServeJSON()
|
||
}
|