├── go.mod ├── custom ├── merchant_disposal.go ├── upload.go ├── basic_struct.go ├── pay_waiter.go ├── complaints.go ├── edu-papay.go ├── edu_school_pay.go ├── profit_sharing.go ├── basic_payment.go ├── merchant_incoming.go └── offline_face_pay.go ├── core ├── option.go ├── certificate_test.go ├── complaints.go ├── sign.go ├── request.go ├── merchant_disposal.go ├── pay_waiter.go ├── merchant_incoming.go ├── settings.go ├── edu_school_pay.go ├── edu-papay.go ├── upload.go ├── verify.go ├── basic_payment.go ├── certificate.go ├── profit_sharing.go ├── offline_face_pay.go └── client.go ├── utils ├── utils_test.go └── utils.go ├── .gitignore ├── auth └── auth.go ├── LICENSE ├── client.go ├── README.md ├── client_test.go ├── settings.go └── constant └── constant.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/louismax/wxpayv3 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /custom/merchant_disposal.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type GeneralViolationNotifications struct { 4 | NotifyUrl string `json:"notify_url"` 5 | } 6 | -------------------------------------------------------------------------------- /custom/upload.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | //ReqUploadImage ReqUploadImage 4 | type ReqUploadImage struct { 5 | Filename string `json:"filename"` 6 | Sha256 string `json:"sha256"` 7 | } 8 | 9 | //RespUploadImage RespUploadImage 10 | type RespUploadImage struct { 11 | MediaId string `json:"media_id"` 12 | } 13 | -------------------------------------------------------------------------------- /core/option.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // ClientOption 微信支付 API v3 HTTPClient core.Client 初始化参数 4 | type ClientOption interface { 5 | Join(settings *DialSettings) error 6 | } 7 | 8 | // ErrorOption 错误初始化参数,用于返回错误 9 | type ErrorOption struct{ Error error } 10 | 11 | // Join 返回初始化错误 12 | func (w ErrorOption) Join(_ *DialSettings) error { 13 | return w.Error 14 | } 15 | -------------------------------------------------------------------------------- /core/certificate_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/x509" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestLoadCertificateWithPath(t *testing.T) { 10 | pv, err := LoadCertificateWithPath("") 11 | if err != nil { 12 | t.Log(err) 13 | return 14 | } 15 | t.Log(GetCertificateSerialNumber(*pv)) 16 | } 17 | 18 | func TestLoadPublicKeyWithPath(t *testing.T) { 19 | pv, err := LoadPublicKeyWithPath("") 20 | if err != nil { 21 | t.Log(err) 22 | return 23 | } 24 | t.Log(pv) 25 | } 26 | 27 | func TestIsCertValid(t *testing.T) { 28 | t.Log(IsCertValid(x509.Certificate{}, time.Now())) 29 | } 30 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/louismax/wxpayv3/custom" 5 | "testing" 6 | ) 7 | 8 | func TestGenerateNonce(t *testing.T) { 9 | t.Log(GenerateNonce()) 10 | } 11 | 12 | func TestFaceMessageDecryption(t *testing.T) { 13 | r, e := FaceMessageDecryption(custom.FaceMessageCiphertext{ 14 | ID: "", 15 | CreateTime: "", 16 | EventType: "", 17 | ResourceType: "", 18 | Resource: struct { 19 | Algorithm string `json:"algorithm"` 20 | Ciphertext string `json:"ciphertext"` 21 | OriginalType string `json:"original_type"` 22 | AssociatedData string `json:"associated_data"` 23 | Nonce string `json:"nonce"` 24 | }{}, 25 | Summary: "", 26 | }, "") 27 | if e != nil { 28 | t.Log(e) 29 | return 30 | } 31 | t.Log(r) 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .idea/ 14 | apiclient_cert.pem 15 | apiclient_key.pem 16 | *.bak 17 | pay_test_local.go 18 | users_test_bak.go 19 | 1603541829_cert.pem 20 | 1603541829_key.pem 21 | applysubject_test_local.go 22 | cert_test_local.go 23 | Public_test_local.go 24 | 微信图片_20201028105758.png 25 | applymentsub_test_local.go 26 | applysubject.go.1 27 | applysubject.go.1_test.go.1 28 | applysubject_test_local.go.1 29 | applysubject_type.go.1 30 | cancel_test_local.go 31 | organization_test_local.go 32 | 1.jpg 33 | xp_c.pem 34 | xp_k.pem 35 | local_test.go 36 | LEZGitoconaBSYXool3Cu.jpg 37 | local_client_test.go 38 | pub_cert.pem 39 | pub_key.pem 40 | qj_key.pem 41 | qj_pub_key.pem 42 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // SignatureResult 数字签名结果 9 | type SignatureResult struct { 10 | MchID string // 商户号 11 | CertificateSerialNo string // 签名对应的证书序列号 12 | Signature string // 签名内容 13 | } 14 | 15 | // Deprecated:Credential 请求报文头 Authorization 信息生成器 16 | type Credential interface { 17 | GenerateAuthorizationHeader(ctx context.Context, method, canonicalURL, signBody string) (authorization string, err error) 18 | } 19 | 20 | // Deprecated:Signer 数字签名生成器 21 | type Signer interface { 22 | Sign(ctx context.Context, message string) (*SignatureResult, error) // 对信息进行签名 23 | Algorithm() string // 返回使用的签名算法 24 | } 25 | 26 | // Deprecated:Validator 应答报文验证器 27 | type Validator interface { 28 | Validate(ctx context.Context, response *http.Response) error // 对 HTTP 应答报文进行验证 29 | } 30 | -------------------------------------------------------------------------------- /custom/basic_struct.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | import "crypto/x509" 4 | 5 | // CertificateResp CertificateResp 6 | type CertificateResp struct { 7 | Data []*CertificateData `json:"data"` 8 | } 9 | 10 | // EncryptCertificate encryptCertificate 11 | type EncryptCertificate struct { 12 | Algorithm string `json:"algorithm"` 13 | Nonce string `json:"nonce"` 14 | AssociatedData string `json:"associated_data"` 15 | Ciphertext string `json:"ciphertext"` 16 | } 17 | 18 | // CertificateData CertificateData 19 | type CertificateData struct { 20 | EncryptCertificate *EncryptCertificate `json:"encrypt_certificate"` 21 | DecryptCertificate string `json:"decrypt_certificate"` 22 | SerialNo string `json:"serial_no"` 23 | EffectiveTime string `json:"effective_time"` 24 | ExpireTime string `json:"expire_time"` 25 | } 26 | 27 | type CertificateDataList struct { 28 | SerialNo string 29 | Certificate *x509.Certificate 30 | } 31 | -------------------------------------------------------------------------------- /core/complaints.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/louismax/wxpayv3/constant" 6 | "github.com/louismax/wxpayv3/custom" 7 | "github.com/louismax/wxpayv3/utils" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | ) 12 | 13 | func (c *PayClient) QueryComplaintsList(beginDate, endDate string, limit, offset int, mchId ...string) (*custom.RespComplaintsList, error) { 14 | query := url.Values{} 15 | query.Set("begin_date", beginDate) 16 | query.Set("end_date", endDate) 17 | if limit > 0 { 18 | query.Set("limit", strconv.Itoa(limit)) 19 | } 20 | if offset > 0 { 21 | query.Set("offset", strconv.Itoa(offset)) 22 | } 23 | if len(mchId) > 0 { 24 | query.Set("complainted_mchid", mchId[0]) 25 | } 26 | body, err := c.doRequest(nil, utils.BuildUrl(nil, query, constant.APIComplaintsList), http.MethodGet) 27 | if err != nil { 28 | return nil, err 29 | } 30 | resp := custom.RespComplaintsList{} 31 | err = json.Unmarshal(body, &resp) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &resp, nil 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Louis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core/sign.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/louismax/wxpayv3/utils" 6 | "time" 7 | ) 8 | 9 | const CertificationType = "WECHATPAY2-SHA256-RSA2048" 10 | 11 | // Authorization 获取WechatPayV3的header信息Authorization 12 | func (c *PayClient) Authorization(httpMethod string, urlString string, body []byte) (string, error) { 13 | token, err := c.Token(httpMethod, urlString, body) 14 | if err != nil { 15 | return "", err 16 | } 17 | return CertificationType + " " + token, nil 18 | } 19 | 20 | // Token 获取签名信息 请求方法为GET时,报文主体为空;当请求方法为POST或PUT时,请使用真实发送的JSON报文;图片上传API,请使用meta对应的JSON报文 21 | func (c *PayClient) Token(httpMethod string, rawUrl string, body []byte) (string, error) { 22 | nonce, err := utils.GenerateNonce() 23 | if err != nil { 24 | return "", err 25 | } 26 | timestamp := time.Now().Unix() 27 | message, err := utils.BuildMessage(httpMethod, rawUrl, body, nonce, timestamp) 28 | if err != nil { 29 | return "", err 30 | } 31 | signature, err := c.Sign(message) 32 | if err != nil { 33 | return "", err 34 | } 35 | return fmt.Sprintf(`mchid="%s",nonce_str="%s",signature="%s",timestamp="%v",serial_no="%s"`, c.MchId, nonce, signature, timestamp, c.ApiSerialNo), nil 36 | } 37 | 38 | // Sign 签名 39 | func (c *PayClient) Sign(message []byte) (string, error) { 40 | return utils.Sign(message, c.ApiPrivateKey) 41 | } 42 | -------------------------------------------------------------------------------- /core/request.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | //WithBaseHeader WithBaseHeader 10 | func WithBaseHeader(req *http.Request, authorization string) *http.Request { 11 | req.Header.Set("Content-Type", "application/json") 12 | req.Header.Set("Accept", "application/json") 13 | req.Header.Set("Authorization", authorization) 14 | return req 15 | } 16 | 17 | //NewRequest NewRequest 18 | func NewRequest(authorization string, url string, method string, body io.Reader) (*http.Request, error) { 19 | req, err := http.NewRequest(method, url, body) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return WithBaseHeader(req, authorization), nil 24 | } 25 | 26 | //SimpleRequest SimpleRequest 27 | func SimpleRequest(client *http.Client, url string, method string, authorization string, body []byte, platformSerialNo string) (*http.Response, error) { 28 | if client == nil { 29 | client = http.DefaultClient 30 | } 31 | var requestBody io.Reader 32 | if body != nil { 33 | requestBody = bytes.NewReader(body) 34 | } 35 | request, err := NewRequest(authorization, url, method, requestBody) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if platformSerialNo != "" { 40 | request.Header.Set("Wechatpay-Serial", platformSerialNo) 41 | } 42 | 43 | resp, err := client.Do(request) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return resp, nil 48 | } 49 | -------------------------------------------------------------------------------- /custom/pay_waiter.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type ReqSmartGuideRegister struct { 4 | SubMchId string `json:"sub_mchid"` 5 | CorpId string `json:"corpid"` 6 | StoreId int `json:"store_id"` 7 | UserId string `json:"userid"` 8 | Name string `json:"name"` 9 | Mobile string `json:"mobile"` 10 | QrCode string `json:"qr_code"` 11 | Avatar string `json:"avatar"` 12 | GroupQrcode string `json:"group_qrcode,omitempty"` 13 | } 14 | 15 | type RespSmartGuideRegister struct { 16 | GuideId string `json:"guide_id"` 17 | } 18 | 19 | type ReqSmartGuideAssign struct { 20 | OutTradeNo string `json:"out_trade_no"` 21 | SubMchid string `json:"sub_mchid"` 22 | } 23 | 24 | type RespSmartGuideQuery struct { 25 | TotalCount int `json:"total_count"` 26 | Limit int `json:"limit"` 27 | Offset int `json:"offset"` 28 | Data []struct { 29 | GuideId string `json:"guide_id"` 30 | StoreId int `json:"store_id"` 31 | Name string `json:"name"` 32 | Mobile string `json:"mobile"` 33 | Userid string `json:"userid"` 34 | WorkId string `json:"work_id"` 35 | } `json:"data"` 36 | } 37 | 38 | type ReqSmartGuideUpdate struct { 39 | SubMchId string `json:"sub_mchid"` 40 | Name string `json:"name"` 41 | Mobile string `json:"mobile"` 42 | QrCode string `json:"qr_code"` 43 | Avatar string `json:"avatar"` 44 | GroupQrcode string `json:"group_qrcode"` 45 | } 46 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package wxpayv3 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "github.com/louismax/wxpayv3/constant" 7 | "github.com/louismax/wxpayv3/core" 8 | "net/http" 9 | ) 10 | 11 | // NewClient NewClient 12 | func NewClient(opts ...core.ClientOption) (core.Client, error) { 13 | settings := &core.DialSettings{} 14 | for _, opt := range opts { 15 | if err := opt.Join(settings); err != nil { 16 | return nil, fmt.Errorf("初始化客户端设置错误:%v", err) 17 | } 18 | } 19 | if err := settings.Validate(); err != nil { 20 | return nil, err 21 | } 22 | if settings.HttpClient == nil { 23 | settings.HttpClient = &http.Client{ 24 | Timeout: constant.DefaultTimeout, 25 | } 26 | } 27 | 28 | client := &core.PayClient{ 29 | MchId: settings.MchId, 30 | ApiV3Key: settings.ApiV3Key, 31 | ApiSerialNo: settings.ApiSerialNo, 32 | ApiPrivateKey: settings.ApiPrivateKey, 33 | ApiCertificate: settings.ApiCertificate, 34 | DefaultPlatformSerialNo: settings.DefaultSerialNo, 35 | PlatformCertMap: settings.PlatformCertMap, 36 | WechatPayPublicKeyID: settings.WechatPayPublicKeyID, 37 | WechatPayPublicKey: settings.WechatPayPublicKey, 38 | HttpClient: settings.HttpClient, 39 | } 40 | client.PlatformCertMap = make(map[string]*x509.Certificate) 41 | client.PlatformCertMap = settings.PlatformCertMap 42 | 43 | return client, nil 44 | } 45 | -------------------------------------------------------------------------------- /core/merchant_disposal.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/louismax/wxpayv3/constant" 6 | "github.com/louismax/wxpayv3/custom" 7 | "github.com/louismax/wxpayv3/utils" 8 | "net/http" 9 | ) 10 | 11 | //QueryViolationNotifications 查询商户违规通知回调地址 12 | func (c *PayClient) QueryViolationNotifications() (*custom.GeneralViolationNotifications, error) { 13 | body, err := c.doRequest(nil, utils.BuildUrl(nil, nil, constant.APIViolationNotifications), http.MethodGet) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | resp := custom.GeneralViolationNotifications{} 19 | err = json.Unmarshal(body, &resp) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &resp, nil 24 | } 25 | 26 | //CreateViolationNotifications 创建商户违规通知回调地址 27 | func (c *PayClient) CreateViolationNotifications(data custom.GeneralViolationNotifications) (*custom.GeneralViolationNotifications, error) { 28 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIViolationNotifications), http.MethodPost) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | resp := custom.GeneralViolationNotifications{} 34 | err = json.Unmarshal(body, &resp) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &resp, nil 39 | } 40 | 41 | //UpdateViolationNotifications 修改商户违规通知回调地址 42 | func (c *PayClient) UpdateViolationNotifications(data custom.GeneralViolationNotifications) (*custom.GeneralViolationNotifications, error) { 43 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIViolationNotifications), http.MethodPut) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | resp := custom.GeneralViolationNotifications{} 49 | err = json.Unmarshal(body, &resp) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &resp, nil 54 | } 55 | 56 | //DeleteViolationNotifications 删除商户违规通知回调地址 57 | func (c *PayClient) DeleteViolationNotifications() error { 58 | _, err := c.doRequest(nil, utils.BuildUrl(nil, nil, constant.APIViolationNotifications), http.MethodDelete) 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /custom/complaints.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type RespComplaintsList struct { 4 | Data []struct { 5 | ComplaintID string `json:"complaint_id"` 6 | ComplaintTime string `json:"complaint_time"` 7 | ComplaintDetail string `json:"complaint_detail"` 8 | ComplaintState string `json:"complaint_state"` 9 | PayerPhone string `json:"payer_phone"` 10 | ComplaintOrderInfo []struct { 11 | TransactionID string `json:"transaction_id"` 12 | OutTradeNo string `json:"out_trade_no"` 13 | Amount int `json:"amount"` 14 | } `json:"complaint_order_info"` 15 | ComplaintFullRefunded bool `json:"complaint_full_refunded"` 16 | IncomingUserResponse bool `json:"incoming_user_response"` 17 | UserComplaintTimes int `json:"user_complaint_times"` 18 | ComplaintMediaList []struct { 19 | MediaType string `json:"media_type"` 20 | MediaURL []string `json:"media_url"` 21 | } `json:"complaint_media_list"` 22 | ProblemDescription string `json:"problem_description"` 23 | ProblemType string `json:"problem_type"` 24 | ApplyRefundAmount int `json:"apply_refund_amount"` 25 | UserTagList []string `json:"user_tag_list"` 26 | ServiceOrderInfo []struct { 27 | OrderID string `json:"order_id"` 28 | OutOrderNo string `json:"out_order_no"` 29 | State string `json:"state"` 30 | } `json:"service_order_info"` 31 | AdditionalInfo struct { 32 | Type string `json:"type"` 33 | SharePowerInfo struct { 34 | ReturnTime string `json:"return_time"` 35 | ReturnAddressInfo struct { 36 | ReturnAddress string `json:"return_address"` 37 | Longitude string `json:"longitude"` 38 | Latitude string `json:"latitude"` 39 | } `json:"return_address_info"` 40 | IsReturnedToSameMachine bool `json:"is_returned_to_same_machine"` 41 | } `json:"share_power_info"` 42 | } `json:"additional_info"` 43 | InPlatformService bool `json:"in_platform_service"` 44 | NeedImmediateService bool `json:"need_immediate_service"` 45 | } `json:"data"` 46 | Limit int `json:"limit"` 47 | Offset int `json:"offset"` 48 | TotalCount int `json:"total_count"` 49 | } 50 | -------------------------------------------------------------------------------- /core/pay_waiter.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/louismax/wxpayv3/constant" 6 | "github.com/louismax/wxpayv3/custom" 7 | "github.com/louismax/wxpayv3/utils" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | func (c *PayClient) SmartGuideRegister(data custom.ReqSmartGuideRegister) (*custom.RespSmartGuideRegister, error) { 13 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APISmartGuideRegister), http.MethodPost) 14 | if err != nil { 15 | return nil, err 16 | } 17 | resp := custom.RespSmartGuideRegister{} 18 | err = json.Unmarshal(body, &resp) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return &resp, nil 23 | } 24 | 25 | func (c *PayClient) SmartGuideAssign(guideId string, data custom.ReqSmartGuideAssign) error { 26 | params := map[string]string{"guide_id": guideId} 27 | _, err := c.doRequest(data, utils.BuildUrl(params, nil, constant.APISmartGuideAssign), http.MethodPost) 28 | if err != nil { 29 | return err 30 | } 31 | return nil 32 | } 33 | 34 | func (c *PayClient) SmartGuideQuery(storeId, subMchid, userId, mobile, workId, limit, offset string) (*custom.RespSmartGuideQuery, error) { 35 | qy := url.Values{} 36 | qy.Set("store_id", storeId) 37 | if subMchid != "" { 38 | qy.Set("sub_mchid", subMchid) 39 | } 40 | if userId != "" { 41 | qy.Set("userid", userId) 42 | } 43 | if mobile != "" { 44 | qy.Set("mobile", mobile) 45 | } 46 | if workId != "" { 47 | qy.Set("work_id", workId) 48 | } 49 | if limit != "" { 50 | qy.Set("limit", limit) 51 | } 52 | if offset != "" { 53 | qy.Set("offset", offset) 54 | } 55 | 56 | body, err := c.doRequest(nil, utils.BuildUrl(nil, qy, constant.APISmartGuideQuery), http.MethodGet) 57 | if err != nil { 58 | return nil, err 59 | } 60 | resp := custom.RespSmartGuideQuery{} 61 | err = json.Unmarshal(body, &resp) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return &resp, nil 66 | } 67 | 68 | func (c *PayClient) SmartGuideUpdate(guideId string, data custom.ReqSmartGuideUpdate) error { 69 | params := map[string]string{"guide_id": guideId} 70 | _, err := c.doRequest(data, utils.BuildUrl(params, nil, constant.APISmartGuideUpdate), http.MethodPatch) 71 | if err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wxpayv3 (wechatpay-api-v3) 2 | [简体中文](README.md) 3 | ## 微信支付API V3 SDK for GO 4 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/louismax/wxpayv3)](https://goreportcard.com/report/github.com/louismax/wxpayv3) 6 | [![GoDoc](https://godoc.org/github.com/louismax/wxpayv3?status.svg)](https://godoc.org/github.com/louismax/wxpayv3) 7 | [![GitHub release](https://img.shields.io/github/tag/louismax/wxpayv3.svg)](https://github.com/louismax/wxpayv3/releases) 8 | [![GitHub license](https://img.shields.io/github/license/louismax/wxpayv3.svg)](https://github.com/louismax/wxpayv3/blob/master/LICENSE) 9 | [![GitHub Repo Size](https://img.shields.io/github/repo-size/louismax/wxpayv3.svg)](https://img.shields.io/github/repo-size/louismax/wxpayv3.svg) 10 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/louismax/wxpayv3.svg)](https://img.shields.io/github/last-commit/louismax/wxpayv3.svg) 11 | 12 | ## 安装 13 | `go get -v github.com/louismax/wxpayv3` 14 | 15 | ## 实现能力(持续更新) 16 | ### 基础 17 | - 获取平台证书 ✔️ 18 | - 上传图片获取MediaId ✔️ 19 | - 上传视频获取MediaId ❌ 20 | 21 | ### 商户进件(仅支持服务商,支持小微商户进件、特约商户进件) 22 | - 提交申请单 ✔️ 23 | - 查询申请单状态 ✔️ 24 | - 修改结算账号 ✔️ 25 | - 查询结算账号 ✔️ 26 | 27 | ### 基础支付(JSAPI支付、小程序支付,支持服务商版、普通商户版) 28 | - JSAPI下单(兼容服务商模式、直连商户模式) ✔️ 29 | - 查询订单-通过微信订单号(兼容服务商模式、直连商户模式) ✔️ 30 | - 查询订单-通过商户订单号(兼容服务商模式、直连商户模式) ✔️ 31 | - 关闭订单 ❌ 32 | - 申请退款 ✔️ 33 | - 查询退款 ❌ 34 | - 申请交易账单(特约商户、普通商户) ✔️ 35 | - 申请资金账单 ✔️ 36 | - 下载账单 ✔️ 37 | 38 | ### 分账(支持服务商版) 39 | - 请求分账 ✔️ 40 | - 查询分账结果 ✔️ 41 | - 请求分账回退 ✔️ 42 | - 查询分账回退结果 ✔️ 43 | - 解冻剩余资金 ✔️ 44 | - 查询剩余待分金额 ✔️ 45 | - 查询最大分账比例 ✔️ 46 | - 添加分账接收方 ✔️ 47 | - 删除分账接收方 ✔️ 48 | - 申请分账账单 ✔️ 49 | - 下载账单 ✔️ 50 | 51 | ### 教培续费通(支持服务商版、普通商户版) 52 | - 预签约 ✔️ 53 | - 协议号查询签约 ✔️ 54 | - 用户标识查询签约 ✔️ 55 | - 解约 ✔️ 56 | - 发送预扣款通知 ✔️ 57 | - 扣款受理 ✔️ 58 | - 微信订单号查单 ✔️ 59 | - 商户订单号查单 ✔️ 60 | 61 | ### 校园刷脸代扣(仅支持服务商) 62 | - 获取机构信息(根据机构ID) ✔️ 63 | - 获取机构信息(根据机构名称) ✔️ 64 | - 获取授权凭证 ✔️ 65 | - 查询刷脸用户信息 ✔️ 66 | - 修改刷脸用户信息 ✔️ 67 | - 解除刷脸用户签约关系 ✔️ 68 | - 预签约 ✔️ 69 | - 申请扣款 ✔️ 70 | - 签约查询 ✔️ 71 | - 人脸报文(签约解约)消息解密 ✔️ 72 | - 查询重采用户列表 ✔️ 73 | - 查询重采 ✔️ 74 | - 离线人脸团餐专属查单 ✔️ 75 | - 获取AuthInfo ✔️ 76 | - 获取还款链接 ✔️ 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | ## 参考资料 85 | * [微信支付商户平台API文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml) 86 | * [微信支付服务商平台API文档](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml) 87 | * [微信支付API V3接口规范](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay-1.shtml) 88 | * [微信支付教培续费通](https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/edu-papay/chapter1_1.shtml) 89 | 90 | ## 协议 91 | MIT 许可证(MIT)。有关更多信息,请参见[协议文件](LICENSE)。 92 | 93 | -------------------------------------------------------------------------------- /core/merchant_incoming.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/louismax/wxpayv3/constant" 6 | "github.com/louismax/wxpayv3/custom" 7 | "github.com/louismax/wxpayv3/utils" 8 | "net/http" 9 | ) 10 | 11 | 12 | //IncomingSubmitApplication 提交进件申请单 13 | func (c *PayClient) IncomingSubmitApplication(data custom.ReqIncomingSubmitApplication) (*custom.RespIncomingSubmitApplication, error) { 14 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIIncomingSubmitApplication), http.MethodPost) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | resp := custom.RespIncomingSubmitApplication{} 20 | err = json.Unmarshal(body, &resp) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &resp, nil 25 | } 26 | 27 | //ModifySettlement 修改结算账号 28 | func (c *PayClient) ModifySettlement(subMchid string,data custom.ReqModifySettlement) error { 29 | params := map[string]string{"sub_mchid": subMchid} 30 | _, err := c.doRequest(data, utils.BuildUrl(params, nil, constant.APIModifySettlement), http.MethodPost) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | //QuerySettlementAccount 查询结算账户 38 | func (c *PayClient) QuerySettlementAccount(subMchid string) (*custom.SettlementAccount, error) { 39 | params := map[string]string{"sub_mchid": subMchid} 40 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQuerySettlementAccount), http.MethodGet) 41 | if err != nil { 42 | return nil, err 43 | } 44 | resp := custom.SettlementAccount{} 45 | err = json.Unmarshal(body, &resp) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return &resp, nil 50 | } 51 | 52 | //GetStatusRepairOrderByBusinessCode 通过业务申请编号查询申请状态 53 | func (c *PayClient) GetStatusRepairOrderByBusinessCode(businessCode string) (*custom.RespGetStatusRepairOrder, error) { 54 | params := map[string]string{"business_code": businessCode} 55 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIGetStatusRepairOrderByBusinessCode), http.MethodGet) 56 | if err != nil { 57 | return nil, err 58 | } 59 | resp := custom.RespGetStatusRepairOrder{} 60 | err = json.Unmarshal(body, &resp) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &resp, nil 65 | } 66 | 67 | //GetStatusRepairOrderByApplymentId 通过申请单号查询申请状态 68 | func (c *PayClient) GetStatusRepairOrderByApplymentId(applymentId string) (*custom.RespGetStatusRepairOrder, error) { 69 | params := map[string]string{"applyment_id": applymentId} 70 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIGetStatusRepairOrderByApplymentId), http.MethodGet) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | resp := custom.RespGetStatusRepairOrder{} 76 | err = json.Unmarshal(body, &resp) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return &resp, nil 81 | } 82 | -------------------------------------------------------------------------------- /core/settings.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "fmt" 7 | "github.com/louismax/wxpayv3/custom" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | // DialSettings DialSettings 13 | type DialSettings struct { 14 | MchId string // 商户号 15 | ApiV3Key string // apiV3密钥 16 | ApiSerialNo string // API证书序列号 17 | ApiPrivateKey *rsa.PrivateKey // API证书私钥 18 | ApiCertificate *x509.Certificate // API证书(非必须,可获取证书序列号和商户API公钥) 19 | DefaultSerialNo string // 默认平台证书序列号 20 | PlatformCertMap map[string]*x509.Certificate // 平台证书集合 21 | WechatPayPublicKeyID string // 平台公钥ID 22 | WechatPayPublicKey *rsa.PublicKey // 平台公钥 23 | HttpClient *http.Client // http客户端 24 | } 25 | 26 | // Validate 校验请求配置是否有效 27 | func (ds *DialSettings) Validate() error { 28 | if ds.MchId == "" { 29 | return fmt.Errorf("商户号无效") 30 | } 31 | if ds.ApiPrivateKey == nil { 32 | return fmt.Errorf("商户API私钥无效") 33 | } 34 | if ds.ApiSerialNo == "" { 35 | if ds.ApiCertificate != nil { 36 | //通过商户证书获取证书编号 37 | ds.ApiSerialNo = GetCertificateSerialNumber(*ds.ApiCertificate) 38 | } else { 39 | return fmt.Errorf("商户API证书序列号无效") 40 | } 41 | } 42 | if ds.ApiCertificate != nil { 43 | if GetCertificateSerialNumber(*ds.ApiCertificate) != ds.ApiSerialNo { 44 | return fmt.Errorf("商户API证书序列号不匹配") 45 | } 46 | if IsCertExpired(*ds.ApiCertificate, time.Now()) { 47 | return fmt.Errorf("商户API证书已过期") 48 | } 49 | } 50 | 51 | if len(ds.PlatformCertMap) > 0 { 52 | for k, v := range ds.PlatformCertMap { 53 | if GetCertificateSerialNumber(*v) != k { 54 | return fmt.Errorf("微信平台证书序列号不匹配") 55 | } 56 | if IsCertExpired(*v, time.Now()) { 57 | return fmt.Errorf("平台证书已过期") 58 | } 59 | } 60 | } 61 | return nil 62 | } 63 | 64 | // BasicInformation 基础参数 65 | type BasicInformation struct { 66 | MchID string 67 | MchAPIv3Key string 68 | ApiCert ApiCert 69 | } 70 | 71 | // Join as join 72 | func (w BasicInformation) Join(o *DialSettings) error { 73 | o.MchId = w.MchID 74 | o.ApiV3Key = w.MchAPIv3Key 75 | o.ApiSerialNo = w.ApiCert.ApiSerialNo 76 | o.ApiPrivateKey = w.ApiCert.ApiPrivateKey 77 | o.ApiCertificate = w.ApiCert.ApiCertificate 78 | return nil 79 | } 80 | 81 | // PlatformCert 微信平台证书参数 82 | type PlatformCert struct { 83 | DefaultSerialNo string // 默认平台证书序列号 84 | CertList []custom.CertificateDataList 85 | } 86 | 87 | // Join as join 88 | func (w PlatformCert) Join(o *DialSettings) error { 89 | o.DefaultSerialNo = w.DefaultSerialNo 90 | if o.PlatformCertMap == nil { 91 | o.PlatformCertMap = make(map[string]*x509.Certificate) 92 | } 93 | for _, v := range w.CertList { 94 | o.PlatformCertMap[v.SerialNo] = v.Certificate 95 | } 96 | return nil 97 | } 98 | 99 | // PlatformPubKey 微信平台公钥参数 100 | type PlatformPubKey struct { 101 | PubKeyId string // 公钥ID 102 | PubKey *rsa.PublicKey // 平台公钥 103 | } 104 | 105 | // Join as join 106 | func (w PlatformPubKey) Join(o *DialSettings) error { 107 | o.WechatPayPublicKeyID = w.PubKeyId 108 | o.WechatPayPublicKey = w.PubKey 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package wxpayv3 2 | 3 | import ( 4 | "github.com/louismax/wxpayv3/core" 5 | "testing" 6 | ) 7 | 8 | func TestNewClient1(t *testing.T) { 9 | client, err := NewClient( 10 | InjectWxPayMchParamExtra("MCH_ID", "API_V3_KEY", "private_key.pem file path", "private_cert.pem file path"), 11 | ) 12 | if err != nil { 13 | t.Log(err) 14 | return 15 | } 16 | 17 | //获取微信平台证书 18 | resp, err := client.GetCertificate() 19 | if err != nil { 20 | t.Log(err) 21 | return 22 | } 23 | t.Log(resp) 24 | } 25 | 26 | func TestNewClient2(t *testing.T) { 27 | pk, err := core.LoadPrivateKey(`-----BEGIN PRIVATE KEY----- 28 | XXXX商户私钥文本内容... 29 | -----END PRIVATE KEY-----`) 30 | if err != nil { 31 | t.Log(err) 32 | return 33 | } 34 | client, err := NewClient( 35 | InjectWxPayMchParamFull("1607549129", core.ApiCert{ 36 | ApiSerialNo: "ApiSerialNo", 37 | ApiPrivateKey: pk, 38 | }, "API_V3_KEY"), 39 | ) 40 | if err != nil { 41 | t.Log(err) 42 | return 43 | } 44 | 45 | resp, err := client.PaymentQueryOrderByOutTradeNo("OUT_TRADE_NO", "MCH_ID", "SUB_MCH_ID") 46 | if err != nil { 47 | t.Log(err) 48 | return 49 | } 50 | t.Log(resp) 51 | } 52 | 53 | func TestLoadPlatformCertStr(t *testing.T) { 54 | client, err := NewClient( 55 | InjectWxPayMchParamExtra("MCH_ID", "API_V3_KEY", "private_key.pem file path", "private_cert.pem file path"), 56 | InjectWxPayPlatformCert([]string{`-----BEGIN CERTIFICATE----- 57 | 平台证书文本内容... 58 | -----END CERTIFICATE-----`}), 59 | ) 60 | if err != nil { 61 | t.Log(err) 62 | return 63 | } 64 | 65 | resp, err := client.PaymentQueryOrderByOutTradeNo("OUT_TRADE_NO", "MCH_ID", "SUB_MCH_ID") 66 | if err != nil { 67 | t.Log(err) 68 | return 69 | } 70 | t.Log(resp) 71 | 72 | } 73 | 74 | func TestLoadPlatformCertFile(t *testing.T) { 75 | client, err := NewClient( 76 | InjectWxPayMchParamExtra("MCH_ID", "API_V3_KEY", "private_key.pem file path", "private_cert.pem file path"), 77 | InjectWxPayPlatformCertUseCertPath([]string{"pub_cert.pem file path"}), //使用文件路径注入平台证书 78 | ) 79 | if err != nil { 80 | t.Log(err) 81 | return 82 | } 83 | 84 | resp, err := client.PaymentQueryOrderByOutTradeNo("OUT_TRADE_NO", "MCH_ID", "SUB_MCH_ID") 85 | if err != nil { 86 | t.Log(err) 87 | return 88 | } 89 | t.Log(resp) 90 | } 91 | 92 | func TestWxPayPlatformPubKey(t *testing.T) { 93 | client, err := NewClient( 94 | InjectWxPayMchParamExtra("MCH_ID", "API_V3_KEY", "private_key.pem file path", "private_cert.pem file path"), 95 | InjectWxPayPlatformPubKey("PUB_KEY_ID_0000000000000000000000000000001", `-----BEGIN PUBLIC KEY----- 96 | 平台公钥文本内容... 97 | -----END PUBLIC KEY----- 98 | `), 99 | ) 100 | if err != nil { 101 | t.Log(err) 102 | return 103 | } 104 | 105 | resp, err := client.PaymentQueryOrderByOutTradeNo("OUT_TRADE_NO", "MCH_ID", "SUB_MCH_ID") 106 | if err != nil { 107 | t.Log(err) 108 | return 109 | } 110 | t.Log(resp) 111 | } 112 | 113 | func TestWxPayPlatformPubKeyFile(t *testing.T) { 114 | client, err := NewClient( 115 | InjectWxPayMchParamExtra("MCH_ID", "API_V3_KEY", "private_key.pem file path", "private_cert.pem file path"), 116 | InjectWxPayPlatformPubKeyUsePath("PUB_KEY_ID_0000000000000000000000000000001", "./pub_key.pem"), //使用平台公钥本地文件 117 | ) 118 | if err != nil { 119 | t.Log(err) 120 | return 121 | } 122 | 123 | resp, err := client.PaymentQueryOrderByOutTradeNo("OUT_TRADE_NO", "MCH_ID", "SUB_MCH_ID") 124 | if err != nil { 125 | t.Log(err) 126 | return 127 | } 128 | t.Log(resp) 129 | } 130 | -------------------------------------------------------------------------------- /core/edu_school_pay.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/louismax/wxpayv3/constant" 7 | "github.com/louismax/wxpayv3/custom" 8 | "github.com/louismax/wxpayv3/utils" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | func (c *PayClient) EduSchoolPayPreSign(data custom.ReqEduSchoolPayPreSign) (*custom.RespEduSchoolPayPreSign, error) { 14 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIEduSchoolPayPreSign), http.MethodPost) 15 | if err != nil { 16 | return nil, err 17 | } 18 | resp := custom.RespEduSchoolPayPreSign{} 19 | err = json.Unmarshal(body, &resp) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &resp, nil 24 | } 25 | 26 | func (c *PayClient) EduSchoolPayContractQueryById(contractId string) (*custom.RespEduSchoolPayContractQuery, error) { 27 | params := map[string]string{"contract_id": contractId} 28 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIEduSchoolPayContractQueryById), http.MethodGet) 29 | if err != nil { 30 | return nil, err 31 | } 32 | resp := custom.RespEduSchoolPayContractQuery{} 33 | err = json.Unmarshal(body, &resp) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &resp, nil 38 | } 39 | 40 | func (c *PayClient) DissolveEduSchoolPayContract(contractId string) error { 41 | params := map[string]string{"contract_id": contractId} 42 | _, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIDissolveEduSchoolPayContract), http.MethodPost) 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (c *PayClient) EduSchoolPayContractQueryByOpenId(openId string, query url.Values) (*custom.RespEduSchoolPayContractQueryPage, error) { 50 | params := map[string]string{"openid": openId} 51 | 52 | if query.Get("plan_id") == "" { 53 | return nil, fmt.Errorf("参数不合法,query中plan_id为必填参数") 54 | } 55 | if query.Get("contract_status") == "" { 56 | return nil, fmt.Errorf("参数不合法,query中contract_status为必填参数") 57 | } 58 | 59 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduSchoolPayContractQueryByOpenId), http.MethodGet) 60 | if err != nil { 61 | return nil, err 62 | } 63 | resp := custom.RespEduSchoolPayContractQueryPage{} 64 | err = json.Unmarshal(body, &resp) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return &resp, nil 69 | } 70 | 71 | func (c *PayClient) EduSchoolPayTransactions(data custom.ReqEduSchoolPayTransactions) (*custom.RespEduSchoolPayTransactions, error) { 72 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIEduSchoolPayTransactions), http.MethodPost) 73 | if err != nil { 74 | return nil, err 75 | } 76 | resp := custom.RespEduSchoolPayTransactions{} 77 | err = json.Unmarshal(body, &resp) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return &resp, nil 82 | } 83 | 84 | func (c *PayClient) EduSchoolPayQueryOrderByTransactionId(transactionId string, query url.Values) (*custom.RespEduSchoolPayTransactions, error) { 85 | params := map[string]string{"transaction_id": transactionId} 86 | if query.Get("sub_mchid") == "" { 87 | return nil, fmt.Errorf("参数不合法,query中sub_mchid为必填参数") 88 | } 89 | 90 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduSchoolPayQueryOrderByTransactionId), http.MethodGet) 91 | if err != nil { 92 | return nil, err 93 | } 94 | resp := custom.RespEduSchoolPayTransactions{} 95 | err = json.Unmarshal(body, &resp) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return &resp, nil 100 | } 101 | 102 | func (c *PayClient) EduSchoolPayQueryOrderByOutTradeNo(outTradeNo string, query url.Values) (*custom.RespEduSchoolPayTransactions, error) { 103 | params := map[string]string{"out_trade_no": outTradeNo} 104 | if query.Get("sub_mchid") == "" { 105 | return nil, fmt.Errorf("参数不合法,query中sub_mchid为必填参数") 106 | } 107 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduSchoolPayQueryOrderByOutTradeNo), http.MethodGet) 108 | if err != nil { 109 | return nil, err 110 | } 111 | resp := custom.RespEduSchoolPayTransactions{} 112 | err = json.Unmarshal(body, &resp) 113 | if err != nil { 114 | return nil, err 115 | } 116 | return &resp, nil 117 | } 118 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto" 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "crypto/rand" 10 | craned "crypto/rand" 11 | "crypto/rsa" 12 | "crypto/sha256" 13 | "encoding/base64" 14 | "encoding/json" 15 | "fmt" 16 | "github.com/louismax/wxpayv3/constant" 17 | "github.com/louismax/wxpayv3/custom" 18 | "net/http" 19 | "net/url" 20 | "strconv" 21 | "strings" 22 | ) 23 | 24 | const ( 25 | // NonceSymbols 随机字符串可用字符集 26 | NonceSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 27 | // NonceLength 随机字符串的长度 28 | NonceLength = 32 29 | ) 30 | 31 | // GenerateNonce 生成32位随机字符串 32 | func GenerateNonce() (string, error) { 33 | bs := make([]byte, NonceLength) 34 | _, err := rand.Read(bs) 35 | if err != nil { 36 | return "", err 37 | } 38 | symbolsByteLength := byte(len(NonceSymbols)) 39 | for i, b := range bs { 40 | bs[i] = NonceSymbols[b%symbolsByteLength] 41 | } 42 | return string(bs), nil 43 | } 44 | 45 | // BuildMessage BuildMessage 46 | func BuildMessage(httpMethod string, urlString string, body []byte, nonceStr string, timestamp int64) ([]byte, error) { 47 | parsedUrl, err := url.Parse(urlString) 48 | if err != nil { 49 | return nil, err 50 | } 51 | urlPart := parsedUrl.Path 52 | if len(parsedUrl.RawQuery) != 0 { 53 | urlPart = urlPart + "?" + parsedUrl.RawQuery 54 | } 55 | 56 | buffer := bytes.NewBuffer([]byte{}) 57 | buff := bufio.NewWriter(buffer) 58 | 59 | _, _ = buff.WriteString(httpMethod) 60 | _ = buff.WriteByte('\n') 61 | _, _ = buff.WriteString(urlPart) 62 | _ = buff.WriteByte('\n') 63 | _, _ = buff.WriteString(strconv.FormatInt(timestamp, 10)) 64 | _ = buff.WriteByte('\n') 65 | _, _ = buff.WriteString(nonceStr) 66 | _ = buff.WriteByte('\n') 67 | if httpMethod == http.MethodPost || httpMethod == http.MethodPut || httpMethod == http.MethodPatch { 68 | _, _ = buff.Write(body) 69 | } 70 | _ = buff.WriteByte('\n') 71 | _ = buff.Flush() 72 | return buffer.Bytes(), nil 73 | } 74 | 75 | // Sign Sign 76 | func Sign(message []byte, privateKey *rsa.PrivateKey) (string, error) { 77 | h := sha256.New() 78 | h.Write(message) 79 | signature, err := rsa.SignPKCS1v15(craned.Reader, privateKey, crypto.SHA256, h.Sum(nil)) 80 | if err != nil { 81 | return "", err 82 | } 83 | return base64.StdEncoding.EncodeToString(signature), nil 84 | } 85 | 86 | // BuildUrl BuildUrl 87 | func BuildUrl(params map[string]string, query url.Values, subRoutes ...string) string { 88 | urlX := constant.ApiDomain 89 | for _, route := range subRoutes { 90 | if strings.Contains(route, constant.ApiDomain) { 91 | urlX = route 92 | break 93 | } else { 94 | urlX += strings.TrimLeft(route, "/") 95 | } 96 | } 97 | for key, param := range params { 98 | urlX = strings.ReplaceAll(urlX, "{"+key+"}", param) 99 | } 100 | if query != nil { 101 | urlX += "?" 102 | urlX += query.Encode() 103 | } 104 | return urlX 105 | } 106 | 107 | // FaceMessageDecryption 无需初始化客户端的进行离线团餐人脸报文解密 108 | func FaceMessageDecryption(data custom.FaceMessageCiphertext, apiV3Key string) (*custom.FaceMessagePlaintext, error) { 109 | // 对编码密文进行base64解码 110 | decodeBytes, err := base64.StdEncoding.DecodeString(data.Resource.Ciphertext) 111 | if err != nil { 112 | return nil, err 113 | } 114 | cx, err := aes.NewCipher([]byte(apiV3Key)) 115 | if err != nil { 116 | return nil, err 117 | } 118 | gcm, err := cipher.NewGCM(cx) 119 | if err != nil { 120 | return nil, err 121 | } 122 | nonceSize := gcm.NonceSize() 123 | if len(decodeBytes) < nonceSize { 124 | return nil, fmt.Errorf("密文证书长度不够") 125 | } 126 | res := custom.FaceMessagePlaintext{} 127 | if data.Resource.AssociatedData != "" { 128 | plaintext, err := gcm.Open(nil, []byte(data.Resource.Nonce), decodeBytes, []byte(data.Resource.AssociatedData)) 129 | if err != nil { 130 | return nil, err 131 | } 132 | err = json.Unmarshal(plaintext, &res) 133 | if err != nil { 134 | return nil, err 135 | } 136 | return &res, nil 137 | } 138 | plaintext, err := gcm.Open(nil, []byte(data.Resource.Nonce), decodeBytes, nil) 139 | if err != nil { 140 | return nil, err 141 | } 142 | err = json.Unmarshal(plaintext, &res) 143 | if err != nil { 144 | return nil, err 145 | } 146 | return &res, nil 147 | } 148 | -------------------------------------------------------------------------------- /core/edu-papay.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/louismax/wxpayv3/constant" 7 | "github.com/louismax/wxpayv3/custom" 8 | "github.com/louismax/wxpayv3/utils" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | //EduPaPayPresign EduPaPayPresign 14 | func (c *PayClient) EduPaPayPresign(data custom.ReqEduPaPayPresign) (*custom.RespEduPaPayPresign, error) { 15 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIEduPaPayPresign), http.MethodPost) 16 | if err != nil { 17 | return nil, err 18 | } 19 | resp := custom.RespEduPaPayPresign{} 20 | err = json.Unmarshal(body, &resp) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &resp, nil 25 | } 26 | 27 | //EduPaPayContractQueryById EduPaPayContractQueryById 28 | func (c *PayClient) EduPaPayContractQueryById(contractId string, query url.Values) (*custom.RespEduPaPayContractQuery, error) { 29 | params := map[string]string{"contract_id": contractId} 30 | if query.Get("appid") == "" { 31 | return nil, fmt.Errorf("参数不合法,query中appid为必填参数") 32 | } 33 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduPaPayContractQueryById), http.MethodGet) 34 | if err != nil { 35 | return nil, err 36 | } 37 | resp := custom.RespEduPaPayContractQuery{} 38 | err = json.Unmarshal(body, &resp) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &resp, nil 43 | } 44 | 45 | //EduPaPayContractQueryByOpenId EduPaPayContractQueryByOpenId 46 | func (c *PayClient) EduPaPayContractQueryByOpenId(openid string, query url.Values) (*custom.RespEduPaPayContractQueryList, error) { 47 | params := map[string]string{"openid": openid} 48 | if query.Get("appid") == "" { 49 | return nil, fmt.Errorf("参数不合法,query中appid为必填参数") 50 | } 51 | if query.Get("plan_id") == "" { 52 | return nil, fmt.Errorf("参数不合法,query中plan_id为必填参数") 53 | } 54 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduPaPayContractQueryByOpenId), http.MethodGet) 55 | if err != nil { 56 | return nil, err 57 | } 58 | resp := custom.RespEduPaPayContractQueryList{} 59 | err = json.Unmarshal(body, &resp) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return &resp, nil 64 | } 65 | 66 | func (c *PayClient) DissolveEduPaPayContract(contractId string) error { 67 | params := map[string]string{"contract_id": contractId} 68 | _, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIDissolveEduPaPayContract), http.MethodDelete) 69 | if err != nil { 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | func (c *PayClient) SendEduPaPayNotifications(contractId string, data custom.ReqSendEduPaPayNotifications) error { 76 | params := map[string]string{"contract_id": contractId} 77 | _, err := c.doRequest(data, utils.BuildUrl(params, nil, constant.APISendEduPaPayNotifications), http.MethodPost) 78 | if err != nil { 79 | return err 80 | } 81 | return nil 82 | } 83 | 84 | func (c *PayClient) EduPaPayTransactions(data custom.ReqEduPaPayTransactions) error { 85 | _, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIEduPaPayTransactions), http.MethodPost) 86 | if err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | 92 | func (c *PayClient) EduPaPayQueryOrderByTransactionId(transactionId string, query url.Values) (*custom.RespEduPaPayQueryOrder, error) { 93 | params := map[string]string{"transaction_id": transactionId} 94 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduPaPayQueryOrderByTransactionId), http.MethodGet) 95 | if err != nil { 96 | return nil, err 97 | } 98 | resp := custom.RespEduPaPayQueryOrder{} 99 | err = json.Unmarshal(body, &resp) 100 | if err != nil { 101 | return nil, err 102 | } 103 | return &resp, nil 104 | } 105 | 106 | func (c *PayClient) EduPaPayQueryOrderByOutTradeNo(outTradeNo string, query url.Values) (*custom.RespEduPaPayQueryOrder, error) { 107 | params := map[string]string{"out_trade_no": outTradeNo} 108 | body, err := c.doRequest(nil, utils.BuildUrl(params, query, constant.APIEduPaPayQueryOrderByOutTradeNo), http.MethodGet) 109 | if err != nil { 110 | return nil, err 111 | } 112 | resp := custom.RespEduPaPayQueryOrder{} 113 | err = json.Unmarshal(body, &resp) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return &resp, nil 118 | } 119 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package wxpayv3 2 | 3 | import ( 4 | "fmt" 5 | "github.com/louismax/wxpayv3/core" 6 | "github.com/louismax/wxpayv3/custom" 7 | ) 8 | 9 | // InjectWxPayMchParam 注入微信支付商户参数(商户号, 商户APIv3密钥, 商户API证书序列号, 商户私钥文件路径) 10 | func InjectWxPayMchParam(mchID, apiV3Key, apiSerialNo, pvtKeyFilePath string) core.ClientOption { 11 | pvt, err := core.LoadPrivateKeyWithPath(pvtKeyFilePath) 12 | if err != nil { 13 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过私钥的文件路径加载商户私钥失败!"+err.Error()) 14 | return core.ErrorOption{Error: err} 15 | } 16 | return core.BasicInformation{ 17 | MchID: mchID, 18 | MchAPIv3Key: apiV3Key, 19 | ApiCert: core.ApiCert{ 20 | ApiSerialNo: apiSerialNo, 21 | ApiPrivateKey: pvt, 22 | }, 23 | } 24 | } 25 | 26 | // InjectWxPayMchParamFull 注入微信支付商户参数(商户号, 商户证书(需自己解析), 商户APIv3密钥) 27 | func InjectWxPayMchParamFull(mchID string, apiCert core.ApiCert, apiV3Key string) core.ClientOption { 28 | return core.BasicInformation{ 29 | MchID: mchID, 30 | MchAPIv3Key: apiV3Key, 31 | ApiCert: apiCert, 32 | } 33 | } 34 | 35 | // InjectWxPayMchParamExtra 注入微信支付商户参数(商户号, 商户APIv3密钥, 商户私钥文件路径, 商户证书文件路径) 36 | func InjectWxPayMchParamExtra(mchID string, apiV3Key string, privateKeyPath string, certificatePath string) core.ClientOption { 37 | pvt, err := core.LoadPrivateKeyWithPath(privateKeyPath) 38 | if err != nil { 39 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过私钥的文件路径加载商户私钥失败!"+err.Error()) 40 | return core.ErrorOption{Error: err} 41 | } 42 | cert, err := core.LoadCertificateWithPath(certificatePath) 43 | if err != nil { 44 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过证书的文件路径加载商户商户失败!"+err.Error()) 45 | return core.ErrorOption{Error: err} 46 | } 47 | return core.BasicInformation{ 48 | MchID: mchID, 49 | MchAPIv3Key: apiV3Key, 50 | ApiCert: core.ApiCert{ 51 | ApiSerialNo: core.GetCertificateSerialNumber(*cert), 52 | ApiPrivateKey: pvt, 53 | ApiCertificate: cert, 54 | }, 55 | } 56 | } 57 | 58 | // InjectWxPayPlatformCert 注入微信支付平台证书 59 | func InjectWxPayPlatformCert(certificateStr []string) core.ClientOption { 60 | defNo := "" 61 | list := make([]custom.CertificateDataList, 0) 62 | for _, v := range certificateStr { 63 | ct, err := core.LoadCertificate(v) 64 | if err != nil { 65 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过平台证书的文本内容加载微信支付平台证书失败!"+err.Error()) 66 | return core.ErrorOption{Error: err} 67 | } 68 | if defNo == "" { 69 | defNo = core.GetCertificateSerialNumber(*ct) 70 | } 71 | list = append(list, custom.CertificateDataList{ 72 | SerialNo: core.GetCertificateSerialNumber(*ct), 73 | Certificate: ct, 74 | }) 75 | } 76 | 77 | return core.PlatformCert{ 78 | DefaultSerialNo: defNo, 79 | CertList: list, 80 | } 81 | } 82 | 83 | // InjectWxPayPlatformCertUseCertPath 注入微信支付平台证书(本地平台证书文件路径) 84 | func InjectWxPayPlatformCertUseCertPath(platformCertificatePath []string) core.ClientOption { 85 | list := make([]custom.CertificateDataList, 0) 86 | defNo := "" 87 | for _, v := range platformCertificatePath { 88 | ct, err := core.LoadCertificateWithPath(v) 89 | if err != nil { 90 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过证书的文件路径加载微信支付平台证书失败!"+err.Error()) 91 | return core.ErrorOption{Error: err} 92 | } 93 | if defNo == "" { 94 | defNo = core.GetCertificateSerialNumber(*ct) 95 | } 96 | list = append(list, custom.CertificateDataList{ 97 | SerialNo: core.GetCertificateSerialNumber(*ct), 98 | Certificate: ct, 99 | }) 100 | } 101 | return core.PlatformCert{ 102 | DefaultSerialNo: defNo, 103 | CertList: list, 104 | } 105 | } 106 | 107 | // InjectWxPayPlatformPubKey 注入微信支付平台公钥 108 | func InjectWxPayPlatformPubKey(pubKeyId, pubKey string) core.ClientOption { 109 | pub, err := core.LoadPublicKey(pubKey) 110 | if err != nil { 111 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过公钥文本内容加载微信支付平台公钥失败!"+err.Error()) 112 | return core.ErrorOption{Error: err} 113 | } 114 | return core.PlatformPubKey{ 115 | PubKeyId: pubKeyId, 116 | PubKey: pub, 117 | } 118 | } 119 | 120 | func InjectWxPayPlatformPubKeyUsePath(pubKeyId, pubKeyPath string) core.ClientOption { 121 | pub, err := core.LoadPublicKeyWithPath(pubKeyPath) 122 | if err != nil { 123 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:通过公钥文件路径加载微信支付平台公钥失败!"+err.Error()) 124 | return core.ErrorOption{Error: err} 125 | } 126 | return core.PlatformPubKey{ 127 | PubKeyId: pubKeyId, 128 | PubKey: pub, 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /core/upload.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "github.com/louismax/wxpayv3/constant" 12 | "github.com/louismax/wxpayv3/custom" 13 | "github.com/louismax/wxpayv3/utils" 14 | "io" 15 | "io/ioutil" 16 | "mime/multipart" 17 | "net/http" 18 | "net/textproto" 19 | "os" 20 | "path" 21 | "strings" 22 | ) 23 | 24 | // UploadImage UploadImage 25 | func (c *PayClient) UploadImage(filePath string) (*custom.RespUploadImage, error) { 26 | //获取文件名带后缀 27 | filenameWithSuffix := path.Base(filePath) 28 | //获取文件后缀 29 | fileSuffix := path.Ext(filenameWithSuffix) 30 | 31 | ctp := "" 32 | if strings.ToLower(fileSuffix) == ".jpg" { 33 | ctp = "image/jpg" 34 | } else if strings.ToLower(fileSuffix) == ".png" { 35 | ctp = "image/png" 36 | } else if strings.ToLower(fileSuffix) == ".bmp" { 37 | ctp = "image/png" 38 | } else { 39 | return nil, errors.New("暂不支持的文件类型") 40 | } 41 | //获取文件的二进制内容 42 | file, err := os.Open(filePath) 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer func() { 47 | _ = file.Close() 48 | }() 49 | stats, statsErr := file.Stat() 50 | if statsErr != nil { 51 | return nil, statsErr 52 | } 53 | var size = stats.Size() 54 | fBytes := make([]byte, size) 55 | bf := bufio.NewReader(file) 56 | 57 | if _, err = bf.Read(fBytes); err != nil { 58 | return nil, err 59 | } 60 | //二进制内容进行sha256计算得到的值 61 | h := sha256.New() 62 | h.Write(fBytes) 63 | 64 | //参数组装 65 | xName, _ := utils.GenerateNonce() 66 | req := custom.ReqUploadImage{ 67 | Filename: fmt.Sprintf("%s%s", xName, fileSuffix), 68 | Sha256: hex.EncodeToString(h.Sum(nil)), 69 | } 70 | data, err := json.Marshal(req) 71 | if err != nil { 72 | return nil, err 73 | } 74 | apiUrl := utils.BuildUrl(nil, nil, constant.ApiUploadImage) 75 | //获取签名 76 | authorization, err := c.Authorization(http.MethodPost, apiUrl, data) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | body := &bytes.Buffer{} // 初始化body参数 82 | writer := multipart.NewWriter(body) // 实例化multipart 83 | 84 | part, err := CreateForm("meta", "application/json", writer) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if _, err = part.Write(data); err != nil { 90 | return nil, err 91 | } 92 | 93 | parTF, err := CreateFormFile("file", req.Filename, ctp, writer) // 创建multipart 文件字段 94 | if err != nil { 95 | return nil, err 96 | } 97 | if _, err = parTF.Write(fBytes); err != nil { 98 | return nil, err 99 | } 100 | 101 | if err = writer.Close(); err != nil { 102 | return nil, err 103 | } 104 | 105 | request, err := http.NewRequest(http.MethodPost, apiUrl, body) 106 | if err != nil { 107 | return nil, err 108 | } 109 | //设置短连接 110 | request.Close = true 111 | 112 | request.Header.Set("Content-Type", "multipart/form-data") 113 | request.Header.Set("Accept", "*/*") 114 | request.Header.Set("Authorization", authorization) 115 | 116 | if c.WechatPayPublicKeyID != "" { 117 | request.Header.Set("Wechatpay-Serial", c.WechatPayPublicKeyID) 118 | } else if c.DefaultPlatformSerialNo != "" { 119 | request.Header.Set("Wechatpay-Serial", c.DefaultPlatformSerialNo) 120 | } 121 | 122 | resp, err := c.HttpClient.Do(request) 123 | if resp != nil { 124 | defer func() { 125 | _ = resp.Body.Close() 126 | }() 127 | } 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | respData, err := ioutil.ReadAll(resp.Body) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | result := custom.RespUploadImage{} 138 | err = json.Unmarshal(respData, &result) 139 | if err != nil { 140 | return nil, err 141 | } 142 | return &result, nil 143 | 144 | } 145 | 146 | // CreateForm CreateForm 147 | func CreateForm(key, contentType string, w *multipart.Writer) (io.Writer, error) { 148 | h := make(textproto.MIMEHeader) 149 | h.Set("Content-Disposition", 150 | fmt.Sprintf(`form-data; name="%s";`, 151 | escapeQuotes(key))) 152 | h.Set("Content-Type", contentType) 153 | return w.CreatePart(h) 154 | } 155 | 156 | var quoteEscape = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 157 | 158 | func escapeQuotes(s string) string { 159 | return quoteEscape.Replace(s) 160 | } 161 | 162 | // CreateFormFile CreateFormFile 163 | func CreateFormFile(fieldName, filename, contentType string, w *multipart.Writer) (io.Writer, error) { 164 | h := make(textproto.MIMEHeader) 165 | h.Set("Content-Disposition", 166 | fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 167 | escapeQuotes(fieldName), escapeQuotes(filename))) 168 | h.Set("Content-Type", contentType) 169 | return w.CreatePart(h) 170 | } 171 | -------------------------------------------------------------------------------- /custom/edu-papay.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type ReqEduPaPayPresign struct { 4 | AppId string `json:"appid"` 5 | SubMchId string `json:"sub_mchid"` 6 | SubAppId string `json:"sub_appid"` 7 | OpenId string `json:"openid"` 8 | SubOpenId string `json:"sub_openid"` 9 | PlanId string `json:"plan_id"` 10 | UserId string `json:"user_id"` 11 | PeriodStartDate string `json:"period_start_date"` 12 | TradeScene string `json:"trade_scene"` 13 | } 14 | 15 | type RespEduPaPayPresign struct { 16 | PresignToken string `json:"presign_token"` 17 | } 18 | 19 | type RespEduPaPayContractQueryList struct { 20 | TotalCount int `json:"total_count"` 21 | Offset int `json:"offset"` 22 | Limit int `json:"limit"` 23 | Data []RespEduPaPayContractQuery `json:"data"` 24 | } 25 | 26 | type RespEduPaPayContractQuery struct { 27 | SpMchId string `json:"sp_mchid"` 28 | AppId string `json:"appid"` 29 | SubMchId string `json:"sub_mchid"` 30 | SubAppId string `json:"sub_appid"` 31 | OpenId string `json:"openid"` 32 | SubOpenId string `json:"sub_openid"` 33 | PlanId string `json:"plan_id"` 34 | ContractInformation ContractInformation `json:"contract_information"` 35 | } 36 | 37 | type ContractInformation struct { 38 | ContractId string `json:"contract_id"` 39 | ContractStatus string `json:"contract_status"` 40 | CreateTime string `json:"create_time"` 41 | } 42 | 43 | type ReqSendEduPaPayNotifications struct { 44 | AppId string `json:"appid"` 45 | SubMchId string `json:"sub_mchid"` 46 | SubAppId string `json:"sub_appid"` 47 | } 48 | 49 | type ReqEduPaPayTransactions struct { 50 | Appid string `json:"appid"` 51 | SubMchId string `json:"sub_mchid"` 52 | SubAppId string `json:"sub_appid"` 53 | Body string `json:"body"` 54 | Attach string `json:"attach"` 55 | OutTradeNo string `json:"out_trade_no"` 56 | GoodsTag string `json:"goods_tag"` 57 | NotifyUrl string `json:"notify_url"` 58 | ContractId string `json:"contract_id"` 59 | TradeScene string `json:"trade_scene"` 60 | Amount EduPaPayAmount `json:"amount"` 61 | DeviceInformation EduPaPayDeviceInformation `json:"device_information"` 62 | } 63 | type EduPaPayAmount struct { 64 | Total int64 `json:"total"` 65 | Currency string `json:"currency"` 66 | } 67 | 68 | type EduPaPayDeviceInformation struct { 69 | DeviceId string `json:"device_id"` 70 | DeviceIp string `json:"device_ip"` 71 | } 72 | 73 | type RespEduPaPayQueryOrder struct { 74 | SpMchId string `json:"sp_mchid"` 75 | AppId string `json:"appid"` 76 | SubMchId string `json:"sub_mchid"` 77 | SubAppId string `json:"sub_appid"` 78 | OutTradeNo string `json:"out_trade_no"` 79 | TransactionId string `json:"transaction_id"` 80 | Attach string `json:"attach"` 81 | BankType string `json:"bank_type"` 82 | SuccessTime string `json:"success_time"` 83 | TradeState string `json:"trade_state"` 84 | TradeStateDescription string `json:"trade_state_description"` 85 | Payer struct { 86 | OpenId string `json:"openid"` 87 | SubOpenId string `json:"sub_openid"` 88 | } `json:"payer"` 89 | Amount struct { 90 | Total int64 `json:"total"` 91 | PayerTotal int64 `json:"payer_total"` 92 | DiscountTotal int64 `json:"discount_total"` 93 | Currency string `json:"currency"` 94 | } `json:"amount"` 95 | DeviceInformation struct { 96 | DeviceId string `json:"device_id"` 97 | DeviceIp string `json:"device_ip"` 98 | } `json:"device_information"` 99 | PromotionDetail []struct { 100 | CouponId string `json:"coupon_id"` 101 | Name string `json:"name"` 102 | Scope string `json:"scope"` 103 | Type string `json:"type"` 104 | Amount int64 `json:"amount"` 105 | StockId string `json:"stock_id"` 106 | WechatpayContribute int64 `json:"wechatpay_contribute"` 107 | MerchantContribute int64 `json:"merchant_contribute"` 108 | OtherContribute int64 `json:"other_contribute"` 109 | } `json:"promotion_detail"` 110 | } 111 | -------------------------------------------------------------------------------- /custom/edu_school_pay.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type ReqEduSchoolPayPreSign struct { 4 | AppId string `json:"appid"` 5 | OpenId string `json:"openid"` 6 | PlanId string `json:"plan_id"` 7 | UserId string `json:"user_id"` 8 | SchoolId string `json:"school_id"` 9 | OutContractCode string `json:"out_contract_code"` 10 | ContractMode string `json:"contract_mode"` 11 | DowngradeDefaultContract bool `json:"downgrade_default_contract"` 12 | Identity *struct { 13 | RealName string `json:"real_name"` 14 | CredentialType string `json:"credential_type"` 15 | IdCardNumber string `json:"id_card_number"` 16 | } `json:"identity,omitempty"` 17 | BankCard *struct { 18 | BankCardNo string `json:"bank_card_no"` 19 | ValidThru string `json:"valid_thru"` 20 | Phone string `json:"phone"` 21 | BankType string `json:"bank_type"` 22 | } `json:"bank_card,,omitempty"` 23 | } 24 | 25 | type RespEduSchoolPayPreSign struct { 26 | PreSignToken string `json:"presign_token"` 27 | } 28 | 29 | type RespEduSchoolPayContractQuery struct { 30 | ContractId string `json:"contract_id"` 31 | MchId string `json:"mchid"` 32 | AppId string `json:"appid"` 33 | OpenId string `json:"openid"` 34 | PlanId string `json:"plan_id"` 35 | ContractStatus string `json:"contract_status"` 36 | CreateTime string `json:"create_time"` 37 | OutContractCode string `json:"out_contract_code"` 38 | } 39 | 40 | type RespEduSchoolPayContractQueryPage struct { 41 | Data []RespEduSchoolPayContractQuery `json:"data"` 42 | Offset int `json:"offset"` 43 | Limit int `json:"limit"` 44 | TotalCount int `json:"total_count"` 45 | Links struct { 46 | Next string `json:"next"` 47 | Prev string `json:"prev"` 48 | Self string `json:"self"` 49 | } `json:"links"` 50 | } 51 | 52 | type ReqEduSchoolPayTransactions struct { 53 | Appid string `json:"appid"` 54 | SubMchId string `json:"sub_mchid"` 55 | SubAppId string `json:"sub_appid"` 56 | Description string `json:"description"` 57 | Attach string `json:"attach"` 58 | OutTradeNo string `json:"out_trade_no"` 59 | GoodsTag string `json:"goods_tag"` 60 | ContractId string `json:"contract_id"` 61 | UserId string `json:"user_id"` 62 | Amount EduSchoolPayAmount `json:"amount"` 63 | SceneInfo EduSchoolPaySceneInfo `json:"scene_info"` 64 | DeviceInfo EduSchoolPayDeviceInfo `json:"device_info"` 65 | SettleInfo EduSchoolPaySettleInfo `json:"settle_info"` 66 | } 67 | 68 | type EduSchoolPayAmount struct { 69 | Total int64 `json:"total"` 70 | Currency string `json:"currency"` 71 | } 72 | 73 | type EduSchoolPaySceneInfo struct { 74 | StartTime string `json:"start_time"` 75 | SchoolId string `json:"school_id"` 76 | SceneType string `json:"scene_type"` 77 | } 78 | 79 | type EduSchoolPayDeviceInfo struct { 80 | DeviceId string `json:"device_id"` 81 | DeviceIp string `json:"device_ip"` 82 | } 83 | 84 | type EduSchoolPaySettleInfo struct { 85 | ProfitSharing bool `json:"profit_sharing"` 86 | } 87 | 88 | type RespEduSchoolPayTransactions struct { 89 | MchId string `json:"mchid"` //商户号 90 | Appid string `json:"appid"` 91 | SubMchId string `json:"sub_mchid"` //子商户号 92 | SubAppid string `json:"sub_appid"` 93 | OutTradeNo string `json:"out_trade_no"` //商户单号 94 | TransactionId string `json:"transaction_id"` //微信订单号 95 | TradeType string `json:"trade_type"` //交易类型 96 | TradeState string `json:"trade_state"` //交易状态 97 | TradeStateDesc string `json:"trade_state_desc"` //交易描述 98 | BankType string `json:"bank_type"` //付款银行 99 | Attach string `json:"attach"` //商户附加信息 100 | SuccessTime string `json:"success_time"` //支付成功时间 101 | Payer EduSchoolPayPayer `json:"payer"` 102 | Amount RespEduSchoolPayAmount `json:"amount"` 103 | DeviceInfo EduSchoolPayDeviceInfo `json:"device_info"` 104 | PromotionDetail []EduSchoolPayPromotionList `json:"promotion_detail"` //优惠信息 105 | 106 | } 107 | type EduSchoolPayPayer struct { 108 | Openid string `json:"openid"` //公众号下的openid 109 | SubOpenid string `json:"sub_openid"` //子公众号下的openid 110 | } 111 | 112 | type RespEduSchoolPayAmount struct { 113 | Total int64 `json:"total"` //订单金额 114 | PayerTotal int64 `json:"payer_total"` //用户支付金额 115 | DiscountTotal int64 `json:"discount_total"` 116 | Currency string `json:"currency"` //货币类型 117 | } 118 | 119 | type EduSchoolPayPromotionList struct { 120 | CouponId string `json:"coupon_id"` //优惠ID 121 | Name string `json:"name"` //优惠名称 122 | Scope string `json:"scope"` 123 | Type string `json:"type"` 124 | Amount int `json:"amount"` 125 | StockId string `json:"stock_id"` 126 | WechatPayContribute int64 `json:"wechatpay_contribute"` //微信出资金额 127 | MerchantContribute int64 `json:"merchant_contribute"` //商家出资金额 128 | OtherContribute int64 `json:"other_contribute"` //其他出资金额 129 | } 130 | -------------------------------------------------------------------------------- /core/verify.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto" 7 | "crypto/rsa" 8 | "crypto/sha256" 9 | "encoding/base64" 10 | "encoding/json" 11 | "fmt" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | // ErrResponseBody ErrResponseBody 17 | type ErrResponseBody struct { 18 | HttpStatus int `json:"http_status"` 19 | Code string `json:"code"` 20 | Message string `json:"message"` 21 | ReqId string `json:"req_id"` 22 | Detail json.RawMessage `json:"detail"` 23 | } 24 | 25 | func (r *ErrResponseBody) Error() string { 26 | if r.Detail == nil { 27 | return fmt.Sprintf("HttpStatus:%v Code:%s Message:%s RequestId:%s", r.HttpStatus, r.Code, r.Message, r.ReqId) 28 | } 29 | return fmt.Sprintf("HttpStatus:%v Code:%s Message:%s RequestId:%s Detail:%s", r.HttpStatus, r.Code, r.Message, r.ReqId, r.Detail) 30 | } 31 | 32 | // VerifyResponse 验签 33 | func (c *PayClient) VerifyResponse(httpStatus int, header *http.Header, body []byte) error { 34 | if httpStatus != http.StatusOK && httpStatus != http.StatusNoContent { 35 | if body == nil { 36 | return fmt.Errorf("验证响应失败") 37 | } 38 | var response ErrResponseBody 39 | err := json.Unmarshal(body, &response) 40 | if err != nil { 41 | return err 42 | } 43 | // 先Unmarshal再赋值,防止被覆盖为空值 44 | response.HttpStatus = httpStatus 45 | response.ReqId = c.getRequestId(header) 46 | return &response 47 | } 48 | headerSerial := c.getWechatPaySerial(header) //获取应答签名证书序列号 49 | headerSignature := c.getWechatPaySignature(header) //获取应答签名值 50 | headerTimestamp := c.getWechatPayTimestamp(header) //获取应答时间戳 51 | headerNonce := c.getWechatPayNonce(header) //获取应答随机串 52 | return c.verify(headerSerial, headerSignature, headerTimestamp, headerNonce, body) 53 | } 54 | 55 | func (c *PayClient) getRequestId(header *http.Header) string { 56 | return header.Get("Request-Id") 57 | } 58 | 59 | // getWechatPaySerial 获取headers中的Wechatpay-Serial 60 | func (c *PayClient) getWechatPaySerial(header *http.Header) string { 61 | return header.Get("Wechatpay-Serial") 62 | } 63 | 64 | // getWechatPaySignature 获取headers中的Wechatpay-Signature 65 | func (c *PayClient) getWechatPaySignature(header *http.Header) string { 66 | return header.Get("Wechatpay-Signature") 67 | } 68 | 69 | // getWechatPaySignature 获取headers中的Wechatpay-Timestamp 70 | func (c *PayClient) getWechatPayTimestamp(header *http.Header) string { 71 | return header.Get("Wechatpay-Timestamp") 72 | } 73 | 74 | // getWechatPaySignature 获取headers中的Wechatpay-Nonce 75 | func (c *PayClient) getWechatPayNonce(header *http.Header) string { 76 | return header.Get("Wechatpay-Nonce") 77 | } 78 | 79 | func (c *PayClient) verify(headerSerial string, headerSignature string, headerTimestamp string, headerNonce string, body []byte) error { 80 | if headerSerial == "" { 81 | //没有证书序列号,直接不用验签?! 82 | fmt.Printf("\033[33m%s\033[0m\n", "[Warning]--WxPayV3:当前请求应答结果中不存在Wechatpay-Serial,请注意应答来源是否合法!!!") 83 | return nil 84 | } 85 | switch { 86 | case headerSignature == "": 87 | return fmt.Errorf("微信支付签名参数无效") 88 | case headerTimestamp == "": 89 | return fmt.Errorf("微信支付时间戳参数无效") 90 | case headerNonce == "": 91 | return fmt.Errorf("微信支付随机字符串参数无效") 92 | } 93 | 94 | //构造验签名串 95 | verificationStr, err := c.buildVerificationString(headerTimestamp, headerNonce, body) 96 | if err != nil { 97 | return err 98 | } 99 | // 应答header中的signature是base64加密的,所以要先解密 100 | decodedSignature, err := base64.StdEncoding.DecodeString(headerSignature) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | //判断Serial是平台证书还是平台公钥 106 | if strings.Contains(headerSerial, "PUB_KEY_ID_") { 107 | if c.WechatPayPublicKeyID != "" && c.WechatPayPublicKey != nil { 108 | fmt.Printf("\033[35m%s\033[0m\n", "[Info]--WxPayV3:使用微信支付平台公钥验签,应答中的公钥ID:"+headerSerial) 109 | if headerSerial == c.WechatPayPublicKeyID { 110 | err = c.verifySignatureByPubKey(string(decodedSignature), verificationStr) 111 | if err != nil { 112 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:签名校验失败!"+err.Error()) 113 | return fmt.Errorf("签名校验失败!err:%s", err.Error()) 114 | } 115 | fmt.Printf("\033[32m%s\033[0m\n", "[Info]--WxPayV3:请求应答签名校验通过!") 116 | } else { 117 | fmt.Printf("\033[33m%s\033[0m\n", "[Warning]--WxPayV3:当前实例配置的平台公钥ID与应答结果中的公钥ID不匹配,请确认请求来源是否合法或平台公钥是否更新!!!") 118 | } 119 | } else { 120 | fmt.Printf("\033[33m%s\033[0m\n", "[Warning]--WxPayV3:当前实例未配置平台公钥,无法进行应答结果签名验证!请通过配置平台平台公钥进行验签,确保请求应答来源为微信支付服务端!") 121 | } 122 | } else { 123 | //通过平台证书 124 | if _, ok := c.PlatformCertMap[headerSerial]; ok { 125 | fmt.Printf("\033[35m%s\033[0m\n", "[Info]--WxPayV3:使用微信支付平台证书验签,应答中的证书编号:"+headerSerial) 126 | err = c.verifySignature(headerSerial, string(decodedSignature), verificationStr) 127 | if err != nil { 128 | fmt.Printf("\033[31m%s\033[0m\n", "[Error]--WxPayV3:签名校验失败!"+err.Error()) 129 | return fmt.Errorf("签名校验失败!err:%s", err.Error()) 130 | } 131 | fmt.Printf("\033[32m%s\033[0m\n", "[Info]--WxPayV3:请求应答签名校验通过!") 132 | } else { 133 | fmt.Printf("\033[33m%s\033[0m\n", "[Warning]--WxPayV3:当前实例未配置平台证书,无法进行应答结果签名验证!请通过配置平台证书进行验签,确保请求应答来源为微信支付服务端!") 134 | } 135 | } 136 | return nil 137 | } 138 | 139 | func (c *PayClient) buildVerificationString(timestamp string, nonce string, body []byte) ([]byte, error) { 140 | buffer := bytes.NewBuffer([]byte{}) 141 | bluff := bufio.NewWriter(buffer) 142 | _, _ = bluff.WriteString(timestamp) 143 | _ = bluff.WriteByte('\n') 144 | _, _ = bluff.WriteString(nonce) 145 | _ = bluff.WriteByte('\n') 146 | if len(body) != 0 { 147 | _, _ = bluff.Write(body) 148 | } 149 | _ = bluff.WriteByte('\n') 150 | err := bluff.Flush() 151 | if err != nil { 152 | return nil, err 153 | } 154 | return buffer.Bytes(), nil 155 | } 156 | 157 | func (c *PayClient) verifySignature(headerSerial, signature string, verificationStr []byte) error { 158 | h := sha256.New() 159 | h.Write(verificationStr) 160 | return rsa.VerifyPKCS1v15(c.PlatformCertMap[headerSerial].PublicKey.(*rsa.PublicKey), crypto.SHA256, h.Sum(nil), []byte(signature)) 161 | } 162 | 163 | func (c *PayClient) verifySignatureByPubKey(signature string, verificationStr []byte) error { 164 | h := sha256.New() 165 | h.Write(verificationStr) 166 | return rsa.VerifyPKCS1v15(c.WechatPayPublicKey, crypto.SHA256, h.Sum(nil), []byte(signature)) 167 | } 168 | -------------------------------------------------------------------------------- /custom/profit_sharing.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | // ReqInitiateProfitSharing 发起分账请求参数 4 | type ReqInitiateProfitSharing struct { 5 | SubMchid string `json:"sub_mchid,omitempty"` //直连商户不需要该字段 6 | Appid string `json:"appid"` 7 | SubAppid string `json:"sub_appid,omitempty"` //直连商户不需要该字段 8 | TransactionID string `json:"transaction_id"` 9 | OutOrderNo string `json:"out_order_no"` 10 | Receivers []ReqProfitSharingReceivers `json:"receivers"` //分账接收方列表 11 | UnfreezeUnsplit bool `json:"unfreeze_unsplit"` 12 | } 13 | 14 | // ReqProfitSharingReceivers 分账接收方信息 15 | type ReqProfitSharingReceivers struct { 16 | Type string `json:"type"` 17 | Account string `json:"account"` 18 | Name string `json:"name"` 19 | Amount int `json:"amount"` 20 | Description string `json:"description"` 21 | } 22 | 23 | // RespInitiateProfitSharing 发起分账返回参数 24 | type RespInitiateProfitSharing struct { 25 | SubMchid string `json:"sub_mchid"` 26 | TransactionID string `json:"transaction_id"` 27 | OutOrderNo string `json:"out_order_no"` 28 | OrderID string `json:"order_id"` 29 | State string `json:"state"` 30 | Receivers []RespProfitSharingReceivers `json:"receivers"` 31 | } 32 | 33 | // RespProfitSharingReceivers is RespProfitSharingReceivers 34 | type RespProfitSharingReceivers struct { 35 | Amount int `json:"amount"` 36 | Description string `json:"description"` 37 | Type string `json:"type"` 38 | Account string `json:"account"` 39 | Result string `json:"result"` 40 | DetailID string `json:"detail_id"` 41 | FailReason string `json:"fail_reason"` 42 | CreateTime string `json:"create_time"` 43 | FinishTime string `json:"finish_time"` 44 | } 45 | 46 | // RespQueryProfitSharingResult is RespQueryProfitSharingResult 47 | type RespQueryProfitSharingResult struct { 48 | SubMchid string `json:"sub_mchid"` 49 | TransactionID string `json:"transaction_id"` 50 | OutOrderNo string `json:"out_order_no"` 51 | OrderID string `json:"order_id"` 52 | State string `json:"state"` 53 | Receivers []RespProfitSharingReceivers `json:"receivers"` 54 | } 55 | 56 | // ReqInitiateProfitSharingReturnOrders 请求分账回退请求参数 57 | type ReqInitiateProfitSharingReturnOrders struct { 58 | SubMchid string `json:"sub_mchid"` 59 | OrderID string `json:"order_id"` 60 | OutReturnNo string `json:"out_return_no"` 61 | ReturnMchid string `json:"return_mchid"` 62 | Amount int `json:"amount"` 63 | Description string `json:"description"` 64 | } 65 | 66 | // RespInitiateProfitSharingReturnOrders 请求分账回退返回结果 67 | type RespInitiateProfitSharingReturnOrders struct { 68 | SubMchid string `json:"sub_mchid"` 69 | OrderID string `json:"order_id"` 70 | OutOrderNo string `json:"out_order_no"` 71 | OutReturnNo string `json:"out_return_no"` 72 | ReturnID string `json:"return_id"` 73 | ReturnMchid string `json:"return_mchid"` 74 | Amount int `json:"amount"` 75 | Description string `json:"description"` 76 | Result string `json:"result"` 77 | FailReason string `json:"fail_reason"` 78 | CreateTime string `json:"create_time"` 79 | FinishTime string `json:"finish_time"` 80 | } 81 | 82 | // RespQueryProfitSharingReturnOrders 查询分账回退结果返回参数 83 | type RespQueryProfitSharingReturnOrders struct { 84 | SubMchid string `json:"sub_mchid"` 85 | OrderID string `json:"order_id"` 86 | OutOrderNo string `json:"out_order_no"` 87 | OutReturnNo string `json:"out_return_no"` 88 | ReturnID string `json:"return_id"` 89 | ReturnMchid string `json:"return_mchid"` 90 | Amount int `json:"amount"` 91 | Description string `json:"description"` 92 | Result string `json:"result"` 93 | FailReason string `json:"fail_reason"` 94 | CreateTime string `json:"create_time"` 95 | FinishTime string `json:"finish_time"` 96 | } 97 | 98 | // ReqUnfreezeRemainingFunds 解冻剩余资金请求参数 99 | type ReqUnfreezeRemainingFunds struct { 100 | SubMchid string `json:"sub_mchid"` 101 | TransactionID string `json:"transaction_id"` 102 | OutOrderNo string `json:"out_order_no"` 103 | Description string `json:"description"` 104 | } 105 | 106 | // RespUnfreezeRemainingFunds 解冻剩余资金返回参数 107 | type RespUnfreezeRemainingFunds struct { 108 | SubMchid string `json:"sub_mchid"` 109 | TransactionID string `json:"transaction_id"` 110 | OutOrderNo string `json:"out_order_no"` 111 | OrderID string `json:"order_id"` 112 | State string `json:"state"` 113 | Receivers []RespProfitSharingReceivers `json:"receivers"` 114 | } 115 | 116 | // RespQueryRemainingFrozenAmount 查询剩余待分金额返回参数 117 | type RespQueryRemainingFrozenAmount struct { 118 | TransactionID string `json:"transaction_id"` 119 | UnsplitAmount int `json:"unsplit_amount"` 120 | } 121 | 122 | // RespQueryMaximumSplitRatio 查询最大分账比例API 123 | type RespQueryMaximumSplitRatio struct { 124 | SubMchid string `json:"sub_mchid"` 125 | MaxRatio int `json:"max_ratio"` 126 | } 127 | 128 | // ReqAddProfitSharingReceiver 添加分账接收方请求参数 129 | type ReqAddProfitSharingReceiver struct { 130 | SubMchid string `json:"sub_mchid,omitempty"` //直连商户不需要该字段 131 | Appid string `json:"appid"` 132 | SubAppid string `json:"sub_appid,omitempty"` //直连商户不需要该字段 133 | Type string `json:"type"` 134 | Account string `json:"account"` 135 | Name string `json:"name,omitempty"` 136 | RelationType string `json:"relation_type"` 137 | CustomRelation string `json:"custom_relation,omitempty"` 138 | } 139 | 140 | // RespAddProfitSharingReceiver 添加分账接收方返回参数 141 | type RespAddProfitSharingReceiver struct { 142 | SubMchid string `json:"sub_mchid"` 143 | Type string `json:"type"` 144 | Account string `json:"account"` 145 | Name string `json:"name"` 146 | RelationType string `json:"relation_type"` 147 | CustomRelation string `json:"custom_relation"` 148 | } 149 | 150 | // ReqDeleteProfitSharingReceiver 删除分账接收方请求参数 151 | type ReqDeleteProfitSharingReceiver struct { 152 | SubMchid string `json:"sub_mchid"` 153 | Appid string `json:"appid"` 154 | SubAppid string `json:"sub_appid"` 155 | Type string `json:"type"` 156 | Account string `json:"account"` 157 | } 158 | 159 | // RespDeleteProfitSharingReceiver 删除分账接收方返回参数 160 | type RespDeleteProfitSharingReceiver struct { 161 | SubMchid string `json:"sub_mchid"` 162 | Type string `json:"type"` 163 | Account string `json:"account"` 164 | } 165 | -------------------------------------------------------------------------------- /core/basic_payment.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/louismax/wxpayv3/constant" 6 | "github.com/louismax/wxpayv3/custom" 7 | "github.com/louismax/wxpayv3/utils" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // PaymentQueryOrderByTransactionId 查询订单-通过微信订单号 13 | func (c *PayClient) PaymentQueryOrderByTransactionId(transactionId, mchID string, subMchId ...string) (*custom.ReqPaymentQueryOrder, error) { 14 | params := map[string]string{"transaction_id": transactionId} 15 | qy := url.Values{} 16 | 17 | resp := custom.ReqPaymentQueryOrder{} 18 | if len(subMchId) > 0 { 19 | //服务商模式 20 | qy.Set("sp_mchid", mchID) 21 | qy.Set("sub_mchid", subMchId[0]) 22 | body, err := c.doRequest(nil, utils.BuildUrl(params, qy, constant.APIPaymentPartnerQueryOrderByTransactionId), http.MethodGet) 23 | if err != nil { 24 | return nil, err 25 | } 26 | if err = json.Unmarshal(body, &resp); err != nil { 27 | return nil, err 28 | } 29 | } else { 30 | qy.Set("mchid", mchID) 31 | //直连商户模式 32 | body, err := c.doRequest(nil, utils.BuildUrl(params, qy, constant.APIPaymentQueryOrderByTransactionId), http.MethodGet) 33 | if err != nil { 34 | return nil, err 35 | } 36 | if err = json.Unmarshal(body, &resp); err != nil { 37 | return nil, err 38 | } 39 | } 40 | return &resp, nil 41 | } 42 | 43 | // PaymentQueryOrderByOutTradeNo 查询订单-通过商户订单号 44 | func (c *PayClient) PaymentQueryOrderByOutTradeNo(outTradeNo, mchID string, subMchId ...string) (*custom.ReqPaymentQueryOrder, error) { 45 | params := map[string]string{"out-trade-no": outTradeNo} 46 | qy := url.Values{} 47 | 48 | resp := custom.ReqPaymentQueryOrder{} 49 | if len(subMchId) > 0 { 50 | //服务商模式 51 | qy.Set("sp_mchid", mchID) 52 | qy.Set("sub_mchid", subMchId[0]) 53 | body, err := c.doRequest(nil, utils.BuildUrl(params, qy, constant.APIPaymentPartnerQueryOrderByOutTradeNo), http.MethodGet) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if err = json.Unmarshal(body, &resp); err != nil { 58 | return nil, err 59 | } 60 | } else { 61 | qy.Set("mchid", mchID) 62 | //直连商户模式 63 | body, err := c.doRequest(nil, utils.BuildUrl(params, qy, constant.APIPaymentQueryOrderByOutTradeNo), http.MethodGet) 64 | if err != nil { 65 | return nil, err 66 | } 67 | if err = json.Unmarshal(body, &resp); err != nil { 68 | return nil, err 69 | } 70 | } 71 | return &resp, nil 72 | } 73 | 74 | // PaymentCloseOrder 关闭订单 75 | func (c *PayClient) PaymentCloseOrder(outTradeNo, mchID string, subMchId ...string) error { 76 | params := map[string]string{"out_trade_no": outTradeNo} 77 | if len(subMchId) > 0 { 78 | //服务商模式 79 | reqData := map[string]interface{}{ 80 | "sp_mchid": mchID, 81 | "sub_mchid": subMchId[0], 82 | } 83 | _, err := c.doRequest(reqData, utils.BuildUrl(params, nil, constant.APIPaymentPartnerCloseOrder), http.MethodPost) 84 | if err != nil { 85 | return err 86 | } 87 | } else { 88 | //直连商户模式 89 | reqData := map[string]interface{}{ 90 | "mchid": mchID, 91 | } 92 | _, err := c.doRequest(reqData, utils.BuildUrl(params, nil, constant.APIPaymentCloseOrder), http.MethodPost) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | // PaymentRefund 直连商户申请退款 101 | func (c *PayClient) PaymentRefund(data custom.ReqPaymentRefund) (*custom.RespPaymentRefund, error) { 102 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIPaymentRefund), http.MethodPost) 103 | if err != nil { 104 | return nil, err 105 | } 106 | resp := custom.RespPaymentRefund{} 107 | err = json.Unmarshal(body, &resp) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return &resp, nil 112 | } 113 | 114 | // PaymentRefundForPartner 服务商申请退款 115 | func (c *PayClient) PaymentRefundForPartner(data custom.ReqPaymentRefundForPartner) (*custom.RespPaymentRefund, error) { 116 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIPaymentRefund), http.MethodPost) 117 | if err != nil { 118 | return nil, err 119 | } 120 | resp := custom.RespPaymentRefund{} 121 | err = json.Unmarshal(body, &resp) 122 | if err != nil { 123 | return nil, err 124 | } 125 | return &resp, nil 126 | } 127 | 128 | // ApplyTransactionBill 申请交易账单 129 | func (c *PayClient) ApplyTransactionBill(billDate, subMchid, billType, tarType string) (*custom.RespApplyTransactionBill, error) { 130 | qy := url.Values{} 131 | qy.Set("bill_date", billDate) 132 | if subMchid != "" { 133 | qy.Set("sub_mchid", subMchid) 134 | } 135 | if billType != "" { 136 | qy.Set("bill_type", billType) 137 | } 138 | if tarType != "" { 139 | qy.Set("tar_type", tarType) 140 | } 141 | body, err := c.doRequest(nil, utils.BuildUrl(nil, qy, constant.APIApplyTransactionBill), http.MethodGet) 142 | if err != nil { 143 | return nil, err 144 | } 145 | resp := custom.RespApplyTransactionBill{} 146 | err = json.Unmarshal(body, &resp) 147 | if err != nil { 148 | return nil, err 149 | } 150 | return &resp, nil 151 | } 152 | 153 | // ApplyFundBill 申请资金账单 154 | func (c *PayClient) ApplyFundBill(billDate, accountType, tarType string) (*custom.RespApplyTransactionBill, error) { 155 | qy := url.Values{} 156 | qy.Set("bill_date", billDate) 157 | if accountType != "" { 158 | qy.Set("account_type", accountType) 159 | } 160 | if tarType != "" { 161 | qy.Set("tar_type", tarType) 162 | } 163 | body, err := c.doRequest(nil, utils.BuildUrl(nil, qy, constant.APIApplyFundBill), http.MethodGet) 164 | if err != nil { 165 | return nil, err 166 | } 167 | resp := custom.RespApplyTransactionBill{} 168 | err = json.Unmarshal(body, &resp) 169 | if err != nil { 170 | return nil, err 171 | } 172 | return &resp, nil 173 | } 174 | 175 | // DownloadBill 下载账单 176 | func (c *PayClient) DownloadBill(downloadUrl string) ([]byte, error) { 177 | body, err := c.doRequest(nil, utils.BuildUrl(nil, nil, downloadUrl), http.MethodGet) 178 | if err != nil { 179 | return nil, err 180 | } 181 | return body, nil 182 | } 183 | 184 | // JSAPIOrders 直连商户JSAPI下单 185 | func (c *PayClient) JSAPIOrders(data custom.ReqJSAPIOrders) (*custom.RespJSAPIOrders, error) { 186 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIJSAPIOrders), http.MethodPost) 187 | if err != nil { 188 | return nil, err 189 | } 190 | resp := custom.RespJSAPIOrders{} 191 | err = json.Unmarshal(body, &resp) 192 | if err != nil { 193 | return nil, err 194 | } 195 | return &resp, nil 196 | } 197 | 198 | // JSAPIOrdersForPartner 服务商JSAPI下单 199 | func (c *PayClient) JSAPIOrdersForPartner(data custom.ReqJSAPIOrdersForPartner) (*custom.RespJSAPIOrders, error) { 200 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIJSAPIOrdersForPartner), http.MethodPost) 201 | if err != nil { 202 | return nil, err 203 | } 204 | resp := custom.RespJSAPIOrders{} 205 | err = json.Unmarshal(body, &resp) 206 | if err != nil { 207 | return nil, err 208 | } 209 | return &resp, nil 210 | } 211 | -------------------------------------------------------------------------------- /core/certificate.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/json" 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "github.com/louismax/wxpayv3/constant" 11 | "github.com/louismax/wxpayv3/custom" 12 | "github.com/louismax/wxpayv3/utils" 13 | "io/ioutil" 14 | "net/http" 15 | "time" 16 | ) 17 | 18 | // ApiCert 商户证书参数 19 | type ApiCert struct { 20 | ApiSerialNo string // 商户API证书序列号 21 | ApiPrivateKey *rsa.PrivateKey // 商户API私钥 22 | ApiCertificate *x509.Certificate // 商户API证书 23 | } 24 | 25 | // GetCertificate 获取微信平台证书(仅从微信获取最新的平台证书) 26 | func (c *PayClient) GetCertificate() ([]custom.CertificateData, error) { 27 | body, err := c.doRequest(nil, utils.BuildUrl(nil, nil, constant.ApiCertification), http.MethodGet) 28 | if err != nil { 29 | return nil, err 30 | } 31 | resp := &custom.CertificateResp{} 32 | err = json.Unmarshal(body, resp) 33 | if err != nil { 34 | return nil, err 35 | } 36 | result := make([]custom.CertificateData, 0) 37 | 38 | for _, data := range resp.Data { 39 | item := custom.CertificateData{ 40 | EncryptCertificate: data.EncryptCertificate, 41 | SerialNo: data.SerialNo, 42 | EffectiveTime: data.EffectiveTime, 43 | ExpireTime: data.ExpireTime, 44 | } 45 | encryptCert := data.EncryptCertificate 46 | if encryptCert == nil { 47 | continue 48 | } 49 | decryptCert, err := c.Decrypt(encryptCert.Algorithm, encryptCert.Ciphertext, encryptCert.AssociatedData, encryptCert.Nonce) 50 | if err != nil { 51 | return nil, err 52 | } 53 | item.DecryptCertificate = string(decryptCert) 54 | 55 | result = append(result, item) 56 | } 57 | return result, nil 58 | } 59 | 60 | // GetAndSetCertificate 获取并设置微信平台证书(一步到位,从微信获取最新的平台证书,并设置到当前客户端) 61 | func (c *PayClient) GetAndSetCertificate() ([]custom.CertificateData, error) { 62 | body, err := c.doRequest(nil, utils.BuildUrl(nil, nil, constant.ApiCertification), http.MethodGet) 63 | if err != nil { 64 | return nil, err 65 | } 66 | resp := &custom.CertificateResp{} 67 | err = json.Unmarshal(body, resp) 68 | if err != nil { 69 | return nil, err 70 | } 71 | result := make([]custom.CertificateData, 0) 72 | if c.PlatformCertMap == nil { 73 | c.PlatformCertMap = make(map[string]*x509.Certificate) 74 | } 75 | 76 | for _, data := range resp.Data { 77 | item := custom.CertificateData{ 78 | EncryptCertificate: data.EncryptCertificate, 79 | SerialNo: data.SerialNo, 80 | EffectiveTime: data.EffectiveTime, 81 | ExpireTime: data.ExpireTime, 82 | } 83 | encryptCert := data.EncryptCertificate 84 | if encryptCert == nil { 85 | continue 86 | } 87 | decryptCert, err := c.Decrypt(encryptCert.Algorithm, encryptCert.Ciphertext, encryptCert.AssociatedData, encryptCert.Nonce) 88 | if err != nil { 89 | return nil, err 90 | } 91 | item.DecryptCertificate = string(decryptCert) 92 | ct, err := LoadCertificate(item.DecryptCertificate) 93 | if err != nil { 94 | return nil, err 95 | } 96 | if c.DefaultPlatformSerialNo == "" { 97 | c.DefaultPlatformSerialNo = GetCertificateSerialNumber(*ct) 98 | } 99 | c.PlatformCertMap[GetCertificateSerialNumber(*ct)] = ct 100 | 101 | result = append(result, item) 102 | } 103 | return result, nil 104 | } 105 | 106 | // SetClientPlatformCert 设置客户端微信平台证书(通过证书字符串设置当前客户端的平台证书) 107 | func (c *PayClient) SetClientPlatformCert(certificateStr []string) error { 108 | if c.PlatformCertMap == nil { 109 | c.PlatformCertMap = make(map[string]*x509.Certificate) 110 | } 111 | for _, v := range certificateStr { 112 | ct, err := LoadCertificate(v) 113 | if err != nil { 114 | return err 115 | } 116 | if c.DefaultPlatformSerialNo == "" { 117 | c.DefaultPlatformSerialNo = GetCertificateSerialNumber(*ct) 118 | } 119 | c.PlatformCertMap[GetCertificateSerialNumber(*ct)] = ct 120 | } 121 | return nil 122 | } 123 | 124 | // LoadCertificate 通过证书的文本内容加载证书 125 | func LoadCertificate(certificateStr string) (certificate *x509.Certificate, err error) { 126 | block, _ := pem.Decode([]byte(certificateStr)) 127 | if block == nil { 128 | return nil, fmt.Errorf("解码证书错误") 129 | } 130 | certificate, err = x509.ParseCertificate(block.Bytes) 131 | if err != nil { 132 | return nil, fmt.Errorf("解析证书错误:%s", err.Error()) 133 | } 134 | return certificate, nil 135 | } 136 | 137 | // LoadPrivateKey 通过私钥的文本内容加载私钥 138 | func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) { 139 | block, _ := pem.Decode([]byte(privateKeyStr)) 140 | if block == nil { 141 | return nil, fmt.Errorf("解码私钥错误") 142 | } 143 | key, err := x509.ParsePKCS8PrivateKey(block.Bytes) 144 | if err != nil { 145 | return nil, fmt.Errorf("解析私钥错误:%s", err.Error()) 146 | } 147 | privateKey, ok := key.(*rsa.PrivateKey) 148 | if !ok { 149 | return nil, fmt.Errorf("[%s]不是RSA私钥", privateKeyStr) 150 | } 151 | return privateKey, nil 152 | } 153 | 154 | // LoadPublicKey 通过公钥的文本内容加载公钥 155 | func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) { 156 | block, _ := pem.Decode([]byte(publicKeyStr)) 157 | if block == nil { 158 | return nil, errors.New("解码公钥错误") 159 | } 160 | key, err := x509.ParsePKIXPublicKey(block.Bytes) 161 | if err != nil { 162 | return nil, fmt.Errorf("解析公钥错误:%s", err.Error()) 163 | } 164 | publicKey, ok := key.(*rsa.PublicKey) 165 | if !ok { 166 | return nil, fmt.Errorf("[%s]不是rsa公钥", publicKeyStr) 167 | } 168 | return publicKey, nil 169 | } 170 | 171 | // LoadCertificateWithPath 通过证书的文件路径加载证书 172 | func LoadCertificateWithPath(path string) (certificate *x509.Certificate, err error) { 173 | certificateBytes, err := ioutil.ReadFile(path) 174 | if err != nil { 175 | return nil, fmt.Errorf("读取证书pem文件错误:%s", err.Error()) 176 | } 177 | return LoadCertificate(string(certificateBytes)) 178 | } 179 | 180 | // LoadPrivateKeyWithPath 通过私钥的文件路径加载私钥 181 | func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) { 182 | privateKeyBytes, err := ioutil.ReadFile(path) 183 | if err != nil { 184 | return nil, fmt.Errorf("读取私有pem文件错误:%s", err.Error()) 185 | } 186 | return LoadPrivateKey(string(privateKeyBytes)) 187 | } 188 | 189 | // LoadPublicKeyWithPath 通过公钥的文件路径加载公钥 190 | func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) { 191 | publicKeyBytes, err := ioutil.ReadFile(path) 192 | if err != nil { 193 | return nil, fmt.Errorf("读取证书pem文件错误:%s", err.Error()) 194 | } 195 | return LoadPublicKey(string(publicKeyBytes)) 196 | } 197 | 198 | // GetCertificateSerialNumber 从证书中获取证书序列号 199 | func GetCertificateSerialNumber(certificate x509.Certificate) string { 200 | return fmt.Sprintf("%X", certificate.SerialNumber) 201 | } 202 | 203 | // IsCertExpired 判定证书在特定时间是否过期 204 | func IsCertExpired(certificate x509.Certificate, now time.Time) bool { 205 | return now.After(certificate.NotAfter) 206 | } 207 | 208 | // IsCertValid 判定证书在特定时间是否有效 209 | func IsCertValid(certificate x509.Certificate, now time.Time) bool { 210 | return now.After(certificate.NotBefore) && now.Before(certificate.NotAfter) 211 | } 212 | -------------------------------------------------------------------------------- /core/profit_sharing.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/louismax/wxpayv3/constant" 7 | "github.com/louismax/wxpayv3/custom" 8 | "github.com/louismax/wxpayv3/utils" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | // InitiateProfitSharing 请求分账(注意,默认会做敏感数据加密) 14 | func (c *PayClient) InitiateProfitSharing(data custom.ReqInitiateProfitSharing) (*custom.RespInitiateProfitSharing, error) { 15 | var err error 16 | for i, v := range data.Receivers { 17 | if v.Name != "" { 18 | if c.WechatPayPublicKeyID != "" && c.WechatPayPublicKey != nil { //优先使用微信平台公钥加密敏感数据 19 | data.Receivers[i].Name, err = c.RsaEncryptByWxPayPubKey(v.Name) 20 | if err != nil { 21 | return nil, err 22 | } 23 | } else if c.DefaultPlatformSerialNo != "" && len(c.PlatformCertMap) > 0 { //使用微信平台证书加密敏感数据 24 | data.Receivers[i].Name, err = c.RsaEncryptByWxPayPubCertKey(v.Name) 25 | if err != nil { 26 | return nil, err 27 | } 28 | } else { 29 | return nil, fmt.Errorf("发起分账需要做敏感数据加密,当前实例的微信平台公钥或证书不允许为空") 30 | } 31 | } 32 | } 33 | 34 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIInitiateProfitSharing), http.MethodPost) 35 | if err != nil { 36 | return nil, err 37 | } 38 | resp := custom.RespInitiateProfitSharing{} 39 | err = json.Unmarshal(body, &resp) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return &resp, nil 44 | } 45 | 46 | // QueryProfitSharingResult 查询分账结果 47 | func (c *PayClient) QueryProfitSharingResult(subMchid, transactionId, outOrderNo string) (*custom.RespQueryProfitSharingResult, error) { 48 | params := map[string]string{"out_order_no": outOrderNo, "sub_mchid": subMchid, "transaction_id": transactionId} 49 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryProfitSharingResult), http.MethodGet) 50 | if err != nil { 51 | return nil, err 52 | } 53 | resp := custom.RespQueryProfitSharingResult{} 54 | err = json.Unmarshal(body, &resp) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &resp, nil 59 | } 60 | 61 | // InitiateProfitSharingReturnOrders is InitiateProfitSharingReturnOrders 62 | func (c *PayClient) InitiateProfitSharingReturnOrders(data custom.ReqInitiateProfitSharingReturnOrders) (*custom.RespInitiateProfitSharingReturnOrders, error) { 63 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIInitiateProfitSharingReturnOrders), http.MethodPost) 64 | if err != nil { 65 | return nil, err 66 | } 67 | resp := custom.RespInitiateProfitSharingReturnOrders{} 68 | err = json.Unmarshal(body, &resp) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return &resp, nil 73 | } 74 | 75 | // QueryProfitSharingReturnOrders IS QueryProfitSharingReturnOrders 76 | func (c *PayClient) QueryProfitSharingReturnOrders(subMchid, outReturnNo, outOrderNo string) (*custom.RespQueryProfitSharingReturnOrders, error) { 77 | params := map[string]string{"out_order_no": outOrderNo, "sub_mchid": subMchid, "out_return_no": outReturnNo} 78 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryProfitSharingReturnOrders), http.MethodGet) 79 | if err != nil { 80 | return nil, err 81 | } 82 | resp := custom.RespQueryProfitSharingReturnOrders{} 83 | err = json.Unmarshal(body, &resp) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return &resp, nil 88 | } 89 | 90 | // UnfreezeRemainingFunds 解冻剩余资金 91 | func (c *PayClient) UnfreezeRemainingFunds(data custom.ReqUnfreezeRemainingFunds) (*custom.RespUnfreezeRemainingFunds, error) { 92 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIUnfreezeRemainingFunds), http.MethodPost) 93 | if err != nil { 94 | return nil, err 95 | } 96 | resp := custom.RespUnfreezeRemainingFunds{} 97 | err = json.Unmarshal(body, &resp) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return &resp, nil 102 | } 103 | 104 | // QueryRemainingFrozenAmount 查询剩余冻结金额 105 | func (c *PayClient) QueryRemainingFrozenAmount(transactionId string) (*custom.RespQueryRemainingFrozenAmount, error) { 106 | params := map[string]string{"transaction_id": transactionId} 107 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryRemainingFrozenAmount), http.MethodGet) 108 | if err != nil { 109 | return nil, err 110 | } 111 | resp := custom.RespQueryRemainingFrozenAmount{} 112 | err = json.Unmarshal(body, &resp) 113 | if err != nil { 114 | return nil, err 115 | } 116 | return &resp, nil 117 | } 118 | 119 | // QueryMaximumSplitRatio 查询最大分账比例 120 | func (c *PayClient) QueryMaximumSplitRatio(subMchid string) (*custom.RespQueryMaximumSplitRatio, error) { 121 | params := map[string]string{"sub_mchid": subMchid} 122 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryMaximumSplitRatio), http.MethodGet) 123 | if err != nil { 124 | return nil, err 125 | } 126 | resp := custom.RespQueryMaximumSplitRatio{} 127 | err = json.Unmarshal(body, &resp) 128 | if err != nil { 129 | return nil, err 130 | } 131 | return &resp, nil 132 | } 133 | 134 | // AddProfitSharingReceiver 添加分账接收方(注意,默认会做敏感数据加密) 135 | func (c *PayClient) AddProfitSharingReceiver(data custom.ReqAddProfitSharingReceiver) (*custom.RespAddProfitSharingReceiver, error) { 136 | if c.WechatPayPublicKeyID != "" && c.WechatPayPublicKey != nil { //优先使用微信平台公钥加密敏感数据 137 | if data.Name != "" { 138 | var err error 139 | data.Name, err = c.RsaEncryptByWxPayPubKey(data.Name) 140 | if err != nil { 141 | return nil, err 142 | } 143 | } 144 | } else if c.DefaultPlatformSerialNo != "" && len(c.PlatformCertMap) > 0 { //使用微信平台证书加密敏感数据 145 | if data.Name != "" { 146 | var err error 147 | data.Name, err = c.RsaEncryptByWxPayPubCertKey(data.Name) 148 | if err != nil { 149 | return nil, err 150 | } 151 | } 152 | } else { 153 | return nil, fmt.Errorf("添加分账接收方需要做敏感数据加密,当前实例的微信平台公钥或证书不允许为空") 154 | } 155 | 156 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIAddProfitSharingReceiver), http.MethodPost) 157 | if err != nil { 158 | return nil, err 159 | } 160 | resp := custom.RespAddProfitSharingReceiver{} 161 | err = json.Unmarshal(body, &resp) 162 | if err != nil { 163 | return nil, err 164 | } 165 | return &resp, nil 166 | } 167 | 168 | // DeleteProfitSharingReceiver 删除分账接受方 169 | func (c *PayClient) DeleteProfitSharingReceiver(data custom.ReqDeleteProfitSharingReceiver) (*custom.RespDeleteProfitSharingReceiver, error) { 170 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIDeleteProfitSharingReceiver), http.MethodPost) 171 | if err != nil { 172 | return nil, err 173 | } 174 | resp := custom.RespDeleteProfitSharingReceiver{} 175 | err = json.Unmarshal(body, &resp) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return &resp, nil 180 | } 181 | 182 | // ApplyProfitSharingBill 申请分账账单 183 | func (c *PayClient) ApplyProfitSharingBill(billDate, subMchid, tarType string) (*custom.RespApplyTransactionBill, error) { 184 | qy := url.Values{} 185 | qy.Set("bill_date", billDate) 186 | if subMchid != "" { 187 | qy.Set("sub_mchid", subMchid) 188 | } 189 | if tarType != "" { 190 | qy.Set("tar_type", tarType) 191 | } 192 | body, err := c.doRequest(nil, utils.BuildUrl(nil, qy, constant.APIApplyProfitSharingBill), http.MethodGet) 193 | if err != nil { 194 | return nil, err 195 | } 196 | resp := custom.RespApplyTransactionBill{} 197 | err = json.Unmarshal(body, &resp) 198 | if err != nil { 199 | return nil, err 200 | } 201 | return &resp, nil 202 | } 203 | -------------------------------------------------------------------------------- /custom/basic_payment.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | type ReqPaymentQueryOrder struct { 4 | Amount struct { 5 | Currency string `json:"currency"` 6 | PayerCurrency string `json:"payer_currency"` 7 | PayerTotal int `json:"payer_total"` 8 | Total int `json:"total"` 9 | } `json:"amount"` 10 | Appid string `json:"appid"` 11 | Attach string `json:"attach"` 12 | BankType string `json:"bank_type"` 13 | Mchid string `json:"mchid"` 14 | OutTradeNo string `json:"out_trade_no"` 15 | Payer struct { 16 | Openid string `json:"openid"` 17 | } `json:"payer"` 18 | PromotionDetail []interface{} `json:"promotion_detail"` 19 | SuccessTime string `json:"success_time"` 20 | TradeState string `json:"trade_state"` 21 | TradeStateDesc string `json:"trade_state_desc"` 22 | TradeType string `json:"trade_type"` 23 | TransactionId string `json:"transaction_id"` 24 | } 25 | 26 | type ReqPaymentRefund struct { 27 | TransactionId string `json:"transaction_id,omitempty"` 28 | OutTradeNo string `json:"out_trade_no,omitempty"` 29 | OutRefundNo string `json:"out_refund_no"` 30 | Reason string `json:"reason,omitempty"` 31 | NotifyUrl string `json:"notify_url,omitempty"` 32 | FundsAccount string `json:"funds_account,omitempty"` 33 | Amount PaymentRefundAmount `json:"amount"` 34 | GoodsDetail *[]PaymentGoodsDetail `json:"goods_detail,omitempty"` 35 | } 36 | 37 | type ReqPaymentRefundForPartner struct { 38 | SubMchid string `json:"sub_mchid"` 39 | TransactionId string `json:"transaction_id,omitempty"` 40 | OutTradeNo string `json:"out_trade_no,omitempty"` 41 | OutRefundNo string `json:"out_refund_no"` 42 | Reason string `json:"reason,omitempty"` 43 | NotifyUrl string `json:"notify_url,omitempty"` 44 | FundsAccount string `json:"funds_account,omitempty"` 45 | Amount PaymentRefundAmount `json:"amount"` 46 | GoodsDetail *[]PaymentGoodsDetail `json:"goods_detail,omitempty"` 47 | } 48 | 49 | type PaymentRefundAmount struct { 50 | Refund int `json:"refund"` 51 | From *[]PaymentRefundFrom `json:"from,omitempty"` 52 | Total int `json:"total"` 53 | Currency string `json:"currency"` 54 | } 55 | 56 | type PaymentRefundFrom struct { 57 | Account string `json:"account"` 58 | Amount string `json:"amount"` 59 | } 60 | 61 | type PaymentGoodsDetail struct { 62 | MerchantGoodsId string `json:"merchant_goods_id"` 63 | WechatpayGoodsId string `json:"wechatpay_goods_id,omitempty"` 64 | GoodsName string `json:"goods_name,omitempty"` 65 | UnitPrice int `json:"unit_price"` 66 | RefundAmount int `json:"refund_amount"` 67 | RefundQuantity int `json:"refund_quantity"` 68 | } 69 | 70 | type RespPaymentRefund struct { 71 | RefundId string `json:"refund_id"` 72 | OutRefundNo string `json:"out_refund_no"` 73 | TransactionId string `json:"transaction_id"` 74 | OutTradeNo string `json:"out_trade_no"` 75 | Channel string `json:"channel"` 76 | UserReceivedAccount string `json:"user_received_account"` 77 | SuccessTime string `json:"success_time"` 78 | CreateTime string `json:"create_time"` 79 | Status string `json:"status"` 80 | FundsAccount string `json:"funds_account"` 81 | Amount struct { 82 | Total int `json:"total"` 83 | Refund int `json:"refund"` 84 | From []struct { 85 | Account string `json:"account"` 86 | Amount int `json:"amount"` 87 | } `json:"from"` 88 | PayerTotal int `json:"payer_total"` 89 | PayerRefund int `json:"payer_refund"` 90 | SettlementRefund int `json:"settlement_refund"` 91 | SettlementTotal int `json:"settlement_total"` 92 | DiscountRefund int `json:"discount_refund"` 93 | Currency string `json:"currency"` 94 | RefundFee int `json:"refund_fee"` 95 | } `json:"amount"` 96 | PromotionDetail []struct { 97 | PromotionId string `json:"promotion_id"` 98 | Scope string `json:"scope"` 99 | Type string `json:"type"` 100 | Amount int `json:"amount"` 101 | RefundAmount int `json:"refund_amount"` 102 | GoodsDetail []struct { 103 | MerchantGoodsId string `json:"merchant_goods_id"` 104 | WechatpayGoodsId string `json:"wechatpay_goods_id"` 105 | GoodsName string `json:"goods_name"` 106 | UnitPrice int `json:"unit_price"` 107 | RefundAmount int `json:"refund_amount"` 108 | RefundQuantity int `json:"refund_quantity"` 109 | } `json:"goods_detail"` 110 | } `json:"promotion_detail"` 111 | } 112 | 113 | type RespApplyTransactionBill struct { 114 | DownloadURL string `json:"download_url"` 115 | HashType string `json:"hash_type"` 116 | HashValue string `json:"hash_value"` 117 | } 118 | 119 | type ReqJSAPIOrdersForPartner struct { 120 | SpAppid string `json:"sp_appid"` 121 | SpMchid string `json:"sp_mchid"` 122 | SubAppid string `json:"sub_appid"` 123 | SubMchid string `json:"sub_mchid"` 124 | Description string `json:"description"` 125 | OutTradeNo string `json:"out_trade_no"` 126 | TimeExpire *string `json:"time_expire,omitempty"` 127 | Attach string `json:"attach"` 128 | NotifyUrl string `json:"notify_url"` 129 | GoodsTag string `json:"goods_tag"` 130 | Amount struct { 131 | Total int `json:"total"` 132 | Currency string `json:"currency"` 133 | } `json:"amount"` 134 | Payer struct { 135 | SpOpenid string `json:"sp_openid"` 136 | SubOpenid string `json:"sub_openid"` 137 | } `json:"payer"` 138 | Detail *struct { 139 | CostPrice int `json:"cost_price"` 140 | InvoiceId string `json:"invoice_id"` 141 | GoodsDetail *[]struct { 142 | MerchantGoodsId string `json:"merchant_goods_id"` 143 | WechatPayGoodsId string `json:"wechatpay_goods_id"` 144 | GoodsName string `json:"goods_name"` 145 | Quantity int `json:"quantity"` 146 | UnitPrice int `json:"unit_price"` 147 | } `json:"goods_detail,omitempty"` 148 | } `json:"detail,omitempty"` 149 | SceneInfo *struct { 150 | PayerClientIp string `json:"payer_client_ip"` 151 | DeviceId string `json:"device_id"` 152 | StoreInfo struct { 153 | Id string `json:"id"` 154 | Name string `json:"name"` 155 | AreaCode string `json:"area_code"` 156 | Address string `json:"address"` 157 | } `json:"store_info"` 158 | } `json:"scene_info,omitempty"` 159 | SettleInfo struct { 160 | ProfitSharing bool `json:"profit_sharing"` 161 | } `json:"settle_info"` 162 | } 163 | 164 | type ReqJSAPIOrders struct { 165 | Appid string `json:"appid"` 166 | Mchid string `json:"mchid"` 167 | Description string `json:"description"` 168 | OutTradeNo string `json:"out_trade_no"` 169 | TimeExpire *string `json:"time_expire,omitempty"` 170 | Attach string `json:"attach"` 171 | NotifyUrl string `json:"notify_url"` 172 | GoodsTag string `json:"goods_tag"` 173 | Amount struct { 174 | Total int `json:"total"` 175 | Currency string `json:"currency"` 176 | } `json:"amount"` 177 | Payer struct { 178 | Openid string `json:"openid"` 179 | } `json:"payer"` 180 | Detail *struct { 181 | CostPrice int `json:"cost_price"` 182 | InvoiceId string `json:"invoice_id"` 183 | GoodsDetail *[]struct { 184 | MerchantGoodsId string `json:"merchant_goods_id"` 185 | WechatPayGoodsId string `json:"wechatpay_goods_id"` 186 | GoodsName string `json:"goods_name"` 187 | Quantity int `json:"quantity"` 188 | UnitPrice int `json:"unit_price"` 189 | } `json:"goods_detail,omitempty"` 190 | } `json:"detail,omitempty"` 191 | SceneInfo *struct { 192 | PayerClientIp string `json:"payer_client_ip"` 193 | DeviceId string `json:"device_id"` 194 | StoreInfo struct { 195 | Id string `json:"id"` 196 | Name string `json:"name"` 197 | AreaCode string `json:"area_code"` 198 | Address string `json:"address"` 199 | } `json:"store_info"` 200 | } `json:"scene_info,omitempty"` 201 | SettleInfo struct { 202 | ProfitSharing bool `json:"profit_sharing"` 203 | } `json:"settle_info"` 204 | } 205 | 206 | type RespJSAPIOrders struct { 207 | PrepayId string `json:"prepay_id"` 208 | } 209 | -------------------------------------------------------------------------------- /constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "time" 4 | 5 | const ( 6 | //AlgorithmAEADAES256GCM AlgorithmAEADAES256GCM 7 | AlgorithmAEADAES256GCM = "AEAD_AES_256_GCM" 8 | ) 9 | const ( 10 | //DefaultTimeout HTTP 请求默认超时时间 11 | DefaultTimeout = 30 * time.Second 12 | ) 13 | 14 | // ApiDomain ApiDomain 15 | const ApiDomain = "https://api.mch.weixin.qq.com/" 16 | 17 | // 基础类接口 18 | const ( 19 | ApiCertification = "/v3/certificates" // 平台证书下载 20 | ApiUploadImage = "/v3/merchant/media/upload" //图片上传 21 | ) 22 | 23 | // 服务商特约商户进件类接口 24 | const ( 25 | APIIncomingSubmitApplication = "/v3/applyment4sub/applyment/" //提交进件申请单 26 | APIModifySettlement = "/v3/apply4sub/sub_merchants/{sub_mchid}/modify-settlement" //修改结算账号 27 | APIQuerySettlementAccount = "/v3/apply4sub/sub_merchants/{sub_mchid}/settlement" //查询结算账户 28 | APIGetStatusRepairOrderByBusinessCode = "/v3/applyment4sub/applyment/business_code/{business_code}" //通过业务申请编号查询申请状态 29 | APIGetStatusRepairOrderByApplymentId = "/v3/applyment4sub/applyment/applyment_id/{applyment_id}" //通过申请单号查询申请状态 30 | ) 31 | 32 | const ( 33 | APIPaymentPartnerQueryOrderByTransactionId = "/v3/pay/partner/transactions/id/{transaction_id}" //服务商通过微信订单号查询订单 34 | APIPaymentQueryOrderByTransactionId = "/v3/pay/transactions/id/{transaction_id}?mchid={mchid}" //直连商户通过微信订单号查询订单 35 | APIPaymentPartnerQueryOrderByOutTradeNo = "/v3/pay/partner/transactions/out-trade-no/{out-trade-no}" //服务商通过微信订单号查询订单 36 | APIPaymentQueryOrderByOutTradeNo = "/v3/pay/transactions/out-trade-no/{out-trade-no}?mchid={mchid}" //直连商户通过微信订单号查询订单 37 | APIPaymentPartnerCloseOrder = "/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close" //服务商关闭订单 38 | APIPaymentCloseOrder = "/v3/pay/transactions/out-trade-no/{out_trade_no}/close" //直连商户关闭订单 39 | APIPaymentRefund = "/v3/refund/domestic/refunds" //基础支付退款 40 | APIPaymentQueryRefund = "/v3/refund/domestic/refunds/{out_refund_no}" //退款查询 41 | APIApplyTransactionBill = "/v3/bill/tradebill" //申请交易账单 42 | APIApplyFundBill = "/v3/bill/fundflowbill" //申请资金账单 43 | APIApplyProfitSharingBill = "/v3/profitsharing/bills" //申请分账账单 44 | APIJSAPIOrdersForPartner = "/v3/pay/partner/transactions/jsapi" //服务商JSAPI下单 45 | APIJSAPIOrders = "/v3/pay/transactions/jsapi" //直连商户JSAPI下单 46 | ) 47 | 48 | // 教培续费通相关接口 49 | const ( 50 | APIEduPaPayPresign = "/v3/edu-papay/contracts/presign" //预签约 51 | APIEduPaPayContractQueryById = "/v3/edu-papay/contracts/id/{contract_id}" //通过签约ID查询签约 52 | APIEduPaPayContractQueryByOpenId = "/v3/edu-papay/user/{openid}/contracts" //通过用户标识查询签约 53 | APIDissolveEduPaPayContract = "/v3/edu-papay/contracts/{contract_id}" //解约 54 | APISendEduPaPayNotifications = "/v3/edu-papay/user-notifications/{contract_id}/send" //发送扣款预通知 55 | APIEduPaPayTransactions = "/v3/edu-papay/transactions" //教培通扣款受理 56 | APIEduPaPayQueryOrderByTransactionId = "/v3/edu-papay/transactions/id/{transaction_id}" //教培通使用微信单号查单 57 | APIEduPaPayQueryOrderByOutTradeNo = "/v3/edu-papay/transactions/out-trade-no/{out_trade_no}" //教培通使用商户订单号查单 58 | ) 59 | 60 | // K12离线团餐类接口 61 | const ( 62 | APIQueryOrganizationInfoById = "/v3/offlinefacemch/organizations?organization_id={organization_id}" //根据机构ID查询机构信息 63 | APIQueryOrganizationInfoByName = "/v3/offlinefacemch/organizations?organization_name={organization_name}" //根据机构名称查询机构信息 64 | APIObtainAuthToken = "/v3/offlinefacemch/tokens" //获取授权凭证 65 | APIPayCredential = "/v3/offlinefacemch/paycredential" //旧扣款接口(已废弃) 66 | APIQueryFaceUserInfo = "/v3/offlinefacemch/organizations/{organization_id}/users/out-user-id/{out_user_id}" //刷脸用户信息查询 67 | APIUpdateFaceUserInfo = "/v3/offlinefacemch/organizations/{organization_id}/users/out-user-id/{out_user_id}" //刷脸用户信息修改 68 | APIDissolveFaceUserContract = "/v3/offlinefacemch/organizations/{organization_id}/users/user-id/{user_id}/terminate-contract" //解除刷脸用户签约关系 69 | APIPreSignature = "/v3/offlineface/contracts/presign" //预签约 70 | APIOfflinefaceTransactions = "/v3/offlineface/transactions" //申请扣款 71 | APIContractQuery = "/v3/offlineface/contracts/{contract_id}?appid={appid}" //签约查询 72 | APIQueryRepurchaseUsersList = "/v3/offlineface/face-collections?organization_id={organization_id}&offset={offset}&limit={limit}" //查询重采用户列表 73 | APIQueryRetake = "/v3/offlineface/face-collections/{collection_id}" //查询重采 74 | APIQueryOfflineFaceOrders = "/v3/offlineface/transactions/out-trade-no/{out_trade_no}?sp_mchid={sp_mchid}&sub_mchid={sub_mchid}&business_product_id={business_product_id}" //离线人脸团餐专属查单 75 | APIGetAuthInfo = "/v3/offlineface/authinfo" //获取AuthInfo 76 | APIGetRepaymentUrl = "/v3/offlineface/repayment-url" //获取还款链接 77 | ) 78 | 79 | const ( 80 | APIInitiateProfitSharing = "/v3/profitsharing/orders" //请求分账 81 | APIQueryProfitSharingResult = "/v3/profitsharing/orders/{out_order_no}?sub_mchid={sub_mchid}&transaction_id={transaction_id}" //查询分账结果 82 | APIInitiateProfitSharingReturnOrders = "/v3/profitsharing/return-orders" //请求分账回退 83 | APIQueryProfitSharingReturnOrders = "/v3/profitsharing/return-orders/{out_return_no}?sub_mchid={sub_mchid}&out_order_no={out_order_no}" //查询分账回退结果 84 | APIUnfreezeRemainingFunds = "/v3/profitsharing/orders/unfreeze" //解冻剩余资金 85 | APIQueryRemainingFrozenAmount = "/v3/profitsharing/transactions/{transaction_id}/amounts" //查询剩余待分金额 86 | APIQueryMaximumSplitRatio = "/v3/profitsharing/merchant-configs/{sub_mchid}" //查询查询最大分账比例 87 | APIAddProfitSharingReceiver = "/v3/profitsharing/receivers/add" //添加分账接收方 88 | APIDeleteProfitSharingReceiver = "/v3/profitsharing/receivers/delete" //删除分账接收方 89 | ) 90 | 91 | const ( 92 | APISmartGuideRegister = "/v3/smartguide/guides" //服务人员注册 93 | APISmartGuideAssign = "/v3/smartguide/guides/{guide_id}/assign" //服务人员分配 94 | APISmartGuideQuery = "/v3/smartguide/guides" //服务人员查询 95 | APISmartGuideUpdate = "/v3/smartguide/guides/{guide_id}" //服务人员信息更新 96 | ) 97 | 98 | const ( 99 | APIEduSchoolPayPreSign = "/v3/eduschoolpay/contracts/presign" //校园轻松付预签约 100 | APIEduSchoolPayContractQueryById = "/v3/eduschoolpay/contracts/{contract_id}" //通过协议号查询签约 101 | APIDissolveEduSchoolPayContract = "/v3/eduschoolpay/contracts/{contract_id}/terminate" //解约 102 | APIEduSchoolPayContractQueryByOpenId = "/v3/eduschoolpay/users/{openid}/contracts" //查询用户签约列表 103 | APIEduSchoolPayTransactions = "/v3/eduschoolpay/transactions" //扣款 104 | APIEduSchoolPayQueryOrderByTransactionId = "/v3/eduschoolpay/transactions/id/{transaction_id}" //使用微信单号查单 105 | APIEduSchoolPayQueryOrderByOutTradeNo = "/v3/eduschoolpay/transactions/out-trade-no/{out_trade_no}" //使用商户订单号查单 106 | ) 107 | 108 | const APIViolationNotifications = "/v3/merchant-risk-manage/violation-notifications" //商户违规通知 109 | 110 | const ( 111 | APIComplaintsList = "/v3/merchant-service/complaints-v2" //申请交易账单 112 | ) 113 | -------------------------------------------------------------------------------- /custom/merchant_incoming.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | //SettlementAccount SettlementAccount 4 | type SettlementAccount struct { 5 | AccountType string `json:"account_type"` 6 | AccountBank string `json:"account_bank"` 7 | BankName string `json:"bank_name"` 8 | BankBranchId string `json:"bank_branch_id"` 9 | AccountNumber string `json:"account_number"` 10 | VerifyResult string `json:"verify_result"` 11 | } 12 | 13 | //RespGetStatusRepairOrder RespGetStatusRepairOrder 14 | type RespGetStatusRepairOrder struct { 15 | BusinessCode string `json:"business_code"` 16 | ApplymentId int64 `json:"applyment_id"` 17 | SubMchid string `json:"sub_mchid"` 18 | SignUrl string `json:"sign_url"` 19 | ApplymentState string `json:"applyment_state"` 20 | ApplymentStateMsg string `json:"applyment_state_msg"` 21 | AuditDetail []AuditDetail `json:"audit_detail"` 22 | } 23 | 24 | //AuditDetail AuditDetail 25 | type AuditDetail struct { 26 | Field string `json:"field"` 27 | FieldName string `json:"field_name"` 28 | RejectReason string `json:"reject_reason"` 29 | } 30 | 31 | type ReqIncomingSubmitApplication struct { 32 | BusinessCode string `json:"business_code"` 33 | ContactInfo ContactInfo `json:"contact_info"` 34 | SubjectInfo SubjectInfo `json:"subject_info"` 35 | BusinessInfo ApplyBusinessInfo `json:"business_info"` 36 | SettlementInfo SettlementInfo `json:"settlement_info"` 37 | BankAccountInfo BankAccountInfo `json:"bank_account_info"` 38 | AdditionInfo AdditionInfo `json:"addition_info"` 39 | } 40 | 41 | // s1 ------------------------------------------------------ 42 | 43 | type ContactInfo struct { 44 | ContactName string `json:"contact_name"` 45 | ContactIDNumber string `json:"contact_id_number,omitempty"` 46 | Openid string `json:"openid,omitempty"` 47 | MobilePhone string `json:"mobile_phone"` 48 | ContactEmail string `json:"contact_email"` 49 | } 50 | 51 | type SubjectInfo struct { 52 | SubjectType string `json:"subject_type"` 53 | MicroBizInfo *MicroBizInfo `json:"micro_biz_info,omitempty"` //小微辅助证明材料(小微专用) 54 | BusinessLicenseInfo *BusinessLicenseInfo `json:"business_license_info,omitempty"` //营业执照(特约) 55 | CertificateInfo *CertificateInfo `json:"certificate_info,omitempty"` //登记证书 (特约) 56 | OrganizationInfo *OrganizationInfo `json:"organization_info,omitempty"` //组织机构代码证(特约) 57 | CertificateLetterCopy string `json:"certificate_letter_copy,omitempty"` //单位证明函照片(特约) 58 | IdentityInfo IdentityInfo `json:"identity_info"` //经营者/法人身份证件(公共) 59 | UboInfo *UboInfo `json:"ubo_info,omitempty"` //最终受益人信息(UBO) (特约) 60 | } 61 | 62 | type ApplyBusinessInfo struct { 63 | MerchantShortname string `json:"merchant_shortname"` 64 | ServicePhone string `json:"service_phone"` 65 | SalesInfo *SalesInfo `json:"sales_info,omitempty"` //特约商户进件需要 66 | } 67 | 68 | type SettlementInfo struct { 69 | SettlementID string `json:"settlement_id"` 70 | QualificationType string `json:"qualification_type"` 71 | Qualifications []string `json:"qualifications,omitempty"` 72 | ActivitiesID string `json:"activities_id,omitempty"` 73 | ActivitiesRate string `json:"activities_rate,omitempty"` 74 | ActivitiesAdditions []string `json:"activities_additions,omitempty"` 75 | } 76 | 77 | type BankAccountInfo struct { 78 | BankAccountType string `json:"bank_account_type"` 79 | AccountName string `json:"account_name"` 80 | AccountBank string `json:"account_bank"` 81 | BankAddressCode string `json:"bank_address_code"` 82 | BankBranchID string `json:"bank_branch_id,omitempty"` 83 | BankName string `json:"bank_name,omitempty"` 84 | AccountNumber string `json:"account_number"` 85 | } 86 | 87 | type AdditionInfo struct { 88 | LegalPersonCommitment string `json:"legal_person_commitment,omitempty"` 89 | LegalPersonVideo string `json:"legal_person_video,omitempty"` 90 | BusinessAdditionPics []string `json:"business_addition_pics,omitempty"` 91 | BusinessAdditionMsg string `json:"business_addition_msg,omitempty"` 92 | } 93 | 94 | // e1 ------------------------------------------------------ 95 | 96 | // s2 ------------------------------------------------------ 97 | 98 | type MicroBizInfo struct { 99 | MicroBizType string `json:"micro_biz_type"` 100 | MicroStoreInfo *MicroStoreInfo `json:"micro_store_info,omitempty"` //门店场所 101 | MicroMobileInfo *MicroMobileInfo `json:"micro_mobile_info,omitempty"` //流动经营/便民服务 102 | MicroOnlineInfo *MicroOnlineInfo `json:"micro_online_info,omitempty"` //线上商品/服务交易 103 | } 104 | 105 | type BusinessLicenseInfo struct { 106 | LicenseCopy string `json:"license_copy"` 107 | LicenseNumber string `json:"license_number"` 108 | MerchantName string `json:"merchant_name"` 109 | LegalPerson string `json:"legal_person"` 110 | } 111 | type CertificateInfo struct { 112 | CertCopy string `json:"cert_copy"` 113 | CertType string `json:"cert_type"` 114 | CertNumber string `json:"cert_number"` 115 | MerchantName string `json:"merchant_name"` 116 | CompanyAddress string `json:"company_address"` 117 | LegalPerson string `json:"legal_person"` 118 | PeriodBegin string `json:"period_begin"` 119 | PeriodEnd string `json:"period_end"` 120 | } 121 | 122 | type OrganizationInfo struct { 123 | OrganizationCopy string `json:"organization_copy"` 124 | OrganizationCode string `json:"organization_code"` 125 | OrgPeriodBegin string `json:"org_period_begin"` 126 | OrgPeriodEnd string `json:"org_period_end"` 127 | } 128 | 129 | type IdentityInfo struct { 130 | IDDocType string `json:"id_doc_type"` 131 | IDCardInfo *IDCardInfo `json:"id_card_info,omitempty"` //身份证信息 132 | IDDocInfo *IDDocInfo `json:"id_doc_info,omitempty"` //其他证件信息 133 | Owner bool `json:"owner"` 134 | } 135 | 136 | type UboInfo struct { 137 | IDType string `json:"id_type"` 138 | IDCardCopy string `json:"id_card_copy"` 139 | IDCardNational string `json:"id_card_national"` 140 | IDDocCopy string `json:"id_doc_copy"` 141 | Name string `json:"name"` 142 | IDNumber string `json:"id_number"` 143 | IDPeriodBegin string `json:"id_period_begin"` 144 | IDPeriodEnd string `json:"id_period_end"` 145 | } 146 | 147 | type SalesInfo struct { 148 | SalesScenesType []string `json:"sales_scenes_type"` 149 | BizStoreInfo *BizStoreInfo `json:"biz_store_info,omitempty"` 150 | MpInfo *MpInfo `json:"mp_info,omitempty"` 151 | MiniProgramInfo *MiniProgramInfo `json:"mini_program_info,omitempty"` 152 | AppInfo *AppInfo `json:"app_info,omitempty"` 153 | WebInfo *WebInfo `json:"web_info,omitempty"` 154 | WeworkInfo *WeworkInfo `json:"wework_info,omitempty"` 155 | } 156 | 157 | // e2 ------------------------------------------------------ 158 | 159 | // s3 ------------------------------------------------------ 160 | 161 | type MicroStoreInfo struct { 162 | MicroName string `json:"micro_name"` 163 | MicroAddressCode string `json:"micro_address_code"` 164 | MicroAddress string `json:"micro_address"` 165 | StoreEntrancePic string `json:"store_entrance_pic"` 166 | MicroIndoorCopy string `json:"micro_indoor_copy"` 167 | StoreLongitude string `json:"store_longitude,omitempty"` 168 | StoreLatitude string `json:"store_latitude,omitempty"` 169 | } 170 | 171 | type MicroMobileInfo struct { 172 | MicroMobileName string `json:"micro_mobile_name"` 173 | MicroMobileCity string `json:"micro_mobile_city"` 174 | MicroMobileAddress string `json:"micro_mobile_address"` 175 | MicroMobilePics []string `json:"micro_mobile_pics"` 176 | } 177 | type MicroOnlineInfo struct { 178 | MicroOnlineStore string `json:"micro_online_store"` 179 | MicroEcName string `json:"micro_ec_name"` 180 | MicroQrcode string `json:"micro_qrcode,omitempty"` 181 | MicroLink string `json:"micro_link,omitempty"` 182 | } 183 | 184 | type IDCardInfo struct { 185 | IDCardCopy string `json:"id_card_copy"` 186 | IDCardNational string `json:"id_card_national"` 187 | IDCardName string `json:"id_card_name"` 188 | IDCardNumber string `json:"id_card_number"` 189 | CardPeriodBegin string `json:"card_period_begin"` 190 | CardPeriodEnd string `json:"card_period_end"` 191 | } 192 | 193 | type IDDocInfo struct { 194 | IDDocCopy string `json:"id_doc_copy"` 195 | IDDocName string `json:"id_doc_name"` 196 | IDDocNumber string `json:"id_doc_number"` 197 | DocPeriodBegin string `json:"doc_period_begin"` 198 | DocPeriodEnd string `json:"doc_period_end"` 199 | } 200 | 201 | type BizStoreInfo struct { 202 | BizStoreName string `json:"biz_store_name"` 203 | BizAddressCode string `json:"biz_address_code"` 204 | BizStoreAddress string `json:"biz_store_address"` 205 | StoreEntrancePic []string `json:"store_entrance_pic"` 206 | IndoorPic []string `json:"indoor_pic"` 207 | BizSubAppid string `json:"biz_sub_appid,omitempty"` 208 | } 209 | type MpInfo struct { 210 | MpAppid string `json:"mp_appid,omitempty"` 211 | MpSubAppid string `json:"mp_sub_appid,omitempty"` 212 | MpPics []string `json:"mp_pics,omitempty"` 213 | } 214 | type MiniProgramInfo struct { 215 | MiniProgramAppid string `json:"mini_program_appid,omitempty"` 216 | MiniProgramSubAppid string `json:"mini_program_sub_appid,omitempty"` 217 | MiniProgramPics []string `json:"mini_program_pics,omitempty"` 218 | } 219 | type AppInfo struct { 220 | AppAppid string `json:"app_appid,omitempty"` 221 | AppSubAppid string `json:"app_sub_appid,omitempty"` 222 | AppPics []string `json:"app_pics,omitempty"` 223 | } 224 | type WebInfo struct { 225 | Domain string `json:"domain"` 226 | WebAuthorisation string `json:"web_authorisation,omitempty"` 227 | WebAppid string `json:"web_appid,omitempty"` 228 | } 229 | type WeworkInfo struct { 230 | //CorpID string `json:"corp_id,omitempty"` 231 | SubCorpID string `json:"sub_corp_id,omitempty"` 232 | WeworkPics []string `json:"wework_pics,omitempty"` 233 | } 234 | 235 | // e3 ------------------------------------------------------ 236 | 237 | type RespIncomingSubmitApplication struct { 238 | ApplymentID int64 `json:"applyment_id"` 239 | } 240 | 241 | type ReqModifySettlement struct { 242 | AccountType string `json:"account_type"` 243 | AccountBank string `json:"account_bank"` 244 | BankAddressCode string `json:"bank_address_code"` 245 | BankName string `json:"bank_name"` 246 | BankBranchID string `json:"bank_branch_id"` 247 | AccountNumber string `json:"account_number"` 248 | } 249 | -------------------------------------------------------------------------------- /core/offline_face_pay.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/louismax/wxpayv3/constant" 10 | "github.com/louismax/wxpayv3/custom" 11 | "github.com/louismax/wxpayv3/utils" 12 | "net/http" 13 | "net/url" 14 | ) 15 | 16 | // QueryOrganizationInfoById is QueryOrganizationInfoById 17 | func (c *PayClient) QueryOrganizationInfoById(organizationId string) (*custom.RespOrganizationInfo, error) { 18 | params := map[string]string{"organization_id": organizationId} 19 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryOrganizationInfoById), http.MethodGet) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | resp := custom.RespOrganizationInfo{} 25 | err = json.Unmarshal(body, &resp) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return &resp, nil 30 | } 31 | 32 | // QueryOrganizationInfoByName is QueryOrganizationInfoByName 33 | func (c *PayClient) QueryOrganizationInfoByName(organizationName string) (*custom.RespOrganizationInfo, error) { 34 | params := map[string]string{"organization_name": url.QueryEscape(organizationName)} 35 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryOrganizationInfoByName), http.MethodGet) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | resp := custom.RespOrganizationInfo{} 41 | err = json.Unmarshal(body, &resp) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return &resp, nil 46 | } 47 | 48 | // ObtainAuthToken ObtainAuthToken 49 | func (c *PayClient) ObtainAuthToken(data custom.ReqObtainAuthToken) (*custom.RespObtainAuthToken, error) { 50 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIObtainAuthToken), http.MethodPost) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | resp := custom.RespObtainAuthToken{} 56 | err = json.Unmarshal(body, &resp) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &resp, nil 61 | } 62 | 63 | // Deprecated: PayCredential 旧版扣款接口,已废弃 64 | func (c *PayClient) PayCredential(data custom.ReqPayCredential) (*custom.RespPayCredential, error) { 65 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIPayCredential), http.MethodPost) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | resp := custom.RespPayCredential{} 71 | err = json.Unmarshal(body, &resp) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &resp, nil 76 | } 77 | 78 | // QueryFaceUserInfo QueryFaceUserInfo 79 | func (c *PayClient) QueryFaceUserInfo(organizationId, outUserId string, isDecrypt ...bool) (*custom.RespQueryFaceUserInfo, error) { 80 | params := map[string]string{"organization_id": organizationId, "out_user_id": outUserId} 81 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryFaceUserInfo), http.MethodGet) 82 | if err != nil { 83 | return nil, err 84 | } 85 | resp := custom.RespQueryFaceUserInfo{} 86 | err = json.Unmarshal(body, &resp) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if len(isDecrypt) > 0 { 91 | if isDecrypt[0] { 92 | resp.UserName, err = c.RsaDecryptByPrivateKey(resp.UserName) 93 | if err != nil { 94 | return nil, err 95 | } 96 | } 97 | } 98 | return &resp, nil 99 | } 100 | 101 | // UpdateFaceUserInfo 更新人脸用户信息 102 | func (c *PayClient) UpdateFaceUserInfo(data custom.ReqUpdateUserInfo) error { 103 | params := map[string]string{"organization_id": data.OrganizationId, "out_user_id": data.OutUserId} 104 | reqData := custom.ReqUpdateRequestData{ 105 | UserType: data.RequestData.UserType, 106 | Status: data.RequestData.Status, 107 | } 108 | if data.RequestData.StudentInfo != nil { 109 | reqData.StudentInfo = data.RequestData.StudentInfo 110 | } 111 | if data.RequestData.StaffInfo != nil { 112 | reqData.StaffInfo = data.RequestData.StaffInfo 113 | } 114 | var err error 115 | 116 | if c.WechatPayPublicKeyID != "" && c.WechatPayPublicKey != nil { //优先使用微信平台公钥加密敏感数据 117 | reqData.UserName, err = c.RsaEncryptByWxPayPubKey(data.RequestData.UserName) 118 | if err != nil { 119 | return err 120 | } 121 | reqData.Phone, err = c.RsaEncryptByWxPayPubKey(data.RequestData.Phone) 122 | if err != nil { 123 | return err 124 | } 125 | } else if c.DefaultPlatformSerialNo != "" && len(c.PlatformCertMap) > 0 { //使用微信平台证书加密敏感数据 126 | reqData.UserName, err = c.RsaEncryptByWxPayPubCertKey(data.RequestData.UserName) 127 | if err != nil { 128 | return err 129 | } 130 | reqData.Phone, err = c.RsaEncryptByWxPayPubCertKey(data.RequestData.Phone) 131 | if err != nil { 132 | return err 133 | } 134 | } else { 135 | return fmt.Errorf("添加分账接收方需要做敏感数据加密,当前实例的微信平台公钥或证书不允许为空") 136 | } 137 | 138 | _, err = c.doRequest(reqData, utils.BuildUrl(params, nil, constant.APIUpdateFaceUserInfo), http.MethodPatch) 139 | if err != nil { 140 | return err 141 | } 142 | return nil 143 | } 144 | 145 | // DissolveFaceUserContract 解约 146 | func (c *PayClient) DissolveFaceUserContract(organizationId, outUserId string) error { 147 | params := map[string]string{"organization_id": organizationId, "user_id": outUserId} 148 | _, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIDissolveFaceUserContract), http.MethodPost) 149 | if err != nil { 150 | return err 151 | } 152 | return nil 153 | } 154 | 155 | // PreSignature PreSignature 156 | func (c *PayClient) PreSignature(data custom.ReqPresignToken) (*custom.RespPresignToken, error) { 157 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIPreSignature), http.MethodPost) 158 | if err != nil { 159 | return nil, err 160 | } 161 | resp := custom.RespPresignToken{} 162 | err = json.Unmarshal(body, &resp) 163 | if err != nil { 164 | return nil, err 165 | } 166 | return &resp, nil 167 | } 168 | 169 | // OfflineFaceTransactions OfflineFaceTransactions 170 | func (c *PayClient) OfflineFaceTransactions(data custom.ReqOfflinefaceTransactions) (*custom.RespOfflinefaceTransactions, error) { 171 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIOfflinefaceTransactions), http.MethodPost) 172 | if err != nil { 173 | return nil, err 174 | } 175 | resp := custom.RespOfflinefaceTransactions{} 176 | err = json.Unmarshal(body, &resp) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return &resp, nil 181 | } 182 | 183 | // ContractQuery ContractQuery 184 | func (c *PayClient) ContractQuery(contractId, AppId string) (*custom.RespContractQuery, error) { 185 | params := map[string]string{"contract_id": contractId, "appid": AppId} 186 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIContractQuery), http.MethodGet) 187 | if err != nil { 188 | return nil, err 189 | } 190 | resp := custom.RespContractQuery{} 191 | err = json.Unmarshal(body, &resp) 192 | if err != nil { 193 | return nil, err 194 | } 195 | return &resp, nil 196 | } 197 | 198 | // FaceMessageDecryption FaceMessageDecryption 199 | func (c *PayClient) FaceMessageDecryption(data custom.FaceMessageCiphertext) (*custom.FaceMessagePlaintext, error) { 200 | // 对编码密文进行base64解码 201 | decodeBytes, err := base64.StdEncoding.DecodeString(data.Resource.Ciphertext) 202 | if err != nil { 203 | return nil, err 204 | } 205 | cx, err := aes.NewCipher([]byte(c.ApiV3Key)) 206 | if err != nil { 207 | return nil, err 208 | } 209 | gcm, err := cipher.NewGCM(cx) 210 | if err != nil { 211 | return nil, err 212 | } 213 | nonceSize := gcm.NonceSize() 214 | if len(decodeBytes) < nonceSize { 215 | return nil, fmt.Errorf("密文证书长度不够") 216 | } 217 | res := custom.FaceMessagePlaintext{} 218 | if data.Resource.AssociatedData != "" { 219 | plaintext, err := gcm.Open(nil, []byte(data.Resource.Nonce), decodeBytes, []byte(data.Resource.AssociatedData)) 220 | if err != nil { 221 | return nil, err 222 | } 223 | err = json.Unmarshal(plaintext, &res) 224 | if err != nil { 225 | return nil, err 226 | } 227 | return &res, nil 228 | } 229 | plaintext, err := gcm.Open(nil, []byte(data.Resource.Nonce), decodeBytes, nil) 230 | if err != nil { 231 | return nil, err 232 | } 233 | err = json.Unmarshal(plaintext, &res) 234 | if err != nil { 235 | return nil, err 236 | } 237 | return &res, nil 238 | } 239 | 240 | // QueryRepurchaseUsersList QueryRepurchaseUsersList 241 | func (c *PayClient) QueryRepurchaseUsersList(organizationId, offset, limit string) (*custom.RespQueryRepurchaseUsersList, error) { 242 | params := map[string]string{"organization_id": organizationId, "offset": offset, "limit": limit} 243 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryRepurchaseUsersList), http.MethodGet) 244 | if err != nil { 245 | return nil, err 246 | } 247 | resp := custom.RespQueryRepurchaseUsersList{} 248 | err = json.Unmarshal(body, &resp) 249 | if err != nil { 250 | return nil, err 251 | } 252 | return &resp, nil 253 | } 254 | 255 | // QueryRetake QueryRetake 256 | func (c *PayClient) QueryRetake(collectionId string) (*custom.FaceCollections, error) { 257 | params := map[string]string{"collection_id": collectionId} 258 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryRetake), http.MethodGet) 259 | if err != nil { 260 | return nil, err 261 | } 262 | resp := custom.FaceCollections{} 263 | err = json.Unmarshal(body, &resp) 264 | if err != nil { 265 | return nil, err 266 | } 267 | return &resp, nil 268 | } 269 | 270 | // QueryOfflineFaceOrders QueryOfflineFaceOrders 271 | func (c *PayClient) QueryOfflineFaceOrders(outTradeNo, spMchid, subMchid, businessProductId string) (*custom.RespOfflinefaceTransactions, error) { 272 | params := map[string]string{"out_trade_no": outTradeNo, "sp_mchid": spMchid, "sub_mchid": subMchid, "business_product_id": businessProductId} 273 | body, err := c.doRequest(nil, utils.BuildUrl(params, nil, constant.APIQueryOfflineFaceOrders), http.MethodGet) 274 | if err != nil { 275 | return nil, err 276 | } 277 | resp := custom.RespOfflinefaceTransactions{} 278 | err = json.Unmarshal(body, &resp) 279 | if err != nil { 280 | return nil, err 281 | } 282 | return &resp, nil 283 | } 284 | 285 | // GetAuthInfo GetAuthInfo 286 | func (c *PayClient) GetAuthInfo(data custom.ReqGetAuthInfo) (*custom.RespGetAuthInfo, error) { 287 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIGetAuthInfo), http.MethodPost) 288 | if err != nil { 289 | return nil, err 290 | } 291 | resp := custom.RespGetAuthInfo{} 292 | err = json.Unmarshal(body, &resp) 293 | if err != nil { 294 | return nil, err 295 | } 296 | return &resp, nil 297 | } 298 | 299 | // GetRepaymentUrl GetRepaymentUrl 300 | func (c *PayClient) GetRepaymentUrl(data custom.ReqGetRepaymentUrl) (*custom.RespGetRepaymentUrl, error) { 301 | body, err := c.doRequest(data, utils.BuildUrl(nil, nil, constant.APIGetRepaymentUrl), http.MethodPost) 302 | if err != nil { 303 | return nil, err 304 | } 305 | resp := custom.RespGetRepaymentUrl{} 306 | err = json.Unmarshal(body, &resp) 307 | if err != nil { 308 | return nil, err 309 | } 310 | return &resp, nil 311 | } 312 | -------------------------------------------------------------------------------- /core/client.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/sha1" 10 | "crypto/x509" 11 | "encoding/base64" 12 | "encoding/json" 13 | "fmt" 14 | "github.com/louismax/wxpayv3/constant" 15 | "github.com/louismax/wxpayv3/custom" 16 | "io/ioutil" 17 | "net/http" 18 | "net/url" 19 | ) 20 | 21 | // Client is Client 22 | type Client interface { 23 | // Authorization 获取签名Authorization,由认证类型和签名信息组成 24 | Authorization(httpMethod string, urlString string, body []byte) (string, error) 25 | 26 | // GetCertificate 获取微信支付平台证书 27 | GetCertificate() ([]custom.CertificateData, error) 28 | // GetAndSetCertificate 获取并设置微信支付平台证书 29 | GetAndSetCertificate() ([]custom.CertificateData, error) 30 | // SetClientPlatformCert 设置微信支付平台证书 31 | SetClientPlatformCert(certificateStr []string) error 32 | // RsaEncryptByPrivateKey 使用商户私钥加密敏感数据 33 | RsaEncryptByPrivateKey(origData []byte) (string, error) 34 | // RsaDecryptByPrivateKey 使用商户私钥解密敏感数据 35 | RsaDecryptByPrivateKey(ciphertext string) (string, error) 36 | // RsaEncryptByWxPayPubCertKey 使用微信支付平台证书公钥RSA加密 37 | RsaEncryptByWxPayPubCertKey(plaintext string) (string, error) 38 | // RsaEncryptByWxPayPubKey 使用微信支付平台公钥RSA加密 39 | RsaEncryptByWxPayPubKey(plaintext string) (string, error) 40 | // Decrypt 通知密文数据使用V3Key解密 (AES_256_GCM) 41 | Decrypt(algorithm string, cipherText string, associatedData string, nonce string) ([]byte, error) 42 | 43 | // UploadImage 上传图片(获取MediaId) 44 | UploadImage(filePath string) (*custom.RespUploadImage, error) 45 | 46 | DownloadBill(downloadUrl string) ([]byte, error) 47 | 48 | //IncomingSubmitApplication 提交进件申请单 49 | IncomingSubmitApplication(data custom.ReqIncomingSubmitApplication) (*custom.RespIncomingSubmitApplication, error) 50 | //ModifySettlement 修改结算账号 51 | ModifySettlement(subMchid string, data custom.ReqModifySettlement) error 52 | // QuerySettlementAccount 查询结算账户 53 | QuerySettlementAccount(subMchid string) (*custom.SettlementAccount, error) 54 | // GetStatusRepairOrderByBusinessCode 通过业务申请编号查询申请状态 55 | GetStatusRepairOrderByBusinessCode(businessCode string) (*custom.RespGetStatusRepairOrder, error) 56 | // GetStatusRepairOrderByApplymentId 通过申请单号查询申请状态 57 | GetStatusRepairOrderByApplymentId(applymentId string) (*custom.RespGetStatusRepairOrder, error) 58 | 59 | //InitiateProfitSharing 发起分账请求(注意,默认会做敏感数据加密) 兼容服务商、直连商户 60 | InitiateProfitSharing(data custom.ReqInitiateProfitSharing) (*custom.RespInitiateProfitSharing, error) 61 | //QueryProfitSharingResult 查询分账结果 62 | QueryProfitSharingResult(subMchid, transactionId, outOrderNo string) (*custom.RespQueryProfitSharingResult, error) 63 | //InitiateProfitSharingReturnOrders 请求分账回退 64 | InitiateProfitSharingReturnOrders(data custom.ReqInitiateProfitSharingReturnOrders) (*custom.RespInitiateProfitSharingReturnOrders, error) 65 | //QueryProfitSharingReturnOrders 查询分账回退结果 66 | QueryProfitSharingReturnOrders(subMchid, outReturnNo, outOrderNo string) (*custom.RespQueryProfitSharingReturnOrders, error) 67 | //UnfreezeRemainingFunds 解冻剩余资金 68 | UnfreezeRemainingFunds(data custom.ReqUnfreezeRemainingFunds) (*custom.RespUnfreezeRemainingFunds, error) 69 | //QueryRemainingFrozenAmount 查询订单待分金额 70 | QueryRemainingFrozenAmount(transactionId string) (*custom.RespQueryRemainingFrozenAmount, error) 71 | //QueryMaximumSplitRatio 查询子商户最大分账比例 72 | QueryMaximumSplitRatio(subMchid string) (*custom.RespQueryMaximumSplitRatio, error) 73 | //AddProfitSharingReceiver 添加分账接收方(注意,默认会做敏感数据加密) 兼容服务商、直连商户 74 | AddProfitSharingReceiver(data custom.ReqAddProfitSharingReceiver) (*custom.RespAddProfitSharingReceiver, error) 75 | //DeleteProfitSharingReceiver 删除分账接收方 76 | DeleteProfitSharingReceiver(data custom.ReqDeleteProfitSharingReceiver) (*custom.RespDeleteProfitSharingReceiver, error) 77 | //ApplyProfitSharingBill 申请分账账单 78 | ApplyProfitSharingBill(billDate, subMchid, tarType string) (*custom.RespApplyTransactionBill, error) 79 | 80 | //PaymentQueryOrderByTransactionId 查询订单-通过微信订单号(兼容服务商模式、直连商户模式) 81 | PaymentQueryOrderByTransactionId(transactionId, mchID string, subMchId ...string) (*custom.ReqPaymentQueryOrder, error) 82 | //PaymentQueryOrderByOutTradeNo 查询订单-通过商户订单号(兼容服务商模式、直连商户模式) 83 | PaymentQueryOrderByOutTradeNo(outTradeNo, mchID string, subMchId ...string) (*custom.ReqPaymentQueryOrder, error) 84 | //PaymentCloseOrder 关闭订单(兼容服务商模式、直连商户模式) 85 | PaymentCloseOrder(outTradeNo, mchID string, subMchId ...string) error 86 | //PaymentRefund 直连商户退款 87 | PaymentRefund(data custom.ReqPaymentRefund) (*custom.RespPaymentRefund, error) 88 | //PaymentRefundForPartner 服务商退款 89 | PaymentRefundForPartner(data custom.ReqPaymentRefundForPartner) (*custom.RespPaymentRefund, error) 90 | //ApplyTransactionBill //申请交易账单 91 | ApplyTransactionBill(billDate, subMchid, billType, tarType string) (*custom.RespApplyTransactionBill, error) 92 | //ApplyFundBill //申请资金账单 93 | ApplyFundBill(billDate, accountType, tarType string) (*custom.RespApplyTransactionBill, error) 94 | //JSAPIOrders 直连商户JSAPI下单 95 | JSAPIOrders(data custom.ReqJSAPIOrders) (*custom.RespJSAPIOrders, error) 96 | //JSAPIOrdersForPartner 服务商JSAPI下单 97 | JSAPIOrdersForPartner(data custom.ReqJSAPIOrdersForPartner) (*custom.RespJSAPIOrders, error) 98 | 99 | // EduPaPayPresign 教培续费通预签约 100 | EduPaPayPresign(data custom.ReqEduPaPayPresign) (*custom.RespEduPaPayPresign, error) 101 | // EduPaPayContractQueryById 通过协议号查询教培续费通签约 102 | EduPaPayContractQueryById(contractId string, query url.Values) (*custom.RespEduPaPayContractQuery, error) 103 | // EduPaPayContractQueryByOpenId 通用户标识查询教培续费通签约 104 | EduPaPayContractQueryByOpenId(openid string, query url.Values) (*custom.RespEduPaPayContractQueryList, error) 105 | // DissolveEduPaPayContract 教培续费通解约 106 | DissolveEduPaPayContract(contractId string) error 107 | // SendEduPaPayNotifications 发送预扣款通知 108 | SendEduPaPayNotifications(contractId string, data custom.ReqSendEduPaPayNotifications) error 109 | // EduPaPayTransactions 教培通扣款受理 110 | EduPaPayTransactions(data custom.ReqEduPaPayTransactions) error 111 | // EduPaPayQueryOrderByTransactionId 教培通微信订单号查单 112 | EduPaPayQueryOrderByTransactionId(transactionId string, query url.Values) (*custom.RespEduPaPayQueryOrder, error) 113 | // EduPaPayQueryOrderByOutTradeNo 教培通商户订单号查单 114 | EduPaPayQueryOrderByOutTradeNo(outTradeNo string, query url.Values) (*custom.RespEduPaPayQueryOrder, error) 115 | 116 | // QueryOrganizationInfoById 获取机构信息(根据机构ID) 117 | QueryOrganizationInfoById(organizationId string) (*custom.RespOrganizationInfo, error) 118 | // QueryOrganizationInfoByName 获取机构信息(根据机构名称) 119 | QueryOrganizationInfoByName(organizationName string) (*custom.RespOrganizationInfo, error) 120 | // ObtainAuthToken 获取授权凭证 121 | ObtainAuthToken(data custom.ReqObtainAuthToken) (*custom.RespObtainAuthToken, error) 122 | // Deprecated: PayCredential 旧版扣款接口,已废弃 123 | PayCredential(data custom.ReqPayCredential) (*custom.RespPayCredential, error) 124 | // QueryFaceUserInfo 查询刷脸用户信息 125 | QueryFaceUserInfo(organizationId, outUserId string, isDecrypt ...bool) (*custom.RespQueryFaceUserInfo, error) 126 | // UpdateFaceUserInfo 修改刷脸用户信息 127 | UpdateFaceUserInfo(data custom.ReqUpdateUserInfo) error 128 | // DissolveFaceUserContract 解除刷脸用户签约关系 129 | DissolveFaceUserContract(organizationId, outUserId string) error 130 | // PreSignature 预签约 131 | PreSignature(data custom.ReqPresignToken) (*custom.RespPresignToken, error) 132 | // OfflineFaceTransactions 申请扣款 133 | OfflineFaceTransactions(data custom.ReqOfflinefaceTransactions) (*custom.RespOfflinefaceTransactions, error) 134 | // ContractQuery 签约查询 135 | ContractQuery(contractId, AppId string) (*custom.RespContractQuery, error) 136 | // FaceMessageDecryption 人脸报文(签约解约)消息解密 137 | FaceMessageDecryption(data custom.FaceMessageCiphertext) (*custom.FaceMessagePlaintext, error) 138 | // QueryRepurchaseUsersList 查询重采用户列表 139 | QueryRepurchaseUsersList(organizationId, offset, limit string) (*custom.RespQueryRepurchaseUsersList, error) 140 | // QueryRetake 查询重采 141 | QueryRetake(collectionId string) (*custom.FaceCollections, error) 142 | // QueryOfflineFaceOrders 离线人脸团餐专属查单 143 | QueryOfflineFaceOrders(outTradeNo, spMchid, subMchid, businessProductId string) (*custom.RespOfflinefaceTransactions, error) 144 | // GetAuthInfo 获取AuthInfo 145 | GetAuthInfo(data custom.ReqGetAuthInfo) (*custom.RespGetAuthInfo, error) 146 | // GetRepaymentUrl 获取还款链接 147 | GetRepaymentUrl(data custom.ReqGetRepaymentUrl) (*custom.RespGetRepaymentUrl, error) 148 | 149 | //SmartGuideRegister 服务人员注册 150 | SmartGuideRegister(data custom.ReqSmartGuideRegister) (*custom.RespSmartGuideRegister, error) 151 | //SmartGuideAssign 服务人员分配 152 | SmartGuideAssign(guideId string, data custom.ReqSmartGuideAssign) error 153 | //SmartGuideQuery 服务人员查询 154 | SmartGuideQuery(storeId, subMchid, userId, mobile, workId, limit, offset string) (*custom.RespSmartGuideQuery, error) 155 | //SmartGuideUpdate 服务人员信息更新 156 | SmartGuideUpdate(guideId string, data custom.ReqSmartGuideUpdate) error 157 | 158 | // EduSchoolPayPreSign 校园轻松付预签约 159 | EduSchoolPayPreSign(data custom.ReqEduSchoolPayPreSign) (*custom.RespEduSchoolPayPreSign, error) 160 | // EduSchoolPayContractQueryById 校园轻松付通过协议号查询签约 161 | EduSchoolPayContractQueryById(contractId string) (*custom.RespEduSchoolPayContractQuery, error) 162 | // DissolveEduSchoolPayContract 校园轻松付解约 163 | DissolveEduSchoolPayContract(contractId string) error 164 | // EduSchoolPayContractQueryByOpenId 校园轻松付查询用户签约列表 165 | EduSchoolPayContractQueryByOpenId(openId string, query url.Values) (*custom.RespEduSchoolPayContractQueryPage, error) 166 | // EduSchoolPayTransactions 校园轻松付扣款 167 | EduSchoolPayTransactions(data custom.ReqEduSchoolPayTransactions) (*custom.RespEduSchoolPayTransactions, error) 168 | // EduSchoolPayQueryOrderByTransactionId 校园轻松付微信支付订单号查单 169 | EduSchoolPayQueryOrderByTransactionId(transactionId string, query url.Values) (*custom.RespEduSchoolPayTransactions, error) 170 | // EduSchoolPayQueryOrderByOutTradeNo 校园轻松付商户订单号查单 171 | EduSchoolPayQueryOrderByOutTradeNo(outTradeNo string, query url.Values) (*custom.RespEduSchoolPayTransactions, error) 172 | 173 | //QueryViolationNotifications 查询商户违规通知回调地址 174 | QueryViolationNotifications() (*custom.GeneralViolationNotifications, error) 175 | //CreateViolationNotifications 创建商户违规通知回调地址 176 | CreateViolationNotifications(data custom.GeneralViolationNotifications) (*custom.GeneralViolationNotifications, error) 177 | //UpdateViolationNotifications 修改商户违规通知回调地址 178 | UpdateViolationNotifications(data custom.GeneralViolationNotifications) (*custom.GeneralViolationNotifications, error) 179 | //DeleteViolationNotifications 删除商户违规通知回调地址 180 | DeleteViolationNotifications() error 181 | 182 | // QueryComplaintsList 查询投诉单列表(兼容服务商模式、直连商户模式) 183 | QueryComplaintsList(beginDate, endDate string, limit, offset int, mchId ...string) (*custom.RespComplaintsList, error) 184 | } 185 | 186 | // PayClient PayClient 187 | type PayClient struct { 188 | MchId string // 商户号 189 | ApiV3Key string // ApiV3Key密钥,用于解密回调通知的密文数据,平台证书密文 190 | ApiSerialNo string // API证书序列号 191 | ApiPrivateKey *rsa.PrivateKey // API证书私钥 192 | ApiCertificate *x509.Certificate // API证书(非必须,可获取证书序列号和商户API公钥) 193 | DefaultPlatformSerialNo string // 默认平台证书序列号 194 | PlatformCertMap map[string]*x509.Certificate // 平台证书集合 195 | WechatPayPublicKeyID string // 平台公钥ID 196 | WechatPayPublicKey *rsa.PublicKey // 平台公钥 197 | HttpClient *http.Client // http客户端 198 | } 199 | 200 | func (c *PayClient) doRequest(requestData interface{}, url string, httpMethod string) ([]byte, error) { 201 | var data []byte 202 | if requestData != nil { 203 | var err error 204 | data, err = json.Marshal(requestData) 205 | if err != nil { 206 | return nil, err 207 | } 208 | } 209 | authorization, err := c.Authorization(httpMethod, url, data) 210 | 211 | //告诉微信需要什么验签方式,2025年开始,优先使用微信平台公钥,不存在时使用微信平台证书,还不存在不传(部分做了敏感数据加密的接口必传) 212 | serial := "" 213 | if c.WechatPayPublicKeyID != "" { 214 | serial = c.WechatPayPublicKeyID 215 | } else if c.DefaultPlatformSerialNo != "" { 216 | serial = c.DefaultPlatformSerialNo 217 | } 218 | 219 | if err != nil { 220 | return nil, err 221 | } 222 | // 重试3次,避免因网络原因导致失败 223 | retryTimes := 3 224 | var resp *http.Response 225 | for i := 0; i < retryTimes; i++ { 226 | resp, err = SimpleRequest(c.HttpClient, url, httpMethod, authorization, data, serial) 227 | if err != nil { 228 | continue 229 | } 230 | break 231 | } 232 | if err != nil { 233 | return nil, err 234 | } 235 | defer func() { 236 | _ = resp.Body.Close() 237 | }() 238 | body, err := ioutil.ReadAll(resp.Body) 239 | if err != nil { 240 | return nil, err 241 | } 242 | 243 | fmt.Printf("\n\033[36m%s\033[0m\n", "--WxPayV3-Request-Id:"+c.getRequestId(&resp.Header)) 244 | 245 | err = c.VerifyResponse(resp.StatusCode, &resp.Header, body) 246 | if err != nil { 247 | return nil, err 248 | } 249 | return body, nil 250 | } 251 | 252 | // Decrypt Decrypt解密 253 | func (c *PayClient) Decrypt(algorithm string, cipherText string, associatedData string, nonce string) ([]byte, error) { 254 | // 默认使用AEAD_AES_256_GCM 255 | switch algorithm { 256 | default: 257 | fallthrough 258 | case constant.AlgorithmAEADAES256GCM: 259 | decodedCipherText, _ := base64.StdEncoding.DecodeString(cipherText) 260 | 261 | block, err := aes.NewCipher([]byte(c.ApiV3Key)) 262 | if err != nil { 263 | return nil, err 264 | } 265 | 266 | aesGcm, err := cipher.NewGCM(block) 267 | if err != nil { 268 | return nil, err 269 | } 270 | 271 | plaintext, err := aesGcm.Open(nil, []byte(nonce), decodedCipherText, []byte(associatedData)) 272 | if err != nil { 273 | return nil, err 274 | } 275 | return plaintext, nil 276 | } 277 | } 278 | 279 | // RsaEncryptByPrivateKey 使用商户私钥RSA加密 280 | func (c *PayClient) RsaEncryptByPrivateKey(origData []byte) (string, error) { 281 | h := crypto.Hash.New(crypto.SHA256) 282 | h.Write(origData) 283 | hashed := h.Sum(nil) 284 | // 进行rsa加密签名 285 | signedData, err := rsa.SignPKCS1v15(rand.Reader, c.ApiPrivateKey, crypto.SHA256, hashed) 286 | if err != nil { 287 | return "", err 288 | } 289 | return base64.StdEncoding.EncodeToString(signedData), nil 290 | } 291 | 292 | // RsaDecryptByPrivateKey 使用商户私钥RSA解密 293 | func (c *PayClient) RsaDecryptByPrivateKey(ciphertext string) (string, error) { 294 | cipherData, _ := base64.StdEncoding.DecodeString(ciphertext) 295 | rng := rand.Reader 296 | 297 | plaintext, err := rsa.DecryptOAEP(sha1.New(), rng, c.ApiPrivateKey, cipherData, nil) 298 | if err != nil { 299 | return "", err 300 | } 301 | return string(plaintext), nil 302 | } 303 | 304 | // RsaEncryptByWxPayPubCertKey 使用微信支付平台证书公钥RSA加密 305 | func (c *PayClient) RsaEncryptByWxPayPubCertKey(plaintext string) (string, error) { 306 | if len(c.PlatformCertMap) < 1 || c.DefaultPlatformSerialNo == "" { 307 | return "", fmt.Errorf("请先初始化平台证书") 308 | } 309 | secretMessage := []byte(plaintext) 310 | rng := rand.Reader 311 | 312 | cipherData, err := rsa.EncryptOAEP(sha1.New(), rng, c.PlatformCertMap[c.DefaultPlatformSerialNo].PublicKey.(*rsa.PublicKey), secretMessage, nil) 313 | if err != nil { 314 | return "", err 315 | } 316 | 317 | ciphertext := base64.StdEncoding.EncodeToString(cipherData) 318 | return ciphertext, nil 319 | } 320 | 321 | // RsaEncryptByWxPayPubKey 使用微信支付平台公钥RSA加密 322 | func (c *PayClient) RsaEncryptByWxPayPubKey(plaintext string) (string, error) { 323 | if c.WechatPayPublicKeyID == "" || c.WechatPayPublicKey == nil { 324 | return "", fmt.Errorf("请先初始化微信支付平台公钥") 325 | } 326 | secretMessage := []byte(plaintext) 327 | rng := rand.Reader 328 | 329 | cipherData, err := rsa.EncryptOAEP(sha1.New(), rng, c.WechatPayPublicKey, secretMessage, nil) 330 | if err != nil { 331 | return "", err 332 | } 333 | ciphertext := base64.StdEncoding.EncodeToString(cipherData) 334 | return ciphertext, nil 335 | } 336 | -------------------------------------------------------------------------------- /custom/offline_face_pay.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | //RespOrganizationInfo RespOrganizationInfo 4 | type RespOrganizationInfo struct { 5 | OrganizationId string `json:"organization_id"` 6 | OrganizationName string `json:"organization_name"` 7 | } 8 | 9 | //ReqObtainAuthToken ReqObtainAuthToken 10 | type ReqObtainAuthToken struct { 11 | Scene string `json:"scene"` 12 | WebInitData WebInitData `json:"web_init_data"` 13 | } 14 | 15 | //WebInitData WebInitData 16 | type WebInitData struct { 17 | OutUserId string `json:"out_user_id"` //商户侧用户ID 18 | OrganizationId string `json:"organization_id"` //机构ID 19 | } 20 | 21 | //RespObtainAuthToken RespObtainAuthToken 22 | type RespObtainAuthToken struct { 23 | Token string `json:"token"` 24 | } 25 | 26 | //ReqPayCredential ReqPayCredential 27 | type ReqPayCredential struct { 28 | PayCredential string `json:"pay_credential"` //支付凭证 29 | MerchantInfo MerchantInfo `json:"merchant_info"` //商户信息 30 | TradeAmountInfo TradeAmountInfo `json:"trade_amount_info"` //金额信息 31 | SceneInfo SceneInfo `json:"scene_info"` //支付场景信息 32 | DeviceInfo DeviceInfo `json:"device_info"` //设备信息 33 | GoodsTag string `json:"goods_tag"` //优惠标记 34 | Description string `json:"description"` //商品信息 35 | Attach string `json:"attach"` //商户附加信息 36 | OutTradeNo string `json:"out_trade_no"` //商户订单号 37 | BusinessInfo BusinessInfo `json:"business_info"` //业务信息 38 | } 39 | 40 | //MerchantInfo MerchantInfo 41 | type MerchantInfo struct { 42 | Mchid string `json:"mchid"` //商户号 43 | SubMchid string `json:"sub_mchid"` //子商户号 44 | Appid string `json:"appid"` //商户公众号 45 | SubAppid string `json:"sub_appid"` //子商户公众号 46 | } 47 | 48 | //TradeAmountInfo TradeAmountInfo 49 | type TradeAmountInfo struct { 50 | Amount int64 `json:"amount"` //总金额 51 | Currency string `json:"currency"` //货币类型 52 | } 53 | 54 | //SceneInfo SceneInfo 55 | type SceneInfo struct { 56 | DeviceIp string `json:"device_ip"` //设备IP 57 | } 58 | 59 | // DeviceInfo DeviceInfo 60 | type DeviceInfo struct { 61 | Mac string `json:"mac"` //设备mc地址 62 | } 63 | 64 | // BusinessInfo BusinessInfo 65 | type BusinessInfo struct { 66 | BusinessProductId int `json:"business_product_id"` //平台产品ID 67 | BusinessSceneId int `json:"business_scene_id"` //平台场景ID 68 | } 69 | 70 | //RespPayCredential RespPayCredential 71 | type RespPayCredential struct { 72 | MerchantInfo MerchantInfo `json:"merchant_info"` //商户信息 73 | PayerInfo PayerInfo `json:"payer_info"` //支付用户信息 74 | TradeAmountInfo TradeAmountInfo `json:"trade_amount_info"` //金额信息 75 | PromotionList []PromotionList `json:"promotion_list"` //优惠信息 76 | SceneInfo SceneInfo `json:"scene_info"` //支付场景信息 77 | CorePaymentInfo CorePaymentInfo `json:"core_payment_info"` //支付信息 78 | TradeType string `json:"trade_type"` //交易类型 79 | TradeState string `json:"trade_state"` //交易状态 80 | TradeStateDesc string `json:"trade_state_desc"` //交易状态描述 81 | Body string `json:"body"` //商品信息 82 | Attach string `json:"attach"` //商户附加信息 83 | PaymentTime string `json:"payment_time"` //支付成功时间 84 | TransactionId string `json:"transaction_id"` //微信订单号 85 | OutTradeNo string `json:"out_trade_no"` //商户订单号 86 | } 87 | 88 | //PayerInfo PayerInfo 89 | type PayerInfo struct { 90 | Openid string `json:"openid"` //公众号下的openid 91 | SubOpenid string `json:"sub_openid"` //子公众号下的openid 92 | } 93 | 94 | //PromotionList PromotionList 95 | type PromotionList struct { 96 | PromotionId string `json:"promotion_id"` //优惠ID 97 | Name string `json:"name"` //优惠名称 98 | AmountInfo TradeAmountInfo `json:"amount_info"` //优惠金额 99 | WxPayContribute int64 `json:"wxpay_contribute"` //微信出资金额 100 | MerchantContribute int64 `json:"merchant_contribute"` //商家出资金额 101 | OtherContribute int64 `json:"other_contribute"` //其他出资金额 102 | } 103 | 104 | //CorePaymentInfo CorePaymentInfo 105 | type CorePaymentInfo struct { 106 | BankType string `json:"bank_type"` //付款银行 107 | CorePaymentInfo struct { 108 | Amount int64 `json:"amount"` //支付金额 109 | Currency string `json:"currency"` //支付币种 110 | } `json:"core_payment_info"` 111 | } 112 | 113 | //RespQueryFaceUserInfo RespQueryFaceUserInfo 114 | type RespQueryFaceUserInfo struct { 115 | UserId string `json:"user_id"` //微信侧刷脸用户唯一ID 116 | OutUserId string `json:"out_user_id"` //商户刷脸用户ID 117 | OrganizationId string `json:"organization_id"` //机构ID 118 | UserName string `json:"user_name"` //姓名 119 | UserType string `json:"user_type"` //用户类型 学生:STUDENT教职工:STAFF 120 | StudentInfo StudentInfo `json:"student_info"` //学生信息 121 | StaffInfo StaffInfo `json:"staff_info"` //教职工信息 122 | Status string `json:"status"` //用户状态 NOMAL:正常状态 DISABLED:禁用状态,此时支付被限制 123 | ContractState string `json:"contract_state"` //签约状态 124 | FaceImageOk bool `json:"face_image_ok"` //人脸图片上传状态 125 | ContractId string `json:"contract_id"` //签约ID 126 | } 127 | 128 | //StudentInfo StudentInfo 129 | type StudentInfo struct { 130 | ClassName string `json:"class_name"` 131 | } 132 | 133 | //StaffInfo StaffInfo 134 | type StaffInfo struct { 135 | Occupation string `json:"occupation"` 136 | } 137 | 138 | //ReqUpdateUserInfo ReqUpdateUserInfo 139 | type ReqUpdateUserInfo struct { 140 | OrganizationId string `json:"organization_id"` 141 | OutUserId string `json:"out_user_id"` 142 | RequestData ReqUpdateRequestData `json:"request_data"` 143 | } 144 | 145 | //ReqUpdateRequestData ReqUpdateRequestData 146 | type ReqUpdateRequestData struct { 147 | UserName string `json:"user_name"` 148 | UserType string `json:"user_type"` 149 | StudentInfo *StudentInfo `json:"student_info,omitempty"` 150 | StaffInfo *StaffInfo `json:"staff_info,omitempty"` 151 | Status string `json:"status"` 152 | Phone string `json:"phone"` 153 | } 154 | 155 | //ReqPresignToken ReqPresignToken 156 | type ReqPresignToken struct { 157 | BusinessName string `json:"business_name"` //业务类型 158 | FacePayUser FacePayUser `json:"facepay_user"` //刷脸用户信息 159 | LimitBankCard *LimitBankCard `json:"limit_bank_card,omitempty"` //签约银行卡信息 160 | ContractMode string `json:"contract_mode,omitempty"` //签约模式 LIMIT_BANK_CARD:指定卡签约;PRIORITY_BANK_CARD:优先卡签约;LIMIT_NONE:任意卡签约 161 | } 162 | 163 | //FacePayUser FacePayUser 164 | type FacePayUser struct { 165 | OutUserId string `json:"out_user_id"` 166 | IdentificationName string `json:"identification_name,omitempty"` 167 | OrganizationId string `json:"organization_id"` 168 | Identification *Identification `json:"identification,omitempty"` 169 | Phone string `json:"phone,omitempty"` 170 | } 171 | 172 | //Identification Identification 173 | type Identification struct { 174 | IdentificationType string `json:"identification_type"` 175 | IdentificationNumber string `json:"identification_number"` 176 | } 177 | 178 | //LimitBankCard LimitBankCard 179 | type LimitBankCard struct { 180 | BankCardNumber string `json:"bank_card_number"` 181 | IdentificationName string `json:"identification_name"` 182 | Identification *Identification `json:"identification,omitempty"` 183 | ValidThru string `json:"valid_thru,omitempty"` 184 | BankType string `json:"bank_type,omitempty"` 185 | Phone string `json:"phone,omitempty"` 186 | } 187 | 188 | //RespPresignToken RespPresignToken 189 | type RespPresignToken struct { 190 | PresignToken string `json:"presign_token"` 191 | } 192 | 193 | //ReqOfflinefaceTransactions ReqOfflinefaceTransactions 194 | type ReqOfflinefaceTransactions struct { 195 | AuthCode string `json:"auth_code"` //支付凭证 196 | SpAppid string `json:"sp_appid"` //服务商appid 197 | SubAppid string `json:"sub_appid,omitempty"` //子商户appid 198 | SpMchid string `json:"sp_mchid"` //商户号 199 | SubMchid string `json:"sub_mchid"` //子商户号 200 | Amount OTReqAmount `json:"amount"` //金额信息 201 | SceneInfo OTReqSceneInfo `json:"scene_info"` //支付场景信息 202 | GoodsTag string `json:"goods_tag,omitempty"` //优惠标记 203 | Description string `json:"description"` //商品信息 204 | Attach string `json:"attach"` //商户附加信息 205 | SettleInfo *OTReqSettleInfo `json:"settle_info,omitempty"` //结算信息 206 | OutTradeNo string `json:"out_trade_no"` // 商户单号 207 | Business OTReqBusiness `json:"business"` //业务信息 208 | } 209 | 210 | //OTReqAmount OTReqAmount 211 | type OTReqAmount struct { 212 | Total int64 `json:"total"` //总金额 213 | Currency string `json:"currency"` //货币类型 214 | } 215 | 216 | //OTReqSceneInfo OTReqSceneInfo 217 | type OTReqSceneInfo struct { 218 | DeviceIp string `json:"device_ip"` //设备IP 219 | } 220 | 221 | //OTReqSettleInfo OTReqSettleInfo 222 | type OTReqSettleInfo struct { 223 | ProfitSharing bool `json:"profit_sharing,omitempty"` //是否支持分账 224 | } 225 | 226 | //OTReqBusiness OTReqBusiness 227 | type OTReqBusiness struct { 228 | BusinessProductId int `json:"business_product_id"` //平台产品ID 229 | BusinessSceneId int `json:"business_scene_id"` //平台场景ID 230 | } 231 | 232 | //RespOfflinefaceTransactions RespOfflinefaceTransactions 233 | type RespOfflinefaceTransactions struct { 234 | SpAppid string `json:"sp_appid"` 235 | SubAppid string `json:"sub_appid"` 236 | SpMchid string `json:"sp_mchid"` //商户号 237 | SubMchid string `json:"sub_mchid"` //子商户号 238 | Payer OTRespPayer `json:"payer"` 239 | Amount OTRespAmount `json:"amount"` 240 | PromotionDetail []PromotionList `json:"promotion_detail"` //优惠信息 241 | SceneInfo OTRespSceneInfo `json:"scene_info"` //支付场景信息 242 | BankType string `json:"bank_type"` //付款银行 243 | TradeType string `json:"trade_type"` //交易类型 244 | TradeState string `json:"trade_state"` //交易状态 245 | TradeStateDescription string `json:"trade_state_description"` //交易描述 246 | ErrorType string `json:"error_type"` //trade_state为PAYERROR时存在,“NOT_ENOUGH”和“NOTENOUGH”表示用户余额不足 247 | DebtState string `json:"debt_state"` //欠款状态 248 | Description string `json:"description"` //商品信息 249 | Attach string `json:"attach"` //商户附加信息 250 | SuccessTime string `json:"success_time"` //支付成功时间 251 | TransactionId string `json:"transaction_id"` //微信订单号 252 | RepaymentTransactionId string `json:"repayment_transaction_id"` //还款微信单号 253 | OutTradeNo string `json:"out_trade_no"` //商户单号 254 | } 255 | 256 | //OTRespPayer OTRespPayer 257 | type OTRespPayer struct { 258 | SPOpenid string `json:"sp_openid"` //公众号下的openid 259 | SubOpenid string `json:"sub_openid"` //子公众号下的openid 260 | } 261 | 262 | //OTRespAmount OTRespAmount 263 | type OTRespAmount struct { 264 | Total int64 `json:"total"` //订单金额 265 | PayTotal int64 `json:"pay_total"` //用户支付金额 266 | Currency string `json:"currency"` //货币类型 267 | PayCurrency string `json:"pay_currency"` //用户支付货币类型 268 | } 269 | 270 | //OTRespSceneInfo OTRespSceneInfo 271 | type OTRespSceneInfo struct { 272 | DeviceIp string `json:"device_ip"` //设备IP 273 | } 274 | 275 | //RespContractQuery RespContractQuery 276 | type RespContractQuery struct { 277 | ContractId string `json:"contract_id"` //签约ID 278 | Mchid string `json:"mchid"` //商户号 279 | OrganizationId string `json:"organization_id"` //机构ID 280 | UserId string `json:"user_id"` //用户ID 281 | OpenId string `json:"openid"` //签约用户openid 282 | ContractState string `json:"contract_state"` //签约状态 283 | ContractSignedTime string `json:"contract_signed_time"` //签约时间 284 | ContractTerminatedTime string `json:"contract_terminated_time"` //解约时间 285 | ContractMode string `json:"contract_mode"` //签约模式 LIMIT_BANK_CARD:指定卡签约;PRIORITY_BANK_CARD:优先卡签约;LIMIT_NONE:任意卡签约 286 | ContractBankCardFrom string `json:"contract_bank_card_from"` //签约卡来源 MERCHANT_LIMITED_BANK_CARD:商户指定的签约卡;USER_SELECT_FREE:用户选择的签约卡 287 | } 288 | 289 | //FaceMessageCiphertext 签约解约报文数据 290 | type FaceMessageCiphertext struct { 291 | ID string `json:"id"` //通知唯一ID 292 | CreateTime string `json:"create_time"` //通知创建的时间 293 | EventType string `json:"event_type"` //通知的类型 294 | ResourceType string `json:"resource_type"` //通知的资源数据类型 295 | Resource struct { 296 | Algorithm string `json:"algorithm"` //加密算法类型 297 | Ciphertext string `json:"ciphertext"` //数据密文 298 | OriginalType string `json:"original_type"` //原始回调类型 299 | AssociatedData string `json:"associated_data"` //附加数据 300 | Nonce string `json:"nonce"` //随机串 301 | } `json:"resource"` //通知资源数据 302 | Summary string `json:"summary"` //回调摘要 303 | } 304 | 305 | // FaceMessagePlaintext 签约解约解密明文 306 | type FaceMessagePlaintext struct { 307 | UserId string `json:"user_id"` //微信刷脸用户唯一标识 308 | OutUserId string `json:"out_user_id"` //商户刷脸用户唯一标识 309 | OrganizationId string `json:"organization_id"` //机构编号 310 | MchId string `json:"mch_id"` //微信支付分配的商户号 311 | NotifyCreateTime string `json:"notify_create_time"` //通知创建时间 312 | Appid string `json:"appid"` //微信APPID 313 | Openid string `json:"openid"` //微信openid 314 | } 315 | 316 | //RespQueryRepurchaseUsersList RespQueryRepurchaseUsersList 317 | type RespQueryRepurchaseUsersList struct { 318 | FaceCollections []FaceCollections `json:"face_collections"` 319 | } 320 | 321 | // FaceCollections FaceCollections 322 | type FaceCollections struct { 323 | CollectionId string `json:"collection_id"` 324 | UserId string `json:"user_id"` 325 | OrganizationId string `json:"organization_id"` 326 | CollectionState string `json:"collection_state"` 327 | RegisterPhotoUploadTime string `json:"register_photo_upload_time"` 328 | ConfirmTime string `json:"confirm_time"` 329 | } 330 | 331 | // ReqGetAuthInfo ReqGetAuthInfo 332 | type ReqGetAuthInfo struct { 333 | SpAppid string `json:"sp_appid"` 334 | SubAppid string `json:"sub_appid"` 335 | SubMchid string `json:"sub_mchid"` 336 | DeviceId string `json:"device_id"` 337 | RawData string `json:"raw_data"` 338 | OrganizationId string `json:"organization_id"` 339 | } 340 | 341 | // RespGetAuthInfo RespGetAuthInfo 342 | type RespGetAuthInfo struct { 343 | AuthInfo string `json:"authinfo"` 344 | } 345 | 346 | // ReqGetRepaymentUrl ReqGetRepaymentUrl 347 | type ReqGetRepaymentUrl struct { 348 | OutUserId string `json:"out_user_id"` 349 | OrganizationId string `json:"organization_id"` 350 | } 351 | 352 | // RespGetRepaymentUrl RespGetRepaymentUrl 353 | type RespGetRepaymentUrl struct { 354 | RepaymentUrl string `json:"repayment_url"` 355 | ExpireAt string `json:"expire_at"` 356 | } 357 | --------------------------------------------------------------------------------