Browse Source

init

master
public_host 3 years ago
parent
commit
bf088e6455
  1. 1
      .gitignore
  2. 21
      LICENSE
  3. 131
      README.md
  4. 2
      doc.go
  5. 34
      global/index.go
  6. 34
      main.go
  7. 38
      main_test.go
  8. 222
      src/V2/entity.go
  9. 13
      src/V2/index.go
  10. 303
      src/V2/pay.go
  11. 240
      src/V2/pay_test.go
  12. 20
      src/V3/index.go
  13. 64
      src/V3/smartGuide/entity.go
  14. 47
      src/V3/smartGuide/index.go
  15. 49
      src/V3/smartGuide/index_test.go
  16. 119
      src/config/index.go
  17. 33
      src/entity/index.go
  18. 151
      utils/public.go

1
.gitignore

@ -0,0 +1 @@
.idea

21
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.

131
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 {
}
```

2
doc.go

@ -0,0 +1,2 @@
// Package pay 微信支付SDK,支持V2/V3版商户和服务商接口,持续更新
package WXPay

34
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: "",
}

34
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 = &params
return
}

38
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)
}
}

222
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"` // 企业付款备注,必填。注意:备注中的敏感词会被转成字符*
}

13
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}
}

303
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))
}

240
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 := `<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)
}
}

20
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)}
}

64
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
}

47
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
}

49
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)
}
}

119
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"` // 错误信息
}

33
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 // 根证书路径
}

151
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><![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)))
}
Loading…
Cancel
Save