diff --git a/README.md b/README.md index a0cd27ee..f2e07d50 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ # GoPay #### QQ、微信、支付宝的Golang版本SDK + [![Github](https://img.shields.io/github/followers/iGoogle-ink?label=Follow&style=social)](https://github.com/iGoogle-ink) [![Github](https://img.shields.io/github/forks/iGoogle-ink/gopay?label=Fork&style=social)](https://github.com/iGoogle-ink/gopay/fork) @@ -80,9 +81,13 @@ func main() { * 商户发起催收扣款:client.V3ScoreOrderPay() * 同步服务订单信息:client.V3ScoreOrderSync() * 微信支付分(免确认模式) - * 待实现 + * 创单结单合并:client.V3ScoreDirectComplete() * 微信支付分(免确认预授权模式) - * 待实现 + * 商户预授权:client.V3ScorePermission() + * 查询用户授权记录(授权协议号):client.V3ScorePermissionQuery() + * 解除用户授权关系(授权协议号):client.V3ScorePermissionTerminate() + * 查询用户授权记录(openid):client.V3ScorePermissionOpenidQuery() + * 解除用户授权关系(openid):client.V3ScorePermissionOpenidTerminate() * 微信先享卡 * 待实现 * 支付即服务 @@ -105,6 +110,7 @@ func main() { * 图片上传(营销专用): * 图片上传: * 视频上传: + ### 微信支付V2 API > #### 推荐使用V3接口,官方在V3接口实现未覆盖或gopay未开发的接口,还继续用V2接口。 diff --git a/constant.go b/constant.go index a44f1007..b47b8ba5 100644 --- a/constant.go +++ b/constant.go @@ -7,7 +7,7 @@ const ( OK = "OK" DebugOff = 0 DebugOn = 1 - Version = "1.5.39" + Version = "1.5.40" ) type DebugSwitch int8 diff --git a/release_note.txt b/release_note.txt index 3cf58e10..8e60bcde 100644 --- a/release_note.txt +++ b/release_note.txt @@ -1,3 +1,8 @@ +版本号:Release 1.5.40 +修改记录: + (1) 微信V3:新增微信支付分(免确认模式)相关接口 + (2) 微信V3:新增微信支付分(免确认预授权模式)相关接口 + 版本号:Release 1.5.39 修改记录: (1) 微信V3:新增微信支付分(公共API)相关接口 diff --git a/wechat/v3/client.go b/wechat/v3/client.go index cf711efe..bdbcead2 100644 --- a/wechat/v3/client.go +++ b/wechat/v3/client.go @@ -33,7 +33,7 @@ type ClientV3 struct { // mchid:商户ID 或者服务商模式的 sp_mchid // serialNo:商户证书的证书序列号 // apiV3Key:apiV3Key,商户平台获取 -// pkContent:私钥 apiclient_key.pem 读取后的内容 +// pkContent:私钥 apiclient_key.pem 读取后的字符串内容 func NewClientV3(appid, mchid, serialNo, apiV3Key, pkContent string) (client *ClientV3, err error) { var ( pk *rsa.PrivateKey diff --git a/wechat/v3/client_test.go b/wechat/v3/client_test.go index bbd944f1..70826ef1 100644 --- a/wechat/v3/client_test.go +++ b/wechat/v3/client_test.go @@ -27,7 +27,7 @@ func TestMain(m *testing.M) { // mchid:商户ID // serialNo:商户证书的证书序列号 // apiV3Key:apiV3Key,商户平台获取 - // pkContent:私钥 apiclient_key.pem 读取后的内容 + // pkContent:私钥 apiclient_key.pem 读取后的字符串内容 client, err = NewClientV3(Appid, MchId, SerialNo, ApiV3Key, PKContent) if err != nil { xlog.Error(err) diff --git a/wechat/v3/constant.go b/wechat/v3/constant.go index 7fa12a47..d7684efd 100644 --- a/wechat/v3/constant.go +++ b/wechat/v3/constant.go @@ -57,13 +57,15 @@ const ( v3ApiFundFlowBill = "/v3/bill/fundflowbill" // 申请资金账单 v3ApiLevel2FundFlowBill = "/v3/ecommerce/bill/fundflowbill" // 申请二级商户资金账单 - // 微信支付分 - v3ScorePermission = "/v3/payscore/permissions" // 商户预授权 POST - v3ScorePermissionAuthorizationQuery = "/v3/payscore/permissions/authorization-code/%s" // authorization_code 查询用户授权记录(授权协议号) GET - v3ScorePermissionAuthorizationTerminate = "/v3/payscore/permissions/authorization-code/%s/terminate" // authorization_code 解除用户授权关系(授权协议号) POST - v3ScorePermissionOpenidQuery = "/v3/payscore/permissions/openid/%s" // openid 查询用户授权记录(openid) GET - v3ScorePermissionOpenidTerminate = "/v3/payscore/permissions/openid/%s/terminate" // openid 解除用户授权记录(openid) POST - v3ScoreOrderDirectComplete = "/payscore/serviceorder/direct-complete" // 创单结单合并 POST + // 微信支付分(免确认模式) + v3ScoreDirectComplete = "/payscore/serviceorder/direct-complete" // 创单结单合并 POST + + // 微信支付分(免确认预授权模式) + v3ScorePermission = "/v3/payscore/permissions" // 商户预授权 POST + v3ScorePermissionQuery = "/v3/payscore/permissions/authorization-code/%s" // authorization_code 查询用户授权记录(授权协议号) GET + v3ScorePermissionTerminate = "/v3/payscore/permissions/authorization-code/%s/terminate" // authorization_code 解除用户授权关系(授权协议号) POST + v3ScorePermissionOpenidQuery = "/v3/payscore/permissions/openid/%s" // openid 查询用户授权记录(openid) GET + v3ScorePermissionOpenidTerminate = "/v3/payscore/permissions/openid/%s/terminate" // openid 解除用户授权记录(openid) POST // 微信支付分(公共API) v3ScoreOrderCreate = "/v3/payscore/serviceorder" // 创建支付分订单 POST diff --git a/wechat/v3/model.go b/wechat/v3/model.go index 6ee6f81b..29ee5bdb 100644 --- a/wechat/v3/model.go +++ b/wechat/v3/model.go @@ -152,6 +152,52 @@ type ScoreOrderSyncRsp struct { Error string `json:"-"` } +// 创单结单合并 Rsp +type ScoreDirectCompleteRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *ScoreDirectComplete `json:"response,omitempty"` + Error string `json:"-"` +} + +// 创单结单合并 Rsp +type ScorePermissionRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *ScorePermission `json:"response,omitempty"` + Error string `json:"-"` +} + +// 查询用户授权记录(授权协议号) Rsp +type ScorePermissionQueryRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *ScorePermissionQuery `json:"response,omitempty"` + Error string `json:"-"` +} + +// 解除用户授权关系(授权协议号) Rsp +type ScorePermissionTerminateRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Error string `json:"-"` +} + +// 查询用户授权记录(openid) Rsp +type ScorePermissionOpenidQueryRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Response *ScorePermissionOpenidQuery `json:"response,omitempty"` + Error string `json:"-"` +} + +// 解除用户授权关系(openid) Rsp +type ScorePermissionOpenidTerminateRsp struct { + Code int `json:"-"` + SignInfo *SignInfo `json:"-"` + Error string `json:"-"` +} + // ==================================分割================================== type JSAPIPayParams struct { @@ -589,3 +635,48 @@ type ScoreOrderSync struct { Collection *Collection `json:"collection,omitempty"` // 收款信息 Openid string `json:"openid"` // 微信用户在商户对应appid下的唯一标识 } + +type ScoreDirectComplete struct { + Appid string `json:"appid"` // 调用接口提交的公众账号ID。 + Mchid string `json:"mchid"` // 调用接口提交的商户号。 + OutTradeNo string `json:"out_trade_no"` // 调用接口提交的商户服务订单号。 + ServiceId string `json:"service_id"` // 调用该接口提交的服务ID。 + OrderId string `json:"order_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"` // 后付费项目列表,最多包含100条付费项目。 如果传入,用户侧则显示此参数。 + PostDiscounts []*PostDiscounts `json:"post_discounts,omitempty"` // 后付费商户优惠,最多包含30条付费项目。 如果传入,用户侧则显示此参数。 + TimeRange *TimeRange `json:"time_range"` // 服务时间范围 + Location *Location `json:"location,omitempty"` // 服务位置信息 + Attach string `json:"attach,omitempty"` // 商户数据包,可存放本订单所需信息,需要先urlencode后传入,总长度不大于256字符,超出报错处理。 + NotifyUrl string `json:"notify_url,omitempty"` // 商户接收用户确认订单或扣款成功回调通知的地址。 + TotalAmount int `json:"total_amount"` // 金额:数字,必须≥0(单位:分) +} + +type ScorePermission struct { + ApplyPermissionsToken string `json:"apply_permissions_token"` // 用于跳转到微信侧小程序授权数据,跳转到微信侧小程序传入,时效性为1小时 +} + +type ScorePermissionQuery struct { + Appid string `json:"appid"` // 调用接口提交的公众账号ID。 + Mchid string `json:"mchid"` // 调用接口提交的商户号。 + ServiceId string `json:"service_id"` // 调用该接口提交的服务ID。 + Openid string `json:"openid,omitempty"` // 微信用户在商户对应appid下的唯一标识 + AuthorizationCode string `json:"authorization_code"` // 预授权成功时的授权协议号。 + AuthorizationState string `json:"authorization_state"` // 标识用户授权服务情况:UNAVAILABLE:用户未授权服务,AVAILABLE:用户已授权服务,UNBINDUSER:未绑定用户(已经预授权但未完成正式授权) + NotifyUrl string `json:"notify_url,omitempty"` // 商户接收用户确认订单或扣款成功回调通知的地址。 + CancelAuthorizationTime string `json:"cancel_authorization_time,omitempty"` // 最近一次解除授权时间, 示例值:2015-05-20T13:29:35.120+08:00 + AuthorizationSuccessTime string `json:"authorization_success_time"` // 最近一次授权成功时间, 示例值:2015-05-20T13:29:35.120+08:00 +} + +type ScorePermissionOpenidQuery struct { + Appid string `json:"appid"` // 调用接口提交的公众账号ID。 + Mchid string `json:"mchid"` // 调用接口提交的商户号。 + ServiceId string `json:"service_id"` // 调用该接口提交的服务ID。 + Openid string `json:"openid,omitempty"` // 微信用户在商户对应appid下的唯一标识 + AuthorizationCode string `json:"authorization_code"` // 预授权成功时的授权协议号。 + AuthorizationState string `json:"authorization_state"` // 标识用户授权服务情况:UNAVAILABLE:用户未授权服务,AVAILABLE:用户已授权服务,UNBINDUSER:未绑定用户(已经预授权但未完成正式授权) + CancelAuthorizationTime string `json:"cancel_authorization_time,omitempty"` // 最近一次解除授权时间, 示例值:2015-05-20T13:29:35.120+08:00 + AuthorizationSuccessTime string `json:"authorization_success_time"` // 最近一次授权成功时间, 示例值:2015-05-20T13:29:35.120+08:00 +} diff --git a/wechat/v3/score_api.go b/wechat/v3/score_api.go index 8af29ed4..3ef35cc2 100644 --- a/wechat/v3/score_api.go +++ b/wechat/v3/score_api.go @@ -10,6 +10,168 @@ import ( "github.com/iGoogle-ink/gopay/pkg/util" ) +// 创单结单合并API +// Code = 0 is success +// 注意:限制条件:【免确认订单模式】,用户已授权状态下,可调用该接口。 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_1.shtml +func (c *ClientV3) V3ScoreDirectComplete(bm gopay.BodyMap) (wxRsp *ScoreDirectCompleteRsp, err error) { + if bm.GetString("appid") == util.NULL { + bm.Set("appid", c.Appid) + } + authorization, err := c.authorization(MethodPost, v3ScoreDirectComplete, bm) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdPost(bm, v3ScoreDirectComplete, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScoreDirectCompleteRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(ScoreDirectComplete) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + 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 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_2.shtml +func (c *ClientV3) V3ScorePermission(bm gopay.BodyMap) (wxRsp *ScorePermissionRsp, err error) { + if bm.GetString("appid") == util.NULL { + bm.Set("appid", c.Appid) + } + authorization, err := c.authorization(MethodPost, v3ScorePermission, bm) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdPost(bm, v3ScorePermission, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScorePermissionRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(ScorePermission) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + 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 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_3.shtml +func (c *ClientV3) V3ScorePermissionQuery(authCode, serviceId string) (wxRsp *ScorePermissionQueryRsp, err error) { + url := fmt.Sprintf(v3ScorePermissionQuery, authCode) + uri := url + "?service_id=" + serviceId + authorization, err := c.authorization(MethodGet, uri, nil) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdGet(uri, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScorePermissionQueryRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(ScorePermissionQuery) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + 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 +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_4.shtml +func (c *ClientV3) V3ScorePermissionTerminate(authCode, serviceId, reason string) (wxRsp *ScorePermissionTerminateRsp, err error) { + url := fmt.Sprintf(v3ScorePermissionTerminate, authCode) + bm := make(gopay.BodyMap) + bm.Set("service_id", serviceId). + Set("reason", reason) + authorization, err := c.authorization(MethodPost, url, bm) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdPost(bm, url, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScorePermissionTerminateRsp{Code: Success, SignInfo: si} + if res.StatusCode != http.StatusNoContent { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } + return wxRsp, c.verifySyncSign(si) +} + +// 查询用户授权记录(openid)API +// Code = 0 is success +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_5.shtml +func (c *ClientV3) V3ScorePermissionOpenidQuery(openId, serviceId string) (wxRsp *ScorePermissionOpenidQueryRsp, err error) { + url := fmt.Sprintf(v3ScorePermissionOpenidQuery, openId) + uri := url + "?appid=" + c.Appid + "&service_id=" + serviceId + authorization, err := c.authorization(MethodGet, uri, nil) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdGet(uri, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScorePermissionOpenidQueryRsp{Code: Success, SignInfo: si} + wxRsp.Response = new(ScorePermissionOpenidQuery) + if err = json.Unmarshal(bs, wxRsp.Response); err != nil { + return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) + } + if res.StatusCode != http.StatusOK { + wxRsp.Code = res.StatusCode + wxRsp.Error = string(bs) + return wxRsp, nil + } + return wxRsp, c.verifySyncSign(si) +} + +// 解除用户授权关系(openid)API +// Code = 0 is success +// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_6.shtml +func (c *ClientV3) V3ScorePermissionOpenidTerminate(openId, serviceId, reason string) (wxRsp *ScorePermissionOpenidTerminateRsp, err error) { + url := fmt.Sprintf(v3ScorePermissionOpenidTerminate, openId) + bm := make(gopay.BodyMap) + bm.Set("service_id", serviceId). + Set("appid", c.Appid). + Set("reason", reason) + authorization, err := c.authorization(MethodPost, url, bm) + if err != nil { + return nil, err + } + res, si, bs, err := c.doProdPost(bm, url, authorization) + if err != nil { + return nil, err + } + wxRsp = &ScorePermissionOpenidTerminateRsp{Code: Success, SignInfo: si} + if 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 // 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_14.shtml @@ -78,9 +240,9 @@ func (c *ClientV3) V3ScoreOrderQuery(orderNoType OrderNoType, orderNo, serviceId func (c *ClientV3) V3ScoreOrderCancel(tradeNo, serviceId, reason string) (wxRsp *ScoreOrderCancelRsp, err error) { url := fmt.Sprintf(v3ScoreOrderCancel, tradeNo) bm := make(gopay.BodyMap) - bm.Set("appid", c.Appid) - bm.Set("service_id", serviceId) - bm.Set("reason", reason) + bm.Set("appid", c.Appid). + Set("service_id", serviceId). + Set("reason", reason) authorization, err := c.authorization(MethodPost, url, bm) if err != nil { return nil, err @@ -166,8 +328,8 @@ func (c *ClientV3) V3ScoreOrderComplete(tradeNo string, bm gopay.BodyMap) (wxRsp func (c *ClientV3) V3ScoreOrderPay(tradeNo, serviceId string) (wxRsp *ScoreOrderPayRsp, err error) { url := fmt.Sprintf(v3ScoreOrderPay, tradeNo) bm := make(gopay.BodyMap) - bm.Set("appid", c.Appid) - bm.Set("service_id", serviceId) + bm.Set("appid", c.Appid). + Set("service_id", serviceId) authorization, err := c.authorization(MethodPost, url, bm) if err != nil { return nil, err