public_host
4 years ago
18 changed files with 1521 additions and 1 deletions
-
1.gitignore
-
21LICENSE
-
131README.md
-
2doc.go
-
34global/index.go
-
34main.go
-
38main_test.go
-
222src/V2/entity.go
-
13src/V2/index.go
-
303src/V2/pay.go
-
240src/V2/pay_test.go
-
20src/V3/index.go
-
64src/V3/smartGuide/entity.go
-
47src/V3/smartGuide/index.go
-
49src/V3/smartGuide/index_test.go
-
119src/config/index.go
-
33src/entity/index.go
-
151utils/public.go
@ -0,0 +1 @@ |
|||
.idea |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2020 黑白配 |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -1,2 +1,131 @@ |
|||
# wxpay |
|||
# 微信支付 SDK |
|||
|
|||
- [x] V2 版支付(商户/服务商) |
|||
- [x] V2 版分账(商户/服务商) |
|||
- [x] V2 版企业付款到零钱 |
|||
- [x] V3 版支付即服务 |
|||
|
|||
![go 1.15](https://img.shields.io/badge/go-1.15-green) |
|||
[![go.dev doc](https://img.shields.io/badge/go.dev-doc-green)](https://pkg.go.dev/github.com/wleven/wxpay) |
|||
[![GitHub license](https://img.shields.io/github/license/wleven/wxpay)](https://github.com/wleven/wxpay/blob/master/LICENSE) |
|||
|
|||
- [微信支付 SDK](#微信支付-sdk) |
|||
- [安装包](#安装包) |
|||
- [查看文档](#查看文档) |
|||
- [V2 版本下单接口](#v2-版本下单接口) |
|||
- [V2 版本分账接口](#v2-版本分账接口) |
|||
- [V2 版本企业付款到零钱](#v2-版本企业付款到零钱) |
|||
- [V3 版本支付即服务接口](#v3-版本支付即服务接口) |
|||
|
|||
## 安装包 |
|||
|
|||
```golang |
|||
go get -u github.com/wleven/wxpay |
|||
``` |
|||
|
|||
## 查看文档 |
|||
|
|||
```golang |
|||
// 执行命令 |
|||
godoc -http=:8888 -play |
|||
// 浏览器打开文档 |
|||
http://127.0.0.1:8888/pkg/github.com/wleven/wxpay/ |
|||
``` |
|||
|
|||
## V2 版本下单接口 |
|||
|
|||
```golang |
|||
config := entity.PayConfig{ |
|||
// 传入支付初始化参数 |
|||
AppID string // 商户/服务商 AppId(公众号/小程序) |
|||
MchID string // 商户/服务商 商户号 |
|||
SubAppID string // 子商户公众号ID |
|||
SubMchID string // 子商户商户号 |
|||
PayNotify string // 支付结果回调地址 |
|||
RefundNotify string // 退款结果回调地址 |
|||
Secret string // 微信支付密钥 |
|||
APIClientPath APIClientPath // API证书路径,使用V3接口必传 |
|||
SerialNo string // 证书编号,使用V3接口必传 |
|||
} |
|||
|
|||
wxpay := WXPay.Init(config) |
|||
// 统一下单 |
|||
if data, err := wxpay.V2.UnifiedOrder(V2.UnifiedOrder{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 小程序支付 |
|||
if data, err := wxpay.V2.WxAppPay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { |
|||
} |
|||
// APP支付 |
|||
if data, err := wxpay.V2.WxAppAppPay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { |
|||
} |
|||
// H5支付 |
|||
if data, err := wxpay.V2.WxH5Pay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 付款码支付 |
|||
if data, err := wxpay.V2.Micropay(V2.Micropay{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 关闭订单 |
|||
if data, err := wxpay.V2.CloseOrder("1111"); err == nil { |
|||
} |
|||
// 撤销订单 |
|||
if data, err := wxpay.V2.ReverseOrder(V2.ReverseOrder{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 查询订单 |
|||
if data, err := wxpay.V2.OrderQuery(V2.OrderQuery{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 申请退款 |
|||
if data, err := wxpay.V2.Refund(V2.Refund{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 查询退款 |
|||
if data, err := wxpay.V2.RefundQuery(V2.RefundQuery{/* 传入参数 */}); err == nil { |
|||
} |
|||
|
|||
``` |
|||
|
|||
## V2 版本分账接口 |
|||
|
|||
```golang |
|||
// 添加分账接收方 |
|||
if data, err := wxpay.V2.ProfitSharingAddReceiver(V2.Receiver{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 删除分账接收方 |
|||
if data, err := wxpay.V2.ProfitSharingRemoveReceiver(V2.Receiver{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 发起分账 第二个参数options为multi为多次分账 默认为单次 |
|||
if data, err := wxpay.V2.ProfitSharing(V2.ProfitSharing{/* 传入参数 */},""); err == nil { |
|||
} |
|||
// 完成分账 |
|||
if data, err := wxpay.V2.ProfitSharingFinish(V2.ProfitSharingFinish{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 查询分账结果 |
|||
if data, err := wxpay.V2.ProfitSharingQuery(V2.ProfitSharingQuery{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 分账回退 |
|||
if data, err := wxpay.V2.ProfitSharingReturn(V2.ProfitSharingReturn{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 分账回退结果查询 |
|||
if data, err := wxpay.V2.ProfitSharingReturnQuery(V2.ProfitSharingReturnQuery{/* 传入参数 */}); err == nil { |
|||
} |
|||
|
|||
``` |
|||
|
|||
## V2 版本企业付款到零钱 |
|||
|
|||
```golang |
|||
if data, err := wxpay.V2.Transfers(V2.Transfers{/* 传入参数 */}); err == nil { |
|||
} |
|||
``` |
|||
|
|||
## V3 版本支付即服务接口 |
|||
|
|||
```golang |
|||
// 注册服务人员 |
|||
if data, err := wxpay.V3.SmartGuide.Register(smartGuide.Register{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 分配服务人员 |
|||
if data, err := wxpay.V3.SmartGuide.Assign(smartGuide.Assign{/* 传入参数 */}); err == nil { |
|||
} |
|||
// 查询服务人员 |
|||
if data, err := wxpay.V3.SmartGuide.Query(smartGuide.Query{/* 传入参数 */}); err == nil { |
|||
} |
|||
``` |
@ -0,0 +1,2 @@ |
|||
// Package pay 微信支付SDK,支持V2/V3版商户和服务商接口,持续更新
|
|||
package WXPay |
@ -0,0 +1,34 @@ |
|||
// @Time : 2020/7/9 14:23
|
|||
// @Author : 黑白配
|
|||
// @File : index.go
|
|||
// @PackageName:global
|
|||
// @Description:
|
|||
|
|||
package global |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/src/config" |
|||
"git.ouxuan.net/3136352472/wxpay/src/entity" |
|||
) |
|||
|
|||
var V3 = config.V3{ |
|||
MchID: "", |
|||
ClientKeyPath: "", |
|||
SerialNo: "", |
|||
} |
|||
|
|||
var V2 = entity.PayConfig{ |
|||
AppID: "", |
|||
MchID: "", |
|||
SubAppID: "", |
|||
SubMchID: "", |
|||
PayNotify: "", |
|||
RefundNotify: "", |
|||
Secret: "", |
|||
APIClientPath: entity.APIClientPath{ |
|||
Cert: "", |
|||
Key: "", |
|||
Root: "", |
|||
}, |
|||
SerialNo: "", |
|||
} |
@ -0,0 +1,34 @@ |
|||
// @Time : 2020/7/9 16:08
|
|||
// @Author : 黑白配
|
|||
// @File : main.go
|
|||
// @PackageName:WXPay
|
|||
// @Description:微信支付
|
|||
|
|||
package WXPay |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/src/V2" |
|||
"git.ouxuan.net/3136352472/wxpay/src/V3" |
|||
"git.ouxuan.net/3136352472/wxpay/src/config" |
|||
"git.ouxuan.net/3136352472/wxpay/src/entity" |
|||
) |
|||
|
|||
type WXPayApi struct { |
|||
V2 *V2.WxPay // V2接口
|
|||
V3 *V3.API // V3接口
|
|||
Config *entity.PayConfig // 配置
|
|||
} |
|||
|
|||
func Init(params entity.PayConfig) (api WXPayApi) { |
|||
|
|||
api.V2 = V2.Init(params) |
|||
|
|||
api.V3 = V3.Init(&config.V3{ |
|||
MchID: params.MchID, |
|||
ClientKeyPath: params.APIClientPath.Key, |
|||
SerialNo: params.SerialNo, |
|||
}) |
|||
api.Config = ¶ms |
|||
|
|||
return |
|||
} |
@ -0,0 +1,38 @@ |
|||
// @Time : 2020/7/9 17:07
|
|||
// @Author : 黑白配
|
|||
// @File : main_test.go
|
|||
// @PackageName:WXPay
|
|||
// @Description:
|
|||
|
|||
package WXPay |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/global" |
|||
"git.ouxuan.net/3136352472/wxpay/src/V2" |
|||
"git.ouxuan.net/3136352472/wxpay/src/V3/smartGuide" |
|||
"testing" |
|||
) |
|||
|
|||
func TestInit(t *testing.T) { |
|||
api := Init(global.V2) |
|||
if data, err := api.V3.SmartGuide.Query(smartGuide.Query{StoreID: 20774227}); err == nil { |
|||
t.Log(data) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
|
|||
if data, err := api.V2.WxAppPay(V2.UnifiedOrder{ |
|||
Attach: "支付测试", |
|||
OutTradeNo: "11111111111115 ", |
|||
TotalFee: 1, |
|||
SpbillCreateIP: "127.0.0.1", |
|||
OpenID: "owJNp5PDj8lja9S3m2l2M_jt3aHY", |
|||
Receipt: "Y", |
|||
Body: "测试", |
|||
TradeType: "JSAPI", |
|||
}); err == nil { |
|||
t.Log(data) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
@ -0,0 +1,222 @@ |
|||
package V2 |
|||
|
|||
import ( |
|||
"errors" |
|||
) |
|||
|
|||
// APIClientPath 微信支付API证书
|
|||
type APIClientPath struct { |
|||
Cert string // 证书路径
|
|||
Key string // 证书密钥路径
|
|||
Root string // 根证书路径
|
|||
} |
|||
|
|||
// UnifiedOrder 统一下单参数
|
|||
type UnifiedOrder struct { |
|||
DeviceInfo string `json:"device_info,omitempty"` // 自定义参数,可以为终端设备号(门店号或收银设备ID)
|
|||
Body string `json:"body,omitempty"` // 商品简单描述,该字段请按照规范传递
|
|||
Detail *UnifiedOrderDetail `json:"detail,omitempty"` // 商品详情
|
|||
Attach string `json:"attach,omitempty"` // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户系统内部订单号
|
|||
FeeType string `json:"fee_type,omitempty"` // 币种,默认人民币:CNY
|
|||
TotalFee int `json:"total_fee,omitempty"` // 订单总金额,单位为分
|
|||
SpbillCreateIP string `json:"spbill_create_ip,omitempty"` // 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
|
|||
TimeStart string `json:"time_start,omitempty"` // 订单生成时间
|
|||
TimeExpire string `json:"time_expire,omitempty"` // 订单失效时间
|
|||
GoodsTag string `json:"goods_tag,omitempty"` // 订单优惠标记
|
|||
NotifyURL string `json:"notify_url,omitempty"` // 支付结果通知的回调地址
|
|||
TradeType string `json:"trade_type,omitempty"` // JSAPI/NATIVE/APP
|
|||
ProductID string `json:"product_id,omitempty"` // 商品ID trade_type=NATIVE时,此参数必传。
|
|||
LimitPay string `json:"limit_pay,omitempty"` // no_credit--可限制用户不能使用信用卡支付
|
|||
OpenID string `json:"openid,omitempty"` // 微信用户标识 trade_type=JSAPI时,此参数必传
|
|||
SubOpenid string `json:"sub_openid,omitempty"` // 用户在子商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid
|
|||
Receipt string `json:"receipt,omitempty"` // Y/N 是否开启电子发票入口
|
|||
SceneInfo *SceneInfo `json:"scene_info,omitempty"` // 场景信息
|
|||
ProfitSharing string `json:"profit_sharing,omitempty"` // 是否分账 Y/N
|
|||
} |
|||
|
|||
// Micropay 付款码支付
|
|||
type Micropay struct { |
|||
DeviceInfo string `json:"device_info,omitempty"` // 自定义参数,可以为终端设备号(门店号或收银设备ID)
|
|||
Body string `json:"body,omitempty"` // 商品简单描述,该字段请按照规范传递
|
|||
Detail *UnifiedOrderDetail `json:"detail,omitempty"` // 商品详情
|
|||
Attach string `json:"attach,omitempty"` // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户系统内部订单号
|
|||
FeeType string `json:"fee_type,omitempty"` // 币种,默认人民币:CNY
|
|||
TotalFee int `json:"total_fee,omitempty"` // 订单总金额,单位为分
|
|||
SpbillCreateIP string `json:"spbill_create_ip,omitempty"` // 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
|
|||
TimeStart string `json:"time_start,omitempty"` // 订单生成时间
|
|||
TimeExpire string `json:"time_expire,omitempty"` // 订单失效时间
|
|||
GoodsTag string `json:"goods_tag,omitempty"` // 订单优惠标记
|
|||
LimitPay string `json:"limit_pay,omitempty"` // no_credit--可限制用户不能使用信用卡支付
|
|||
SubOpenid string `json:"sub_openid,omitempty"` // 用户在子商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid
|
|||
Receipt string `json:"receipt,omitempty"` // Y/N 是否开启电子发票入口
|
|||
SceneInfo *SceneInfo `json:"scene_info,omitempty"` // 场景信息
|
|||
ProfitSharing string `json:"profit_sharing,omitempty"` // 是否分账 Y/N
|
|||
AuthCode string `json:"auth_code,omitempty"` // 扫码支付付款码,设备读取用户微信中的条码或者二维码信息
|
|||
} |
|||
|
|||
// UnifiedOrderDetail 商品详情
|
|||
type UnifiedOrderDetail struct { |
|||
CostPrice int `json:"cost_price,omitempty"` // 订单原价
|
|||
ReceiptID string `json:"receipt_id,omitempty"` // 小票ID
|
|||
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"` // 商品详情
|
|||
} |
|||
|
|||
// GoodsDetail 商品详情
|
|||
type GoodsDetail struct { |
|||
GoodsID string `json:"goods_id,omitempty"` // 商品ID
|
|||
WxpayGoodsID string `json:"wxpay_goods_id,omitempty"` // 微信支付定义的统一商品编号(没有可不传)
|
|||
GoodsName string `json:"goods_name,omitempty"` // 商品的实际名称
|
|||
Quantity int `json:"quantity,omitempty"` // 用户购买的数量
|
|||
Price int `json:"price,omitempty"` // 单位为:分。如果商户有优惠,需传输商户优惠后的单价
|
|||
} |
|||
|
|||
// SceneInfo 场景信息
|
|||
type SceneInfo struct { |
|||
ID string `json:"id,omitempty"` // 门店编号,由商户自定义
|
|||
Name string `json:"name,omitempty"` // 门店名称 ,由商户自定义
|
|||
AreaCode string `json:"area_code,omitempty"` // 门店所在地行政区划码
|
|||
Address string `json:"address,omitempty"` // 门店详细地址 ,由商户自定义
|
|||
H5Info *H5Info `json:"h5_info,omitempty"` //h5支付场景信息
|
|||
} |
|||
|
|||
// H5Info h5支付场景信息
|
|||
type H5Info struct { |
|||
Type string `json:"type,omitempty"` // 场景类型
|
|||
AppName string `json:"app_name,omitempty"` // 应用名
|
|||
PackageName string `json:"package_name,omitempty"` // 包名
|
|||
BundleID string `json:"bundle_id,omitempty"` // bundle_id
|
|||
WapURL string `json:"wap_url,omitempty"` // WAP网站URL地址
|
|||
WapName string `json:"wap_name,omitempty"` // WAP网站名
|
|||
} |
|||
|
|||
// ResultCheck 返回结果检查接口
|
|||
type ResultCheck interface { |
|||
ResultCheck() error |
|||
} |
|||
|
|||
// PublicResponse 通用返回
|
|||
type PublicResponse struct { |
|||
ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"` // 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
|
|||
ReturnMsg string `xml:"return_msg,omitempty" json:"return_msg,omitempty"` // 当return_code为FAIL时返回信息为错误原因
|
|||
ResultCode string `xml:"result_code,omitempty" json:"result_code,omitempty"` // SUCCESS/FAIL
|
|||
ResultMsg string `xml:"result_msg,omitempty" json:"result_msg,omitempty"` // 对于业务执行的详细描述
|
|||
ErrCode string `xml:"err_code,omitempty" json:"err_code,omitempty"` // 当result_code为FAIL时返回错误代码
|
|||
ErrCodeDes string `xml:"err_code_des,omitempty" json:"err_code_des,omitempty"` // 当result_code为FAIL时返回错误描述
|
|||
ErrorMsg string `xml:"error_msg,omitempty" json:"error_msg,omitempty"` // 当result_code为FAIL时返回错误描述
|
|||
} |
|||
|
|||
// ResultCheck 检查是否返回成功
|
|||
func (m PublicResponse) ResultCheck() error { |
|||
//s ,_ :=json.Marshal(m)
|
|||
//log.Println(string(s))
|
|||
if m.ReturnCode == "FAIL" { |
|||
if m.ReturnMsg != "" { |
|||
return errors.New(m.ReturnMsg) |
|||
} |
|||
if m.ResultMsg != "" { |
|||
return errors.New(m.ResultMsg) |
|||
} |
|||
return errors.New(m.ErrorMsg) |
|||
} else if m.ResultCode == "FAIL" { |
|||
return errors.New(m.ErrCodeDes) |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
// CDATA xml
|
|||
type CDATA struct { |
|||
Text string `xml:",cdata"` |
|||
} |
|||
|
|||
// ReverseOrder 撤销订单
|
|||
type ReverseOrder struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID 2选1
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户订单ID 2选1
|
|||
} |
|||
|
|||
// OrderQuery 查询订单参数
|
|||
type OrderQuery struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户订单ID
|
|||
} |
|||
|
|||
// Refund 退款参数
|
|||
type Refund struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户订单ID
|
|||
OutRefundNo string `json:"out_refund_no,omitempty"` // 商户系统内部的退款单号
|
|||
TotalFee int `json:"total_fee,omitempty"` // 订单总金额,单位为分
|
|||
RefundFee int `json:"refund_fee,omitempty"` // 退款总金额,订单总金额
|
|||
RefundFeeType string `json:"refund_fee_type,omitempty"` // 退款货币类型,需与支付一致,或者不填。
|
|||
RefundDesc string `json:"refund_desc,omitempty"` // 若商户传入,会在下发给用户的退款消息中体现退款原因
|
|||
RefundAccount string `json:"refund_account,omitempty"` // 退款资金来源 仅针对老资金流商户使用
|
|||
} |
|||
|
|||
// RefundQuery 退款查询参数
|
|||
type RefundQuery struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID 四选一
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户订单ID 四选一
|
|||
OutRefundNo string `json:"out_refund_no,omitempty"` // 商户系统内部的退款单号 四选一
|
|||
RefundID string `json:"refund_id,omitempty"` // 微信退款单号 四选一
|
|||
Offset int `json:"offset,omitempty"` // 偏移量,当部分退款次数超过10次时可使用,表示返回的查询结果从这个偏移量开始取记录
|
|||
} |
|||
|
|||
// Receiver 分账接收方
|
|||
type Receiver struct { |
|||
Type string `json:"type,omitempty"` // 账号类型
|
|||
Account string `json:"account,omitempty"` // 接收方账号
|
|||
Name string `json:"name,omitempty"` // 商户全称
|
|||
CustomRelation string `json:"custom_relation,omitempty"` // 子商户与接收方具体的关系,本字段最多10个字
|
|||
RelationType string `json:"relation_type,omitempty"` // 分账关系
|
|||
Amount int `json:"amount,omitempty"` // 分账金额
|
|||
Description string `json:"description,omitempty"` // 分账描述
|
|||
} |
|||
|
|||
// ProfitSharingFinish 分账完成参数
|
|||
type ProfitSharingFinish struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID
|
|||
OutOrderNo string `json:"out_order_no,omitempty"` // 商户订单ID
|
|||
Description string `json:"description,omitempty"` // 分账完结的原因描述
|
|||
} |
|||
|
|||
// ProfitSharing 单次分账参数
|
|||
type ProfitSharing struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID
|
|||
OutOrderNo string `json:"out_order_no,omitempty"` // 商户订单ID
|
|||
Receivers []Receiver `json:"receivers,omitempty"` // 接收方列表
|
|||
} |
|||
|
|||
// ProfitSharingQuery 分账结果查询参数
|
|||
type ProfitSharingQuery struct { |
|||
TransactionID string `json:"transaction_id,omitempty"` // 微信支付ID
|
|||
OutOrderNo string `json:"out_order_no,omitempty"` // 商户订单ID
|
|||
} |
|||
|
|||
// ProfitSharingReturn 分账回退参数
|
|||
type ProfitSharingReturn struct { |
|||
OrderID string `json:"order_id,omitempty"` // 原发起分账请求时,微信返回的微信分账单号,与商户分账单号一一对应 2选1
|
|||
OutOrderNo string `json:"out_order_no,omitempty"` // 原发起分账请求时使用的商户后台系统的分账单号 2选1
|
|||
OutReturnNo string `json:"out_return_no,omitempty"` // 商户在自己后台生成的一个新的回退单号
|
|||
ReturnAccount string `json:"return_account,omitempty"` // 回退方类型是MERCHANT_ID时,填写商户ID
|
|||
ReturnAmount int `json:"return_amount,omitempty"` // 回退金额
|
|||
Description string `json:"description,omitempty"` // 分账回退的原因描述
|
|||
} |
|||
|
|||
// ProfitSharingReturnQuery 分账回退查询参数
|
|||
type ProfitSharingReturnQuery struct { |
|||
OrderID string `json:"order_id,omitempty"` // 原发起分账请求时,微信返回的微信分账单号,与商户分账单号一一对应 2选1
|
|||
OutOrderNo string `json:"out_order_no,omitempty"` // 原发起分账请求时使用的商户后台系统的分账单号 2选1
|
|||
OutReturnNo string `json:"out_return_no,omitempty"` // 商户系统内部的回退单号
|
|||
} |
|||
|
|||
// Transfers 付款到零钱
|
|||
type Transfers struct { |
|||
PartnerTradeNo string `json:"partner_trade_no"` // 商户订单号
|
|||
OpenID string `json:"openid"` // OPENID
|
|||
CheckName string `json:"check_name"` // NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
|
|||
ReUserName string `json:"re_user_name"` // 收款用户真实姓名。如果check_name设置为FORCE_CHECK,则必填用户真实姓名。如需电子回单,需要传入收款用户姓名
|
|||
Amount int `json:"amount"` // 企业付款金额,单位为分
|
|||
Desc string `json:"desc"` // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
|
|||
} |
@ -0,0 +1,13 @@ |
|||
// @Time : 2020/7/9 16:03
|
|||
// @Author : 黑白配
|
|||
// @File : index.go
|
|||
// @PackageName:V2
|
|||
// @Description:V2版本接口
|
|||
|
|||
package V2 |
|||
|
|||
import "git.ouxuan.net/3136352472/wxpay/src/entity" |
|||
|
|||
func Init(config entity.PayConfig) *WxPay { |
|||
return &WxPay{config: config} |
|||
} |
@ -0,0 +1,303 @@ |
|||
package V2 |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"crypto/x509" |
|||
"encoding/json" |
|||
"encoding/xml" |
|||
"errors" |
|||
"io" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"git.ouxuan.net/3136352472/wxpay/src/entity" |
|||
"git.ouxuan.net/3136352472/wxpay/utils" |
|||
) |
|||
|
|||
// WxPay 微信支付
|
|||
type WxPay struct { |
|||
config entity.PayConfig |
|||
} |
|||
|
|||
// 网络请求
|
|||
func (c WxPay) request(url string, body io.Reader, cert bool) (map[string]string, error) { |
|||
|
|||
var client http.Client |
|||
|
|||
if cert { |
|||
if err := c.checkClient(); err != nil { |
|||
return nil, err |
|||
} |
|||
// 微信提供的API证书,证书和证书密钥 .pem格式
|
|||
certs, _ := tls.LoadX509KeyPair(c.config.APIClientPath.Cert, c.config.APIClientPath.Key) |
|||
// 微信支付HTTPS服务器证书的根证书 .pem格式
|
|||
rootCa, _ := ioutil.ReadFile(c.config.APIClientPath.Root) |
|||
|
|||
pool := x509.NewCertPool() |
|||
pool.AppendCertsFromPEM(rootCa) |
|||
|
|||
client = http.Client{Transport: &http.Transport{ |
|||
TLSClientConfig: &tls.Config{ |
|||
RootCAs: pool, |
|||
Certificates: []tls.Certificate{certs}, |
|||
InsecureSkipVerify: true, |
|||
}, |
|||
}} |
|||
} |
|||
|
|||
if err := c.checkConfig(); err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
resp, err := client.Post(url, "", body) |
|||
if err == nil { |
|||
defer resp.Body.Close() |
|||
b, _ := ioutil.ReadAll(resp.Body) |
|||
var result PublicResponse |
|||
_ = xml.Unmarshal(b, &result) |
|||
err := result.ResultCheck() |
|||
if err == nil { |
|||
return utils.XML2MAP(b), nil |
|||
} |
|||
return nil, err |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
// 公共参数
|
|||
func (c WxPay) publicParams() (m map[string]interface{}) { |
|||
m = make(map[string]interface{}) |
|||
m["appid"] = c.config.AppID |
|||
m["mch_id"] = c.config.MchID |
|||
m["nonce_str"] = utils.RandomStr() |
|||
if c.config.SubMchID != "" { |
|||
m["sub_mch_id"] = c.config.SubMchID |
|||
} |
|||
if c.config.SubAppID != "" { |
|||
m["sub_appid"] = c.config.SubAppID |
|||
} |
|||
m["sign_type"] = "HMAC-SHA256" |
|||
return |
|||
} |
|||
|
|||
// 检查支付配置
|
|||
func (c WxPay) checkConfig() (err error) { |
|||
|
|||
if c.config.AppID == "" { |
|||
err = errors.New("AppID不能为空") |
|||
} else if c.config.MchID == "" { |
|||
err = errors.New("MchID不能为空") |
|||
} else if c.config.Secret == "" { |
|||
err = errors.New("Secret不能为空") |
|||
} else { |
|||
err = nil |
|||
} |
|||
return |
|||
} |
|||
|
|||
// 检查支付证书
|
|||
func (c WxPay) checkClient() (err error) { |
|||
if c.config.APIClientPath.Cert == "" { |
|||
err = errors.New("APIClientPath.Cert 不能为空") |
|||
} else if c.config.APIClientPath.Key == "" { |
|||
err = errors.New("APIClientPath.Key 不能为空") |
|||
} else if c.config.APIClientPath.Root == "" { |
|||
err = errors.New("APIClientPath.Key 不能为空") |
|||
} else { |
|||
err = nil |
|||
} |
|||
return |
|||
} |
|||
|
|||
// UnifiedOrder 统一下单接口
|
|||
func (c WxPay) UnifiedOrder(params UnifiedOrder) (map[string]string, error) { |
|||
|
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
|
|||
m["notify_url"] = c.config.PayNotify |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
|
|||
return c.request("https://api.mch.weixin.qq.com/pay/unifiedorder", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// WxAppPay 小程序下单接口
|
|||
func (c WxPay) WxAppPay(params UnifiedOrder) (map[string]interface{}, error) { |
|||
params.TradeType = "JSAPI" |
|||
m, err := c.UnifiedOrder(params) |
|||
if err == nil { |
|||
result := make(map[string]interface{}) |
|||
result["appId"] = m["appid"] |
|||
result["nonceStr"] = m["nonce_str"] |
|||
result["package"] = "prepay_id=" + m["prepay_id"] |
|||
result["timeStamp"] = strconv.FormatInt(time.Now().Unix(), 10) |
|||
result["signType"] = "HMAC-SHA256" |
|||
result["paySign"] = utils.SignHMACSHA256(result, c.config.Secret) |
|||
return result, err |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
// WxAppAppPay APP下单接口
|
|||
func (c WxPay) WxAppAppPay(params UnifiedOrder) (map[string]interface{}, error) { |
|||
params.TradeType = "APP" |
|||
m, err := c.UnifiedOrder(params) |
|||
if err == nil { |
|||
result := make(map[string]interface{}) |
|||
result["appid"] = m["appid"] |
|||
result["noncestr"] = m["nonce_str"] |
|||
result["package"] = "Sign=WXPay" |
|||
if params.ProfitSharing != "" { |
|||
result["partnerid"] = m["sub_mch_id"] |
|||
} else { |
|||
result["partnerid"] = m["mch_id"] |
|||
} |
|||
result["prepayid"] = m["prepay_id"] |
|||
result["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) |
|||
result["paySign"] = utils.SignHMACSHA256(result, c.config.Secret) |
|||
return result, err |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
// WxH5Pay H5下单接口 SceneInfo 场景信息需使用json字符串
|
|||
func (c WxPay) WxH5Pay(params UnifiedOrder) (map[string]interface{}, error) { |
|||
params.TradeType = "MWEB" |
|||
m, err := c.UnifiedOrder(params) |
|||
if err == nil { |
|||
result := make(map[string]interface{}) |
|||
result["mweburl"] = m["mweb_url"] |
|||
return result, err |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
// Micropay 付款码支付
|
|||
func (c WxPay) Micropay(params Micropay) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/micropay", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// CloseOrder 关闭订单
|
|||
func (c WxPay) CloseOrder(outTradeNo string) (map[string]string, error) { |
|||
m := c.publicParams() |
|||
m["out_trade_no"] = outTradeNo |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/closeorder", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// ReverseOrder 撤销订单
|
|||
func (c WxPay) ReverseOrder(params ReverseOrder) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/reverse", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// OrderQuery 查询订单
|
|||
func (c WxPay) OrderQuery(params OrderQuery) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/orderquery", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// Refund 申请退款
|
|||
func (c WxPay) Refund(params Refund) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["notify_url"] = c.config.RefundNotify |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/refund", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// RefundQuery 查询退款
|
|||
func (c WxPay) RefundQuery(params RefundQuery) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/refundquery", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// ProfitSharingAddReceiver 添加分账接受方
|
|||
func (c WxPay) ProfitSharingAddReceiver(params Receiver) (map[string]string, error) { |
|||
m := c.publicParams() |
|||
b, _ := json.Marshal(params) |
|||
m["receiver"] = string(b) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// ProfitSharingRemoveReceiver 删除分账接收方
|
|||
func (c WxPay) ProfitSharingRemoveReceiver(params Receiver) (map[string]string, error) { |
|||
m := c.publicParams() |
|||
b, _ := json.Marshal(params) |
|||
m["receiver"] = string(b) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/profitsharingremovereceiver", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// ProfitSharingFinish 完成分账
|
|||
func (c WxPay) ProfitSharingFinish(params ProfitSharingFinish) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
delete(m, "sub_appid") |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// ProfitSharing 开始分账 默认为单次分账 option=multi为多次分账
|
|||
func (c WxPay) ProfitSharing(params ProfitSharing, option string) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
b, _ := json.Marshal(params.Receivers) |
|||
m["receivers"] = string(b) |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
if option == "multi" { |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/multiprofitsharing", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/profitsharing", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// ProfitSharingQuery 查询分账结果
|
|||
func (c WxPay) ProfitSharingQuery(params ProfitSharingQuery) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
delete(m, "appid") |
|||
delete(m, "sub_appid") |
|||
|
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/profitsharingquery", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// ProfitSharingReturn 分账回退
|
|||
func (c WxPay) ProfitSharingReturn(params ProfitSharingReturn) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
m["return_account_type"] = "MERCHANT_ID" |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/secapi/pay/profitsharingreturn", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// ProfitSharingReturnQuery 分账回退结果查询
|
|||
func (c WxPay) ProfitSharingReturnQuery(params ProfitSharingReturnQuery) (map[string]string, error) { |
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
delete(m, "sub_appid") |
|||
m["sign"] = utils.SignHMACSHA256(m, c.config.Secret) |
|||
return c.request("https://api.mch.weixin.qq.com/pay/profitsharingreturnquery", strings.NewReader(utils.MAP2XML(m)), false) |
|||
} |
|||
|
|||
// Transfers 企业付款到零钱
|
|||
func (c WxPay) Transfers(params Transfers) (map[string]string, error) { |
|||
|
|||
m := utils.MAPMerge(utils.Struct2Map(params), c.publicParams()) |
|||
delete(m, "appid") |
|||
delete(m, "mch_id") |
|||
delete(m, "sign_type") |
|||
|
|||
m["mchid"] = c.config.MchID |
|||
m["mch_appid"] = c.config.AppID |
|||
m["sign"] = utils.SignMD5(m, c.config.Secret) |
|||
|
|||
return c.request("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers", strings.NewReader(utils.MAP2XML(m)), true) |
|||
} |
|||
|
|||
// NotifyFormat 通知数据解析
|
|||
func NotifyFormat(data string) map[string]string { |
|||
return utils.XML2MAP([]byte(data)) |
|||
} |
@ -0,0 +1,240 @@ |
|||
package V2 |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/global" |
|||
"log" |
|||
"testing" |
|||
) |
|||
|
|||
var pay = WxPay{ |
|||
global.V2, |
|||
} |
|||
|
|||
func TestWxPay_UnifiedOrder(t *testing.T) { |
|||
if result, err := pay.UnifiedOrder(UnifiedOrder{ |
|||
Attach: "支付测试", |
|||
OutTradeNo: "11111111111114", |
|||
TotalFee: 1, |
|||
SpbillCreateIP: "127.0.0.1", |
|||
OpenID: "owJNp5PDj8lja9S3m2l2M_jt3aHY", |
|||
Receipt: "Y", |
|||
Body: "测试", |
|||
TradeType: "JSAPI", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_WxAppPay(t *testing.T) { |
|||
if result, err := pay.WxAppPay(UnifiedOrder{ |
|||
Attach: "支付测试", |
|||
OutTradeNo: "11111111111115 ", |
|||
TotalFee: 1, |
|||
SpbillCreateIP: "127.0.0.1", |
|||
OpenID: "owJNp5PDj8lja9S3m2l2M_jt3aHY", |
|||
Receipt: "Y", |
|||
Body: "测试", |
|||
TradeType: "JSAPI", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_Micropay(t *testing.T) { |
|||
if result, err := pay.Micropay(Micropay{ |
|||
Attach: "支付测试", |
|||
OutTradeNo: "11111111111115", |
|||
TotalFee: 1, |
|||
SpbillCreateIP: "127.0.0.1", |
|||
Receipt: "Y", |
|||
Body: "测试", |
|||
AuthCode: "12312312312", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_CloseOrder(t *testing.T) { |
|||
if result, err := pay.CloseOrder("11111111111112"); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ReverseOrder(t *testing.T) { |
|||
if result, err := pay.ReverseOrder(ReverseOrder{ |
|||
OutTradeNo: "11111111111112", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_OrderQuery(t *testing.T) { |
|||
if result, err := pay.OrderQuery(OrderQuery{ |
|||
OutTradeNo: "674BB66E408A6931788347BF25E9BCAA", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_Refund(t *testing.T) { |
|||
if result, err := pay.Refund(Refund{ |
|||
OutTradeNo: "BC9AA9B9E43C13E7932CB3B181468A4F", |
|||
TotalFee: 990, |
|||
RefundFee: 990, |
|||
OutRefundNo: "BC9AA9B9E43C13E7932CB3B181468A4F", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_RefundQuery(t *testing.T) { |
|||
if result, err := pay.RefundQuery(RefundQuery{ |
|||
OutTradeNo: "11111111111113", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingAddReceiver(t *testing.T) { |
|||
if result, err := pay.ProfitSharingAddReceiver(Receiver{ |
|||
Type: "PERSONAL_OPENID", |
|||
Account: "owJNp5M6Trxg5qBjh8KTPnTm65Sg", |
|||
RelationType: "DISTRIBUTOR", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingRemoveReceiver(t *testing.T) { |
|||
if result, err := pay.ProfitSharingRemoveReceiver(Receiver{ |
|||
Type: "PERSONAL_OPENID", |
|||
Account: "owJNp5PDj8lja9S3m2l2M_jt3aHY", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingFinish(t *testing.T) { |
|||
if result, err := pay.ProfitSharingFinish(ProfitSharingFinish{ |
|||
TransactionID: "11111", |
|||
OutOrderNo: "111111", |
|||
Description: "分账完成", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharing(t *testing.T) { |
|||
if result, err := pay.ProfitSharing(ProfitSharing{ |
|||
TransactionID: "11111", |
|||
OutOrderNo: "111111", |
|||
Receivers: []Receiver{{Type: "PERSONAL_OPENID", Account: "owJNp5PDj8lja9S3m2l2M_jt3aHY", Amount: 1, Description: "个人分成"}}, |
|||
}, "multi"); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingQuery(t *testing.T) { |
|||
if result, err := pay.ProfitSharingQuery(ProfitSharingQuery{ |
|||
TransactionID: "11111", |
|||
OutOrderNo: "111111", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingReturn(t *testing.T) { |
|||
if result, err := pay.ProfitSharingReturn(ProfitSharingReturn{ |
|||
OutOrderNo: "1111111", |
|||
OutReturnNo: "1111112", |
|||
ReturnAccount: "12312312", |
|||
ReturnAmount: 100, |
|||
Description: "回退", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestWxPay_ProfitSharingReturnQuery(t *testing.T) { |
|||
if result, err := pay.ProfitSharingReturnQuery(ProfitSharingReturnQuery{ |
|||
OutOrderNo: "1111111", |
|||
OutReturnNo: "1111112", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestNotifyDataFormat(t *testing.T) { |
|||
x := `<xml> |
|||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid> |
|||
<attach><![CDATA[支付测试]]></attach> |
|||
<bank_type><![CDATA[CFT]]></bank_type> |
|||
<fee_type><![CDATA[CNY]]></fee_type> |
|||
<is_subscribe><![CDATA[Y]]></is_subscribe> |
|||
<mch_id><![CDATA[10000100]]></mch_id> |
|||
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str> |
|||
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid> |
|||
<out_trade_no><![CDATA[1409811653]]></out_trade_no> |
|||
<result_code><![CDATA[SUCCESS]]></result_code> |
|||
<return_code><![CDATA[SUCCESS]]></return_code> |
|||
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign> |
|||
<time_end><![CDATA[20140903131540]]></time_end> |
|||
<total_fee>1</total_fee> |
|||
<coupon_fee><![CDATA[10]]></coupon_fee> |
|||
<coupon_count><![CDATA[1]]></coupon_count> |
|||
<coupon_type><![CDATA[CASH]]></coupon_type> |
|||
<coupon_id><![CDATA[10000]]></coupon_id> |
|||
<coupon_fee><![CDATA[100]]></coupon_fee> |
|||
<trade_type><![CDATA[JSAPI]]></trade_type> |
|||
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id> |
|||
</xml>` |
|||
m := NotifyFormat(x) |
|||
|
|||
log.Println(m) |
|||
|
|||
} |
|||
|
|||
func TestWxPay_Transfers(t *testing.T) { |
|||
|
|||
if result, err := pay.Transfers(Transfers{ |
|||
PartnerTradeNo: "1", |
|||
OpenID: "owJNp5PDj8lja9S3m2l2M_jt3aHY", |
|||
CheckName: "NO_CHECK", |
|||
Amount: 100, |
|||
Desc: "付款测试", |
|||
}); err == nil { |
|||
log.Println(result) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
// @Time : 2020/7/8 16:04
|
|||
// @Author : 黑白配
|
|||
// @File : index.go
|
|||
// @PackageName:V3
|
|||
// @Description:V3版本接口
|
|||
|
|||
package V3 |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/src/V3/smartGuide" |
|||
"git.ouxuan.net/3136352472/wxpay/src/config" |
|||
) |
|||
|
|||
type API struct { |
|||
SmartGuide *smartGuide.SmartGuide |
|||
} |
|||
|
|||
func Init(params *config.V3) *API { |
|||
return &API{SmartGuide: smartGuide.New(params)} |
|||
} |
@ -0,0 +1,64 @@ |
|||
// @Time : 2020/7/8 14:38
|
|||
// @Author : 黑白配
|
|||
// @File : entity.go
|
|||
// @PackageName:smartGuide
|
|||
// @Description:entity
|
|||
|
|||
package smartGuide |
|||
|
|||
import "git.ouxuan.net/3136352472/wxpay/src/config" |
|||
|
|||
// Register 注册服务人员参数
|
|||
type Register struct { |
|||
SubMchID string `json:"sub_mchid,omitempty"` // 子商户ID
|
|||
CorpID string `json:"corpid,omitempty"` // 企业ID
|
|||
StoreID int `json:"store_id,omitempty"` // 门店ID
|
|||
Name string `json:"name,omitempty"` // 员工姓名
|
|||
UserId string `json:"userid,omitempty"` // 企业微信的员工ID
|
|||
Mobile string `json:"mobile,omitempty"` // 手机号码
|
|||
QRCode string `json:"qr_code,omitempty"` // 二维码
|
|||
Avatar string `json:"avatar,omitempty"` // 头像
|
|||
} |
|||
|
|||
// RegisterResult 注册服务人员返回数据
|
|||
type RegisterResult struct { |
|||
GuideID string `json:"guide_id,omitempty"` // 服务人员ID
|
|||
config.Result |
|||
} |
|||
|
|||
// Assign 服务人员分配参数
|
|||
type Assign struct { |
|||
GuideID string `json:"guide_id,omitempty"` // 服务人员ID
|
|||
SubMchID string `json:"sub_mchid,omitempty"` // 子商户ID
|
|||
OutTradeNo string `json:"out_trade_no,omitempty"` // 商户订单号
|
|||
} |
|||
|
|||
// Query 查询参数
|
|||
type Query struct { |
|||
SubMchID string `json:"sub_mchid,omitempty"` // 子商户ID
|
|||
StoreID int `json:"store_id,omitempty"` // 门店ID
|
|||
UserId string `json:"userid,omitempty"` // 企业微信的员工ID
|
|||
Mobile string `json:"mobile,omitempty"` // 手机号码
|
|||
WorkID string `json:"work_id,omitempty"` // 工号
|
|||
Limit int `json:"limit,omitempty"` // 最大资源条数
|
|||
Offset int `json:"offset,omitempty"` // 请求资源起始位置
|
|||
} |
|||
|
|||
// UserInfo 服务人员信息
|
|||
type UserInfo struct { |
|||
GuideID string `json:"guide_id,omitempty"` // 服务人员ID
|
|||
StoreID int `json:"store_id,omitempty"` // 门店ID
|
|||
Name string `json:"name,omitempty"` // 员工姓名
|
|||
UserId string `json:"userid,omitempty"` // 企业微信的员工ID
|
|||
Mobile string `json:"mobile,omitempty"` // 手机号码
|
|||
WorkID string `json:"work_id,omitempty"` // 工号
|
|||
} |
|||
|
|||
// QueryResult 查询结果
|
|||
type QueryResult struct { |
|||
Data []UserInfo `json:"data,omitempty"` // 服务人员列表
|
|||
TotalCount int `json:"total_count,omitempty"` // 服务人员数量
|
|||
Limit int `json:"limit,omitempty"` // 最大资源条数
|
|||
Offset int `json:"offset,omitempty"` // 请求资源起始位置
|
|||
config.Result |
|||
} |
@ -0,0 +1,47 @@ |
|||
// @Time : 2020/7/8 14:26
|
|||
// @Author : 黑白配
|
|||
// @File : index.go
|
|||
// @PackageName:smartGuide
|
|||
// @Description: 支付即服务
|
|||
|
|||
package smartGuide |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/src/config" |
|||
"git.ouxuan.net/3136352472/wxpay/utils" |
|||
) |
|||
|
|||
func New(config *config.V3) *SmartGuide { |
|||
return &SmartGuide{config: config} |
|||
} |
|||
|
|||
type SmartGuide struct { |
|||
config *config.V3 |
|||
} |
|||
|
|||
// Register 服务人员注册
|
|||
//
|
|||
// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/smartguide/chapter3_1.shtml
|
|||
func (m SmartGuide) Register(register Register) (result RegisterResult, err error) { |
|||
err = m.config.Request("/v3/smartguide/guides", register, &result) |
|||
return |
|||
} |
|||
|
|||
// Assign 服务人员分配
|
|||
//
|
|||
// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/smartguide/chapter3_1.shtml
|
|||
func (m SmartGuide) Assign(assign Assign) (result interface{}, err error) { |
|||
id := assign.GuideID |
|||
assign.GuideID = "" |
|||
err = m.config.Request("/v3/smartguide/guides/"+id+"/assign", assign, nil) |
|||
return |
|||
} |
|||
|
|||
// Query 服务人员查询
|
|||
//
|
|||
// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/smartguide/chapter3_3.shtml
|
|||
func (m SmartGuide) Query(query Query) (result QueryResult, err error) { |
|||
params := utils.SortKey(utils.Struct2Map(query)) |
|||
err = m.config.Request("/v3/smartguide/guides?"+params, nil, &result) |
|||
return |
|||
} |
@ -0,0 +1,49 @@ |
|||
// @Time : 2020/7/8 14:49
|
|||
// @Author : 黑白配
|
|||
// @File : index_test.go
|
|||
// @PackageName:smartGuide
|
|||
// @Description:
|
|||
|
|||
package smartGuide |
|||
|
|||
import ( |
|||
"git.ouxuan.net/3136352472/wxpay/global" |
|||
"testing" |
|||
) |
|||
|
|||
var smartGuide = New(&global.V3) |
|||
|
|||
func TestSmartGuide_Query(t *testing.T) { |
|||
if data, err := smartGuide.Query(Query{StoreID: 20774227}); err == nil { |
|||
t.Log(data) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestSmartGuide_Assign(t *testing.T) { |
|||
if data, err := smartGuide.Assign(Assign{ |
|||
GuideID: "01007196110000000001", |
|||
OutTradeNo: "8319B95CFEDE8F60A77A5BDE4E359E01", |
|||
}); err == nil { |
|||
t.Log(data) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
|||
|
|||
func TestSmartGuide_Register(t *testing.T) { |
|||
if data, err := smartGuide.Register(Register{ |
|||
CorpID: "123", |
|||
StoreID: 20774227, |
|||
Name: "test", |
|||
UserId: "123", |
|||
Mobile: "123", |
|||
QRCode: "123", |
|||
Avatar: "123", |
|||
}); err == nil { |
|||
t.Log(data) |
|||
} else { |
|||
t.Error(err) |
|||
} |
|||
} |
@ -0,0 +1,119 @@ |
|||
// @Time : 2020/7/9 11:14
|
|||
// @Author : 黑白配
|
|||
// @File : index
|
|||
// @PackageName:public
|
|||
// @Description:
|
|||
|
|||
package config |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto" |
|||
"crypto/rand" |
|||
"crypto/rsa" |
|||
"crypto/sha256" |
|||
"crypto/x509" |
|||
"encoding/base64" |
|||
"encoding/json" |
|||
"encoding/pem" |
|||
"errors" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"time" |
|||
|
|||
"git.ouxuan.net/3136352472/wxpay/utils" |
|||
) |
|||
|
|||
type V3 struct { |
|||
MchID string `json:"mchid"` // 商户ID
|
|||
ClientKeyPath string `json:"clientKeyPath"` // 证书私钥路径
|
|||
SerialNo string `json:"serialNo"` // 证书编号
|
|||
} |
|||
|
|||
func (m V3) Request(api string, body interface{}, out interface{}) error { |
|||
|
|||
var request *http.Request |
|||
client := http.Client{} |
|||
|
|||
host := "https://api.mch.weixin.qq.com" |
|||
if body != nil { |
|||
b, _ := json.Marshal(body) |
|||
request, _ = http.NewRequest("POST", host+api, bytes.NewReader(b)) |
|||
} else { |
|||
request, _ = http.NewRequest("GET", host+api, nil) |
|||
} |
|||
sign, err := m.Sign(api, body) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
request.Header.Add("Accept", "application/json") |
|||
request.Header.Add("Content-Type", "application/json") |
|||
request.Header.Add("Authorization", sign) |
|||
if resp, err := client.Do(request); err == nil { |
|||
defer resp.Body.Close() |
|||
b, _ := ioutil.ReadAll(resp.Body) |
|||
if len(b) > 0 && out != nil { |
|||
return json.Unmarshal(b, out) |
|||
} else { |
|||
return nil |
|||
} |
|||
} else { |
|||
return err |
|||
} |
|||
} |
|||
|
|||
// Sign 签名
|
|||
// @param method string 请求类型
|
|||
// @param url string 请求地址
|
|||
// @param body interface 请求数据
|
|||
func (m V3) Sign(url string, body interface{}) (string, error) { |
|||
var data = "" |
|||
var method = "GET" |
|||
t := time.Now().Unix() |
|||
randomStr := utils.RandomStr() |
|||
|
|||
if body != nil { |
|||
b, _ := json.Marshal(body) |
|||
data = string(b) |
|||
method = "POST" |
|||
} |
|||
|
|||
str := fmt.Sprintf("%s\n%s\n%d\n%s\n%s\n", method, url, t, randomStr, data) |
|||
key, _ := ioutil.ReadFile(m.ClientKeyPath) |
|||
|
|||
sign, err := m.rsaEncrypt([]byte(str), key) |
|||
|
|||
return fmt.Sprintf("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\","+ |
|||
"signature=\"%s\",timestamp=\"%d\",serial_no=\"%s\"", m.MchID, randomStr, sign, t, m.SerialNo), err |
|||
} |
|||
|
|||
// 私钥加密
|
|||
func (m V3) rsaEncrypt(data, keyBytes []byte) (string, error) { |
|||
//解密pem格式的私钥
|
|||
block, _ := pem.Decode(keyBytes) |
|||
if block == nil { |
|||
return "", errors.New("public key error") |
|||
} |
|||
// 解析私钥
|
|||
pubInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes) |
|||
if err != nil { |
|||
return "", err |
|||
} |
|||
// 类型断言
|
|||
pub := pubInterface.(*rsa.PrivateKey) |
|||
//加密
|
|||
msg := sha256.Sum256(data) |
|||
sign, err := rsa.SignPKCS1v15(rand.Reader, pub, crypto.SHA256, msg[:]) |
|||
if err != nil { |
|||
return "", err |
|||
} |
|||
|
|||
return base64.StdEncoding.EncodeToString(sign), nil |
|||
} |
|||
|
|||
type Result struct { |
|||
Code string `json:"code,omitempty"` // 错误代码
|
|||
Message string `json:"message,omitempty"` // 错误信息
|
|||
} |
@ -0,0 +1,33 @@ |
|||
// @Time : 2020/7/9 16:51
|
|||
// @Author : 黑白配
|
|||
// @File : index
|
|||
// @PackageName:entity
|
|||
// @Description:
|
|||
|
|||
package entity |
|||
|
|||
type PayConfig struct { |
|||
AppID string // 商户/服务商 AppId(公众号/小程序)
|
|||
MchID string // 商户/服务商 商户号
|
|||
SubAppID string // 子商户公众号ID
|
|||
SubMchID string // 子商户商户号
|
|||
PayNotify string // 支付结果回调地址
|
|||
RefundNotify string // 退款结果回调地址
|
|||
Secret string // 微信支付密钥
|
|||
APIClientPath APIClientPath // API证书路径,使用V3接口必传
|
|||
SerialNo string // 证书编号,使用V3接口必传
|
|||
} |
|||
|
|||
// APIClientPath 微信支付API证书
|
|||
type APIClientPath struct { |
|||
Cert string // 证书路径
|
|||
Key string // 私钥证书路径,使用V3接口必传
|
|||
Root string // 根证书路径
|
|||
} |
|||
|
|||
// APIClientData 微信支付API证书
|
|||
type APIClientData struct { |
|||
Cert []byte // 证书路径
|
|||
Key []byte // 私钥证书路径,使用V3接口必传
|
|||
Root []byte // 根证书路径
|
|||
} |
@ -0,0 +1,151 @@ |
|||
package utils |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/hmac" |
|||
"crypto/md5" |
|||
"crypto/sha256" |
|||
"encoding/hex" |
|||
"encoding/json" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"sort" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
) |
|||
|
|||
// Struct2Map 结构体转map
|
|||
func Struct2Map(params interface{}) (m map[string]interface{}) { |
|||
m = make(map[string]interface{}) |
|||
|
|||
if b, err := json.Marshal(params); err == nil { |
|||
_ = json.Unmarshal(b, &m) |
|||
} |
|||
return |
|||
} |
|||
|
|||
// SortKey map排序
|
|||
func SortKey(m map[string]interface{}) string { |
|||
arr := make([]string, 0) |
|||
for k := range m { |
|||
arr = append(arr, k) |
|||
} |
|||
|
|||
strArr := make([]string, 0) |
|||
|
|||
for _, key := range arr { |
|||
switch m[key].(type) { |
|||
case string: |
|||
value := m[key].(string) |
|||
if value != "" { |
|||
strArr = append(strArr, key+"="+value) |
|||
} |
|||
case int: |
|||
if m[key] != 0 { |
|||
value := strconv.Itoa(m[key].(int)) |
|||
strArr = append(strArr, key+"="+value) |
|||
} |
|||
case float64: |
|||
if m[key] != 0.00 { |
|||
value := strconv.Itoa(int(m[key].(float64))) |
|||
strArr = append(strArr, key+"="+value) |
|||
} |
|||
} |
|||
} |
|||
|
|||
sort.Strings(strArr) |
|||
return strings.Join(strArr, "&") |
|||
} |
|||
|
|||
// MAP2XML map转xml
|
|||
func MAP2XML(m map[string]interface{}) string { |
|||
str := "" |
|||
for k, v := range m { |
|||
switch v.(type) { |
|||
case string: |
|||
str = str + fmt.Sprintf("<%s><![CDATA[%s]]></%s>", k, v, k) |
|||
case int: |
|||
str = str + fmt.Sprintf("<%s><![CDATA[%d]]></%s>", k, v, k) |
|||
case interface{}: |
|||
b, _ := json.Marshal(v) |
|||
str = str + fmt.Sprintf("<%s><![CDATA[%s]]></%s>", k, string(b), k) |
|||
} |
|||
} |
|||
return "<xml>" + str + "</xml>" |
|||
} |
|||
|
|||
// MAPMerge map合并
|
|||
func MAPMerge(args ...map[string]interface{}) (m map[string]interface{}) { |
|||
m = make(map[string]interface{}) |
|||
for _, item := range args { |
|||
for k, v := range item { |
|||
m[k] = v |
|||
} |
|||
} |
|||
return m |
|||
} |
|||
|
|||
// XML2MAP xml转map
|
|||
func XML2MAP(b []byte) (m map[string]string) { |
|||
|
|||
decoder := xml.NewDecoder(bytes.NewReader(b)) |
|||
m = make(map[string]string) |
|||
tag := "" |
|||
for { |
|||
token, err := decoder.Token() |
|||
|
|||
if err != nil { |
|||
break |
|||
} |
|||
switch t := token.(type) { |
|||
case xml.StartElement: |
|||
if t.Name.Local != "xml" { |
|||
tag = t.Name.Local |
|||
} else { |
|||
tag = "" |
|||
} |
|||
break |
|||
case xml.EndElement: |
|||
break |
|||
case xml.CharData: |
|||
data := strings.TrimSpace(string(t)) |
|||
if len(data) != 0 { |
|||
m[tag] = data |
|||
} |
|||
break |
|||
} |
|||
} |
|||
return |
|||
} |
|||
|
|||
// RandomStr 随机字符串
|
|||
func RandomStr() string { |
|||
return strings.ToUpper(MD5(strconv.FormatInt(time.Now().UnixNano(), 19))) |
|||
} |
|||
|
|||
// MD5 md5加密
|
|||
func MD5(str string) string { |
|||
hash := md5.Sum([]byte(str)) |
|||
md5str := fmt.Sprintf("%x", hash) |
|||
return strings.ToUpper(md5str) |
|||
} |
|||
|
|||
// Sign 生成签名 HMAC-SHA256加密
|
|||
func SignHMACSHA256(m map[string]interface{}, key string) (sign string) { |
|||
sign = HmacSha256(SortKey(m)+"&key="+key, key) |
|||
return |
|||
} |
|||
|
|||
// SignMD5 生成签名 MD5加密
|
|||
func SignMD5(m map[string]interface{}, key string) (sign string) { |
|||
sign = MD5(SortKey(m) + "&key=" + key) |
|||
return |
|||
} |
|||
|
|||
// HmacSha256 HMAC-SHA256加密
|
|||
func HmacSha256(str string, key string) string { |
|||
h := hmac.New(sha256.New, []byte(key)) |
|||
h.Write([]byte(str)) |
|||
return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue