Skip to content

Commit

Permalink
feat: 支持微信服务商模式支付分API
Browse files Browse the repository at this point in the history
  • Loading branch information
wbl committed Nov 23, 2023
1 parent b135c2b commit b2d4289
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 8 deletions.
18 changes: 11 additions & 7 deletions wechat/v3/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,17 @@ const (
v3ScorePermissionOpenidTerminate = "/v3/payscore/permissions/openid/%s/terminate" // openid 解除用户授权记录(openid) POST

// 微信支付分(公共API)
v3ScoreOrderCreate = "/v3/payscore/serviceorder" // 创建支付分订单 POST
v3ScoreOrderQuery = "/v3/payscore/serviceorder" // 查询支付分订单 GET
v3ScoreOrderCancel = "/v3/payscore/serviceorder/%s/cancel" // out_trade_no 取消支付分订单 POST
v3ScoreOrderModify = "/v3/payscore/serviceorder/%s/modify" // out_trade_no 修改订单金额 POST
v3ScoreOrderComplete = "/v3/payscore/serviceorder/%s/complete" // out_trade_no 完结支付分订单 POST
v3ScoreOrderPay = "/v3/payscore/serviceorder/%s/pay" // out_trade_no 商户发起催收扣款 POST
v3ScoreOrderSync = "/v3/payscore/serviceorder/%s/sync" // out_trade_no 同步服务订单信息 POST
v3ScoreOrderCreate = "/v3/payscore/serviceorder" // 创建支付分订单 POST
v3ScoreOrderQuery = "/v3/payscore/serviceorder" // 查询支付分订单 GET
v3ScoreOrderCancel = "/v3/payscore/serviceorder/%s/cancel" // out_trade_no 取消支付分订单 POST
v3ScoreOrderModify = "/v3/payscore/serviceorder/%s/modify" // out_trade_no 修改订单金额 POST
v3ScoreOrderComplete = "/v3/payscore/serviceorder/%s/complete" // out_trade_no 完结支付分订单 POST
v3ScoreOrderPay = "/v3/payscore/serviceorder/%s/pay" // out_trade_no 商户发起催收扣款 POST
v3ScoreOrderSync = "/v3/payscore/serviceorder/%s/sync" // out_trade_no 同步服务订单信息 POST
v3ScoreOrderPartnerCreate = "/v3/payscore/partner/serviceorder" // 服务商模式创建支付分订单
v3ScoreOrderPartnerQuery = "/v3/payscore/partner/serviceorder" // 服务商模式查询支付分订单
v3ScoreOrderPartnerCancel = "/v3/payscore/partner/serviceorder/%s/cancel" // 服务商模式取消支付分订单
v3ScoreOrderPartnerComplete = "/v3/payscore/partner/serviceorder/%s/complete" // 服务商模式完结支付分订单

// 微信先享卡
v3CardPre = "/v3/discount-card/cards" // 预受理领卡请求 POST
Expand Down
4 changes: 4 additions & 0 deletions wechat/v3/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ type APPScoreQuery struct {
Sign string `json:"sign"`
}

type AppletScorePartnerExtraData struct {
Package string `json:"package"`
}

// ==================================分割==================================

type SignInfo struct {
Expand Down
79 changes: 79 additions & 0 deletions wechat/v3/model_score.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ type ScoreOrderCreateRsp struct {
Error string `json:"-"`
}

// ScoreOrderPartnerCreateRsp 服务商模式创建支付分订单 Rsp
type ScoreOrderPartnerCreateRsp struct {
Code int `json:"code"`
SignInfo *SignInfo `json:"-"`
Response *ScoreOrderPartnerCreate `json:"response,omitempty"`
Error string `json:"error"`
}

// 查询支付分订单 Rsp
type ScoreOrderQueryRsp struct {
Code int `json:"-"`
Expand All @@ -16,6 +24,14 @@ type ScoreOrderQueryRsp struct {
Error string `json:"-"`
}

// ScoreOrderPartnerQueryRsp 服务商模式查询支付分订单 Rsp
type ScoreOrderPartnerQueryRsp struct {
Code int `json:"code"`
SignInfo *SignInfo `json:"-"`
Response *ScoreOrderPartnerQuery `json:"response,omitempty"`
Error string `json:"error"`
}

// 取消支付分订单 Rsp
type ScoreOrderCancelRsp struct {
Code int `json:"-"`
Expand All @@ -24,6 +40,13 @@ type ScoreOrderCancelRsp struct {
Error string `json:"-"`
}

// ScoreOrderPartnerCancelRsp 服务商模式取消支付分订单 Rsp
type ScoreOrderPartnerCancelRsp struct {
Code int `json:"code"`
SignInfo *SignInfo `json:"-"`
Error string `json:"error"`
}

// 修改订单金额 Rsp
type ScoreOrderModifyRsp struct {
Code int `json:"-"`
Expand All @@ -40,6 +63,13 @@ type ScoreOrderCompleteRsp struct {
Error string `json:"-"`
}

// ScoreOrderPartnerCompleteRsp 服务商模式完结支付分订单 Rsp
type ScoreOrderPartnerCompleteRsp struct {
Code int `json:"code"`
SignInfo *SignInfo `json:"-"`
Error string `json:"error"`
}

// 商户发起催收扣款 Rsp
type ScoreOrderPayRsp struct {
Code int `json:"-"`
Expand Down Expand Up @@ -109,6 +139,30 @@ type ScoreOrderCreate struct {
Package string `json:"package"` // 用户跳转到微信侧小程序订单数据,需确认模式特有API中调起支付分-确认订单传入。该数据一小时内有效。
}

type ScoreOrderPartnerCreate struct {
Appid string `json:"appid"` // 调用接口提交的公众账号Id。
Mchid string `json:"mchid"` // 调用接口提交的商户号。
OutOrderNo string `json:"out_order_no"` // 调用接口提交的商户服务订单号。
ServiceId string `json:"service_id"` // 调用该接口提交的服务Id。
ServiceIntroduction string `json:"service_introduction"` // 服务信息,用于介绍本订单所提供的服务。
State string `json:"state"` // 表示当前单据状态。枚举值:CREATED:商户已创建服务订单,DOING:服务订单进行中,DONE:服务订单完成,REVOKED:商户取消服务订单,EXPIRED:服务订单已失效
StateDescription string `json:"state_description,omitempty"` // 对服务订单"进行中"状态的附加说明。USER_CONFIRM:用户确认,MCH_COMPLETE:商户完结
PostPayments []*PostPayments `json:"post_payments,omitempty"` // 后付费项目列表,最多包含100条付费项目。 如果传入,用户侧则显示此参数。
PostDiscounts []*PostDiscounts `json:"post_discounts,omitempty"` // 后付费商户优惠,最多包含30条付费项目。 如果传入,用户侧则显示此参数。
RiskFund *RiskFund `json:"risk_fund"` // 订单风险金信息
TimeRange *TimeRange `json:"time_range"` // 服务时间范围
Location *Location `json:"location,omitempty"` // 服务位置信息
Attach string `json:"attach,omitempty"` // 商户数据包,可存放本订单所需信息,需要先urlencode后传入,总长度不大于256字符,超出报错处理。
NotifyUrl string `json:"notify_url,omitempty"` // 商户接收用户确认订单或扣款成功回调通知的地址。
OrderId string `json:"order_id"` // 微信支付服务订单号,每个微信支付服务订单号与商户号下对应的商户服务订单号一一对应。
Package string `json:"package"` // 用户跳转到微信侧小程序订单数据,需确认模式特有API中调起支付分-确认订单传入。该数据一小时内有效。
SubAppid string `json:"sub_appid"` // 调用接口传入的子商户应用ID
SubMchid string `json:"sub_mchid"` // 调用接口传入的子商户商户号
Openid string `json:"openid"` // 用户在子商户appid下的唯一标识
SubOpenid string `json:"sub_openid"` // 用户在子商户appid下的唯一标识
NeedUserConfirm bool `json:"need_user_confirm"` // 是否需要用户确认,商户可通过该字段设置是否在商户侧完成服务订单的确认,枚举值:true:是,false:否。默认为false。当商户选择了服务订单需要用户确认时,用户确认后才会发起扣款。
}

type PostPayments struct {
Name string `json:"name"` // 付费项目名称
Amount int `json:"amount"` // 此付费项目总金额,大于等于0,单位为分,等于0时代表不需要扣费,只能为整数
Expand Down Expand Up @@ -163,6 +217,31 @@ type ScoreOrderQuery struct {
Openid string `json:"openid,omitempty"` // 微信用户在商户对应appid下的唯一标识
}

type ScoreOrderPartnerQuery struct {
Appid string `json:"appid"` // 调用接口提交的公众账号Id。
Mchid string `json:"mchid"` // 调用接口提交的商户号。
ServiceId string `json:"service_id"` // 调用该接口提交的服务Id。
OutOrderNo string `json:"out_order_no"` // 调用接口提交的商户服务订单号。
ServiceIntroduction string `json:"service_introduction"` // 服务信息,用于介绍本订单所提供的服务。
State string `json:"state"` // 表示当前单据状态。枚举值:CREATED:商户已创建服务订单,DOING:服务订单进行中,DONE:服务订单完成,REVOKED:商户取消服务订单,EXPIRED:服务订单已失效
StateDescription string `json:"state_description,omitempty"` // 对服务订单"进行中"状态的附加说明。USER_CONFIRM:用户确认,MCH_COMPLETE:商户完结
TotalAmount int `json:"total_amount,omitempty"` // 总金额,大于等于0的数字,单位为分,只能为整数
PostPayments []*PostPayments `json:"post_payments,omitempty"` // 后付费项目列表,最多包含100条付费项目。 如果传入,用户侧则显示此参数。
PostDiscounts []*PostDiscounts `json:"post_discounts,omitempty"` // 后付费商户优惠,最多包含30条付费项目。 如果传入,用户侧则显示此参数。
RiskFund *RiskFund `json:"risk_fund"` // 订单风险金信息
TimeRange *TimeRange `json:"time_range"` // 服务时间范围
Location *Location `json:"location,omitempty"` // 服务位置信息
Attach string `json:"attach,omitempty"` // 商户数据包,可存放本订单所需信息,需要先urlencode后传入,总长度不大于256字符,超出报错处理。
NotifyUrl string `json:"notify_url"` // 商户接收用户确认订单或扣款成功回调通知的地址。
OrderId string `json:"order_id"` // 微信支付服务订单号,每个微信支付服务订单号与商户号下对应的商户服务订单号一一对应。
NeedCollection bool `json:"need_collection,omitempty"` // 是否需要收款
Collection *Collection `json:"collection,omitempty"` // 收款信息
Openid string `json:"openid,omitempty"` // 微信用户在商户对应appid下的唯一标识
SubAppid string `json:"sub_appid"` // 调用接口传入的子商户应用ID
SubMchid string `json:"sub_mchid"` // 调用接口传入的子商户商户号
SubOpenid string `json:"sub_openid"` // 子商户公众号下的用户标识
}

type Collection struct {
State string `json:"state"` // 收款状态,USER_PAYING:待支付,USER_PAID:已支付
TotalAmount int `json:"total_amount,omitempty"` // 总金额,大于等于0的数字,单位为分,只能为整数
Expand Down
118 changes: 117 additions & 1 deletion wechat/v3/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"

"github.com/go-pay/gopay"
)
Expand Down Expand Up @@ -182,6 +183,33 @@ func (c *ClientV3) V3ScoreOrderCreate(ctx context.Context, bm gopay.BodyMap) (wx
return wxRsp, c.verifySyncSign(si)
}

// V3ScoreOrderPartnerCreate 服务行模式创建支付分订单
// Code = 0 is success
// 微信文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_1.shtml
func (c *ClientV3) V3ScoreOrderPartnerCreate(ctx context.Context, bm gopay.BodyMap) (*ScoreOrderPartnerCreateRsp, error) {
authorization, err := c.authorization(MethodPost, v3ScoreOrderPartnerCreate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ScoreOrderPartnerCreate, authorization)
if err != nil {
return nil, err
}

wxRsp := &ScoreOrderPartnerCreateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderPartnerCreate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}

return wxRsp, c.verifySyncSign(si)
}

// 查询支付分订单API
// Code = 0 is success
func (c *ClientV3) V3ScoreOrderQuery(ctx context.Context, orderNoType OrderNoType, appid, orderNo, serviceid string) (wxRsp *ScoreOrderQueryRsp, err error) {
Expand Down Expand Up @@ -215,6 +243,44 @@ func (c *ClientV3) V3ScoreOrderQuery(ctx context.Context, orderNoType OrderNoTyp
return wxRsp, c.verifySyncSign(si)
}

// V3ScoreOrderPartnerQuery 服务商模式查询支付分订单
// Code = 0 is success
// 微信文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_2.shtml
func (c *ClientV3) V3ScoreOrderPartnerQuery(ctx context.Context, orderNoType OrderNoType, orderNo, serviceid, subMchid string) (*ScoreOrderPartnerQueryRsp, error) {
query := url.Values{}
query.Set("service_id", serviceid)
query.Set("sub_mchid", subMchid)
switch orderNoType {
case OutTradeNo:
query.Set("out_order_no", orderNo)
case QueryId:
query.Set("query_id", orderNo)
default:
return nil, errors.New("unsupported order number type")
}
uri := fmt.Sprintf("%s?%s", v3ScoreOrderPartnerQuery, query.Encode())
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &ScoreOrderPartnerQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderPartnerQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}

return wxRsp, c.verifySyncSign(si)
}

// 取消支付分订单API
// Code = 0 is success
func (c *ClientV3) V3ScoreOrderCancel(ctx context.Context, appid, tradeNo, serviceid, reason string) (wxRsp *ScoreOrderCancelRsp, err error) {
Expand Down Expand Up @@ -244,6 +310,33 @@ func (c *ClientV3) V3ScoreOrderCancel(ctx context.Context, appid, tradeNo, servi
return wxRsp, c.verifySyncSign(si)
}

// V3ScoreOrderPartnerCancel 服务商模式取消支付分订单
// Code = 0 is success
// 微信文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_3.shtml
func (c *ClientV3) V3ScoreOrderPartnerCancel(ctx context.Context, subMchid, tradeNo, serviceid, reason string) (*ScoreOrderPartnerCancelRsp, error) {
path := fmt.Sprintf(v3ScoreOrderPartnerCancel, tradeNo)
bm := make(gopay.BodyMap)
bm.Set("sub_mchid", subMchid).
Set("service_id", serviceid).
Set("reason", reason)
authorization, err := c.authorization(MethodPost, path, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, path, authorization)
if err != nil {
return nil, err
}
wxRsp := &ScoreOrderPartnerCancelRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}

return wxRsp, c.verifySyncSign(si)
}

// 修改订单金额API
// Code = 0 is success
func (c *ClientV3) V3ScoreOrderModify(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderModifyRsp, err error) {
Expand All @@ -261,7 +354,7 @@ func (c *ClientV3) V3ScoreOrderModify(ctx context.Context, tradeNo string, bm go
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
Expand Down Expand Up @@ -294,6 +387,29 @@ func (c *ClientV3) V3ScoreOrderComplete(ctx context.Context, tradeNo string, bm
return wxRsp, c.verifySyncSign(si)
}

// V3ScoreOrderPartnerComplete 服务商模式完结支付分订单A
// Code = 0 is success
// 微信文档: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_5.shtml
func (c *ClientV3) V3ScoreOrderPartnerComplete(ctx context.Context, tradeNo string, bm gopay.BodyMap) (*ScoreOrderPartnerCompleteRsp, error) {
path := fmt.Sprintf(v3ScoreOrderPartnerComplete, tradeNo)
authorization, err := c.authorization(MethodPost, path, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, path, authorization)
if err != nil {
return nil, err
}
wxRsp := &ScoreOrderPartnerCompleteRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}

return wxRsp, c.verifySyncSign(si)
}

// 商户发起催收扣款API
// Code = 0 is success
func (c *ClientV3) V3ScoreOrderPay(ctx context.Context, appid, tradeNo, serviceid string) (wxRsp *ScoreOrderPayRsp, err error) {
Expand Down

0 comments on commit b2d4289

Please sign in to comment.