172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
|
/*
|
|||
|
微信支付
|
|||
|
文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
|
|||
|
*/
|
|||
|
|
|||
|
package wechat
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"crypto/aes"
|
|||
|
"crypto/cipher"
|
|||
|
"crypto/md5"
|
|||
|
"encoding/base64"
|
|||
|
"encoding/hex"
|
|||
|
"encoding/xml"
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"io"
|
|||
|
"io/ioutil"
|
|||
|
"net/http"
|
|||
|
"strings"
|
|||
|
|
|||
|
"github.com/go-pay/gopay"
|
|||
|
xaes "github.com/go-pay/gopay/pkg/aes"
|
|||
|
"github.com/go-pay/gopay/pkg/util"
|
|||
|
"github.com/go-pay/gopay/pkg/xhttp"
|
|||
|
)
|
|||
|
|
|||
|
// ParseNotifyToBodyMap 解析微信支付异步通知的结果到BodyMap(推荐)
|
|||
|
// req:*http.Request
|
|||
|
// 返回参数bm:Notify请求的参数
|
|||
|
// 返回参数err:错误信息
|
|||
|
func ParseNotifyToBodyMap(req *http.Request) (bm gopay.BodyMap, err error) {
|
|||
|
bs, err := ioutil.ReadAll(io.LimitReader(req.Body, int64(3<<20))) // default 3MB change the size you want;
|
|||
|
defer req.Body.Close()
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("ioutil.ReadAll:%w", err)
|
|||
|
}
|
|||
|
bm = make(gopay.BodyMap)
|
|||
|
if err = xml.Unmarshal(bs, &bm); err != nil {
|
|||
|
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// Deprecated
|
|||
|
// 推荐使用 ParseNotifyToBodyMap
|
|||
|
func ParseNotify(req *http.Request) (notifyReq *NotifyRequest, err error) {
|
|||
|
notifyReq = new(NotifyRequest)
|
|||
|
err = xml.NewDecoder(req.Body).Decode(notifyReq)
|
|||
|
defer req.Body.Close()
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("xml.NewDecoder.Decode:%w", err)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// ParseRefundNotify 解析微信退款异步通知的参数
|
|||
|
// req:*http.Request
|
|||
|
// 返回参数notifyReq:Notify请求的参数
|
|||
|
// 返回参数err:错误信息
|
|||
|
func ParseRefundNotify(req *http.Request) (notifyReq *RefundNotifyRequest, err error) {
|
|||
|
notifyReq = new(RefundNotifyRequest)
|
|||
|
err = xml.NewDecoder(req.Body).Decode(notifyReq)
|
|||
|
defer req.Body.Close()
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("xml.NewDecoder.Decode:%w", err)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// DecryptRefundNotifyReqInfo 解密微信退款异步通知的加密数据
|
|||
|
// reqInfo:gopay.ParseRefundNotify() 方法获取的加密数据 req_info
|
|||
|
// apiKey:API秘钥值
|
|||
|
// 返回参数refundNotify:RefundNotify请求的加密数据
|
|||
|
// 返回参数err:错误信息
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10
|
|||
|
func DecryptRefundNotifyReqInfo(reqInfo, apiKey string) (refundNotify *RefundNotify, err error) {
|
|||
|
if reqInfo == util.NULL || apiKey == util.NULL {
|
|||
|
return nil, errors.New("reqInfo or apiKey is null")
|
|||
|
}
|
|||
|
var (
|
|||
|
encryptionB, bs []byte
|
|||
|
block cipher.Block
|
|||
|
blockSize int
|
|||
|
)
|
|||
|
if encryptionB, err = base64.StdEncoding.DecodeString(reqInfo); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
h := md5.New()
|
|||
|
h.Write([]byte(apiKey))
|
|||
|
key := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
|
|||
|
if len(encryptionB)%aes.BlockSize != 0 {
|
|||
|
return nil, errors.New("encryptedData is error")
|
|||
|
}
|
|||
|
if block, err = aes.NewCipher([]byte(key)); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
blockSize = block.BlockSize()
|
|||
|
|
|||
|
err = func(dst, src []byte) error {
|
|||
|
if len(src)%blockSize != 0 {
|
|||
|
return errors.New("crypto/cipher: input not full blocks")
|
|||
|
}
|
|||
|
if len(dst) < len(src) {
|
|||
|
return errors.New("crypto/cipher: output smaller than input")
|
|||
|
}
|
|||
|
for len(src) > 0 {
|
|||
|
block.Decrypt(dst, src[:blockSize])
|
|||
|
src = src[blockSize:]
|
|||
|
dst = dst[blockSize:]
|
|||
|
}
|
|||
|
return nil
|
|||
|
}(encryptionB, encryptionB)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
bs = xaes.PKCS7UnPadding(encryptionB)
|
|||
|
refundNotify = new(RefundNotify)
|
|||
|
if err = xml.Unmarshal(bs, refundNotify); err != nil {
|
|||
|
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
type NotifyResponse struct {
|
|||
|
ReturnCode string `xml:"return_code"`
|
|||
|
ReturnMsg string `xml:"return_msg"`
|
|||
|
}
|
|||
|
|
|||
|
// ToXmlString 返回数据给微信
|
|||
|
func (w *NotifyResponse) ToXmlString() (xmlStr string) {
|
|||
|
var buffer strings.Builder
|
|||
|
buffer.WriteString("<xml><return_code><![CDATA[")
|
|||
|
buffer.WriteString(w.ReturnCode)
|
|||
|
buffer.WriteString("]]></return_code>")
|
|||
|
buffer.WriteString("<return_msg><![CDATA[")
|
|||
|
buffer.WriteString(w.ReturnMsg)
|
|||
|
buffer.WriteString("]]></return_msg></xml>")
|
|||
|
xmlStr = buffer.String()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// GetOpenIdByAuthCode 授权码查询openid(AccessToken:157字符)
|
|||
|
// appId:APPID
|
|||
|
// mchId:商户号
|
|||
|
// ApiKey:apiKey
|
|||
|
// authCode:用户授权码
|
|||
|
// nonceStr:随即字符串
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_13&index=9
|
|||
|
func GetOpenIdByAuthCode(ctx context.Context, appId, mchId, apiKey, authCode, nonceStr string) (openIdRsp *OpenIdByAuthCodeRsp, err error) {
|
|||
|
var (
|
|||
|
url string
|
|||
|
bm gopay.BodyMap
|
|||
|
)
|
|||
|
url = "https://api.mch.weixin.qq.com/tools/authcodetoopenid"
|
|||
|
bm = make(gopay.BodyMap)
|
|||
|
bm.Set("appid", appId)
|
|||
|
bm.Set("mch_id", mchId)
|
|||
|
bm.Set("auth_code", authCode)
|
|||
|
bm.Set("nonce_str", nonceStr)
|
|||
|
bm.Set("sign", GetReleaseSign(apiKey, SignType_MD5, bm))
|
|||
|
|
|||
|
openIdRsp = new(OpenIdByAuthCodeRsp)
|
|||
|
_, err = xhttp.NewClient().Type(xhttp.TypeXML).Post(url).SendString(GenerateXml(bm)).EndStruct(ctx, openIdRsp)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
return openIdRsp, nil
|
|||
|
}
|