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