/payjs

个人支付收款解决方案PayJS的Golang版本SDK

Primary LanguageGoMIT LicenseMIT

PayJS SDK for Go

Go Report Card

使用Golang开发的PayJS SDK,简单、易用。

PayJS是支付宝与微信支付个人接口解决方案,感兴趣的可以去官网看下。

这里是SDK的演示地址:https://payjs.qingwuguo.com(最近接到阿里通知要大检查,虽然没啥违规的东西,但是还有有点怕,所以先关闭演示地址,过些日子放出来)

求助:此SDK签名验证算法与微信相同。但是如果碰到[]string等,或多维结构体,例如JSAPI支付接口的Response,则无法正确签名验证。该如何处理呢? issue链接

TODO

  • JSAPI支付签名验证失败BUG(为了正常使用,暂取消验证报错)
  • 获取用户资料验证失败BUG(为了正常使用,暂取消验证报错)
  • 获取异步通知服务器IP列表验证失败BUG(为了正常使用,暂取消验证报错)
  • JSAPI支付演示(没有设置JSAPI支付目录暂无法完成)
  • 小程序支付演示(没有申请小程序暂无法完成)
  • 人脸支付测试及演示(没有硬件设备暂无法完成)
  • 演示程序还有一些细节需要完成(完成50%)

获取

go get github.com/qingwg/payjs

基本配置及初始化

下面的是伪代码,请自行理解

payjsConfig := &payjs.Config{
    Key:       "PayJS的通信密钥",
    MchID:     "PayJS的商户号",
    NotifyUrl: "异步通知的路由",
}
Pay = payjs.New(payjsConfig)

基本API使用

扫码支付

下面的是伪代码,请自行理解

type Request struct {
    TotalFee     int64  `json:"total_fee"`       //Y	金额。单位:分
    Body         string `json:"body"`            //N	订单标题
    Attach       string `json:"attach"`          //N	用户自定义数据,在notify的时候会原样返回
    OutTradeNo   string `json:"out_trade_no"`    //Y	用户端自主生成的订单号
    Type         string `json:"type"`            //N	留空表示微信支付。支付宝交易传值:alipay
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	1:请求成功,0:请求失败
    Msg          string `json:"msg"`            //N	return_code为0时返回的错误消息
    ReturnMsg    string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    OutTradeNo   string `json:"out_trade_no"`   //Y	用户生成的订单号原样返回
    TotalFee     int64  `json:"total_fee"`      //Y	金额。单位:分
    Qrcode       string `json:"qrcode"`         //Y	二维码图片地址
    CodeUrl      string `json:"code_url"`       //Y	可将该参数生成二维码展示出来进行扫码支付
    Status       int    `json:"status"`         //Y	0:未支付,1:支付成功(官方表示此参数以后会取消)
    Sign         string `json:"sign"`           //Y	数据签名 详见签名算法
}
PayNative := Pay.GetNative()
Response, err := PayNative.Create(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.Type)

官方文档:扫码支付

付款码支付

下面的是伪代码,请自行理解

type Request struct {
    TotalFee     int64  `json:"total_fee"`       //Y	金额。单位:分
    Body         string `json:"body"`            //N	订单标题
    Attach       string `json:"attach"`          //N	用户自定义数据,在notify的时候会原样返回
    OutTradeNo   string `json:"out_trade_no"`    //Y	用户端自主生成的订单号
    AuthCode     string `json:"auth_code"`       //Y	扫码支付授权码,设备读取用户微信中的条码或者二维码信息(注:用户刷卡条形码规则:18位纯数字,以10、11、12、13、14、15开头)
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	1:请求成功,0:请求失败
    Msg          string `json:"msg"`            //N	return_code为0时返回的错误消息
    ReturnMsg    string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    OutTradeNo   string `json:"out_trade_no"`   //Y	用户生成的订单号原样返回
    TotalFee     int64  `json:"total_fee"`      //Y	金额。单位:分
    Status       int    `json:"status"`         //Y	0:未支付,1:支付成功(官方表示此参数以后会取消)
    Sign         string `json:"sign"`           //Y	数据签名 详见签名算法
}
PayMicropay := Pay.GetMicropay()
Response, err := PayMicropay.Create(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.AuthCode)

这里需要注意。在需要等待用户输入密码的情况下,该接口PayJS返回的ReturnCode也是0,但PayJSOrderID会返回。 需要自行拿返回中的PayJSOrderID通过订单check接口检查订单状态,30秒后则超时,订单不能被支付,订单状态变为 未支付 状态

官方文档:付款码支付

收银台支付

下面的是伪代码,请自行理解

type Request struct {
    TotalFee     int64  `json:"total_fee"`       //Y	金额。单位:分
    Body         string `json:"body"`            //N	订单标题
    Attach       string `json:"attach"`          //N	用户自定义数据,在notify的时候会原样返回
    OutTradeNo   string `json:"out_trade_no"`    //Y	用户端自主生成的订单号
    CallbackUrl  string `json:"callback_url"`    //N	用户支付成功后,前端跳转地址。留空则支付后关闭webview
    Auto         int    `json:"auto"`            //N	auto=1:无需点击支付按钮,自动发起支付。默认手动点击发起支付(这里官方文档虽然是bool类型,但是如果传true是没用的,必须传1,所以我这里改成了int类型)
    Hide         int    `json:"hide"`            //N	hide=1:隐藏收银台背景界面。默认显示背景界面(这里hide为1时,自动忽略auto参数)(这里官方文档虽然是bool类型,但是如果传true是没用的,必须传1,所以我这里改成了int类型)
}
PayCashier := Pay.GetCashier()
requestUrl, err := PayCashier.GetRequestUrl(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.CallbackUrl, Request.Auto, Request.Hide)

官方文档:收银台支付

JSAPI支付

注意:签名验证有bug,暂取消验证报错

下面的是伪代码,请自行理解

type Request struct {
    TotalFee   int64  `json:"total_fee"`    //Y	金额。单位:分
    OutTradeNo string `json:"out_trade_no"` //Y	用户端自主生成的订单号,在用户端要保证唯一性
    Body       string `json:"body"`         //N	订单标题
    Attach     string `json:"attach"`       //N	用户自定义数据,在notify的时候会原样返回
    Openid     string `json:"openid"`       //Y	用户openid
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	0:失败 1:成功
    ReturnMsg    string `json:"return_msg"`     //Y	失败原因
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 侧订单号
    JsApi        JsApi  `json:"jsapi"`          //N	用于发起支付的支付参数
    Sign         string `json:"sign"`           //Y	数据签名
}
// JsApi
type JsApi struct {
	AppID     string `json:"appId"`
	TimeStamp string `json:"timeStamp"`
	NonceStr  string `json:"nonceStr"`
	Package   string `json:"package"`
	SignType  string `json:"signType"`
	PaySign   string `json:"paySign"`
}
PayJS := Pay.GetJs()
Response, err := PayJS.Create(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.Openid)

官方文档:JSAPI支付

小程序支付

下面的是伪代码,请自行理解

小程序发起支付的解决方案有两种,仅供测试使用

  • 方案一:使用小程序消息,结合收银台模式,可以解决小程序支付
  • 方案二:使用小程序跳转到 PAYJS 小程序,支付后返回(下面代码是方案二)
type Request struct {
    TotalFee   int64  `json:"total_fee"`    //Y	金额。单位:分
    OutTradeNo string `json:"out_trade_no"` //Y	用户端自主生成的订单号,在用户端要保证唯一性
    Body       string `json:"body"`         //N	订单标题
    Attach     string `json:"attach"`       //N	用户自定义数据,在notify的时候会原样返回
    Nonce      string `json:"nonce"`        //Y 随机字符串
}
type Response struct {
    MchID      string `json:"mch_id"`       //Y 商户号
    TotalFee   int64  `json:"total_fee"`    //Y 金额。单位:分
    OutTradeNo string `json:"out_trade_no"` //Y 用户端自主生成的订单号
    Body       string `json:"body"`         //N 订单标题
    Attach     string `json:"attach"`       //N 用户自定义数据,在notify的时候会原样返回
    NotifyUrl  string `json:"notify_url"`   //N 异步通知地址
    Nonce      string `json:"nonce"`        //Y 随机字符串
    Sign       string `json:"sign"`         //Y 数据签名 详见签名算法
}
PayMiniApp := Pay.GetMiniApp()
// 获取小程序跳转所需的参数
Response, err := PayMiniApp.GetOrderInfo(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.Nonce)

官方文档:小程序支付

人脸支付

注意:未测试

下面的是伪代码,请自行理解

type Request struct {
    TotalFee   int64  `json:"total_fee"`    //Y	金额。单位:分
    OutTradeNo string `json:"out_trade_no"` //Y	用户端自主生成的订单号,在用户端要保证唯一性
    Body       string `json:"body"`         //N	订单标题
    Attach     string `json:"attach"`       //N	用户自定义数据,在notify的时候会原样返回
    Openid     string `json:"openid"`       //Y	OPENID
    FaceCode   string `json:"face_code"`    //Y	人脸支付识别码
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	1:请求成功,0:请求失败
    Msg          string `json:"msg"`            //N	return_code为0时返回的错误消息
    ReturnMsg    string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    OutTradeNo   string `json:"out_trade_no"`   //Y	用户生成的订单号原样返回
    TotalFee     string `json:"total_fee"`      //Y	金额。单位:分
    Sign         string `json:"sign"`           //Y	数据签名 详见签名算法
}
PayFacepay := Pay.GetFacepay()
Response, err := PayFacepay.Create(Request.TotalFee, Request.Body, Request.OutTradeNo, Request.Attach, Request.Openid, Request.FaceCode)

官方文档:人脸支付

订单

下面的是伪代码,请自行理解

// 初始化
PayOrder := Pay.GetOrder()

查询

type Request struct {
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
}
type Response struct {
    ReturnCode    int    `json:"return_code"`    //Y	1:请求成功 0:请求失败
    MchID         string `json:"mchid"`          //Y	PAYJS 平台商户号
    OutTradeNo    string `json:"out_trade_no"`   //Y	用户端订单号
    PayJSOrderID  string `json:"payjs_order_id"` //Y	PAYJS 订单号
    TransactionID string `json:"transaction_id"` //N	微信显示订单号
    Status        int    `json:"status"`         //Y	0:未支付,1:支付成功
    Openid        string `json:"openid"`         //N	用户 OPENID
    TotalFee      int64  `json:"total_fee"`      //N	订单金额
    PaidTime      string `json:"paid_time"`      //N	订单支付时间)
    Attach        string `json:"attach"`         //N	用户自定义数据
    Sign          string `json:"sign"`           //Y	数据签名 详见签名算法
}
Response, err := PayOrder.Check(Request.PayJSOrderID)

官方文档:订单-查询

关闭

type Request struct {
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	1:请求成功 0:请求失败
    ReturnMsg    string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    Sign         string `json:"sign"`           //Y	数据签名 详见签名算法
}
Response, err := Response, err := PayOrder.Close(Request.PayJSOrderID)

官方文档:订单-关闭

撤销

撤销订单主要是针对一些异常订单,例如无法查询或确定订单状态。一般在人脸支付场景中可能会出现。其它场景没遇到过

type Request struct {
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
}
type Response struct {
    ReturnCode   int    `json:"return_code"`    //Y	1:请求成功 0:请求失败
    ReturnMsg    string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    Sign         string `json:"sign"`           //Y	数据签名 详见签名算法
}
Response, err := PayOrder.Reverse(Request.PayJSOrderID)

官方文档:订单-撤销

退款

type Request struct {
    PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
}
type Response struct {
    ReturnCode    int    `json:"return_code"`    //Y	1:请求成功 0:请求失败
    ReturnMsg     string `json:"return_msg"`     //Y	返回消息
    PayJSOrderID  string `json:"payjs_order_id"` //Y	PAYJS 平台订单号
    OutTradeNo    string `json:"out_trade_no"`   //N	用户侧订单号
    TransactionID string `json:"transaction_id"` //N	微信支付订单号
    Sign          string `json:"sign"`           //Y	数据签名 详见签名算法
}
Response, err := PayOrder.Refund(Request.PayJSOrderID)

官方文档:订单-退款

异步通知

下面的是伪代码,请自行理解

// Message PayJS支付成功异步通知过来的内容
type Message struct {
    ReturnCode    int    `json:"return_code"`    // 必填	1:支付成功
    TotalFee      int64  `json:"total_fee"`      // 必填	金额。单位:分
    OutTradeNo    string `json:"out_trade_no"`   // 必填	用户端自主生成的订单号
    PayJSOrderID  string `json:"payjs_order_id"` // 必填	PAYJS 订单号
    TransactionID string `json:"transaction_id"` // 必填	微信用户手机显示订单号
    TimeEnd       string `json:"time_end"`       // 必填	支付成功时间
    Openid        string `json:"openid"`         // 必填	用户OPENID标示,本参数没有实际意义,旨在方便用户端区分不同用户
    Attach        string `json:"attach"`         // 非必填 用户自定义数据
    MchID         string `json:"mchid"`          // 必填	PAYJS 商户号
    Sign          string `json:"sign"`           // 必填	数据签名 详见签名算法
}

// 传入request和responseWriter
PayNotify := Pay.GetNotify(request, responseWriter)

//设置接收消息的处理方法
PayNotify.SetMessageHandler(func(msg notify.Message) {
    //这里处理支付成功回调,一般是修改数据库订单信息等等
    //msg即为支付成功异步通知过来的内容
})

//处理消息接收以及回复
err := PayNotify.Serve()
if err != nil {
    fmt.Println(err)
    return
}

//发送回复的消息
PayNotify.SendResponseMsg()

官方文档:异步通知

用户

下面的是伪代码,请自行理解

// 初始化
PayUser := Pay.GetUser()

获取浏览器跳转的url

type Request struct {
    CallbackUrl string `json:"callback_url"` //Y	接收 openid 的 url。必须为可直接访问的url,不能带session验证、csrf验证。url 可携带最多1个参数,多个参数会自动忽略
}
url, err := PayUser.GetUserOpenIDUrl(Request.CallbackUrl)

官方文档:用户-获取浏览器跳转的url

获取openid

// 在callback_url方法内,传入request
openid, err := PayUser.GetUserOpenID(request)

官方文档:用户-获取openid

获取用户资料

注意:PayJS官方即将废弃此接口

注意:签名验证有bug,暂取消验证报错

type Request struct {
	Openid string `json:"openid"` //Y	openid
}
type Response struct {
	ReturnCode int      `json:"return_code"` //Y	1:请求成功 0:请求失败
	ReturnMsg  string   `json:"return_msg"`  //Y	返回消息
	User       UserInfo `json:"user"`        //N	用户资料
	Sign       string   `json:"sign"`        //Y	数据签名 详见签名算法
}
// UserInfo 用户参数说明(同微信官方文档)
type UserInfo struct {
	Subscribe      int    `json:"subscribe"`       //	用户是否订阅 PAYJS 公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息,只有openid和UnionID
	Openid         string `json:"openid"`          //	用户的标识,对公众号唯一
	Nickname       string `json:"nickname"`        //   用户的昵称
	Sex            int    `json:"sex"`             //   用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
	City           string `json:"city"`            //	用户所在城市
	Country        string `json:"country"`         //   用户所在国家
	Province       string `json:"province"`        //   用户所在省份
	Language       string `json:"language"`        //   用户的语言,简体中文为zh_CN
	Headimgurl     string `json:"headimgurl"`      //   用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
	SubscribeTime  int    `json:"subscribe_time"`  //   用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
	Remark         string `json:"remark"`          //   公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注
	Groupid        int    `json:"groupid"`         //   用户所在的分组ID
	TagidList      []int  `json:"tagid_list"`      //   用户被打上的标签ID列表
	SubscribeScene string `json:"subscribe_scene"` //   返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENE_PROFILE_LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他
	QrScene        int    `json:"qr_scene"`        //   二维码扫码场景
	QrSceneStr     string `json:"qr_scene_str"`    //   二维码扫码场景描述
}
Response, err := PayUser.GetUserInfo(Request.Openid)

官方文档:用户-获取用户资料

商户资料

下面的是伪代码,请自行理解

type Response struct {
    ReturnCode int    `json:"return_code"` //Y	1:请求成功 0:请求失败
    ReturnMsg  string `json:"return_msg"`  //Y	返回消息
    Doudou     int64  `json:"doudou"`      //Y	用户豆豆数
    Name       string `json:"name"`        //Y	商户名称
    Username   string `json:"username"`    //Y	用户姓名
    IDcardNo   string `json:"idcardno"`    //Y	身份证号
    JsApiPath  string `json:"jsapi_path"`  //Y	JSAPI 支付目录
    Phone      string `json:"phone"`       //Y	客服电话
    MchID      string `json:"mchid"`       //Y	商户号
    Sign       string `json:"sign"`        //Y	数据签名 详见签名算法
}
PayMch := Pay.GetMch()
Response, err := PayMch.GetMchInfo()

官方文档:商户资料

银行编码查询

下面的是伪代码,请自行理解

type Request struct {
    Bank string `json:"bank"` //Y	银行简写
}
type Response struct {
    ReturnCode int    `json:"return_code"` //Y	1:请求成功 0:请求失败
    ReturnMsg  string `json:"return_msg"`  //Y	返回消息
    Bank       string `json:"bank"`        //Y	银行名称
    Sign       string `json:"sign"`        //Y	数据签名 详见签名算法
}
PayBank := Pay.GetBank()
Response, err := PayBank.GetBankInfo(Request.Bank)

官方文档:银行编码查询

获取异步通知服务器IP列表

注意:签名验证有bug,暂取消验证报错

下面的是伪代码,请自行理解

type Response struct {
    ReturnCode int      `json:"return_code"` //Y	1:请求成功 0:请求失败
    ReturnMsg  string   `json:"return_msg"`  //Y	返回消息
    IPList     []string `json:"iplist"`      //Y	ip地址列表
    Sign       string   `json:"sign"`        //Y	数据签名 详见签名算法
}
PayIP := Pay.GetIP()
Response, err := PayIP.GetIPList()

此接口未公开,文档需要登录控制台才能看到

官方文档:获取异步通知服务器IP列表

License

MIT