diff --git a/CODEBUDDY.md b/CODEBUDDY.md new file mode 100644 index 0000000..302a6ea --- /dev/null +++ b/CODEBUDDY.md @@ -0,0 +1,58 @@ +kami_shop + +Development commands + +- Build (Linux amd64): ./build.sh +- Run (dev): go run main.go +- Tests: go test ./... +- Single test file: go test ./internal/service/scan_shop_test.go +- Verbose tests: go test -v ./... +- Docker build: docker build -t kami_shop . -f deploy/Dockerfile +- Docker Compose up: export VERSION=latest && docker-compose -f deploy/docker-compose.yaml up -d + +Project architecture overview + +- Runtime: Go 1.24, Beego v2 +- Entry: main.go initializes OpenTelemetry/logging and starts Beego +- Config: conf/app.local.conf (dev), conf/app.conf (prod); MySQL, Redis, ports, gateway URLs, secrets +- HTTP routing: internal/routers/router.go maps routes to controllers +- Controllers: internal/controllers/ handle payment endpoints (HTML and JSON flows) +- Services: internal/service/ business logic; payment orchestration and external calls +- Models: internal/models/ data structures and DB ops +- Views/static: views/ templates and static assets for payment pages +- Trace/log: internal/traceRouter provides OTEL setup, context propagation, and zap logger +- Deploy: deploy/Dockerfile, docker-compose*.yaml; .drone.yml for CI image build and remote compose deploy + +Key flows + +- Web pay (GET/HTML): /pay.html → PayController.Pay validates params, decrypts sign, checks timeout/keys, builds exValue, calls ScanShopController.Shop, redirects to /order-confirm.html or /error.html +- API pay (form): POST /pay → PayWithJson parses params, VerifyPaySign, service.Pay, JSON response +- API pay v2 (JSON): POST /api/pay → PayV2 JSON body, VerifyPaySign, ScanShopController.Shop, returns redirectUrl for client navigation +- JD native: POST /order/pay/original/jd → PayOriginalJD validates OriginalJdReq, VerifyPaySign, service.PayWithJd, returns wxPay + +Configuration essentials + +- MySQL: dev juhe_pay, prod kami; credentials/hosts in conf/*.conf +- Redis: dev localhost:6379, prod redis:6379 +- Gateways: gateway/partial endpoints in conf/*.conf +- Secrets: [secret] key/iv used for encryption/sign; do not commit real secrets + +Observability/logging + +- traceRouter.InitTracer sets OTLP exporters (traces noop by default), metrics/log exporters, zap logger bridged via otelzap +- Middleware utilities for context propagation and CreateSpan helpers; Logger.WithContext to include trace_id + +CI/CD + +- .drone.yml builds and pushes image to git.oceanpay.cc and runs remote docker compose with BRANCH/DRONE vars + +Local tips + +- Windows binary main.exe present; prefer go run main.go during development +- build.sh compiles static Linux binary for container use + +Important repository rules from CLAUDE.md + +- Use provided dev commands for build/run/test +- Respect architecture boundaries: controllers → services → models; keep validation in controllers, business in services, persistence in models +- Prefer JSON API (/api/pay) for programmatic integrations; HTML endpoints for browser flows diff --git a/internal/controllers/page_controller.go b/internal/controllers/page_controller.go index 59547ad..97ad645 100644 --- a/internal/controllers/page_controller.go +++ b/internal/controllers/page_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "context" "encoding/json" "fmt" "html/template" @@ -24,8 +23,6 @@ import ( "github.com/beego/beego/v2/server/web" "github.com/mitchellh/mapstructure" "github.com/panjf2000/ants/v2" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) @@ -133,7 +130,7 @@ func (c *HomeAction) ShowHome() { } if r.Code == -1 { flash := web.NewFlash() - flash.Error(r.Msg) + flash.Error("%s", r.Msg) flash.Store(&c.Controller) c.Redirect("/error.html", 302) return @@ -236,7 +233,7 @@ func (c *HomeAction) ShowHome() { if res.Code != 0 { flash := web.NewFlash() - flash.Error(fmt.Sprintf("创建订单失败:%s", res.Msg)) + flash.Error("创建订单失败:%s", res.Msg) flash.Store(&c.Controller) c.Redirect("/error.html", 302) } @@ -265,32 +262,6 @@ func (c *HomeAction) ShowHome() { c.Data["productCode"] = m.ProductCode c.Data["profitMarginList"] = profitMarginList - if createOrderParams.PaymentName == "index-wxpay.html" { - traceRouter.Span( - c.Ctx.Request.Context(), "拉单", "微信支付", - trace.WithAttributes(attribute.String("orderNo", m.OrderNo)), - ) - err = p.Submit(func() { - ctx, span = traceRouter.CreateSpan( - context.WithoutCancel(c.Ctx.Request.Context()), "拉单", "微信支付", - trace.WithAttributes(attribute.String("orderNo", m.OrderNo)), - ) - defer span.End() - _, ok, err2 := service.PayWithJd(ctx, c.Ctx.Input.UserAgent(), m.OrderNo, profitMarginList[0].ShowLabel) - if err2 != nil || !ok { - traceRouter.Logger.WithContext(ctx).Error("微信支付拉单错误!", zap.String("orderNo", m.OrderNo), - zap.String("error", err.Error()), - ) - return - } - }) - if err != nil { - traceRouter.Logger.WithContext(c.Ctx.Request.Context()).Error("微信支付拉单错误!", - zap.String("orderNo", m.OrderNo), zap.String("error", err.Error()), - ) - } - } - if slices.Contains([]string{"index-aolai.html", "百礼卡"}, createOrderParams.PaymentName) { for i := range profitMarginList { profitMarginList[i].Color = "--bs-orange" diff --git a/internal/controllers/pay.go b/internal/controllers/pay.go index 296372d..67c286e 100644 --- a/internal/controllers/pay.go +++ b/internal/controllers/pay.go @@ -175,17 +175,25 @@ func (c *PayController) PayWithJson() { sign := strings.TrimSpace(c.GetString("sign")) deviceId := strings.TrimSpace(c.GetString("deviceId")) - m, msg, ok := service.VerifyPaySign(ctx, sign) - if !ok || m == nil { + m, err := service.VerifyPaySign(ctx, sign) + if err != nil { c.Data["json"] = response.CommonResponse{ Code: -1, - Msg: msg, + Msg: err.Error(), + } + _ = c.ServeJSON() + return + } + if m == nil { + c.Data["json"] = response.CommonResponse{ + Code: -1, + Msg: "支付秘钥错误", } _ = c.ServeJSON() return } - ok, _ = service.Pay(ctx, &models.OriginalJdParams{ + ok2, _ := service.Pay(ctx, &models.OriginalJdParams{ OrderId: orderNo, FactMMValue: faceMM, ProductCode: productCode, @@ -195,7 +203,7 @@ func (c *PayController) PayWithJson() { DeviceId: deviceId, NotifyUrl: m.NotifyUrl, }, m, c.Ctx.Input.IP()) - if ok { + if ok2 { c.Data["json"] = response.CommonResponse{ Code: 0, Msg: "成功!", @@ -223,15 +231,15 @@ func (c *PayController) judgeAmount(amount string) bool { // PayOriginalJD /* 支付接口 */ func (c *PayController) PayOriginalJD() { - ctx, span := traceRouter.CreateSpan(c.Ctx.Request.Context(), "支付链接", "京东原生支付") + ctx, span := traceRouter.CreateSpan(c.Ctx.Request.Context(), "PayController", "PayController.ayOriginalJD") defer span.End() input := models.OriginalJdReq{} - err := c.BindForm(&input) + err := c.BindJSON(&input) if err != nil { c.Data["json"] = response.CommonResponse{ Code: -1, - Msg: "解析失败", + Msg: "JSON解析失败", } _ = c.ServeJSON() return @@ -250,11 +258,11 @@ func (c *PayController) PayOriginalJD() { } // 校验sign - m, msg, ok := service.VerifyPaySign(ctx, input.Sign) - if !ok || m == nil { + m, err := service.VerifyPaySign(ctx, input.Sign) + if err != nil || m == nil { c.Data["json"] = response.CommonResponse{ Code: -1, - Msg: msg, + Msg: err.Error(), } _ = c.ServeJSON() return @@ -270,8 +278,8 @@ func (c *PayController) PayOriginalJD() { return } - wxPay, ok, err := service.PayWithJd(ctx, c.Ctx.Input.UserAgent(), m.OrderNo, faceMMValue) - if err != nil || !ok { + wxPay, err := service.PayWithJd(ctx, m.OrderNo, "cTrip", faceMMValue) + if err != nil { traceRouter.Logger.WithContext(ctx).Error("微信支付拉单错误!", zap.String("orderNo", m.OrderNo), zap.String("error", err.Error()), ) @@ -325,11 +333,11 @@ func (c *PayController) PayV2() { } // 解析签名 - m, msg, ok := service.VerifyPaySign(ctx, req.Sign) - if !ok || m == nil { + m, err := service.VerifyPaySign(ctx, req.Sign) + if err != nil || m == nil { c.Data["json"] = response.CommonResponse{ Code: -1, - Msg: msg, + Msg: err.Error(), } _ = c.ServeJSON() return diff --git a/internal/service/pay.go b/internal/service/pay.go index 76bbd11..6f3d4ea 100644 --- a/internal/service/pay.go +++ b/internal/service/pay.go @@ -13,33 +13,35 @@ import ( "go.uber.org/zap" ) -// 判断sign是否正确 -func VerifyPaySign(ctx context.Context, sign string) (*models.OrderParams, string, bool) { +// VerifyPaySign 判断sign是否正确 +func VerifyPaySign(ctx context.Context, sign string) (*models.OrderParams, error) { m := models.OrderParams{} - m.Decrypt(ctx, sign) + if err := m.Decrypt(ctx, sign); err != nil { + return nil, err + } traceRouter.Logger.WithContext(ctx).Info("订单参数", zap.Any("订单参数", m)) if time.Since(time.Unix(m.GeneratedTime, 0)).Hours() > float64(m.Duration) { - return nil, "订单超时", false + return nil, errors.New("订单超时") } if m.PayKey == "" { - return nil, "支付秘钥错误", false + return nil, errors.New("支付秘钥错误") } if m.NotifyUrl == "" { - return nil, "通知地址为空", false + return nil, errors.New("通知地址为空") } if m.OrderNo == "" { - return nil, "订单号为空", false + return nil, errors.New("订单号为空") } - return &m, "", true + return &m, nil } -// 抽离支付接口 +// Pay 抽离支付接口 func Pay(ctx context.Context, m *models.OriginalJdParams, order *models.OrderParams, ip string) (bool, error) { marshal, err := json.Marshal(map[string]string{ "recoveryType": m.RecoveryType, @@ -75,38 +77,35 @@ func Pay(ctx context.Context, m *models.OriginalJdParams, order *models.OrderPar } // PayWithJd 京东原生支付 -func PayWithJd(ctx context.Context, userAgent string, orderId string, orderAmount float64) (string, bool, error) { - ctx, span := traceRouter.CreateSpan(ctx, "支付链接", "京东请求") +func PayWithJd(ctx context.Context, orderId, category string, orderAmount float64) (string, error) { + ctx, span := traceRouter.CreateSpan(ctx, "PayWithJd", "PayWithJd") defer span.End() - orderResp, err := client.Post(ctx, "http://kami_backend:12401/api/cookieInfo/jd/order/placeOrder", + orderResp, err := client.Post(ctx, "http://kami_backend:12401/api/jd-cookie/order/create", nil, map[string]any{ - "merchantOrderId": orderId, - "orderAmount": orderAmount, - "userAgent": userAgent, + "orderId": orderId, + "amount": orderAmount, + "category": category, }, ) traceRouter.Logger.WithContext(ctx).Info("发送请求", zap.String("请求地址", - "http://kami_backend:12401/api/cookieInfo/jd/order/placeOrder"), zap.Any("请求参数", map[string]any{ + "http://kami_backend:12401/api/jd-cookie/order/create"), zap.Any("请求参数", map[string]any{ "merchantOrderId": orderId, "orderAmount": orderAmount, - "userAgent": userAgent, })) if err != nil { traceRouter.Logger.WithContext(ctx).Error("请求失败", zap.Error(err)) - return "", false, err + return "", err } traceRouter.Logger.WithContext(ctx).Info("请求结果", zap.String("response", orderResp)) type V1CardRedeemCookieOrder struct { - WxPay string `json:"wxPay" description:"微信支付"` - MerchantOrder string `json:"merchantOrder" description:"银行订单id"` - OrderNo string `json:"orderNo" description:"订单号"` - OrderAmount float64 `json:"orderAmount" description:"订单金额"` - OrderStatus string `json:"orderStatus" description:"订单状态"` + WxPayUrl string `json:"wxPayUrl" dc:"微信支付链接"` + ExpireTime string `json:"expireTime" dc:"链接过期时间"` + JdOrderId string `json:"jdOrderId" dc:"京东订单号"` } type Response struct { @@ -119,15 +118,11 @@ func PayWithJd(ctx context.Context, userAgent string, orderId string, orderAmoun err = json.Unmarshal([]byte(orderResp), &res) if err != nil { traceRouter.Logger.WithContext(ctx).Error("解析失败", zap.Error(err)) - return "", false, err + return "", err } - if res.Data.OrderStatus != "placeSuccess" { - traceRouter.Logger.WithContext(ctx).Error("订单拉单失败", zap.String("订单状态", res.Data.OrderStatus), - zap.String("订单号", res.Data.OrderNo), - ) - return "", false, errors.New("订单拉单失败") - } - - return res.Data.WxPay, true, nil + traceRouter.Logger.WithContext(ctx).Info("订单拉单失败", zap.Any("订单状态", res.Data), + zap.String("订单号", res.Data.JdOrderId), + ) + return res.Data.WxPayUrl, nil } diff --git a/kami_shop b/kami_shop new file mode 100644 index 0000000..bf4559e Binary files /dev/null and b/kami_shop differ diff --git a/views/index-wxpay.html b/views/index-wxpay.html index 21faf9f..72690b6 100644 --- a/views/index-wxpay.html +++ b/views/index-wxpay.html @@ -1,149 +1,435 @@ - - + - + 微信支付 + +
+
+
+
+
- - -
-
- - +
+
+
+
+
+
- -
-
支付金额
-
- ¥ - {{.mmValue}} +

微信支付

+

请在有效期内完成支付,超时订单将自动取消

+ +
+
+ 订单金额 + ¥{{.mmValue}} +
+
+ 订单编号 + {{.orderNo}} +
+
+ 商品名称 + 微信支付 +
+
+ 剩余时间 + --:--:--
- -
-
-
- 订单编号 - {{.orderNo}} -
-
- 商品名称 - 微信支付 -
-
-
-

请在24小时内完成支付

-

超时订单将自动取消

-
-
-
+ +
本次交易安全可靠
+
- - - -
-

本次交易安全可靠

-
- - - - - - - - - - - + \ No newline at end of file