forked from golang/hotime
329 lines
14 KiB
Go
329 lines
14 KiB
Go
|
package wechat
|
|||
|
|
|||
|
import (
|
|||
|
"crypto/rsa"
|
|||
|
"encoding/json"
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"io"
|
|||
|
"io/ioutil"
|
|||
|
"net/http"
|
|||
|
|
|||
|
"github.com/go-pay/gopay"
|
|||
|
"github.com/go-pay/gopay/pkg/xlog"
|
|||
|
)
|
|||
|
|
|||
|
type Resource struct {
|
|||
|
OriginalType string `json:"original_type"`
|
|||
|
Algorithm string `json:"algorithm"`
|
|||
|
Ciphertext string `json:"ciphertext"`
|
|||
|
AssociatedData string `json:"associated_data"`
|
|||
|
Nonce string `json:"nonce"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptResult struct {
|
|||
|
Appid string `json:"appid"`
|
|||
|
Mchid string `json:"mchid"`
|
|||
|
OutTradeNo string `json:"out_trade_no"`
|
|||
|
TransactionId string `json:"transaction_id"`
|
|||
|
TradeType string `json:"trade_type"`
|
|||
|
TradeState string `json:"trade_state"`
|
|||
|
TradeStateDesc string `json:"trade_state_desc"`
|
|||
|
BankType string `json:"bank_type"`
|
|||
|
Attach string `json:"attach"`
|
|||
|
SuccessTime string `json:"success_time"`
|
|||
|
Payer *Payer `json:"payer"`
|
|||
|
Amount *Amount `json:"amount"`
|
|||
|
SceneInfo *SceneInfo `json:"scene_info"`
|
|||
|
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptPartnerResult struct {
|
|||
|
SpAppid string `json:"sp_appid"`
|
|||
|
SpMchid string `json:"sp_mchid"`
|
|||
|
SubAppid string `json:"sub_appid"`
|
|||
|
SubMchid string `json:"sub_mchid"`
|
|||
|
OutTradeNo string `json:"out_trade_no"`
|
|||
|
TransactionId string `json:"transaction_id"`
|
|||
|
TradeType string `json:"trade_type"`
|
|||
|
TradeState string `json:"trade_state"`
|
|||
|
TradeStateDesc string `json:"trade_state_desc"`
|
|||
|
BankType string `json:"bank_type"`
|
|||
|
Attach string `json:"attach"`
|
|||
|
SuccessTime string `json:"success_time"`
|
|||
|
Payer *PartnerPayer `json:"payer"`
|
|||
|
Amount *Amount `json:"amount"`
|
|||
|
SceneInfo *SceneInfo `json:"scene_info"`
|
|||
|
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptRefundResult struct {
|
|||
|
Mchid string `json:"mchid"`
|
|||
|
OutTradeNo string `json:"out_trade_no"`
|
|||
|
TransactionId string `json:"transaction_id"`
|
|||
|
OutRefundNo string `json:"out_refund_no"`
|
|||
|
RefundId string `json:"refund_id"`
|
|||
|
RefundStatus string `json:"refund_status"`
|
|||
|
SuccessTime string `json:"success_time"`
|
|||
|
UserReceivedAccount string `json:"user_received_account"`
|
|||
|
Amount *RefundAmount `json:"amount"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptPartnerRefundResult struct {
|
|||
|
SpMchid string `json:"sp_mchid"`
|
|||
|
SubMchid string `json:"sub_mchid"`
|
|||
|
OutTradeNo string `json:"out_trade_no"`
|
|||
|
TransactionId string `json:"transaction_id"`
|
|||
|
OutRefundNo string `json:"out_refund_no"`
|
|||
|
RefundId string `json:"refund_id"`
|
|||
|
RefundStatus string `json:"refund_status"`
|
|||
|
SuccessTime string `json:"success_time"`
|
|||
|
UserReceivedAccount string `json:"user_received_account"`
|
|||
|
Amount *RefundAmount `json:"amount"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptCombineResult struct {
|
|||
|
CombineAppid string `json:"combine_appid"`
|
|||
|
CombineMchid string `json:"combine_mchid"`
|
|||
|
CombineOutTradeNo string `json:"combine_out_trade_no"`
|
|||
|
SceneInfo *SceneInfo `json:"scene_info"`
|
|||
|
SubOrders []*SubOrders `json:"sub_orders"` // 最多支持子单条数:50
|
|||
|
CombinePayerInfo *Payer `json:"combine_payer_info"` // 支付者信息
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptScoreResult struct {
|
|||
|
Appid string `json:"appid"`
|
|||
|
Mchid string `json:"mchid"`
|
|||
|
OutOrderNo string `json:"out_order_no"`
|
|||
|
ServiceId string `json:"service_id"`
|
|||
|
Openid string `json:"openid"`
|
|||
|
State string `json:"state"`
|
|||
|
StateDescription string `json:"state_description"`
|
|||
|
TotalAmount int `json:"total_amount"`
|
|||
|
ServiceIntroduction string `json:"service_introduction"`
|
|||
|
PostPayments []*PostPayments `json:"post_payments"`
|
|||
|
PostDiscounts []*PostDiscounts `json:"post_discounts"`
|
|||
|
RiskFund *RiskFund `json:"risk_fund"`
|
|||
|
TimeRange *TimeRange `json:"time_range"`
|
|||
|
Location *Location `json:"location"`
|
|||
|
Attach string `json:"attach"`
|
|||
|
NotifyUrl string `json:"notify_url"`
|
|||
|
OrderId string `json:"order_id"`
|
|||
|
NeedCollection bool `json:"need_collection"`
|
|||
|
Collection *Collection `json:"collection"`
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptProfitShareResult struct {
|
|||
|
SpMchid string `json:"sp_mchid"` // 服务商商户号
|
|||
|
SubMchid string `json:"sub_mchid"` // 子商户号
|
|||
|
TransactionId string `json:"transaction_id"` // 微信订单号
|
|||
|
OrderId string `json:"order_id"` // 微信分账/回退单号
|
|||
|
OutOrderNo string `json:"out_order_no"` // 商户分账/回退单号
|
|||
|
Receiver *Receiver `json:"receiver"`
|
|||
|
SuccessTime string `json:"success_time"` // 成功时间
|
|||
|
}
|
|||
|
|
|||
|
type Receiver struct {
|
|||
|
Type string `json:"type"` // 分账接收方类型
|
|||
|
Account string `json:"account"` // 分账接收方账号
|
|||
|
Amount int `json:"amount"` // 分账动账金额
|
|||
|
Description string `json:"description"` // 分账/回退描述
|
|||
|
}
|
|||
|
|
|||
|
type V3DecryptBusifavorResult struct {
|
|||
|
EventType string `json:"event_type"` // 事件类型
|
|||
|
CouponCode string `json:"coupon_code"` // 券code
|
|||
|
StockId string `json:"stock_id"` // 批次号
|
|||
|
SendTime string `json:"send_time"` // 发放时间
|
|||
|
Openid string `json:"openid"` // 用户标识
|
|||
|
Unionid string `json:"unionid"` // 用户统一标识
|
|||
|
SendChannel string `json:"send_channel"` // 发放渠道
|
|||
|
SendMerchant string `json:"send_merchant"` // 发券商户号
|
|||
|
AttachInfo *BusifavorAttachInfo `json:"attach_info"` // 发券附加信息
|
|||
|
}
|
|||
|
|
|||
|
type BusifavorAttachInfo struct {
|
|||
|
TransactionId string `json:"transaction_id"` // 交易订单编号
|
|||
|
ActCode string `json:"act_code"` // 支付有礼活动编号/营销馆活动ID
|
|||
|
HallCode string `json:"hall_code"` // 营销馆ID
|
|||
|
HallBelongMchID int `json:"hall_belong_mch_id"` // 营销馆所属商户号
|
|||
|
CardID string `json:"card_id"` // 会员卡ID
|
|||
|
Code string `json:"code"` // 会员卡code
|
|||
|
ActivityID string `json:"activity_id"` // 会员活动ID
|
|||
|
}
|
|||
|
|
|||
|
type V3NotifyReq struct {
|
|||
|
Id string `json:"id"`
|
|||
|
CreateTime string `json:"create_time"`
|
|||
|
ResourceType string `json:"resource_type"`
|
|||
|
EventType string `json:"event_type"`
|
|||
|
Summary string `json:"summary"`
|
|||
|
Resource *Resource `json:"resource"`
|
|||
|
SignInfo *SignInfo `json:"-"`
|
|||
|
}
|
|||
|
|
|||
|
type V3NotifyRsp struct {
|
|||
|
Code string `json:"code"`
|
|||
|
Message string `json:"message"`
|
|||
|
}
|
|||
|
|
|||
|
// 解析微信回调请求的参数到 V3NotifyReq 结构体
|
|||
|
func V3ParseNotify(req *http.Request) (notifyReq *V3NotifyReq, 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("read request body error:%w", err)
|
|||
|
}
|
|||
|
si := &SignInfo{
|
|||
|
HeaderTimestamp: req.Header.Get(HeaderTimestamp),
|
|||
|
HeaderNonce: req.Header.Get(HeaderNonce),
|
|||
|
HeaderSignature: req.Header.Get(HeaderSignature),
|
|||
|
HeaderSerial: req.Header.Get(HeaderSerial),
|
|||
|
SignBody: string(bs),
|
|||
|
}
|
|||
|
notifyReq = &V3NotifyReq{SignInfo: si}
|
|||
|
if err = json.Unmarshal(bs, notifyReq); err != nil {
|
|||
|
return nil, fmt.Errorf("json.Unmarshal(%s, %+v):%w", string(bs), notifyReq, err)
|
|||
|
}
|
|||
|
return notifyReq, nil
|
|||
|
}
|
|||
|
|
|||
|
// Deprecated
|
|||
|
// 推荐使用 VerifySignByPK()
|
|||
|
func (v *V3NotifyReq) VerifySign(wxPkContent string) (err error) {
|
|||
|
if v.SignInfo != nil {
|
|||
|
return V3VerifySign(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPkContent)
|
|||
|
}
|
|||
|
return errors.New("verify notify sign, bug SignInfo is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 异步通知验签
|
|||
|
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKey() 获取
|
|||
|
func (v *V3NotifyReq) VerifySignByPK(wxPublicKey *rsa.PublicKey) (err error) {
|
|||
|
if v.SignInfo != nil {
|
|||
|
return V3VerifySignByPK(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPublicKey)
|
|||
|
}
|
|||
|
return errors.New("verify notify sign, bug SignInfo is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 普通支付 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptCipherText(apiV3Key string) (result *V3DecryptResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 服务商支付 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptPartnerCipherText(apiV3Key string) (result *V3DecryptPartnerResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptPartnerNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 普通退款 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptRefundCipherText(apiV3Key string) (result *V3DecryptRefundResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 服务商退款 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptPartnerRefundCipherText(apiV3Key string) (result *V3DecryptPartnerRefundResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptPartnerRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 合单支付 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptCombineCipherText(apiV3Key string) (result *V3DecryptCombineResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptCombineNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密 支付分 回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptScoreCipherText(apiV3Key string) (result *V3DecryptScoreResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptScoreNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密分账动账回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptProfitShareCipherText(apiV3Key string) (result *V3DecryptProfitShareResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptProfitShareNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// 解密商家券回调中的加密信息
|
|||
|
func (v *V3NotifyReq) DecryptBusifavorCipherText(apiV3Key string) (result *V3DecryptBusifavorResult, err error) {
|
|||
|
if v.Resource != nil {
|
|||
|
result, err = V3DecryptBusifavorNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
|
|||
|
if err != nil {
|
|||
|
bytes, _ := json.Marshal(v)
|
|||
|
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
|
|||
|
}
|
|||
|
return result, nil
|
|||
|
}
|
|||
|
return nil, errors.New("notify data Resource is nil")
|
|||
|
}
|
|||
|
|
|||
|
// Deprecated
|
|||
|
// 暂时不推荐此方法,请使用 wechat.V3ParseNotify()
|
|||
|
// 解析微信回调请求的参数到 gopay.BodyMap
|
|||
|
func V3ParseNotifyToBodyMap(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 {
|
|||
|
xlog.Error("err:", err)
|
|||
|
return
|
|||
|
}
|
|||
|
bm = make(gopay.BodyMap)
|
|||
|
if err = json.Unmarshal(bs, &bm); err != nil {
|
|||
|
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err)
|
|||
|
}
|
|||
|
return bm, nil
|
|||
|
}
|