增加vendor
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
# 微信支付
|
||||
|
||||
[官方文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
|
||||
|
||||
## 快速入门
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
// Config .config for pay
|
||||
type Config struct {
|
||||
AppID string `json:"app_id"`
|
||||
MchID string `json:"mch_id"`
|
||||
Key string `json:"key"`
|
||||
NotifyURL string `json:"notify_url"`
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2/pay/config"
|
||||
)
|
||||
|
||||
// Notify 回调
|
||||
type Notify struct {
|
||||
*config.Config
|
||||
}
|
||||
|
||||
// NewNotify new
|
||||
func NewNotify(cfg *config.Config) *Notify {
|
||||
return &Notify{cfg}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// doc: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
|
||||
|
||||
// PaidResult 下单回调
|
||||
type PaidResult struct {
|
||||
ReturnCode *string `xml:"return_code"`
|
||||
ReturnMsg *string `xml:"return_msg"`
|
||||
|
||||
AppID *string `xml:"appid" json:"appid"`
|
||||
MchID *string `xml:"mch_id"`
|
||||
DeviceInfo *string `xml:"device_info"`
|
||||
NonceStr *string `xml:"nonce_str"`
|
||||
Sign *string `xml:"sign"`
|
||||
SignType *string `xml:"sign_type"`
|
||||
ResultCode *string `xml:"result_code"`
|
||||
ErrCode *string `xml:"err_code"`
|
||||
ErrCodeDes *string `xml:"err_code_des"`
|
||||
OpenID *string `xml:"openid"`
|
||||
IsSubscribe *string `xml:"is_subscribe"`
|
||||
TradeType *string `xml:"trade_type"`
|
||||
TradeState *string `xml:"trade_state"`
|
||||
BankType *string `xml:"bank_type"`
|
||||
TotalFee *int `xml:"total_fee"`
|
||||
SettlementTotalFee *int `xml:"settlement_total_fee"`
|
||||
FeeType *string `xml:"fee_type"`
|
||||
CashFee *string `xml:"cash_fee"`
|
||||
CashFeeType *string `xml:"cash_fee_type"`
|
||||
CouponFee *int `xml:"coupon_fee"`
|
||||
CouponCount *int `xml:"coupon_count"`
|
||||
|
||||
// coupon_type_$n 这里只声明 3 个,如果有更多的可以自己组合
|
||||
CouponType0 *string `xml:"coupon_type_0"`
|
||||
CouponType1 *string `xml:"coupon_type_1"`
|
||||
CouponType2 *string `xml:"coupon_type_2"`
|
||||
CouponID0 *string `xml:"coupon_id_0"`
|
||||
CouponID1 *string `xml:"coupon_id_1"`
|
||||
CouponID2 *string `xml:"coupon_id_2"`
|
||||
CouponFee0 *string `xml:"coupon_fee_0"`
|
||||
CouponFee1 *string `xml:"coupon_fee_1"`
|
||||
CouponFee2 *string `xml:"coupon_fee_2"`
|
||||
|
||||
TransactionID *string `xml:"transaction_id"`
|
||||
OutTradeNo *string `xml:"out_trade_no"`
|
||||
Attach *string `xml:"attach"`
|
||||
TimeEnd *string `xml:"time_end"`
|
||||
}
|
||||
|
||||
// PaidResp 消息通知返回
|
||||
type PaidResp struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
}
|
||||
|
||||
// PaidVerifySign 支付成功结果验签
|
||||
func (notify *Notify) PaidVerifySign(notifyRes PaidResult) bool {
|
||||
// STEP1, 转换 struct 为 map,并对 map keys 做排序
|
||||
resMap := structs.Map(notifyRes)
|
||||
|
||||
sortedKeys := make([]string, 0, len(resMap))
|
||||
for k := range resMap {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
// STEP2, 对key=value的键值对用&连接起来,略过空值 & sign
|
||||
var signStrings string
|
||||
for _, k := range sortedKeys {
|
||||
value := fmt.Sprintf("%v", cast.ToString(resMap[k]))
|
||||
if value != "" && strings.ToLower(k) != "sign" {
|
||||
signStrings = signStrings + getTagKeyName(k, ¬ifyRes) + "=" + value + "&"
|
||||
}
|
||||
}
|
||||
|
||||
// STEP3, 在键值对的最后加上key=API_KEY
|
||||
signStrings = signStrings + "key=" + notify.Key
|
||||
|
||||
// STEP4, 根据SignType计算出签名
|
||||
var signType string
|
||||
if notifyRes.SignType != nil {
|
||||
signType = *notifyRes.SignType
|
||||
}
|
||||
sign, err := util.CalculateSign(signStrings, signType, notify.Key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if sign != *notifyRes.Sign {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getTagKeyName(key string, notifyRes *PaidResult) string {
|
||||
s := reflect.TypeOf(notifyRes).Elem()
|
||||
f, _ := s.FieldByName(key)
|
||||
name := f.Tag.Get("xml")
|
||||
return name
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// reference: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10
|
||||
|
||||
// RefundedResult 退款回调
|
||||
type RefundedResult struct {
|
||||
ReturnCode *string `xml:"return_code"`
|
||||
ReturnMsg *string `xml:"return_msg"`
|
||||
|
||||
AppID *string `xml:"appid"`
|
||||
MchID *string `xml:"mch_id"`
|
||||
NonceStr *string `xml:"nonce_str"`
|
||||
ReqInfo *string `xml:"req_info"`
|
||||
}
|
||||
|
||||
// RefundedReqInfo 退款结果(明文)
|
||||
type RefundedReqInfo struct {
|
||||
TransactionID *string `xml:"transaction_id"`
|
||||
OutTradeNO *string `xml:"out_trade_no"`
|
||||
RefundID *string `xml:"refund_id"`
|
||||
OutRefundNO *string `xml:"out_refund_no"`
|
||||
TotalFee *int `xml:"total_fee"`
|
||||
SettlementTotalFee *int `xml:"settlement_total_fee"`
|
||||
RefundFee *int `xml:"refund_fee"`
|
||||
SettlementRefundFee *int `xml:"settlement_refund_fee"`
|
||||
RefundStatus *string `xml:"refund_status"`
|
||||
SuccessTime *string `xml:"success_time"`
|
||||
RefundRecvAccount *string `xml:"refund_recv_accout"`
|
||||
RefundAccount *string `xml:"refund_account"`
|
||||
RefundRequestSource *string `xml:"refund_request_source"`
|
||||
}
|
||||
|
||||
// RefundedResp 消息通知返回
|
||||
type RefundedResp struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
}
|
||||
|
||||
// DecryptReqInfo 对退款结果进行解密
|
||||
func (notify *Notify) DecryptReqInfo(result *RefundedResult) (*RefundedReqInfo, error) {
|
||||
var err error
|
||||
if result == nil || result.ReqInfo == nil {
|
||||
return nil, errors.New("empty refunded_result or req_info")
|
||||
}
|
||||
|
||||
base64Decode, err := base64.StdEncoding.DecodeString(*result.ReqInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
if _, err = hash.Write([]byte(notify.Key)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
md5APIKey := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
data, err := util.AesECBDecrypt(base64Decode, []byte(md5APIKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &RefundedReqInfo{}
|
||||
if err = xml.Unmarshal(data, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
|
||||
var closeGateway = "https://api.mch.weixin.qq.com/pay/closeorder"
|
||||
|
||||
// CloseParams 传入的参数
|
||||
type CloseParams struct {
|
||||
OutTradeNo string // 商户订单号
|
||||
SignType string // 签名类型
|
||||
}
|
||||
|
||||
// closeRequest 接口请求参数
|
||||
type closeRequest struct {
|
||||
AppID string `xml:"appid"` // 公众账号ID
|
||||
MchID string `xml:"mch_id"` // 商户号
|
||||
NonceStr string `xml:"nonce_str"` // 随机字符串
|
||||
Sign string `xml:"sign"` // 签名
|
||||
SignType string `xml:"sign_type,omitempty"` // 签名类型
|
||||
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
||||
}
|
||||
|
||||
// CloseResult 关闭订单返回结果
|
||||
type CloseResult struct {
|
||||
ReturnCode *string `xml:"return_code"`
|
||||
ReturnMsg *string `xml:"return_msg"`
|
||||
|
||||
AppID *string `xml:"appid" json:"appid"`
|
||||
MchID *string `xml:"mch_id"`
|
||||
NonceStr *string `xml:"nonce_str"`
|
||||
Sign *string `xml:"sign"`
|
||||
ResultCode *string `xml:"result_code"`
|
||||
ResultMsg *string `xml:"result_msg"`
|
||||
ErrCode *string `xml:"err_code"`
|
||||
ErrCodeDes *string `xml:"err_code_des"`
|
||||
}
|
||||
|
||||
// CloseOrder 关闭订单
|
||||
func (o *Order) CloseOrder(p *CloseParams) (closeResult CloseResult, err error) {
|
||||
nonceStr := util.RandomStr(32)
|
||||
// 签名类型
|
||||
if p.SignType == "" {
|
||||
p.SignType = "MD5"
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
params["appid"] = o.AppID
|
||||
params["mch_id"] = o.MchID
|
||||
params["nonce_str"] = nonceStr
|
||||
params["out_trade_no"] = p.OutTradeNo
|
||||
params["sign_type"] = p.SignType
|
||||
|
||||
var (
|
||||
sign string
|
||||
rawRet []byte
|
||||
)
|
||||
|
||||
sign, err = util.ParamSign(params, o.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request := closeRequest{
|
||||
AppID: o.AppID,
|
||||
MchID: o.MchID,
|
||||
NonceStr: nonceStr,
|
||||
Sign: sign,
|
||||
OutTradeNo: p.OutTradeNo,
|
||||
SignType: p.SignType,
|
||||
}
|
||||
|
||||
rawRet, err = util.PostXML(closeGateway, request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(rawRet, &closeResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if *closeResult.ReturnCode == SUCCESS {
|
||||
// close success
|
||||
if *closeResult.ResultCode == SUCCESS {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
err = errors.New(*closeResult.ErrCode + *closeResult.ErrCodeDes)
|
||||
return
|
||||
}
|
||||
err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
|
||||
return
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/silenceper/wechat/v2/pay/config"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
|
||||
var payGateway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
|
||||
|
||||
// SUCCESS 表示支付成功
|
||||
const SUCCESS = "SUCCESS"
|
||||
|
||||
// Order struct extends context
|
||||
type Order struct {
|
||||
*config.Config
|
||||
}
|
||||
|
||||
// NewOrder return an instance of order package
|
||||
func NewOrder(cfg *config.Config) *Order {
|
||||
order := Order{cfg}
|
||||
return &order
|
||||
}
|
||||
|
||||
// Params was NEEDED when request Unified order
|
||||
// 传入的参数,用于生成 prepay_id 的必需参数
|
||||
type Params struct {
|
||||
TotalFee string
|
||||
CreateIP string
|
||||
Body string
|
||||
OutTradeNo string
|
||||
TimeExpire string // 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。
|
||||
OpenID string
|
||||
TradeType string
|
||||
SignType string
|
||||
Detail string
|
||||
Attach string
|
||||
GoodsTag string
|
||||
NotifyURL string
|
||||
}
|
||||
|
||||
// Config 是传出用于 js sdk 用的参数
|
||||
type Config struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
NonceStr string `json:"nonceStr"`
|
||||
PrePayID string `json:"prePayId"`
|
||||
SignType string `json:"signType"`
|
||||
Package string `json:"package"`
|
||||
PaySign string `json:"paySign"`
|
||||
}
|
||||
|
||||
// ConfigForApp 是传出用于 app sdk 用的参数
|
||||
type ConfigForApp struct {
|
||||
AppID string `json:"appid"`
|
||||
MchID string `json:"partnerid"` // 微信支付分配的商户号
|
||||
PrePayID string `json:"prepayid"`
|
||||
Package string `json:"package"`
|
||||
NonceStr string `json:"nonceStr"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
// PreOrder 是 Unified order 接口的返回
|
||||
type PreOrder struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
AppID string `xml:"appid,omitempty"`
|
||||
MchID string `xml:"mch_id,omitempty"`
|
||||
NonceStr string `xml:"nonce_str,omitempty"`
|
||||
Sign string `xml:"sign,omitempty"`
|
||||
ResultCode string `xml:"result_code,omitempty"`
|
||||
TradeType string `xml:"trade_type,omitempty"`
|
||||
PrePayID string `xml:"prepay_id,omitempty"`
|
||||
CodeURL string `xml:"code_url,omitempty"`
|
||||
MWebURL string `xml:"mweb_url,omitempty"`
|
||||
ErrCode string `xml:"err_code,omitempty"`
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
||||
}
|
||||
|
||||
// payRequest 接口请求参数
|
||||
type payRequest struct {
|
||||
AppID string `xml:"appid"` // 公众账号ID
|
||||
MchID string `xml:"mch_id"` // 商户号
|
||||
DeviceInfo string `xml:"device_info,omitempty"` // 设备号
|
||||
NonceStr string `xml:"nonce_str"` // 随机字符串
|
||||
Sign string `xml:"sign"` // 签名
|
||||
SignType string `xml:"sign_type,omitempty"` // 签名类型
|
||||
Body string `xml:"body"` // 商品描述
|
||||
Detail string `xml:"detail,omitempty"` // 商品详情
|
||||
Attach string `xml:"attach,omitempty"` // 附加数据
|
||||
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
||||
FeeType string `xml:"fee_type,omitempty"` // 标价币种
|
||||
TotalFee string `xml:"total_fee"` // 标价金额
|
||||
SpbillCreateIP string `xml:"spbill_create_ip"` // 终端IP
|
||||
TimeStart string `xml:"time_start,omitempty"` // 交易起始时间
|
||||
TimeExpire string `xml:"time_expire,omitempty"` // 交易结束时间
|
||||
GoodsTag string `xml:"goods_tag,omitempty"` // 订单优惠标记
|
||||
NotifyURL string `xml:"notify_url"` // 通知地址
|
||||
TradeType string `xml:"trade_type"` // 交易类型
|
||||
ProductID string `xml:"product_id,omitempty"` // 商品ID
|
||||
LimitPay string `xml:"limit_pay,omitempty"` // 指定支付方式
|
||||
OpenID string `xml:"openid,omitempty"` // 用户标识
|
||||
SceneInfo string `xml:"scene_info,omitempty"` // 场景信息
|
||||
|
||||
XMLName struct{} `xml:"xml"`
|
||||
}
|
||||
|
||||
func (req *payRequest) BridgePayRequest(p *Params, AppID, MchID, nonceStr, sign string) *payRequest {
|
||||
request := payRequest{
|
||||
AppID: AppID,
|
||||
MchID: MchID,
|
||||
NonceStr: nonceStr,
|
||||
Sign: sign,
|
||||
Body: p.Body,
|
||||
OutTradeNo: p.OutTradeNo,
|
||||
TotalFee: p.TotalFee,
|
||||
SpbillCreateIP: p.CreateIP,
|
||||
NotifyURL: p.NotifyURL,
|
||||
TradeType: p.TradeType,
|
||||
OpenID: p.OpenID,
|
||||
SignType: p.SignType,
|
||||
Detail: p.Detail,
|
||||
Attach: p.Attach,
|
||||
GoodsTag: p.GoodsTag,
|
||||
}
|
||||
return &request
|
||||
}
|
||||
|
||||
// BridgeConfig get js bridge config
|
||||
func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
|
||||
var (
|
||||
buffer strings.Builder
|
||||
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
)
|
||||
order, err := o.PrePayOrder(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buffer.WriteString("appId=")
|
||||
buffer.WriteString(order.AppID)
|
||||
buffer.WriteString("&nonceStr=")
|
||||
buffer.WriteString(order.NonceStr)
|
||||
buffer.WriteString("&package=")
|
||||
buffer.WriteString("prepay_id=" + order.PrePayID)
|
||||
buffer.WriteString("&signType=")
|
||||
buffer.WriteString(p.SignType)
|
||||
buffer.WriteString("&timeStamp=")
|
||||
buffer.WriteString(timestamp)
|
||||
buffer.WriteString("&key=")
|
||||
buffer.WriteString(o.Key)
|
||||
|
||||
sign, err := util.CalculateSign(buffer.String(), p.SignType, o.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 签名
|
||||
cfg.PaySign = sign
|
||||
cfg.NonceStr = order.NonceStr
|
||||
cfg.Timestamp = timestamp
|
||||
cfg.PrePayID = order.PrePayID
|
||||
cfg.SignType = p.SignType
|
||||
cfg.Package = "prepay_id=" + order.PrePayID
|
||||
return
|
||||
}
|
||||
|
||||
// BridgeAppConfig get app bridge config
|
||||
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
||||
var (
|
||||
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
noncestr = util.RandomStr(32)
|
||||
_package = "Sign=WXPay"
|
||||
)
|
||||
order, err := o.PrePayOrder(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]string{
|
||||
"appid": order.AppID,
|
||||
"partnerid": order.MchID,
|
||||
"prepayid": order.PrePayID,
|
||||
"package": _package,
|
||||
"noncestr": noncestr,
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
// 签名
|
||||
sign, err := util.ParamSign(result, o.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result["sign"] = sign
|
||||
cfg = ConfigForApp{
|
||||
AppID: result["appid"],
|
||||
MchID: result["partnerid"],
|
||||
PrePayID: result["prepayid"],
|
||||
Package: result["package"],
|
||||
NonceStr: result["noncestr"],
|
||||
Timestamp: result["timestamp"],
|
||||
Sign: result["sign"],
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PrePayOrder return data for invoke wechat payment
|
||||
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
||||
nonceStr := util.RandomStr(32)
|
||||
|
||||
// 通知地址
|
||||
if len(p.NotifyURL) == 0 {
|
||||
p.NotifyURL = o.NotifyURL // 默认使用order.NotifyURL
|
||||
}
|
||||
|
||||
// 签名类型
|
||||
if p.SignType == "" {
|
||||
p.SignType = util.SignTypeMD5
|
||||
}
|
||||
|
||||
param := map[string]string{
|
||||
"appid": o.AppID,
|
||||
"body": p.Body,
|
||||
"mch_id": o.MchID,
|
||||
"nonce_str": nonceStr,
|
||||
"out_trade_no": p.OutTradeNo,
|
||||
"spbill_create_ip": p.CreateIP,
|
||||
"total_fee": p.TotalFee,
|
||||
"trade_type": p.TradeType,
|
||||
"openid": p.OpenID,
|
||||
"sign_type": p.SignType,
|
||||
"detail": p.Detail,
|
||||
"attach": p.Attach,
|
||||
"goods_tag": p.GoodsTag,
|
||||
"notify_url": p.NotifyURL,
|
||||
}
|
||||
|
||||
if p.TimeExpire != "" {
|
||||
// 如果有传入交易结束时间
|
||||
param["time_expire"] = p.TimeExpire
|
||||
}
|
||||
|
||||
sign, err := util.ParamSign(param, o.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request := new(payRequest).BridgePayRequest(p, o.AppID, o.MchID, nonceStr, sign)
|
||||
if len(p.TimeExpire) > 0 {
|
||||
// 如果有传入交易结束时间
|
||||
request.TimeExpire = p.TimeExpire
|
||||
}
|
||||
rawRet, err := util.PostXML(payGateway, request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = xml.Unmarshal(rawRet, &payOrder)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if payOrder.ReturnCode == SUCCESS {
|
||||
// pay success
|
||||
if payOrder.ResultCode == SUCCESS {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
err = errors.New(payOrder.ErrCode + payOrder.ErrCodeDes)
|
||||
return
|
||||
}
|
||||
err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
|
||||
return
|
||||
}
|
||||
|
||||
// PrePayID will request wechat merchant api and request for a pre payment order id
|
||||
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
|
||||
order, err := o.PrePayOrder(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if order.PrePayID == "" {
|
||||
err = errors.New("empty prepayid")
|
||||
}
|
||||
prePayID = order.PrePayID
|
||||
return
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
|
||||
"github.com/silenceper/wechat/v2/pay/notify"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
var queryGateway = "https://api.mch.weixin.qq.com/pay/orderquery"
|
||||
|
||||
// QueryParams 传入的参数
|
||||
type QueryParams struct {
|
||||
OutTradeNo string // 商户订单号
|
||||
SignType string // 签名类型
|
||||
TransactionID string // 微信订单号
|
||||
}
|
||||
|
||||
// queryRequest 接口请求参数
|
||||
type queryRequest struct {
|
||||
AppID string `xml:"appid"` // 公众账号ID
|
||||
MchID string `xml:"mch_id"` // 商户号
|
||||
NonceStr string `xml:"nonce_str"` // 随机字符串
|
||||
Sign string `xml:"sign"` // 签名
|
||||
SignType string `xml:"sign_type,omitempty"` // 签名类型
|
||||
TransactionID string `xml:"transaction_id"` // 微信订单号
|
||||
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
||||
}
|
||||
|
||||
// QueryOrder 查询订单
|
||||
func (o *Order) QueryOrder(p *QueryParams) (paidResult notify.PaidResult, err error) {
|
||||
nonceStr := util.RandomStr(32)
|
||||
// 签名类型
|
||||
if p.SignType == "" {
|
||||
p.SignType = "MD5"
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
params["appid"] = o.AppID
|
||||
params["mch_id"] = o.MchID
|
||||
params["nonce_str"] = nonceStr
|
||||
params["out_trade_no"] = p.OutTradeNo
|
||||
params["sign_type"] = p.SignType
|
||||
params["transaction_id"] = p.TransactionID
|
||||
|
||||
sign, err := util.ParamSign(params, o.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request := queryRequest{
|
||||
AppID: o.AppID,
|
||||
MchID: o.MchID,
|
||||
NonceStr: nonceStr,
|
||||
Sign: sign,
|
||||
OutTradeNo: p.OutTradeNo,
|
||||
TransactionID: p.TransactionID,
|
||||
SignType: p.SignType,
|
||||
}
|
||||
|
||||
rawRet, err := util.PostXML(queryGateway, request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(rawRet, &paidResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if *paidResult.ReturnCode == SUCCESS {
|
||||
// query success
|
||||
if *paidResult.ResultCode == SUCCESS {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
err = errors.New(*paidResult.ErrCode + *paidResult.ErrCodeDes)
|
||||
return
|
||||
}
|
||||
err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
|
||||
return
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2/pay/config"
|
||||
"github.com/silenceper/wechat/v2/pay/notify"
|
||||
"github.com/silenceper/wechat/v2/pay/order"
|
||||
"github.com/silenceper/wechat/v2/pay/refund"
|
||||
"github.com/silenceper/wechat/v2/pay/transfer"
|
||||
)
|
||||
|
||||
// Pay 微信支付相关API
|
||||
type Pay struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewPay 实例化微信支付相关API
|
||||
func NewPay(cfg *config.Config) *Pay {
|
||||
return &Pay{cfg}
|
||||
}
|
||||
|
||||
// GetOrder 下单
|
||||
func (pay *Pay) GetOrder() *order.Order {
|
||||
return order.NewOrder(pay.cfg)
|
||||
}
|
||||
|
||||
// GetNotify 通知
|
||||
func (pay *Pay) GetNotify() *notify.Notify {
|
||||
return notify.NewNotify(pay.cfg)
|
||||
}
|
||||
|
||||
// GetRefund 退款
|
||||
func (pay *Pay) GetRefund() *refund.Refund {
|
||||
return refund.NewRefund(pay.cfg)
|
||||
}
|
||||
|
||||
// GetTransfer 付款
|
||||
func (pay *Pay) GetTransfer() *transfer.Transfer {
|
||||
return transfer.NewTransfer(pay.cfg)
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package refund
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/pay/config"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
var refundGateway = "https://api.mch.weixin.qq.com/secapi/pay/refund"
|
||||
|
||||
// Refund struct extends context
|
||||
type Refund struct {
|
||||
*config.Config
|
||||
}
|
||||
|
||||
// NewRefund return an instance of refund package
|
||||
func NewRefund(cfg *config.Config) *Refund {
|
||||
refund := Refund{cfg}
|
||||
return &refund
|
||||
}
|
||||
|
||||
// Params 调用参数
|
||||
type Params struct {
|
||||
TransactionID string
|
||||
OutRefundNo string
|
||||
OutTradeNo string
|
||||
TotalFee string
|
||||
RefundFee string
|
||||
RefundDesc string
|
||||
RootCa string // ca证书
|
||||
NotifyURL string
|
||||
SignType string
|
||||
}
|
||||
|
||||
// request 接口请求参数
|
||||
type request struct {
|
||||
AppID string `xml:"appid"`
|
||||
MchID string `xml:"mch_id"`
|
||||
NonceStr string `xml:"nonce_str"`
|
||||
Sign string `xml:"sign"`
|
||||
SignType string `xml:"sign_type,omitempty"`
|
||||
TransactionID string `xml:"transaction_id,omitempty"`
|
||||
OutTradeNo string `xml:"out_trade_no,omitempty"`
|
||||
OutRefundNo string `xml:"out_refund_no"`
|
||||
TotalFee string `xml:"total_fee"`
|
||||
RefundFee string `xml:"refund_fee"`
|
||||
RefundDesc string `xml:"refund_desc,omitempty"`
|
||||
NotifyURL string `xml:"notify_url,omitempty"`
|
||||
}
|
||||
|
||||
// Response 接口返回
|
||||
type Response struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
AppID string `xml:"appid,omitempty"`
|
||||
MchID string `xml:"mch_id,omitempty"`
|
||||
NonceStr string `xml:"nonce_str,omitempty"`
|
||||
Sign string `xml:"sign,omitempty"`
|
||||
ResultCode string `xml:"result_code,omitempty"`
|
||||
ErrCode string `xml:"err_code,omitempty"`
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
||||
TransactionID string `xml:"transaction_id,omitempty"`
|
||||
OutTradeNo string `xml:"out_trade_no,omitempty"`
|
||||
OutRefundNo string `xml:"out_refund_no,omitempty"`
|
||||
RefundID string `xml:"refund_id,omitempty"`
|
||||
RefundFee string `xml:"refund_fee,omitempty"`
|
||||
SettlementRefundFee string `xml:"settlement_refund_fee,omitempty"`
|
||||
TotalFee string `xml:"total_fee,omitempty"`
|
||||
SettlementTotalFee string `xml:"settlement_total_fee,omitempty"`
|
||||
FeeType string `xml:"fee_type,omitempty"`
|
||||
CashFee string `xml:"cash_fee,omitempty"`
|
||||
CashFeeType string `xml:"cash_fee_type,omitempty"`
|
||||
}
|
||||
|
||||
// Refund 退款申请
|
||||
func (refund *Refund) Refund(p *Params) (rsp Response, err error) {
|
||||
param := refund.GetSignParam(p)
|
||||
|
||||
sign, err := util.ParamSign(param, refund.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req := request{
|
||||
AppID: param["appid"],
|
||||
MchID: param["mch_id"],
|
||||
NonceStr: param["nonce_str"],
|
||||
Sign: sign,
|
||||
SignType: param["sign_type"],
|
||||
OutRefundNo: param["out_refund_no"],
|
||||
TotalFee: param["total_fee"],
|
||||
RefundFee: param["refund_fee"],
|
||||
RefundDesc: param["refund_desc"],
|
||||
NotifyURL: param["notify_url"],
|
||||
}
|
||||
if p.OutTradeNo != "" {
|
||||
req.OutTradeNo = p.OutTradeNo
|
||||
}
|
||||
if p.TransactionID != "" {
|
||||
req.TransactionID = p.TransactionID
|
||||
}
|
||||
|
||||
rawRet, err := util.PostXMLWithTLS(refundGateway, req, p.RootCa, refund.MchID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = xml.Unmarshal(rawRet, &rsp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rsp.ReturnCode == "SUCCESS" {
|
||||
if rsp.ResultCode == "SUCCESS" {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("refund error, errcode=%s,errmsg=%s", rsp.ErrCode, rsp.ErrCodeDes)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("[msg : xmlUnmarshalError] [rawReturn : %s] [sign : %s]", string(rawRet), sign)
|
||||
return
|
||||
}
|
||||
|
||||
// GetSignParam 获取签名的参数
|
||||
func (refund *Refund) GetSignParam(p *Params) (param map[string]string) {
|
||||
nonceStr := util.RandomStr(32)
|
||||
param = make(map[string]string)
|
||||
param["appid"] = refund.AppID
|
||||
param["mch_id"] = refund.MchID
|
||||
param["nonce_str"] = nonceStr
|
||||
param["out_refund_no"] = p.OutRefundNo
|
||||
param["refund_desc"] = p.RefundDesc
|
||||
param["refund_fee"] = p.RefundFee
|
||||
param["total_fee"] = p.TotalFee
|
||||
|
||||
if p.SignType == "" {
|
||||
param["sign_type"] = util.SignTypeMD5
|
||||
}
|
||||
if p.OutTradeNo != "" {
|
||||
param["out_trade_no"] = p.OutTradeNo
|
||||
}
|
||||
if p.TransactionID != "" {
|
||||
param["transaction_id"] = p.TransactionID
|
||||
}
|
||||
if p.NotifyURL != "" {
|
||||
param["notify_url"] = p.NotifyURL
|
||||
}
|
||||
|
||||
return param
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/silenceper/wechat/v2/pay/config"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// 付款到零钱
|
||||
// https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
|
||||
var walletTransferGateway = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"
|
||||
|
||||
// Transfer struct extends context
|
||||
type Transfer struct {
|
||||
*config.Config
|
||||
}
|
||||
|
||||
// NewTransfer return an instance of Transfer package
|
||||
func NewTransfer(cfg *config.Config) *Transfer {
|
||||
transfer := Transfer{cfg}
|
||||
return &transfer
|
||||
}
|
||||
|
||||
// Params 调用参数
|
||||
type Params struct {
|
||||
DeviceInfo string
|
||||
PartnerTradeNo string
|
||||
OpenID string
|
||||
CheckName bool
|
||||
ReUserName string
|
||||
Amount int
|
||||
Desc string
|
||||
SpbillCreateIP string
|
||||
RootCa string // ca证书
|
||||
}
|
||||
|
||||
// request 接口请求参数
|
||||
type request struct {
|
||||
AppID string `xml:"mch_appid"`
|
||||
MchID string `xml:"mchid"`
|
||||
NonceStr string `xml:"nonce_str"`
|
||||
Sign string `xml:"sign"`
|
||||
DeviceInfo string `xml:"device_info,omitempty"`
|
||||
PartnerTradeNo string `xml:"partner_trade_no"`
|
||||
OpenID string `xml:"openid"`
|
||||
CheckName string `xml:"check_name"`
|
||||
ReUserName string `xml:"re_user_name,omitempty"`
|
||||
Amount int `xml:"amount"`
|
||||
Desc string `xml:"desc"`
|
||||
SpbillCreateIP string `xml:"spbill_create_ip,omitempty"`
|
||||
}
|
||||
|
||||
// Response 接口返回
|
||||
type Response struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
AppID string `xml:"appid,omitempty"`
|
||||
MchID string `xml:"mch_id,omitempty"`
|
||||
DeviceInfo string `xml:"device_info,omitempty"`
|
||||
NonceStr string `xml:"nonce_str,omitempty"`
|
||||
ResultCode string `xml:"result_code,omitempty"`
|
||||
ErrCode string `xml:"err_code,omitempty"`
|
||||
ErrCodeDes string `xml:"err_code_des,omitempty"`
|
||||
PartnerTradeNo string `xml:"partner_trade_no"`
|
||||
PaymentNo string `xml:"payment_no"`
|
||||
PaymentTime string `xml:"payment_time"`
|
||||
}
|
||||
|
||||
// WalletTransfer 付款到零钱
|
||||
func (transfer *Transfer) WalletTransfer(p *Params) (rsp *Response, err error) {
|
||||
nonceStr := util.RandomStr(32)
|
||||
param := make(map[string]string)
|
||||
param["mch_appid"] = transfer.AppID
|
||||
param["mchid"] = transfer.MchID
|
||||
param["nonce_str"] = nonceStr
|
||||
param["partner_trade_no"] = p.PartnerTradeNo
|
||||
param["openid"] = p.OpenID
|
||||
param["amount"] = strconv.Itoa(p.Amount)
|
||||
param["desc"] = p.Desc
|
||||
if p.DeviceInfo != "" {
|
||||
param["device_info"] = p.DeviceInfo
|
||||
}
|
||||
param["check_name"] = "NO_CHECK"
|
||||
if p.CheckName {
|
||||
param["check_name"] = "FORCE_CHECK"
|
||||
param["re_user_name"] = p.ReUserName
|
||||
}
|
||||
if p.SpbillCreateIP != "" {
|
||||
param["spbill_create_ip"] = p.SpbillCreateIP
|
||||
}
|
||||
|
||||
sign, err := util.ParamSign(param, transfer.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req := request{
|
||||
AppID: transfer.AppID,
|
||||
MchID: transfer.MchID,
|
||||
NonceStr: nonceStr,
|
||||
Sign: sign,
|
||||
DeviceInfo: p.DeviceInfo,
|
||||
PartnerTradeNo: p.PartnerTradeNo,
|
||||
OpenID: p.OpenID,
|
||||
Amount: p.Amount,
|
||||
Desc: p.Desc,
|
||||
SpbillCreateIP: p.SpbillCreateIP,
|
||||
}
|
||||
req.CheckName = "NO_CHECK"
|
||||
if p.CheckName {
|
||||
req.CheckName = "FORCE_CHECK"
|
||||
req.ReUserName = p.ReUserName
|
||||
}
|
||||
rawRet, err := util.PostXMLWithTLS(walletTransferGateway, req, p.RootCa, transfer.MchID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = xml.Unmarshal(rawRet, &rsp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rsp.ReturnCode == "SUCCESS" {
|
||||
if rsp.ResultCode == "SUCCESS" {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("transfer error, errcode=%s,errmsg=%s", rsp.ErrCode, rsp.ErrCodeDes)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("[msg : xmlUnmarshalError] [rawReturn : %s] [sign : %s]", string(rawRet), sign)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user