feat: 购买下单透传终端类型,手机端收银台拉起支付宝 App(契约 v1.1.0)

根因:pay 判手机/PC 用下单请求 UA,而下单由 jiu 后端 Go client 发出,
恒被判 PC 扫码页——手机用户拿不到 wap 拉起页。

- backend:/license/purchase 请求体加可选 client_type(oneof=pc mobile),
  缺省按本请求 UA 兜底(官网浏览器购买自动受益);CreatePurchase 透传给 pay;
  单测断言透传 + 既有用例补参
- client:createPurchase 支持 clientType;PurchaseCard 按平台声明
  (iOS/Android=mobile,桌面=pc,Web 不传走 UA),kIsWeb 先于 dart:io;
  launchUrl 外部浏览器不变,wap 收银台自动拉起支付宝
- 配套:pay-contract v1.1.0(744725b)+ pay 侧 resolveIsMobile(dbdadd1)已各自提交

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-04 10:31:49 +08:00
parent aa3aad417c
commit f3cfdcadca
5 changed files with 64 additions and 15 deletions
+24 -1
View File
@@ -4,6 +4,7 @@ import (
"errors"
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
@@ -12,6 +13,17 @@ import (
"github.com/wangjia/jiu/backend/internal/util"
)
// isMobileUA 判断是否手机浏览器(词表与 pay 侧 handler/order.go 保持一致)。
func isMobileUA(ua string) bool {
ua = strings.ToLower(ua)
for _, kw := range []string{"android", "iphone", "ipod", "mobile", "harmony", "windows phone"} {
if strings.Contains(ua, kw) {
return true
}
}
return false
}
type PayHandler struct {
svc *service.PayService
}
@@ -30,13 +42,24 @@ func (h *PayHandler) Purchase(c *gin.Context) {
}
var req struct {
BizCode string `json:"biz_code" binding:"required"`
// 终端类型(可选,"pc"/"mobile"):App 显式声明优先;缺省按本请求 UA 判断
// (官网浏览器购买的 UA 是真实终端 UA)。透传给 pay 决定收银台形态。
ClientType string `json:"client_type" binding:"omitempty,oneof=pc mobile"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
clientType := req.ClientType
if clientType == "" {
if isMobileUA(c.Request.UserAgent()) {
clientType = "mobile"
} else {
clientType = "pc"
}
}
res, err := h.svc.CreatePurchase(middleware.GetShopID(c), middleware.GetUserID(c), req.BizCode)
res, err := h.svc.CreatePurchase(middleware.GetShopID(c), middleware.GetUserID(c), req.BizCode, clientType)
if err != nil {
switch {
case errors.Is(err, service.ErrPayNotConfigured):