diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e0414a6
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
index 7d597b3..7adce98 100644
--- a/README.md
+++ b/README.md
@@ -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 {
+}
+```
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..fa8bdd6
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,2 @@
+// Package pay 微信支付SDK,支持V2/V3版商户和服务商接口,持续更新
+package WXPay
diff --git a/global/index.go b/global/index.go
new file mode 100644
index 0000000..6ffaa4c
--- /dev/null
+++ b/global/index.go
@@ -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: "",
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..3c641d0
--- /dev/null
+++ b/main.go
@@ -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
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..aa230ed
--- /dev/null
+++ b/main_test.go
@@ -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)
+ }
+}
diff --git a/src/V2/entity.go b/src/V2/entity.go
new file mode 100644
index 0000000..81be88e
--- /dev/null
+++ b/src/V2/entity.go
@@ -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"` // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
+}
diff --git a/src/V2/index.go b/src/V2/index.go
new file mode 100644
index 0000000..54e0457
--- /dev/null
+++ b/src/V2/index.go
@@ -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}
+}
diff --git a/src/V2/pay.go b/src/V2/pay.go
new file mode 100644
index 0000000..cd05a4d
--- /dev/null
+++ b/src/V2/pay.go
@@ -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))
+}
diff --git a/src/V2/pay_test.go b/src/V2/pay_test.go
new file mode 100644
index 0000000..b258ca2
--- /dev/null
+++ b/src/V2/pay_test.go
@@ -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 := `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+ `
+ 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)
+ }
+}
diff --git a/src/V3/index.go b/src/V3/index.go
new file mode 100644
index 0000000..c09c720
--- /dev/null
+++ b/src/V3/index.go
@@ -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)}
+}
diff --git a/src/V3/smartGuide/entity.go b/src/V3/smartGuide/entity.go
new file mode 100644
index 0000000..5e163d3
--- /dev/null
+++ b/src/V3/smartGuide/entity.go
@@ -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
+}
diff --git a/src/V3/smartGuide/index.go b/src/V3/smartGuide/index.go
new file mode 100644
index 0000000..43dfcec
--- /dev/null
+++ b/src/V3/smartGuide/index.go
@@ -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
+}
diff --git a/src/V3/smartGuide/index_test.go b/src/V3/smartGuide/index_test.go
new file mode 100644
index 0000000..486e913
--- /dev/null
+++ b/src/V3/smartGuide/index_test.go
@@ -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)
+ }
+}
diff --git a/src/config/index.go b/src/config/index.go
new file mode 100644
index 0000000..ec84c70
--- /dev/null
+++ b/src/config/index.go
@@ -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"` // 错误信息
+}
diff --git a/src/entity/index.go b/src/entity/index.go
new file mode 100644
index 0000000..449e667
--- /dev/null
+++ b/src/entity/index.go
@@ -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 // 根证书路径
+}
diff --git a/utils/public.go b/utils/public.go
new file mode 100644
index 0000000..c00c02b
--- /dev/null
+++ b/utils/public.go
@@ -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>%s>", k, v, k)
+ case int:
+ str = str + fmt.Sprintf("<%s>%s>", k, v, k)
+ case interface{}:
+ b, _ := json.Marshal(v)
+ str = str + fmt.Sprintf("<%s>%s>", k, string(b), k)
+ }
+ }
+ return "" + str + ""
+}
+
+// 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)))
+}