diff --git a/web/globals/shangfutong.go b/web/globals/shangfutong.go index 9ddc1fc..79ddeaf 100644 --- a/web/globals/shangfutong.go +++ b/web/globals/shangfutong.go @@ -109,17 +109,17 @@ type PaymentScanPayReq struct { Currency string `json:"currency"` ClientIp string `json:"clientIp"` MchOrderNo string `json:"mchOrderNo"` - StoreId *string `json:"storeId"` - RouteNo *string `json:"routeNo"` - HbFqNum *int `json:"hbFqNum"` - HbFqPercent *int `json:"hbFqPercent"` - BuyerRemark *string `json:"buyerRemark"` - NotifyUrl *string `json:"notifyUrl"` - ReturnUrl *string `json:"returnUrl"` - ExpiredTime *int `json:"expiredTime"` - OrderTimeout *string `json:"orderTimeout"` - ExtParam *string `json:"extParam"` - LimitPay *int `json:"limitPay"` + StoreId *string `json:"storeId,omitempty"` + RouteNo *string `json:"routeNo,omitempty"` + HbFqNum *int `json:"hbFqNum,omitempty"` + HbFqPercent *int `json:"hbFqPercent,omitempty"` + BuyerRemark *string `json:"buyerRemark,omitempty"` + NotifyUrl *string `json:"notifyUrl,omitempty"` + ReturnUrl *string `json:"returnUrl,omitempty"` + ExpiredTime *int `json:"expiredTime,omitempty"` + OrderTimeout *string `json:"orderTimeout,omitempty"` + ExtParam *string `json:"extParam,omitempty"` + LimitPay *int `json:"limitPay,omitempty"` } type PaymentH5PayReq struct { @@ -130,27 +130,27 @@ type PaymentH5PayReq struct { PayType SftPayType `json:"payType"` ClientIp string `json:"clientIp"` MchOrderNo string `json:"mchOrderNo"` - StoreId *string `json:"storeId"` - RouteNo *string `json:"routeNo"` - HbFqNum *int `json:"hbFqNum"` - HbFqPercent *int `json:"hbFqPercent"` - BuyerRemark *string `json:"buyerRemark"` - NotifyUrl *string `json:"notifyUrl"` - ReturnUrl *string `json:"returnUrl"` - ExpiredTime *int `json:"expiredTime"` - OrderTimeout *string `json:"orderTimeout"` - ExtParam *string `json:"extParam"` - LimitPay *int `json:"limitPay"` + StoreId *string `json:"storeId,omitempty"` + RouteNo *string `json:"routeNo,omitempty"` + HbFqNum *int `json:"hbFqNum,omitempty"` + HbFqPercent *int `json:"hbFqPercent,omitempty"` + BuyerRemark *string `json:"buyerRemark,omitempty"` + NotifyUrl *string `json:"notifyUrl,omitempty"` + ReturnUrl *string `json:"returnUrl,omitempty"` + ExpiredTime *int `json:"expiredTime,omitempty"` + OrderTimeout *string `json:"orderTimeout,omitempty"` + ExtParam *string `json:"extParam,omitempty"` + LimitPay *int `json:"limitPay,omitempty"` } type QueryTradeReq struct { - PayOrderId *string `json:"payOrderId"` - MchOrderNo *string `json:"mchOrderNo"` + PayOrderId *string `json:"payOrderId,omitempty"` + MchOrderNo *string `json:"mchOrderNo,omitempty"` } type OrderCloseReq struct { - MchOrderNo *string `json:"mchOrderNo"` - PayOrderId *string `json:"payOrderId"` + MchOrderNo *string `json:"mchOrderNo,omitempty"` + PayOrderId *string `json:"payOrderId,omitempty"` } // type OrderRefundReq struct { @@ -315,6 +315,9 @@ func (s *SftClient) sign(msg any) (*request, error) { return nil, fmt.Errorf("格式化加密正文失败:%w", err) } + pretty, _ := json.MarshalIndent(msg, "", " ") + println("content:\n" + string(pretty) + "\n\n") + body := request{ AppId: s.appid, Version: "1.0", @@ -330,7 +333,7 @@ func (s *SftClient) sign(msg any) (*request, error) { return nil, fmt.Errorf("签名失败:%w", err) } - body.Sign = string(signature) + body.Sign = base64.StdEncoding.EncodeToString(signature) return &body, nil } @@ -350,13 +353,18 @@ func (s *SftClient) verify(str []byte) (string, error) { return "", core.NewServErr("响应数据签名为空") } + sign, err := base64.StdEncoding.DecodeString(*resp.Sign) + if err != nil { + return "", core.NewServErr("响应数据签名 base64 解码失败") + } + ser, err := resp.String() if err != nil { return "", fmt.Errorf("格式化响应内容失败:%w", err) } hashed := sha256.Sum256([]byte(ser)) - err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign)) + err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], sign) if err != nil { return "", fmt.Errorf("验签失败:%w", err) } diff --git a/web/handlers/resource.go b/web/handlers/resource.go index 75d052b..4b5b65d 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -5,7 +5,6 @@ import ( "platform/web/auth" "platform/web/core" resource2 "platform/web/domains/resource" - trade2 "platform/web/domains/trade" g "platform/web/globals" "platform/web/globals/orm" q "platform/web/queries" @@ -242,7 +241,7 @@ func AllActiveResource(c *fiber.Ctx) error { // region 创建套餐 type CreateResourceReq struct { - s.CreateResourceSerializer + s.CreateResourceData } func CreateResource(c *fiber.Ctx) error { @@ -260,7 +259,7 @@ func CreateResource(c *fiber.Ctx) error { } // 创建套餐 - err = s.Resource.CreateResource(authCtx.Payload.Id, time.Now(), &req.CreateResourceSerializer) + err = s.Resource.CreateResource(authCtx.Payload.Id, time.Now(), &req.CreateResourceData) if err != nil { return err } @@ -269,8 +268,7 @@ func CreateResource(c *fiber.Ctx) error { } type PrepareResourceReq struct { - Method trade2.Method `json:"method" validate:"required"` - s.CreateResourceSerializer + s.PrepareResourceData } type PrepareResourceResp struct { @@ -293,7 +291,7 @@ func PrepareCreateResource(c *fiber.Ctx) error { } // 准备创建套餐 - result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), &req.CreateResourceSerializer) + result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), &req.PrepareResourceData) if err != nil { return err } @@ -342,7 +340,7 @@ func ResourcePrice(c *fiber.Ctx) error { } // 解析请求参数 - var req = new(s.CreateResourceSerializer) + var req = new(s.PrepareResourceData) if err := g.Validator.Validate(c, req); err != nil { return err } diff --git a/web/handlers/user.go b/web/handlers/user.go index 36ba1a3..24ca870 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -353,7 +353,7 @@ func RechargePrepare(c *fiber.Ctx) error { }) } -func RechargeConfirm(c *fiber.Ctx) error { +func RechargeComplete(c *fiber.Ctx) error { // 检查权限 _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) if err != nil { @@ -361,9 +361,7 @@ func RechargeConfirm(c *fiber.Ctx) error { } // 解析请求参数 - req := new(struct { - TradeNo string `json:"trade_no" validate:"required"` - }) + req := new(RechargeConfirmReq) if err := c.BodyParser(req); err != nil { return err } @@ -371,7 +369,7 @@ func RechargeConfirm(c *fiber.Ctx) error { // 验证支付结果 result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{ TradeNo: req.TradeNo, - Method: trade2.MethodWeChat, + Method: trade2.MethodSft, }) if err != nil { return err diff --git a/web/router.go b/web/router.go index ca6cb85..1663d94 100644 --- a/web/router.go +++ b/web/router.go @@ -24,12 +24,8 @@ func ApplyRouters(app *fiber.App) { user.Post("/update/password", handlers.UpdatePassword) user.Post("/identify", handlers.Identify) user.Post("/identify/callback", handlers.IdentifyCallback) - user.Post("/recharge/prepare/alipay", handlers.RechargePrepareAlipay) - user.Post("/recharge/confirm/alipay", handlers.RechargeConfirmAlipay) - user.Post("/recharge/prepare/wechat", handlers.RechargePrepareWechat) - user.Post("/recharge/confirm/wechat", handlers.RechargeConfirmWechat) user.Post("/recharge/prepare", handlers.RechargePrepare) - user.Post("/recharge/confirm", handlers.RechargeConfirm) + user.Post("/recharge/complete", handlers.RechargeComplete) // 白名单 whitelist := api.Group("/whitelist") diff --git a/web/services/resource.go b/web/services/resource.go index dfd563c..503192a 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -22,7 +22,7 @@ var Resource = &resourceService{} type resourceService struct{} -func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateResourceSerializer) error { +func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateResourceData) error { data, err := ser.ToData() if err != nil { @@ -87,7 +87,7 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe return nil } -func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *CreateResourceSerializer) (*TradeCreateResult, error) { +func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *PrepareResourceData) (*TradeCreateResult, error) { data, err := ser.ToData() if err != nil { @@ -119,16 +119,16 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *CreateR } // 保存请求缓存 - resourceSerializer := new(CreateResourceSerializer) + resourceSerializer := new(PrepareResourceData) if err := resourceSerializer.ByData(data); err != nil { return err } - err = g.Redis.Set(context.Background(), result.TradeNo, &CreateResourceCache{ - Uid: uid, - TradeId: result.Trade.ID, - BillId: result.Bill.ID, - CreateResourceSerializer: resourceSerializer, + err = g.Redis.Set(context.Background(), result.TradeNo, &PrepareResourceCache{ + Uid: uid, + TradeId: result.Trade.ID, + BillId: result.Bill.ID, + PrepareResourceData: resourceSerializer, }, 30*time.Minute).Err() if err != nil { return err @@ -150,7 +150,7 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu if err != nil { return err } - cache := new(CreateResourceCache) + cache := new(PrepareResourceCache) if err := json.Unmarshal([]byte(reqStr), cache); err != nil { return err } @@ -225,7 +225,7 @@ func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoke if err != nil { return err } - cache := new(CreateResourceCache) + cache := new(PrepareResourceCache) if err := json.Unmarshal([]byte(cacheStr), cache); err != nil { return err } @@ -247,7 +247,7 @@ func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoke return nil } -func createResource(q *q.Query, uid int32, now time.Time, data CreateResourceData) (*m.Resource, error) { +func createResource(q *q.Query, uid int32, now time.Time, data CreateTypeResourceDataInter) (*m.Resource, error) { // 套餐基本信息 var resource = m.Resource{ @@ -293,7 +293,7 @@ func createResource(q *q.Query, uid int32, now time.Time, data CreateResourceDat return &resource, nil } -type CreateResourceData interface { +type CreateTypeResourceDataInter interface { GetName() string GetPrice() decimal.Decimal } @@ -405,15 +405,13 @@ func (data *CreateLongResourceData) GetPrice() decimal.Decimal { return *data.price } -type CreateResourceSerializer struct { - Type resource2.Type `json:"type" validate:"required"` - Short *CreateShortResourceData `json:"short,omitempty"` - Long *CreateLongResourceData `json:"long,omitempty"` - PaymentMethod trade2.Method `json:"payment_method" validate:"required"` - PaymentPlatform trade2.Platform `json:"payment_platform" validate:"required"` +type CreateResourceData struct { + Type resource2.Type `json:"type" validate:"required"` + Short *CreateShortResourceData `json:"short,omitempty"` + Long *CreateLongResourceData `json:"long,omitempty"` } -func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) { +func (s *CreateResourceData) ToData() (CreateTypeResourceDataInter, error) { switch s.Type { case resource2.TypeShort: return s.Short, nil @@ -423,7 +421,7 @@ func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) { return nil, fmt.Errorf("不支持的套餐类型") } -func (s *CreateResourceSerializer) ByData(data CreateResourceData) error { +func (s *CreateResourceData) ByData(data CreateTypeResourceDataInter) error { switch data := data.(type) { case *CreateShortResourceData: s.Type = resource2.TypeShort @@ -437,14 +435,20 @@ func (s *CreateResourceSerializer) ByData(data CreateResourceData) error { return nil } -type CreateResourceCache struct { +type PrepareResourceData struct { + CreateResourceData + PaymentMethod trade2.Method `json:"payment_method" validate:"required"` + PaymentPlatform trade2.Platform `json:"payment_platform" validate:"required"` +} + +type PrepareResourceCache struct { Uid int32 `json:"uid"` TradeId int32 `json:"trade_id"` BillId int32 `json:"bill_id"` - *CreateResourceSerializer + *PrepareResourceData } -func (c CreateResourceCache) MarshalBinary() (data []byte, err error) { +func (c PrepareResourceCache) MarshalBinary() (data []byte, err error) { data, err = json.Marshal(c) if err != nil { return nil, err @@ -452,7 +456,7 @@ func (c CreateResourceCache) MarshalBinary() (data []byte, err error) { return data, nil } -func (c CreateResourceCache) UnmarshalBinary(data []byte) error { +func (c PrepareResourceCache) UnmarshalBinary(data []byte) error { if err := json.Unmarshal(data, &c); err != nil { return err } diff --git a/web/services/trade.go b/web/services/trade.go index 5f2eb40..ed9639b 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -3,7 +3,9 @@ package services import ( "context" "errors" + "fmt" "github.com/shopspring/decimal" + wecahtpay_core "github.com/wechatpay-apiv3/wechatpay-go/core" "io" "log/slog" "net/http" @@ -151,7 +153,7 @@ func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *T Body: subject, Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(), Currency: "cny", - ClientIp: "", + ClientIp: "123.52.74.23", OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")), }) if err != nil { @@ -174,7 +176,7 @@ func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *T Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(), PayType: payType, Currency: "cny", - ClientIp: "", + ClientIp: "123.52.74.23", OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")), }) if err != nil { @@ -273,7 +275,9 @@ func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.T trade.Acquirer = int32(acquirer) trade.PaidAt = u.P(orm.LocalDateTime(paidAt)) trade.PayURL = u.P("") - _, err = q.Trade.Updates(trade) + _, err = q.Trade. + Where(q.Trade.ID.Eq(trade.ID)). + Updates(trade) if err != nil { return nil, err } @@ -397,10 +401,22 @@ func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult, Mchid: &env.WechatPayMchId, }) if err != nil { + var apiErr *wecahtpay_core.APIError + if errors.As(err, &apiErr) { + if apiErr.Code == "ORDER_NOT_EXIST" { + return nil, ErrTransactionNotPaid + } + return nil, core.NewServErr( + fmt.Sprintf("微信上游接口异常:code=%v,message=%v", apiErr.Code, apiErr.Message), + apiErr, + ) + } return nil, err } if *resp.TradeState != "SUCCESS" { - return nil, ErrTransactionNotPaid + return nil, core.NewServErr( + fmt.Sprintf("预期之外的支付未完成:state=%v, stateDesc=%v", resp.TradeState, resp.TradeStateDesc), + ) } transId = *resp.TransactionId