增加vendor

This commit is contained in:
hoteas
2022-05-24 13:49:25 +08:00
parent 77753a123f
commit b638d8bd65
742 changed files with 509206 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
kind: pipeline
type: docker
name: gopay
clone:
depth: 1
platform:
os: linux
arch: amd64
steps:
- name: helloworld
pull: if-not-exists
image: hello-world
- name: ci_1.16
pull: if-not-exists
image: golang:1.16
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn,direct"
GOSUMDB: "off"
CGO_ENABLED: "0"
GOOS: "linux"
depends_on:
- helloworld
commands:
- go version
- go env
- go mod tidy
- go test ./...
trigger:
branch:
- main
event:
- push
- pull_request
- tag
+7
View File
@@ -0,0 +1,7 @@
/.idea
.vscode
settings.json
vendor
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019 Jerry
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+108
View File
@@ -0,0 +1,108 @@
<div align=center><img width="240" height="240" alt="Logo was Loading Faild!" src="https://raw.githubusercontent.com/go-pay/gopay/main/logo.png"/></div>
# GoPay
### 微信、支付宝、PayPal、QQ 的 Golang 版本SDK
[![Github](https://img.shields.io/github/followers/iGoogle-ink?label=Follow&style=social)](https://github.com/iGoogle-ink)
[![Github](https://img.shields.io/github/forks/go-pay/gopay?label=Fork&style=social)](https://github.com/go-pay/gopay/fork)
[![Golang](https://img.shields.io/badge/golang-1.16-brightgreen.svg)](https://golang.google.cn)
[![GoDoc](https://img.shields.io/badge/doc-pkg.go.dev-informational.svg)](https://pkg.go.dev/github.com/go-pay/gopay)
[![Drone CI](https://cloud.drone.io/api/badges/go-pay/gopay/status.svg)](https://cloud.drone.io/go-pay/gopay)
[![GitHub Release](https://img.shields.io/github/v/release/go-pay/gopay)](https://github.com/go-pay/gopay/releases)
[![License](https://img.shields.io/github/license/go-pay/gopay)](https://www.apache.org/licenses/LICENSE-2.0)
[![GoDoc](https://tokei.rs/b1/github.com/go-pay/gopay?category=lines)](https://github.com/go-pay/gopay)
---
- #### 近期计划:
> 将 gopay 库中,非支付相关的一些接口方法独立出去另外的 sdk 库,在 go-pay 组织下新建 `wechat-sdk` 和 `alipay-sdk` 两个项目,分别实现各个平台相关接口方法,优先进行 `wechat-sdk` 开发。
> 微信小程序或公众号相关接口方法:已从 `微信v2` 移步替换成 `github.com/go-pay/wechat-sdk`
<br>
# 一、安装
```bash
go get -u github.com/go-pay/gopay
```
#### 查看 GoPay 版本
[版本更新记录](https://github.com/go-pay/gopay/blob/main/release_note.txt)
```go
import (
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/xlog"
)
func main() {
xlog.Info("GoPay Version: ", gopay.Version)
}
```
---
<br>
# 二、文档目录
> ### 点击查看不同支付方式的使用文档。方便的话,请留下您认可的小星星,十分感谢!
* #### [Alipay](https://github.com/go-pay/gopay/blob/main/doc/alipay.md)
* #### [Wechat](https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md)
* #### [QQ](https://github.com/go-pay/gopay/blob/main/doc/qq.md)
* #### [Paypal](https://github.com/go-pay/gopay/blob/main/doc/paypal.md)
* #### [Apple](https://github.com/go-pay/gopay/blob/main/doc/apple.md)
---
<br>
# 三、其他说明
* 各支付方式接入,请仔细查看 `xxx_test.go` 使用方式
* `gopay/wechat/v3/client_test.go`
* `gopay/alipay/client_test.go`
* `gopay/qq/client_test.go`
* `gopay/paypal/client_test.go`
* `gopay/apple/verify_test.go`
* 或 examples
* 有问题请加QQ群(加群验证答案:gopay),或加微信好友拉群。在此,非常感谢提出宝贵意见和反馈问题的同志们!
* 开发过程中,请尽量使用正式环境,1分钱测试法!
QQ群:
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/qq_gopay.png"/>
加微信拉群:
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/wechat_jerry.png"/>
---
<br>
## 赞赏多少是您的心意,感谢支持!
微信赞赏码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang.png"/>
支付宝赞助码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang_zfb.png"/>
## License
```
Copyright 2019 Jerry
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+242
View File
@@ -0,0 +1,242 @@
package gopay
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/url"
"sort"
"strings"
"github.com/go-pay/gopay/pkg/util"
)
type BodyMap map[string]interface{}
type xmlMapMarshal struct {
XMLName xml.Name
Value interface{} `xml:",cdata"`
}
type xmlMapUnmarshal struct {
XMLName xml.Name
Value string `xml:",cdata"`
}
// 设置参数
func (bm BodyMap) Set(key string, value interface{}) BodyMap {
bm[key] = value
return bm
}
func (bm BodyMap) SetBodyMap(key string, value func(b BodyMap)) BodyMap {
_bm := make(BodyMap)
value(_bm)
bm[key] = _bm
return bm
}
// 设置 FormFile
func (bm BodyMap) SetFormFile(key string, file *util.File) BodyMap {
bm[key] = file
return bm
}
// 获取参数,同 GetString()
func (bm BodyMap) Get(key string) string {
return bm.GetString(key)
}
// 获取参数转换string
func (bm BodyMap) GetString(key string) string {
if bm == nil {
return NULL
}
value, ok := bm[key]
if !ok {
return NULL
}
v, ok := value.(string)
if !ok {
return convertToString(value)
}
return v
}
// 获取原始参数
func (bm BodyMap) GetInterface(key string) interface{} {
if bm == nil {
return nil
}
return bm[key]
}
// 删除参数
func (bm BodyMap) Remove(key string) {
delete(bm, key)
}
// 置空BodyMap
func (bm BodyMap) Reset() {
for k := range bm {
delete(bm, k)
}
}
func (bm BodyMap) JsonBody() (jb string) {
bs, err := json.Marshal(bm)
if err != nil {
return ""
}
jb = string(bs)
return jb
}
// Unmarshal to struct or slice point
func (bm BodyMap) Unmarshal(ptr interface{}) (err error) {
bs, err := json.Marshal(bm)
if err != nil {
return err
}
return json.Unmarshal(bs, ptr)
}
func (bm BodyMap) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
if len(bm) == 0 {
return nil
}
start.Name = xml.Name{Space: NULL, Local: "xml"}
if err = e.EncodeToken(start); err != nil {
return
}
for k := range bm {
if v := bm.GetString(k); v != NULL {
e.Encode(xmlMapMarshal{XMLName: xml.Name{Local: k}, Value: v})
}
}
return e.EncodeToken(start.End())
}
func (bm *BodyMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
for {
var e xmlMapUnmarshal
err = d.Decode(&e)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
bm.Set(e.XMLName.Local, e.Value)
}
}
// ("bar=baz&foo=quux") sorted by key.
func (bm BodyMap) EncodeWeChatSignParams(apiKey string) string {
if bm == nil {
return NULL
}
var (
buf strings.Builder
keyList []string
)
for k := range bm {
keyList = append(keyList, k)
}
sort.Strings(keyList)
for _, k := range keyList {
if v := bm.GetString(k); v != NULL {
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v)
buf.WriteByte('&')
}
}
buf.WriteString("key")
buf.WriteByte('=')
buf.WriteString(apiKey)
return buf.String()
}
// ("bar=baz&foo=quux") sorted by key.
func (bm BodyMap) EncodeAliPaySignParams() string {
if bm == nil {
return NULL
}
var (
buf strings.Builder
keyList []string
)
for k := range bm {
keyList = append(keyList, k)
}
sort.Strings(keyList)
for _, k := range keyList {
if v := bm.GetString(k); v != NULL {
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v)
buf.WriteByte('&')
}
}
if buf.Len() <= 0 {
return NULL
}
return buf.String()[:buf.Len()-1]
}
// ("bar=baz&foo=quux") sorted by key.
func (bm BodyMap) EncodeURLParams() string {
if bm == nil {
return NULL
}
var (
buf strings.Builder
keys []string
)
for k := range bm {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if v := bm.GetString(k); v != NULL {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(v))
buf.WriteByte('&')
}
}
if buf.Len() <= 0 {
return NULL
}
return buf.String()[:buf.Len()-1]
}
func (bm BodyMap) CheckEmptyError(keys ...string) error {
var emptyKeys []string
for _, k := range keys {
if v := bm.GetString(k); v == NULL {
emptyKeys = append(emptyKeys, k)
}
}
if len(emptyKeys) > 0 {
return fmt.Errorf("[%w], %v", MissParamErr, strings.Join(emptyKeys, ", "))
}
return nil
}
func convertToString(v interface{}) (str string) {
if v == nil {
return NULL
}
var (
bs []byte
err error
)
if bs, err = json.Marshal(v); err != nil {
return NULL
}
str = string(bs)
return
}
+13
View File
@@ -0,0 +1,13 @@
package gopay
const (
NULL = ""
SUCCESS = "SUCCESS"
FAIL = "FAIL"
OK = "OK"
DebugOff = 0
DebugOn = 1
Version = "1.5.78"
)
type DebugSwitch int8
+16
View File
@@ -0,0 +1,16 @@
package gopay
import "errors"
var (
MissWechatInitParamErr = errors.New("missing wechat init parameter")
MissAlipayInitParamErr = errors.New("missing alipay init parameter")
MissPayPalInitParamErr = errors.New("missing paypal init parameter")
MissParamErr = errors.New("missing required parameter")
MarshalErr = errors.New("marshal error")
UnmarshalErr = errors.New("unmarshal error")
SignatureErr = errors.New("signature error")
VerifySignatureErr = errors.New("verify signature error")
CertNotMatchErr = errors.New("cert not match error")
GetSignDataErr = errors.New("get signature data error")
)
+5
View File
@@ -0,0 +1,5 @@
module github.com/go-pay/gopay
go 1.16
require golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
+9
View File
@@ -0,0 +1,9 @@
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+43
View File
@@ -0,0 +1,43 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"errors"
)
// AES-CBC 加密数据
func CBCEncrypt(originData, key, iv []byte) ([]byte, error) {
return cbcEncrypt(originData, key, iv)
}
// AES-CBC 解密数据
func CBCDecrypt(secretData, key, iv []byte) ([]byte, error) {
return cbcDecrypt(secretData, key, iv)
}
func cbcEncrypt(originData, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originData = PKCS7Padding(originData, block.BlockSize())
secretData := make([]byte, len(originData))
blockMode := cipher.NewCBCEncrypter(block, iv[:block.BlockSize()])
blockMode.CryptBlocks(secretData, originData)
return secretData, nil
}
func cbcDecrypt(secretData, key, iv []byte) (originByte []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originByte = make([]byte, len(secretData))
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
blockMode.CryptBlocks(originByte, secretData)
if len(originByte) == 0 {
return nil, errors.New("blockMode.CryptBlocks error")
}
return PKCS7UnPadding(originByte), nil
}
+105
View File
@@ -0,0 +1,105 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"errors"
)
// AES-ECB 加密数据
func ECBEncrypt(originData, key []byte) ([]byte, error) {
return ecbEncrypt(originData, key)
}
// AES-ECB 解密数据
func ECBDecrypt(secretData, key []byte) ([]byte, error) {
return ecbDecrypt(secretData, key)
}
func ecbEncrypt(originData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originData = PKCS7Padding(originData, block.BlockSize())
secretData := make([]byte, len(originData))
blockMode := newECBEncrypter(block)
blockMode.CryptBlocks(secretData, originData)
return secretData, nil
}
func ecbDecrypt(secretData, key []byte) (originByte []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := newECBDecrypter(block)
originByte = make([]byte, len(secretData))
blockMode.CryptBlocks(originByte, secretData)
if len(originByte) == 0 {
return nil, errors.New("blockMode.CryptBlocks error")
}
return PKCS7UnPadding(originByte), nil
}
// ===========
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
// newECBEncrypter returns a BlockMode which encrypts in electronic code book
// mode, using the given Block.
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
// newECBDecrypter returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
+49
View File
@@ -0,0 +1,49 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"github.com/go-pay/gopay/pkg/util"
)
// AES-GCM 加密数据
func GCMEncrypt(originText, additional, key []byte) (nonce []byte, cipherText []byte, err error) {
return gcmEncrypt(originText, additional, key)
}
// AES-GCM 解密数据
func GCMDecrypt(cipherText, nonce, additional, key []byte) ([]byte, error) {
return gcmDecrypt(cipherText, nonce, additional, key)
}
func gcmDecrypt(secretData, nonce, additional, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
}
originByte, err := gcm.Open(nil, nonce, secretData, additional)
if err != nil {
return nil, err
}
return originByte, nil
}
func gcmEncrypt(originText, additional, key []byte) ([]byte, []byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
nonce := []byte(util.RandomString(12))
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
}
cipherBytes := gcm.Seal(nil, nonce, originText, additional)
return nonce, cipherBytes, nil
}
+42
View File
@@ -0,0 +1,42 @@
package aes
import "bytes"
// 加密填充模式(添加补全码) PKCS5Padding
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
paddingBytes := []byte{byte(paddingCount)}
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
return append(ciphertext, padtext...)
}
// 解密填充模式(去除补全码) PKCS5UnPadding
// 解密时,需要在最后面去掉加密时添加的填充byte
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1]) //找到Byte数组最后的填充byte
return origData[:(length - unpadding)] //只截取返回有效数字内的byte数组
}
// 加密填充模式(添加补全码) PKCS5Padding
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
paddingBytes := []byte{byte(paddingCount)}
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
return append(ciphertext, padtext...)
}
// 解密填充模式(去除补全码) PKCS7UnPadding
// 解密时,需要在最后面去掉加密时添加的填充byte
func PKCS7UnPadding(origData []byte) (bs []byte) {
length := len(origData)
unPaddingNumber := int(origData[length-1]) // 找到Byte数组最后的填充byte 数字
if unPaddingNumber <= 16 {
bs = origData[:(length - unPaddingNumber)] // 只截取返回有效数字内的byte数组
} else {
bs = origData
}
return
}
+126
View File
@@ -0,0 +1,126 @@
package errgroup
import (
"context"
"fmt"
"runtime"
"sync"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
err error
wg sync.WaitGroup
errOnce sync.Once
workerOnce sync.Once
ch chan func(ctx context.Context) error
chs []func(ctx context.Context) error
workerNum int
ctx context.Context
cancel func()
}
// WithContext create a Group.
// given function from Go will receive this context,
func WithContext(ctx context.Context) *Group {
return &Group{ctx: ctx}
}
// WithCancel create a new Group and an associated Context derived from ctx.
//
// given function from Go will receive context derived from this ctx,
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithCancel(ctx context.Context) *Group {
ctx, cancel := context.WithCancel(ctx)
return &Group{ctx: ctx, cancel: cancel}
}
func (g *Group) do(f func(ctx context.Context) error) {
ctx := g.ctx
if ctx == nil {
ctx = context.Background()
}
var err error
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)]
err = fmt.Errorf("errgroup: panic recovered: %s\n%s", r, buf)
}
if err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
g.wg.Done()
}()
err = f(ctx)
}
// GOMAXPROCS set max goroutine to work.
func (g *Group) GOMAXPROCS(n int) {
if n <= 0 {
panic("errgroup: GOMAXPROCS must great than 0")
}
g.workerOnce.Do(func() {
g.ch = make(chan func(context.Context) error, n)
for i := 0; i < n; i++ {
go func() {
for f := range g.ch {
g.do(f)
}
}()
}
})
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by Wait.
func (g *Group) Go(f func(ctx context.Context) error) {
g.wg.Add(1)
g.workerNum++
if g.ch != nil {
select {
case g.ch <- f:
default:
g.chs = append(g.chs, f)
}
return
}
go g.do(f)
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
if g.ch != nil {
for _, f := range g.chs {
g.ch <- f
}
}
g.wg.Wait()
if g.ch != nil {
close(g.ch) // let all receiver exit
}
if g.cancel != nil {
g.cancel()
}
g.workerNum = 0
return g.err
}
func (g *Group) WorkNum() int {
return g.workerNum
}
+13
View File
@@ -0,0 +1,13 @@
package util
const (
TimeLayout = "2006-01-02 15:04:05"
TimeLayout_2 = "20060102150405"
DateLayout = "2006-01-02"
NULL = ""
)
type File struct {
Name string `json:"name"`
Content []byte `json:"content"`
}
+100
View File
@@ -0,0 +1,100 @@
package util
import (
"math"
"reflect"
"strconv"
"strings"
"unsafe"
)
// 字符串转Int
// intStr:数字的字符串
func String2Int(intStr string) (intNum int) {
intNum, _ = strconv.Atoi(intStr)
return
}
// 字符串转Int64
// intStr:数字的字符串
func String2Int64(intStr string) (int64Num int64) {
intNum, _ := strconv.Atoi(intStr)
int64Num = int64(intNum)
return
}
// 字符串转Float64
// floatStr:小数点数字的字符串
func String2Float64(floatStr string) (floatNum float64) {
floatNum, _ = strconv.ParseFloat(floatStr, 64)
return
}
// 字符串转Float32
// floatStr:小数点数字的字符串
func String2Float32(floatStr string) (floatNum float32) {
floatNum64, _ := strconv.ParseFloat(floatStr, 32)
floatNum = float32(floatNum64)
return
}
// Int转字符串
// intNum:数字字符串
func Int2String(intNum int) (intStr string) {
intStr = strconv.Itoa(intNum)
return
}
// Int64转字符串
// intNum:数字字符串
func Int642String(intNum int64) (int64Str string) {
//10, 代表10进制
int64Str = strconv.FormatInt(intNum, 10)
return
}
// Float64转字符串
// floatNumfloat64数字
// prec:精度位数(不传则默认float数字精度)
func Float64ToString(floatNum float64, prec ...int) (floatStr string) {
if len(prec) > 0 {
floatStr = strconv.FormatFloat(floatNum, 'f', prec[0], 64)
return
}
floatStr = strconv.FormatFloat(floatNum, 'f', -1, 64)
return
}
// Float32转字符串
// floatNumfloat32数字
// prec:精度位数(不传则默认float数字精度)
func Float32ToString(floatNum float32, prec ...int) (floatStr string) {
if len(prec) > 0 {
floatStr = strconv.FormatFloat(float64(floatNum), 'f', prec[0], 32)
return
}
floatStr = strconv.FormatFloat(float64(floatNum), 'f', -1, 32)
return
}
// 二进制转10进制
func BinaryToDecimal(bit string) (num int) {
fields := strings.Split(bit, "")
lens := len(fields)
var tempF float64 = 0
for i := 0; i < lens; i++ {
floatNum := String2Float64(fields[i])
tempF += floatNum * math.Pow(2, float64(lens-i-1))
}
num = int(tempF)
return
}
// BytesToString 0 拷贝转换 slice byte 为 string
func BytesToString(b []byte) (s string) {
_bptr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
_sptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
_sptr.Data = _bptr.Data
_sptr.Len = _bptr.Len
return s
}
+42
View File
@@ -0,0 +1,42 @@
package util
import (
"math/rand"
"time"
)
//随机生成字符串
func RandomString(l int) string {
str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
bytes := []byte(str)
var result []byte = make([]byte, 0, l)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return BytesToString(result)
}
//随机生成纯字符串
func RandomPureString(l int) string {
str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
bytes := []byte(str)
var result []byte = make([]byte, 0, l)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return BytesToString(result)
}
//随机生成数字字符串
func RandomNumber(l int) string {
str := "0123456789"
bytes := []byte(str)
var result []byte
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return BytesToString(result)
}
+18
View File
@@ -0,0 +1,18 @@
package util
import "encoding/json"
func ConvertToString(v interface{}) (str string) {
if v == nil {
return NULL
}
var (
bs []byte
err error
)
if bs, err = json.Marshal(v); err != nil {
return NULL
}
str = string(bs)
return
}
+361
View File
@@ -0,0 +1,361 @@
package xhttp
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
type Client struct {
HttpClient *http.Client
Transport *http.Transport
Header http.Header
Timeout time.Duration
url string
Host string
method string
requestType RequestType
FormString string
ContentType string
unmarshalType string
multipartBodyMap map[string]interface{}
jsonByte []byte
err error
}
// NewClient , default tls.Config{InsecureSkipVerify: true}
func NewClient() (client *Client) {
client = &Client{
HttpClient: &http.Client{
Timeout: 60 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
},
},
Transport: nil,
Header: make(http.Header),
requestType: TypeJSON,
unmarshalType: string(TypeJSON),
}
return client
}
func (c *Client) SetTransport(transport *http.Transport) (client *Client) {
c.Transport = transport
return c
}
func (c *Client) SetTLSConfig(tlsCfg *tls.Config) (client *Client) {
c.Transport = &http.Transport{TLSClientConfig: tlsCfg, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment}
return c
}
func (c *Client) SetTimeout(timeout time.Duration) (client *Client) {
c.Timeout = timeout
return c
}
func (c *Client) SetHost(host string) (client *Client) {
c.Host = host
return c
}
func (c *Client) Type(typeStr RequestType) (client *Client) {
if _, ok := types[typeStr]; ok {
c.requestType = typeStr
}
return c
}
func (c *Client) Get(url string) (client *Client) {
c.method = GET
c.url = url
return c
}
func (c *Client) Post(url string) (client *Client) {
c.method = POST
c.url = url
return c
}
func (c *Client) Put(url string) (client *Client) {
c.method = PUT
c.url = url
return c
}
func (c *Client) Delete(url string) (client *Client) {
c.method = DELETE
c.url = url
return c
}
func (c *Client) Patch(url string) (client *Client) {
c.method = PATCH
c.url = url
return c
}
func (c *Client) SendStruct(v interface{}) (client *Client) {
if v == nil {
return c
}
bs, err := json.Marshal(v)
if err != nil {
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, v)
return c
}
switch c.requestType {
case TypeJSON:
c.jsonByte = bs
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
body := make(map[string]interface{})
if err = json.Unmarshal(bs, &body); err != nil {
c.err = fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
return c
}
c.FormString = FormatURLParam(body)
}
return c
}
func (c *Client) SendBodyMap(bm map[string]interface{}) (client *Client) {
if bm == nil {
return c
}
switch c.requestType {
case TypeJSON:
bs, err := json.Marshal(bm)
if err != nil {
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
return c
}
c.jsonByte = bs
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
c.FormString = FormatURLParam(bm)
}
return c
}
func (c *Client) SendMultipartBodyMap(bm map[string]interface{}) (client *Client) {
if bm == nil {
return c
}
switch c.requestType {
case TypeJSON:
bs, err := json.Marshal(bm)
if err != nil {
c.err = fmt.Errorf("[%w]: %v, value: %v", gopay.MarshalErr, err, bm)
return c
}
c.jsonByte = bs
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
c.FormString = FormatURLParam(bm)
case TypeMultipartFormData:
c.multipartBodyMap = bm
}
return c
}
// encodeStr: url.Values.Encode() or jsonBody
func (c *Client) SendString(encodeStr string) (client *Client) {
switch c.requestType {
case TypeJSON:
c.jsonByte = []byte(encodeStr)
case TypeXML, TypeUrlencoded, TypeForm, TypeFormData:
c.FormString = encodeStr
}
return c
}
func (c *Client) EndStruct(ctx context.Context, v interface{}) (res *http.Response, err error) {
res, bs, err := c.EndBytes(ctx)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return res, fmt.Errorf("StatusCode(%d) != 200", res.StatusCode)
}
switch c.unmarshalType {
case string(TypeJSON):
err = json.Unmarshal(bs, &v)
if err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
return res, nil
case string(TypeXML):
err = xml.Unmarshal(bs, &v)
if err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
return res, nil
default:
return nil, errors.New("unmarshalType Type Wrong")
}
}
func (c *Client) EndBytes(ctx context.Context) (res *http.Response, bs []byte, err error) {
if c.err != nil {
return nil, nil, c.err
}
var (
body io.Reader
bw *multipart.Writer
)
// multipart-form-data
if c.requestType == TypeMultipartFormData {
body = &bytes.Buffer{}
bw = multipart.NewWriter(body.(io.Writer))
}
reqFunc := func() (err error) {
switch c.method {
case GET:
switch c.requestType {
case TypeJSON:
c.ContentType = types[TypeJSON]
case TypeForm, TypeFormData, TypeUrlencoded:
c.ContentType = types[TypeForm]
case TypeMultipartFormData:
c.ContentType = bw.FormDataContentType()
case TypeXML:
c.ContentType = types[TypeXML]
c.unmarshalType = string(TypeXML)
default:
return errors.New("Request type Error ")
}
case POST, PUT, DELETE, PATCH:
switch c.requestType {
case TypeJSON:
if c.jsonByte != nil {
body = strings.NewReader(string(c.jsonByte))
}
c.ContentType = types[TypeJSON]
case TypeForm, TypeFormData, TypeUrlencoded:
body = strings.NewReader(c.FormString)
c.ContentType = types[TypeForm]
case TypeMultipartFormData:
for k, v := range c.multipartBodyMap {
// file 参数
if file, ok := v.(*util.File); ok {
fw, err := bw.CreateFormFile(k, file.Name)
if err != nil {
return err
}
_, _ = fw.Write(file.Content)
continue
}
// text 参数
vs, ok2 := v.(string)
if ok2 {
_ = bw.WriteField(k, vs)
} else if ss := util.ConvertToString(v); ss != "" {
_ = bw.WriteField(k, ss)
}
}
_ = bw.Close()
c.ContentType = bw.FormDataContentType()
case TypeXML:
body = strings.NewReader(c.FormString)
c.ContentType = types[TypeXML]
c.unmarshalType = string(TypeXML)
default:
return errors.New("Request type Error ")
}
default:
return errors.New("Only support GET and POST and PUT and DELETE ")
}
req, err := http.NewRequestWithContext(ctx, c.method, c.url, body)
if err != nil {
return err
}
req.Header = c.Header
req.Header.Set("Content-Type", c.ContentType)
if c.Transport != nil {
c.HttpClient.Transport = c.Transport
}
if c.Host != "" {
req.Host = c.Host
}
if c.Timeout > 0 {
c.HttpClient.Timeout = c.Timeout
}
res, err = c.HttpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
bs, err = ioutil.ReadAll(io.LimitReader(res.Body, int64(5<<20))) // default 5MB change the size you want
if err != nil {
return err
}
return nil
}
if err = reqFunc(); err != nil {
return nil, nil, err
}
return res, bs, nil
}
func FormatURLParam(body map[string]interface{}) (urlParam string) {
var (
buf strings.Builder
keys []string
)
for k := range body {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v, ok := body[k].(string)
if !ok {
v = convertToString(body[k])
}
if v != "" {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(v))
buf.WriteByte('&')
}
}
if buf.Len() <= 0 {
return ""
}
return buf.String()[:buf.Len()-1]
}
func convertToString(v interface{}) (str string) {
if v == nil {
return ""
}
var (
bs []byte
err error
)
if bs, err = json.Marshal(v); err != nil {
return ""
}
str = string(bs)
return
}
+26
View File
@@ -0,0 +1,26 @@
package xhttp
type RequestType string
const (
GET = "GET"
POST = "POST"
PUT = "PUT"
DELETE = "DELETE"
PATCH = "PATCH"
TypeJSON RequestType = "json"
TypeXML RequestType = "xml"
TypeUrlencoded RequestType = "urlencoded"
TypeForm RequestType = "form"
TypeFormData RequestType = "form-data"
TypeMultipartFormData RequestType = "multipart-form-data"
)
var types = map[RequestType]string{
TypeJSON: "application/json",
TypeXML: "application/xml",
TypeUrlencoded: "application/x-www-form-urlencoded",
TypeForm: "application/x-www-form-urlencoded",
TypeFormData: "application/x-www-form-urlencoded",
TypeMultipartFormData: "multipart/form-data",
}
+112
View File
@@ -0,0 +1,112 @@
package xlog
type ColorType string
var (
Reset = ColorType([]byte{27, 91, 48, 109})
// 标准
White = ColorType([]byte{27, 91, 51, 48, 109}) // 白色
Red = ColorType([]byte{27, 91, 51, 49, 109}) // 红色
Green = ColorType([]byte{27, 91, 51, 50, 109}) // 绿色
Yellow = ColorType([]byte{27, 91, 51, 51, 109}) // 黄色
Blue = ColorType([]byte{27, 91, 51, 52, 109}) // 蓝色
Magenta = ColorType([]byte{27, 91, 51, 53, 109}) // 紫色
Cyan = ColorType([]byte{27, 91, 51, 54, 109}) // 青色
// 高亮
WhiteBright = ColorType([]byte{27, 91, 49, 59, 51, 48, 109})
RedBright = ColorType([]byte{27, 91, 49, 59, 51, 49, 109})
GreenBright = ColorType([]byte{27, 91, 49, 59, 51, 50, 109})
YellowBright = ColorType([]byte{27, 91, 49, 59, 51, 51, 109})
BlueBright = ColorType([]byte{27, 91, 49, 59, 51, 52, 109})
MagentaBright = ColorType([]byte{27, 91, 49, 59, 51, 53, 109})
CyanBright = ColorType([]byte{27, 91, 49, 59, 51, 54, 109})
// 斜体
WhiteBevel = ColorType([]byte{27, 91, 51, 59, 51, 48, 109})
RedBevel = ColorType([]byte{27, 91, 51, 59, 51, 49, 109})
GreenBevel = ColorType([]byte{27, 91, 51, 59, 51, 50, 109})
YellowBevel = ColorType([]byte{27, 91, 51, 59, 51, 51, 109})
BlueBevel = ColorType([]byte{27, 91, 51, 59, 51, 52, 109})
MagentaBevel = ColorType([]byte{27, 91, 51, 59, 51, 53, 109})
CyanBevel = ColorType([]byte{27, 91, 51, 59, 51, 54, 109})
// 下划线
WhiteUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 48, 109})
RedUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 49, 109})
GreenUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 50, 109})
YellowUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 51, 109})
BlueUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 52, 109})
MagentaUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 53, 109})
CyanUnderLine = ColorType([]byte{27, 91, 52, 59, 51, 54, 109})
// 背景色
WhiteBg = ColorType([]byte{27, 91, 55, 59, 51, 48, 109})
RedBg = ColorType([]byte{27, 91, 55, 59, 51, 49, 109})
GreenBg = ColorType([]byte{27, 91, 55, 59, 51, 50, 109})
YellowBg = ColorType([]byte{27, 91, 55, 59, 51, 51, 109})
BlueBg = ColorType([]byte{27, 91, 55, 59, 51, 52, 109})
MagentaBg = ColorType([]byte{27, 91, 55, 59, 51, 53, 109})
CyanBg = ColorType([]byte{27, 91, 55, 59, 51, 54, 109})
// 删除线
WhiteDelLine = ColorType([]byte{27, 91, 57, 59, 51, 48, 109})
RedDelLine = ColorType([]byte{27, 91, 57, 59, 51, 49, 109})
GreenDelLine = ColorType([]byte{27, 91, 57, 59, 51, 50, 109})
YellowDelLine = ColorType([]byte{27, 91, 57, 59, 51, 51, 109})
BlueDelLine = ColorType([]byte{27, 91, 57, 59, 51, 52, 109})
MagentaDelLine = ColorType([]byte{27, 91, 57, 59, 51, 53, 109})
CyanDelLine = ColorType([]byte{27, 91, 57, 59, 51, 54, 109})
)
var cl *ColorLogger
type ColorLogger struct {
Color ColorType
i *InfoLogger
d *DebugLogger
w *WarnLogger
e *ErrorLogger
}
func Color(color ColorType) *ColorLogger {
if cl == nil {
cl = &ColorLogger{
Color: color,
i: &InfoLogger{},
d: &DebugLogger{},
w: &WarnLogger{},
e: &ErrorLogger{},
}
return cl
}
cl.Color = color
return cl
}
func (l *ColorLogger) Info(args ...interface{}) {
l.i.LogOut(&l.Color, nil, args...)
}
func (l *ColorLogger) Infof(format string, args ...interface{}) {
l.i.LogOut(&l.Color, &format, args...)
}
func (l *ColorLogger) Debug(args ...interface{}) {
l.d.LogOut(&l.Color, nil, args...)
}
func (l *ColorLogger) Debugf(format string, args ...interface{}) {
l.d.LogOut(&l.Color, &format, args...)
}
func (l *ColorLogger) Warn(args ...interface{}) {
l.w.LogOut(&l.Color, nil, args...)
}
func (l *ColorLogger) Warnf(format string, args ...interface{}) {
l.w.LogOut(&l.Color, &format, args...)
}
func (l *ColorLogger) Error(args ...interface{}) {
l.e.LogOut(&l.Color, nil, args...)
}
func (l *ColorLogger) Errorf(format string, args ...interface{}) {
l.e.LogOut(&l.Color, &format, args...)
}
+41
View File
@@ -0,0 +1,41 @@
package xlog
import (
"fmt"
"log"
"os"
"sync"
)
type DebugLogger struct {
logger *log.Logger
once sync.Once
}
func (i *DebugLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
i.once.Do(func() {
i.init()
})
if Level >= DebugLevel {
if col != nil {
if format != nil {
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
return
}
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
return
}
if format != nil {
i.logger.Output(3, fmt.Sprintf(*format, v...))
return
}
i.logger.Output(3, fmt.Sprintln(v...))
}
}
func (i *DebugLogger) init() {
if Level == 0 {
Level = DebugLevel
}
i.logger = log.New(os.Stdout, "[DEBUG] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
}
+41
View File
@@ -0,0 +1,41 @@
package xlog
import (
"fmt"
"log"
"os"
"sync"
)
type ErrorLogger struct {
logger *log.Logger
once sync.Once
}
func (e *ErrorLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
e.once.Do(func() {
e.init()
})
if Level >= ErrorLevel {
if col != nil {
if format != nil {
e.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
return
}
e.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
return
}
if format != nil {
e.logger.Output(3, fmt.Sprintf(*format, v...))
return
}
e.logger.Output(3, fmt.Sprintln(v...))
}
}
func (e *ErrorLogger) init() {
if Level == 0 {
Level = DebugLevel
}
e.logger = log.New(os.Stdout, "[ERROR] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
}
+41
View File
@@ -0,0 +1,41 @@
package xlog
import (
"fmt"
"log"
"os"
"sync"
)
type InfoLogger struct {
logger *log.Logger
once sync.Once
}
func (i *InfoLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
i.once.Do(func() {
i.init()
})
if Level >= InfoLevel {
if col != nil {
if format != nil {
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
return
}
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
return
}
if format != nil {
i.logger.Output(3, fmt.Sprintf(*format, v...))
return
}
i.logger.Output(3, fmt.Sprintln(v...))
}
}
func (i *InfoLogger) init() {
if Level == 0 {
Level = DebugLevel
}
i.logger = log.New(os.Stdout, "[INFO] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
}
+55
View File
@@ -0,0 +1,55 @@
package xlog
const (
ErrorLevel LogLevel = iota + 1
WarnLevel
InfoLevel
DebugLevel
)
type LogLevel int
var (
debugLog XLogger = &DebugLogger{}
infoLog XLogger = &InfoLogger{}
warnLog XLogger = &WarnLogger{}
errLog XLogger = &ErrorLogger{}
Level LogLevel
)
type XLogger interface {
LogOut(col *ColorType, format *string, args ...interface{})
}
func Info(args ...interface{}) {
infoLog.LogOut(nil, nil, args...)
}
func Infof(format string, args ...interface{}) {
infoLog.LogOut(nil, &format, args...)
}
func Debug(args ...interface{}) {
debugLog.LogOut(nil, nil, args...)
}
func Debugf(format string, args ...interface{}) {
debugLog.LogOut(nil, &format, args...)
}
func Warn(args ...interface{}) {
warnLog.LogOut(nil, nil, args...)
}
func Warnf(format string, args ...interface{}) {
warnLog.LogOut(nil, &format, args...)
}
func Error(args ...interface{}) {
errLog.LogOut(nil, nil, args...)
}
func Errorf(format string, args ...interface{}) {
errLog.LogOut(nil, &format, args...)
}
+41
View File
@@ -0,0 +1,41 @@
package xlog
import (
"fmt"
"log"
"os"
"sync"
)
type WarnLogger struct {
logger *log.Logger
once sync.Once
}
func (i *WarnLogger) LogOut(col *ColorType, format *string, v ...interface{}) {
i.once.Do(func() {
i.init()
})
if Level >= WarnLevel {
if col != nil {
if format != nil {
i.logger.Output(3, string(*col)+fmt.Sprintf(*format, v...)+string(Reset))
return
}
i.logger.Output(3, string(*col)+fmt.Sprintln(v...)+string(Reset))
return
}
if format != nil {
i.logger.Output(3, fmt.Sprintf(*format, v...))
return
}
i.logger.Output(3, fmt.Sprintln(v...))
}
}
func (i *WarnLogger) init() {
if Level == 0 {
Level = DebugLevel
}
i.logger = log.New(os.Stdout, "[WARN] >> ", log.Lmsgprefix|log.Lshortfile|log.Ldate|log.Lmicroseconds)
}
+64
View File
@@ -0,0 +1,64 @@
package xpem
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
)
func DecodePublicKey(pemContent []byte) (publicKey *rsa.PublicKey, err error) {
block, _ := pem.Decode(pemContent)
if block == nil {
return nil, fmt.Errorf("pem.Decode(%s)pemContent decode error", pemContent)
}
switch block.Type {
case "CERTIFICATE":
pubKeyCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("x509.ParseCertificate(%s)%w", pemContent, err)
}
pubKey, ok := pubKeyCert.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("公钥证书提取公钥出错 [%s]", pemContent)
}
publicKey = pubKey
case "PUBLIC KEY":
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("x509.ParsePKIXPublicKey(%s),err:%w", pemContent, err)
}
pubKey, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("公钥解析出错 [%s]", pemContent)
}
publicKey = pubKey
case "RSA PUBLIC KEY":
pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("x509.ParsePKCS1PublicKey(%s)%w", pemContent, err)
}
publicKey = pubKey
}
return publicKey, nil
}
func DecodePrivateKey(pemContent []byte) (privateKey *rsa.PrivateKey, err error) {
block, _ := pem.Decode(pemContent)
if block == nil {
return nil, fmt.Errorf("pem.Decode(%s)pemContent decode error", pemContent)
}
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
pk8, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
}
var ok bool
privateKey, ok = pk8.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
}
}
return privateKey, nil
}
+8
View File
@@ -0,0 +1,8 @@
package xtime
const (
TimeLayout = "2006-01-02 15:04:05"
TimeLayout_1 = "2006-01-02 15:04:05.000"
TimeLayout_2 = "20060102150405"
DateLayout = "2006-01-02"
)
+99
View File
@@ -0,0 +1,99 @@
package xtime
import (
"time"
)
var daysBefore = [...]int32{
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
}
func MonthDays(m time.Month, year int) int {
if m == time.February && isLeap(year) {
return 29
}
return int(daysBefore[m] - daysBefore[m-1])
}
func isLeap(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}
// 获取最近 7天 日期
func GetRecentSevenDay() (sevenDays []string) {
now := time.Now()
nowDay := time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local)
for i := 0; i < 7; i++ {
date := nowDay.AddDate(0, 0, i)
sevenDays = append(sevenDays, date.Format(DateLayout))
}
return sevenDays
}
// 获取最近 30天 日期
func GetRecentThirtyDay() (thirtyDays []string) {
now := time.Now()
nowDay := time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local)
for i := 0; i < 30; i++ {
date := nowDay.AddDate(0, 0, i)
thirtyDays = append(thirtyDays, date.Format(DateLayout))
}
return thirtyDays
}
// 获取本周 7天 日期
func GetCurWeekDays() (curWeekDays []string) {
now := time.Now()
offset := int(time.Monday - now.Weekday())
if offset > 0 {
offset = -6
}
weekStartDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
for i := 0; i < 7; i++ {
date := weekStartDate.AddDate(0, 0, i)
curWeekDays = append(curWeekDays, date.Format(DateLayout))
}
return
}
// 获取本月 日期
func GetCurMonthDays() (curMonthDays []string) {
now := time.Now()
year := now.Year()
month := now.Month()
days := MonthDays(month, year)
monthFirstDay := time.Date(year, month, 01, 0, 0, 0, 0, time.Local)
for i := 0; i < days; i++ {
date := monthFirstDay.AddDate(0, 0, i)
curMonthDays = append(curMonthDays, date.Format(DateLayout))
}
return curMonthDays
}
// 获取上一个月 日期
func GetLastMonthDays() (monthDays []string) {
now := time.Now()
year := now.Year()
month := now.Month()
days := MonthDays(month-1, year)
monthFirstDay := time.Date(year, month-1, 01, 0, 0, 0, 0, time.Local)
for i := 0; i < days; i++ {
date := monthFirstDay.AddDate(0, 0, i)
monthDays = append(monthDays, date.Format(DateLayout))
}
return monthDays
}
+107
View File
@@ -0,0 +1,107 @@
package xtime
import (
"strings"
"time"
"github.com/go-pay/gopay/pkg/util"
)
//解析时间
// 时间字符串格式:2006-01-02 15:04:05
func ParseDateTime(timeStr string) (datetime time.Time) {
datetime, _ = time.ParseInLocation(TimeLayout, timeStr, time.Local)
return
}
//解析日期
// 日期字符串格式:2006-01-02
func ParseDate(timeStr string) (date time.Time) {
date, _ = time.ParseInLocation(DateLayout, timeStr, time.Local)
return
}
//格式化Datetime字符串
// 格式化前输入样式:2019-01-04T15:40:00Z 或 2019-01-04T15:40:00+08:00
// 格式化后返回样式:2019-01-04 15:40:00
func FormatDateTime(timeStr string) (formatTime string) {
if timeStr == "" {
return ""
}
replace := strings.Replace(timeStr, "T", " ", 1)
formatTime = replace[:19]
return
}
//格式化Date成字符串
// 格式化前输入样式:2019-01-04T15:40:00Z 或 2019-01-04T15:40:00+08:00
// 格式化后返回样式:2019-01-04
func FormatDate(dateStr string) (formatDate string) {
if dateStr == "" {
return ""
}
split := strings.Split(dateStr, "T")
formatDate = split[0]
return
}
func DurationToUnit(duration time.Duration) string {
var (
t string
intNs = int64(duration)
)
if intNs >= 0 && intNs < int64(time.Second) {
t = util.Int642String(intNs/int64(time.Millisecond)) + "ms"
}
// 大于等于 1秒,小于 1分钟
if intNs >= int64(time.Second) && intNs < int64(time.Minute) {
s := intNs / int64(time.Second)
ms := (intNs - s*int64(time.Second)) / int64(time.Millisecond)
t = util.Int642String(s) + "s"
if ms > 0 {
t += util.Int642String(ms) + "ms"
}
}
// 大于等于 1分钟,小于 1小时
if intNs >= int64(time.Minute) && intNs < int64(time.Hour) {
m := intNs / int64(time.Minute)
s := (intNs - m*int64(time.Minute)) / int64(time.Second)
t = util.Int642String(m) + "m"
if s > 0 {
t += util.Int642String(s) + "s"
}
}
// 大于等于 1小时,小于 1天
if intNs >= int64(time.Hour) && intNs < 24*int64(time.Hour) {
h := intNs / int64(time.Hour)
m := (intNs - h*int64(time.Hour)) / int64(time.Minute)
s := (intNs - h*int64(time.Hour) - m*int64(time.Minute)) / int64(time.Second)
t = util.Int642String(h) + "h"
if m > 0 {
t += util.Int642String(m) + "m"
}
if s > 0 {
t += util.Int642String(s) + "s"
}
}
// 大于等于 1天
if intNs >= 24*int64(time.Hour) {
d := intNs / (24 * int64(time.Hour))
h := (intNs - d*24*int64(time.Hour)) / int64(time.Hour)
m := (intNs - d*24*int64(time.Hour) - h*int64(time.Hour)) / int64(time.Minute)
s := ((intNs - m*int64(time.Minute)) % int64(time.Minute)) / int64(time.Second)
t = util.Int642String(d) + "d"
if h > 0 {
t += util.Int642String(h) + "h"
}
if m > 0 {
t += util.Int642String(m) + "m"
}
if s > 0 {
t += util.Int642String(s) + "s"
}
}
return t
}
+84
View File
@@ -0,0 +1,84 @@
package xtime
import (
"context"
"database/sql/driver"
"strconv"
"time"
)
// Time be used to MySql timestamp converting.
type Time int64
// Scan scan time.
func (t *Time) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case time.Time:
if sc.IsZero() {
return
}
*t = Time(sc.Unix())
case string:
var i int64
i, err = strconv.ParseInt(sc, 10, 64)
*t = Time(i)
}
return
}
// Value get time value.
func (t Time) Value() (driver.Value, error) {
return time.Unix(int64(t), 0), nil
}
// Time get time.
func (t Time) Time() time.Time {
return time.Unix(int64(t), 0)
}
func (t *Time) FromDB(bs []byte) error {
timeStr := string(bs)
ti, err := time.ParseInLocation("2006-01-02T15:04:05", timeStr[:19], time.Local)
if err != nil {
return err
}
if ti.IsZero() {
return nil
}
*t = Time(ti.Unix())
return nil
}
func (t Time) ToDB() ([]byte, error) {
unix := time.Unix(int64(t), 0)
return []byte(unix.String()), nil
}
// Duration be used json unmarshal string time, like 1s, 500ms.
type Duration time.Duration
// UnmarshalText unmarshal text to duration.
func (d *Duration) UnmarshalText(text []byte) error {
tmp, err := time.ParseDuration(string(text))
if err == nil {
*d = Duration(tmp)
}
return err
}
// UnitTime duration parse to unit, such as "300ms", "1h30m" or "2h10s".
func (d *Duration) UnitTime() string {
return DurationToUnit(time.Duration(*d))
}
// Shrink will decrease the duration by comparing with context's timeout duration and return new timeout\context\CancelFunc.
func (d Duration) Shrink(c context.Context) (Duration, context.Context, context.CancelFunc) {
if deadline, ok := c.Deadline(); ok {
if ctimeout := time.Until(deadline); ctimeout < time.Duration(d) {
// deliver small timeout
return Duration(ctimeout), c, func() {}
}
}
ctx, cancel := context.WithTimeout(c, time.Duration(d))
return d, ctx, cancel
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+670
View File
@@ -0,0 +1,670 @@
版本号:Release 1.5.78
修改记录:
(1) 微信V3V3EcommerceBalance() 缺失参数补充
(2) 微信V3:新增 client.V3EcommerceDayBalance() 方法,电商平台预约提现
(3) 微信V3:修复 银行列表获取相关接口,路由修正
(4) 微信V3:修复 client.V3BankSearchBank() 接口,私钥解密出错的问题
版本号:Release 1.5.77
修改记录:
(1) 微信V3V3EcommerceRefundQueryById()、V3EcommerceRefundQueryByNo(),缺失参数补充
(2) 微信V3:新增 client.V3EcommerceWithdraw() 方法,电商平台预约提现
(3) 微信V3:新增 client.V3EcommerceWithdrawStatus() 方法,电商平台查询预约提现状态
版本号:Release 1.5.76
修改记录:
(1) gopay:大量优化error处理和返回,统一部分通用错误到 error.go 中
(2) 支付宝:新增 alipay.IsBizError(),判断并捕获业务错误
(3) 微信、支付宝:优化部分 error 返回格式以及透传,优化参数校验返回
版本号:Release 1.5.75
修改记录:
(1) 微信V3client.V3Apply4SubModifySettlement()sub_mchid 问题处理
(2) 微信V3:微信分账接收方,model参数补充添加 detail_id
(3) PayPal:注释中接口文档地址更新
(4) PayPal:新增 client.OrderConfirm(),订单确认
(5) PayPalOrderDetail、Capture、Payer、Name 等结构体,遗漏参数补充
(6) gopaypkg/xtime/parse_format.go,优化 DurationToUnit() 方法,int -> int64
版本号:Release 1.5.74
修改记录:
(1) gopay:一些小改动,util.GetRandomString() -> util.RandomString()
(2) gopay:升级 xlog
版本号:Release 1.5.73
修改记录:
(1) Apple:新增内购支付通知V2解析
版本号:Release 1.5.72
修改记录:
(1) Apple:返回参数类型错误修复,pending_renewal_info -> 数组类型
(2) QQ:获取 AccessToken 结果 expires_in 类型修复,expires_in -> 字符串类型
(3) 微信V3:证书相关代码优化
版本号:Release 1.5.71
修改记录:
(1) 微信V2:去除所有微信小程序、公众号相关接口,请使用 wechat-sdk
(2) 支付宝:client.UserCertdocCertverifyConsult() 方法,增加 authToken 参数
(2) 微信V3:新增 银行组件(服务商) 相关接口,详情查看v3文档最下方的接口列表
版本号:Release 1.5.69
修改记录:
(1) 微信V3:修改 client.V3RefundQuery()、增加入参参数,适配 服务商 模式
(2) 微信V3:修复 client.V3Apply4SubSubmit(),接口路由修复
(3) gopayBodyMap 新增 Unmarshal() 方法,解析数据到结构体、数组指针
版本号:Release 1.5.68
修改记录:
(1) 微信V3:修复 client.V3ComplaintResponse()、client.V3ComplaintComplete() complaintId 参数类型错误问题
(2) 微信V3:新增 电商收付通(分账)相关接口,详情查看v3文档最下方的接口列表
(3) 微信V3:新增 电商收付通(补差)相关接口,详情查看v3文档最下方的接口列表
(4) 微信V3:新增 电商收付通(退款)相关接口,详情查看v3文档最下方的接口列表
(5) 微信V3:返回参数中字段,ID写法全部改写为Id写法
版本号:Release 1.5.67
修改记录:
(1) 微信V3:配合微信文档修改,拆分服务商 批量转账 相关接口,接口如下:
(2) 微信V3:新增 client.V3PartnerTransfer()
(3) 微信V3:新增 client.V3PartnerTransferQuery()
(4) 微信V3:新增 client.V3PartnerTransferDetail()
(5) 微信V3:新增 client.V3PartnerTransferMerchantQuery()
(6) 微信V3:新增 client.V3PartnerTransferMerchantDetail()
(7) 微信V3:新增 client.V3Withdraw()
(8) 微信V3:新增 client.V3WithdrawStatus()
(9) 微信V3:新增 client.V3WithdrawDownloadErrBill()
(10) 微信V3:修改 V3TransferDetailQuery() => V3TransferDetail()
(11) 微信V3:修改 V3TransferMerchantDetailQuery() => V3TransferMerchantDetail()
版本号:Release 1.5.66
修改记录:
(1) 微信V3fix bug that `{"code":"PARAM_ERROR","message":"平台证书序列号Wechatpay-Serial错误"}`
版本号:Release 1.5.65
修改记录:
(1) 微信V3:新增 client.V3EcommerceApply(),二级商户进件
(2) 微信V3:新增 client.V3EcommerceApplyStatus(),查询申请状态
(3) 微信V3:新增 client.V3GoldPlanManage(),点金计划管理
(4) 微信V3:新增 client.V3GoldPlanBillManage(),商家小票管理
(5) 微信V3:新增 client.V3GoldPlanFilterManage(),同业过滤标签管理
(6) 微信V3:新增 client.V3GoldPlanOpenAdShow(),开通广告展示
(7) 微信V3:新增 client.V3GoldPlanCloseAdShow(),关闭广告展示
(8) 微信V3:公有化 wechat.GetReleaseSign()、wechat.GetSandBoxSign() 方法
(9) 微信V3:修改 client.V3PartnerCloseOrder() 入参参数
(10) GoPay:一些小修改优化
版本号:Release 1.5.64
修改记录:
(1) xhttp:恢复 xhttp
版本号:Release 1.5.63
修改记录:
(1) GoPay:部分代码优化
(2) xhttpxhttp client 优化,支持自定义client,默认还是使用标准 http.Client
版本号:Release 1.5.62
修改记录:
(1) 微信V3client 内 WxSerialNo、ApiV3Key 公有化
(2) 微信V3client 提供新方法 client.WxPublicKey() 直接获取 微信平台公钥
(3) 微信V3wechat 提供新方法 wechat.V3VerifySignByPK(),不再推荐使用 wechat.V3VerifySign()
(4) 微信V3V3NotifyReq 提供新方法 notify.VerifySignByPK(),不再推荐使用 notify.VerifySign()
(5) 微信V3:整理微信v3说明文档
版本号:Release 1.5.61
修改记录:
(1) gopay:更新 xhttp pkg, 方法全部增加 context 传递
版本号:Release 1.5.60
修改记录:
(1) 微信V3:不再推荐使用 client.SetPlatformCert() 方法
(2) 微信V3:新增 client.GetAndSelectNewestCert() 方法
(3) 微信V3:重构 client.AutoVerifySign() 方法
(4) QQ:新增 qq.GetAccessToken() 方法
(5) QQ:新增 qq.GetOpenId() 方法
(6) QQ:新增 qq.GetUserInfo() 方法
版本号:Release 1.5.59
修改记录:
(1) 微信V3:证书获取方法返回结构体,去除 SignInfo 字段
(2) gopayBodyMapEncodeURLParams 方法稍作调整
(3) PayPalPayPal支付能力接入(订单、支付)
版本号:Release 1.5.58
修改记录:
(1) 微信V3:新增 client.V3FavorMediaUploadImage() 图片上传(营销专用)
(2) 微信V3:新增 client.V3EcommerceIncomeRecord() 特约商户银行来账查询
(3) 微信V3:新增 client.V3EcommerceBalance() 查询特约商户账户实时余额
(4) 微信V3:新增 client.V3BusiFavorSend() 发放消费卡
(5) 微信V3:新增 client.V3PartnershipsBuild() 建立合作关系
(6) 微信V3:新增 client.V3PartnershipsTerminate() 终止合作关系
(7) 微信V3:新增 client.V3PartnershipsList() 查询合作关系列表
(8) 微信V3:修改 client.V3PartnerQueryOrder() 入参参数调整
(9) 微信V3:修改 client.V3BillLevel2FundFlowBill() => client.V3BillEcommerceFundFlowBill() 申请特约商户资金账单
(10) 支付宝:按照支付宝更新后的文档,修改大量接口返回参数结构体字段
版本号:Release 1.5.57
修改记录:
(1) 微信V3:修复一些已知问题
(2) 支付宝:一些细小的修复,部分参数类型更正
版本号:Release 1.5.56
修改记录:
(1) 微信V3:修改 client.V3ProfitShareReturnResult() 接口入参,适配服务商模式
(2) 微信V3:部分接口参数需要加密,修复 V3EncryptText() 和 V3DecryptText() 方法
(3) 支付宝:修改 alipay.NewClient(),增加error返回值,去除Client内部分字段
(4) Apple:新增apple pay的 apple.VerifyReceipt() 校验收据API
(5) 优化代码中所有有关证书的解析操作
版本号:Release 1.5.55
修改记录:
(1) 微信V3wechat.NewClientV3(),去掉初始化参数 appid,所以方法中需要 appid 或sp_appid 的,需要自行传参
(2) 微信V3:新增 代金券 相关接口
(3) 微信V3:新增 商家券 相关接口
(4) 微信V2、V3:修复部分接口发现的Bug
版本号:Release 1.5.54
修改记录:
(1) 微信V3:新增微信支付分回调参数解密方法 notifyReq.DecryptScoreCipherText()
(2) 微信V3:新增分账接口 client.V3ProfitShareMerchantConfigs()
(3) Readme:更新Readme
版本号:Release 1.5.53
修改记录:
(1) 支付宝:补充接口
(2) 微信V3:修改支付分相关接口的返回参数字段, out_trade_no 为 out_order_no
版本号:Release 1.5.52
修改记录:
(1) 支付宝:补充 支付API 相关接口
(2) pkgxhttp.Client 的 Transport 默认配置:Proxy: http.ProxyFromEnvironment
版本号:Release 1.5.51
修改记录:
(1) 微信:新增 特约商户进件(服务商平台) 相关接口
(2) 支付宝:补充完整 芝麻分 相关接口
(3) 支付宝:补充 会员API 相关接口
版本号:Release 1.5.50
修改记录:
(1) 支付宝:新增 芝麻分 相关接口
(2) 支付宝:当判断 Response 中 code!="10000" 时,不再返回nil,而是返回 aliRsp 结果
版本号:Release 1.5.49
修改记录:
(1) 微信V3:新增 wechat.GetPlatformCerts(),无需初始化V3client,直接获取微信平台证书和序列号等信息
(2) gopay:更新 go mod version
(3) 微信V2:新增 client.CustomsDeclareOrder(),订单附加信息提交(正式环境)
(4) 微信V2:新增 client.CustomsDeclareQuery(),订单附加信息查询(正式环境)
(5) 微信V2:新增 client.CustomsReDeclareOrder(),订单附加信息重推(正式环境)
(6) 支付宝:新增 client.TradeCustomsDeclare(),统一收单报关接口(正式环境)
(7) 支付宝:新增 client.AcquireCustoms(),报关接口(正式环境),未经测试
(8) 支付宝:新增 client.AcquireCustomsQuery(),报关查询接口(正式环境),未经测试
版本号:Release 1.5.48
修改记录:
(1) 微信V3:修复 平台证书序列号Wechatpay-Serial错误 问题
(2) 微信V3:新增 client.SetPlatformCert(),设置 微信支付平台证书 和 证书序列号 方法
(3) 微信V3:新增 client.V3EncryptText(),请求参数 敏感信息 加密方法
(4) 微信V3:新增 client.V3DecryptText(),返回参数 敏感信息 解密方法
(5) 微信V3:修改 client.AutoVerifySign() 方法无需传参,但需要提前调用 client.SetPlatformCert() 设置 微信支付平台证书 和 证书序列号
版本号:Release 1.5.47
修改记录:
(1) 微信V3:新增 转账相关 相关接口
(2) 微信V3:新增 账户余额查询 相关接口
(3) 微信V3:新增 来账识别 相关接口
版本号:Release 1.5.46
修改记录:
(1) 微信V3:新增敏感信息加解密方法,wechat.V3EncryptText() 加密数据,wechat.V3DecryptText() 解密数据
(2) 微信V3:新增 微信先享卡 相关接口
(3) 微信V3:新增 支付即服务 相关接口
(4) 微信V3:新增 智慧商圈 相关接口
版本号:Release 1.5.45
修改记录:
(1) 支付宝:优化现有代码,修复公钥证书模式下,同步验签失败的问题
(2) 支付宝:新增 client.AutoVerifySign(),自动同步验签设置(公钥证书模式)
(3) 支付宝:新增 client.PublicCertDownload(),应用支付宝公钥证书下载
(4) 支付宝:新增 client.FundTransPayeeBindQuery(),资金收款账号绑定关系查询
(5) 支付宝:新增 client.OpenAppQrcodeCreate(),小程序生成推广二维码接口
(6) 支付宝:新增 client.UserAgreementPageSign(),支付宝个人协议页面签约接口
(7) 支付宝:新增 client.UserAgreementPageUnSign(),支付宝个人代扣协议解约接口
(8) 支付宝:新增 client.UserAgreementQuery(),支付宝个人代扣协议查询接口
(9) 支付宝:新增 client.TradeOrderInfoSync(),支付宝订单信息同步接口
(10) 支付宝:新增 client.TradeAdvanceConsult(),订单咨询服务
版本号:Release 1.5.44
修改记录:
(1) 微信V3:新增 图片上传 接口
(2) 微信V3:新增 视频上传 接口
(3) 微信V3:修复消费者投诉接口中的图片上传失败问题
版本号:Release 1.5.42
修改记录:
(1) 迁移新仓库 https://github.com/go-pay/gopay
版本号:Release 1.5.41
修改记录:
(1) 微信V3:新增 消费者投诉2.0 相关接口
(2) 微信V3:新增 分账 相关接口
版本号:Release 1.5.40
修改记录:
(1) 微信V3:新增微信支付分(免确认模式)相关接口
(2) 微信V3:新增微信支付分(免确认预授权模式)相关接口
版本号:Release 1.5.39
修改记录:
(1) 微信V3:新增微信支付分(公共API)相关接口
版本号:Release 1.5.38
修改记录:
(1) 微信:去掉所以微信 client 方法中需要传证书的参数,请统一在初始化client时,添加证书
(2) 微信:使用方法请参考 wechat/client_test.go 下的初始化,以及各个方法使用
版本号:Release 1.5.37
修改记录:
(1) 支付宝:修改 client.FundAuthOrderAppFreeze() 接口返回参数
(2) 支付宝:新增 client.GetRequestSignParam(),获取已签名的完整请求参数
(3) 微信V3:增加 client.GetPlatformCerts(),获取微信平台证书公钥,增加注释说明
(4) 支付宝:拆分 _test.go 文件
版本号:Release 1.5.36
修改记录:
(1) 支付宝:新增 资金API 类别 接口实现
(2) 微信V3:修复银行转账接口,银行卡号和收款人的 RSA 加密bug
(3) 一些其他小修复调整
版本号:Release 1.5.35
修改记录:
(1) 微信V3:修复 普通支付回调通知 解密后的结构体问题
(2) 微信V3:新增 合单支付回调通知
(3) 微信V3:修复 退款回调通知 解密后的结构体问题
版本号:Release 1.5.34
修改记录:
(1) 微信V3:修复 client.GetPlatformCerts() 的返回值 code 问题
(2) ReadMe:补充 README.md 说明
(3) 微信V3:修复 PaySignOfApp() 签名出错的问题
(4) 微信V2:部分文件调整
版本号:Release 1.5.32
修改记录:
(1) xhttpFix bug about Transport
版本号:Release 1.5.31
修改记录:
(1) 微信:新增服务商支付接口
版本号:Release 1.5.30
修改记录:
(1) BodyMap:恢复 bm.Get() 方法获取的是string类型,增加 bm.GetInterface()
(2) 微信:新增V3版本退款查询接口
版本号:Release 1.5.29
发布时间:2021/02/27 22:49
修改记录:
(1) 支付宝:新增 client.PostAliPayAPISelfV2(),比非V2版本更灵活化,具体参考 client_test.go 内的 TestClient_PostAliPayAPISelfV2() 方法
(2) BodyMap:新增 bm.SetFormFile() 的部分方法,修改 bm.Get() 方法,新增bm.GetString() 方法
(3) xHttp:更新 httpClient httpClient.Type() 支持 TypeMultipartFormData 类型
(4) go mod 版本改为 1.14
版本号:Release 1.5.28
发布时间:2021/02/19 18:48
修改记录:
(1) QQ:新增 client.AddCertFileContent(),解决无证书文件,只有证书内容的问题
(2) 支付宝:新增 alipay.VerifySyncSignWithCert(),同步证书验签
(3) 支付宝:新增 client.SetCertSnByContent(),通过应用公钥证书内容设置 app_cert_sn、alipay_root_cert_sn、alipay_cert_sn
(4) 支付宝:删除废弃接口 client.FundTransToaccountTransfer()
(5) fix BodyMap 的部分方法
版本号:Release 1.5.27
发布时间:2021/02/03 18:50
修改记录:
(1) GoPay:去掉对 gotil 的强依赖
版本号:Release 1.5.26
发布时间:2021/01/29 19:38
修改记录:
(1) 微信:重新整理文件分级,商户分账模块增加test方法说明
(2) BodyMap: 去除 GetArrayBodyMap()、GetBodyMap() 方法
版本号:Release 1.5.25
发布时间:2020/12/31 18:38
修改记录:
(1) 微信:v3 基础支付接口完成,使用请参考:gopay/wechat/v3/client_test.go
版本号:Release 1.5.24
发布时间:2020/12/21 18:58
修改记录:
(1) 微信:证书支持二选一,只传 apiclient_cert.pem 和 apiclient_key.pem 或者只传 apiclient_cert.p12
版本号:Release 1.5.23
发布时间:2020/12/15 17:58
修改记录:
(1) 微信:新增 client.AddCertFileContent(),解决无证书文件,只有证书内容的问题
版本号:Release 1.5.22
发布时间:2020/12/04 02:58
修改记录:
(1) 更新 gotil,修复xlog导致 go 1.14 以下版本报bug问题
(2) 采纳 WenyXu 的意见,优化BodyMap的用法
版本号:Release 1.5.20
发布时间:2020/09/30 23:58
修改记录:
(1) 微信:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
(2) 支付宝:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
(3) QQclient 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
(4) 更新 Gotil
版本号:Release 1.5.19
发布时间:2020/09/20 23:58
修改记录:
(1) 微信:修复 client.ProfitSharingQuery() 接口的Bughttps://github.com/iGoogle-ink/gopay/issues/68
(2) 微信:优化 client.doProdPost()
(3) 支付宝:优化 client.doAliPay()
(4) 微信:项目文件区分改动,开放平台接口和微信公众号区分
(5) 微信:替换 wechat.GetAppLoginAccessToken() = > wechat.GetOauth2AccessToken()
(6) 微信:替换 wechat.RefreshAppLoginAccessToken() = > wechat.RefreshOauth2AccessToken()
(7) 微信:替换 wechat.GetUserInfoOpen() = > wechat.GetOauth2UserInfo()
(8) 微信:替换 wechat.GetUserInfo() = > wechat.GetPublicUserInfo()
(9) 微信:新增 wechat.CheckOauth2AccessToken() 检验授权凭证(access_token)是否有效
(10) 微信:新增 wechat.GetPublicUserInfoBatch() 批量获取用户基本信息(微信公众号)
(11) 微信:新增 client.SendCashRed() 发放现金红包
(12) 微信:新增 client.SendGroupCashRed() 发放现金裂变红包
(13) 微信:新增 client.SendAppletRed() 发放小程序红包
(14) 微信:新增 client.QueryRedRecord() 查询红包记录
(15) QQ:新增 client.SendCashRed() 创建现金红包,(未经测试,有条件的帮忙测一下吧,有问题提PR)
(16) QQ:新增 client.DownloadRedListFile() 对账单下载,(未经测试,有条件的帮忙测一下吧,有问题提PR)
(17) QQ:新增 client.QueryRedInfo() 查询红包详情,(未经测试,有条件的帮忙测一下吧,有问题提PR)
版本号:Release 1.5.18
发布时间:2020/08/29 18:30
修改记录:
(1) 微信:修复 client.AddCertFilePath() 无效的Bug
(2) QQ:修复 client.AddCertFilePath() 无效的Bug
(3) Gotil:升级 gotil 到 v1.0.7-beta2 版本
(4) 支付宝:OpenAuthTokenAppResponse 结构体中 ExpiresIn、ReExpiresIn 字段改为int64(有用户反馈返回的是int类型,但文档写的是string),如果此处有问题,请立马联系改回去。
版本号:Release 1.5.17
发布时间:2020/08/23 15:30
修改记录:
(1) 微信:Response model 增加字段
(2) ReadMe:修改部分遗留未更改的文档内容
(3) 支付宝:添加证书由只支持证书路径,改为支持证书路径或者这书Byte数组
(4) 支付宝:修复SystemOauthToken()方法未添加 AppCertSN 和 AliPayRootCertSN 的问题
版本号:Release 1.5.16
发布时间:2020/07/29 18:30
修改记录:
(1) 微信:新增公共方法:wechat.GetUserInfoOpen(),微信开放平台:获取用户个人信息(UnionID机制)
(2) Gotil:升级 gotil 到 v1.0.4 版本
(3) 微信:新增ReadMe说明,微信支付下单等操作可用沙箱环境测试是否成功,但真正支付时,请使用正式环境,isProd = true,不然会报错
版本号:Release 1.5.15
发布时间:2020/07/09 18:30
修改记录:
(1) 微信:新增client方法:client.ProfitSharing(),请求单次分账
(2) 微信:新增client方法:client.MultiProfitSharing(),请求多次分账
(3) 微信:新增client方法:client.ProfitSharingQuery(),查询分账结果
(4) 微信:新增client方法:client.ProfitSharingAddReceiver(),添加分账接收方
(5) 微信:新增client方法:client.ProfitSharingRemoveReceiver(),删除分账接收方
(6) 微信:新增client方法:client.ProfitSharingFinish(),完结分账
(7) 微信:新增client方法:client.ProfitSharingReturn(),分账回退
(8) 微信:新增client方法:client.ProfitSharingReturnQuery(),分账回退结果查询
(9) 微信:新增client方法:client.PayBank(),企业付款到银行卡API
(10) 微信:新增client方法:client.QueryBank(),查询企业付款到银行卡API
(11) 微信:新增client方法:client.GetRSAPublicKey(),获取RSA加密公钥API
(12) 微信:修改client方法名:client.PostRequest() -> client.PostWeChatAPISelf()
(13) QQ:修改client方法名:client.PostRequest() -> client.PostQQAPISelf()
(14) 说明:方法未经严格测试,还请开发者在开始使用时确认是否正常使用,有问题请提 issue
版本号:Release 1.5.14
发布时间:2020/06/27 3:30
修改记录:
(1) 引入 github.com/iGoogle-ink/gotil 包
(2) 替换 log 输出样式
(3) 支付宝:新增client方法:client.PostAliPayAPISelf(),支付宝接口自行实现方法
版本号:Release 1.5.12
发布时间:2020/05/20 02:10
修改记录:
(1) http_client:增加默认请求的超时时间 60s,增加 SetTimeout() 方法,可自定义超时时间
(2) 微信:修改 申请退款、退款查询、订单查询 接口的返回结构体,增加带下标的部分字段
(3) 微信:增加 申请退款、退款查询、订单查询 接口的返回值,新增了 resBm BodyMap 类型,方便接收结构体中未定义到的下标字段
(4) 微信:新增client方法:client.GetTransferInfo(),查询企业退款,此方法实际暂未测试,请自行测试,有问题提issue
版本号:Release 1.5.11
发布时间:2020/05/13 17:15
修改记录:
(1) 支付宝:修复rsp解析出错的问题 client.SystemOauthToken()
(2) 微信:修改部分公共方法Rsp结构体参数问题,同步微信文档
版本号:Release 1.5.10
发布时间:2020/05/06 20:15
修改记录:
(1) 微信:修改部分公共方法返回值结构体字段类型
(2) drone fix
版本号:Release 1.5.9
发布时间:2020/04/25 15:32
修改记录:
(1) 支付宝:异步验签,推荐使用 alipay.ParseNotifyToBodyMap(),解析参数后参数在Verify验签时,推荐传入参数BodyMap bm。
(2) 支付宝:修改公共方法:alipay.ParseNotifyResultToBodyMap() 为 alipay.ParseNotifyToBodyMap()
(3) 支付宝:修改公共方法:alipay.ParseNotifyResultByURLValues() 为 alipay.ParseNotifyByURLValues()
(4) 支付宝:废弃公共方法:alipay.ParseNotifyResult(),因为异步通知有参数因为支付接口不同,返回的字段不同,无法使用结构体全部定义好
(5) 支付宝:调整了部分接口的文档地址
(6) 微信:修改公共方法:wechat.ParseNotifyResultToBodyMap() 为 wechat.ParseNotifyToBodyMap()
(7) 微信:修改公共方法:wechat.ParseNotifyResult() 为 wechat.ParseNotify()
(8) 微信:修改公告方法:wechat.ParseRefundNotifyResult() 为 wechat.ParseRefundNotify()
版本号:Release 1.5.8
发布时间:2020/04/18 21:32
修改记录:
(1) 微信:新增Client方法:client.PostRequest(),向微信发送Post请求,对于本库未提供的微信API,可自行实现,通过此方法发送请求
(2) 微信:微信同步返回结构体类型全部修改为string类型,验签出错的问题
(3) 微信:Client方法,需要传证书的接口方法,入参类型统一改为interface{},无需传证书地址时,由 "" 改为 nil
(4) QQ:同微信改动
(5) 支付宝:model结构体参数全部修改为string类型
版本号:Release 1.5.7
发布时间:2020/03/25 20:32
修改记录:
(1) 支付宝:修改 client.UserCertifyOpenQuery() 方法的返回值解析类型报错问题,官方文档类型实例有误
版本号:Release 1.5.6
发布时间:2020/03/06 17:32
修改记录:
(1) 支付宝:新增Client方法:client.SetPrivateKeyType(),设置 支付宝 私钥类型,alipay.PKCS1 或 alipay.PKCS8,默认 PKCS1。
(2) 支付宝:修改公共方法:alipay.GetRsaSign(),增加了私钥类型参数,并将私钥的格式化操作,移动到该方法内,传入的私钥无需事先格式化。
版本号:Release 1.5.5
发布时间:2020/03/05 18:32
修改记录:
(1) 支付宝:新增Client方法:client.DataBillBalanceQuery(),支付宝商家账户当前余额查询。
(2) 支付宝:新增Client方法:client.DataBillDownloadUrlQuery(),查询对账单下载地址。
(3) 支付宝:开放公共方法:alipay.GetRsaSign(),获取支付宝参数签名(参数sign值)。
(4) 支付宝:开放公共方法:alipay.FormatURLParam(),格式化支付宝请求URL参数。
(5) 支付宝:新增公共方法:alipay.ParseNotifyResultByURLValues(),通过 url.Values 解析支付宝支付异步通知的参数到Struct。
版本号:Release 1.5.4
发布时间:2020/02/29 14:32
修改记录:
(1) 支付宝:新增Client方法:client.UserInfoAuth(),用户登陆授权。(方法未测试通过,待有测试条件的同学测试一下吧)
(2) 支付宝:新增公共方法:alipay.MonitorHeartbeatSyn(),验签接口。(方法未测试通过,待有测试条件的同学测试一下吧)
版本号:Release 1.5.3
发布时间:2020/02/19 11:32
修改记录:
(1) 支付宝:修改公共方法:SystemOauthToken(),添加参数 signType
版本号:Release 1.5.2
发布时间:2020/02/14 13:32
修改记录:
(1) 支付宝:官方单笔转账接口更新,新增Client方法:client.FundTransUniTransfer(),单笔转账接口
(2) 支付宝:新增Client方法:client.FundTransCommonQuery(),转账业务单据查询接口
(3) 支付宝:新增Client放大:client.FundAccountQuery(),支付宝资金账户资产查询接口
(3) 支付宝:Client的方法,必选参数校验
版本号:Release 1.5.1
发布时间:2020/01/03 17:32
修改记录:
(1) 由于下载包需要 /v2 的问题,替换版本号到 1.x,代码不变,只改变版本号记录
版本号:Release 2.0.5
发布时间:2020/01/01 22:55
修改记录:
(1) 添加一些函数参数判空操作,避免Panic
(2) 去掉不用的结构体 ReturnMessage
(3) 去掉 go mod v1.4.8版本的依赖
版本号:Release 2.0.4
发布时间:2019/12/24 14:29
修改记录:
(1) 支付宝:新增支付宝公钥文件验证签方法(公钥证书模式):client.VerifySignWithCert()
版本号:Release 2.0.3
发布时间:2019/12/18 19:25
修改记录:
(1) 微信:新增Client方法:client.AuthCodeToOpenId(),授权码查询OpenId(正式)
(2) 微信:新增Client方法:client.Report(),交易保障
(3) 微信:新增Client方法:client.EntrustPublic(),公众号纯签约(正式)
(4) 微信:新增Client方法:client.EntrustAppPre()APP纯签约-预签约接口-获取预签约ID(正式)
(5) 微信:新增Client方法:client.EntrustH5()H5纯签约(正式)
(5) 微信:新增Client方法:client.EntrustPaying(),支付中签约(正式)
版本号:Release 2.0.2
发布时间:2019/12/13 23:25
修改记录:
(1) 微信:删除Client方法:client.AddCertFileByte()
(2) 版本限制 golang 1.13fmt.Errorf() 使用 %w 格式化 error1.13新特性
版本号:Release 2.0.1
发布时间:2019/12/13 17:20
修改记录:
(1) 处理 go mod 包,go get github.com/iGoogle-ink/gopay/v2
版本号:Release 2.0.0
发布时间:2019/12/12 18:22
修改记录:
(1) 按支付渠道模块分包大调整
(2) 一大推细小改动,不一一描述了
(3) 支付宝:修改公共API方法:alipay.GetCertSN(),不再支持支付宝根证书的SN获取
(4) 支付宝:新增公共API方法:alipay.GetRootCertSN(),获取root证书序列号SN
(5) 支付宝:新增Client方法:alipay.SetLocation(),设置时区,不设置或出错均为默认服务器时间
版本号:Release 1.4.8
发布时间:2019/12/11 16:40
修改记录:
(1) 1.几 最后一个版本
(2) 修复一些问题
(3) 支付宝:修改公共API方法:gopay.GetCertSN(),不再支持支付宝根证书的SN获取
(4) 支付宝:新增公共API方法:gopay.GetRootCertSN(),获取root证书序列号SN
(5) 微信:修改公共API方法:gopay.GetWeChatSanBoxParamSign(),修复 沙箱验签出错问题
版本号:Release 1.4.6
发布时间:2019/12/09 18:37
修改记录:
(1) 移除第三方http请求库,自己封装了一个请求库使用,解决不会设置 goproxy 无法下载包的问题
(2) 使用中如有问题,请微信联系作者处理 或 提issue
版本号:Release 1.4.5
发布时间:2019/12/07 21:56
修改记录:
(1) 支付宝:修复 公钥证书模式 下,同步返回参数接收问题,返回接收结构体增加参数 alipay_cert_sn,同步返回证书模式验签,暂时未解决
(2) 支付宝:修改公共API方法:gopay.VerifyAliPaySign(),不再支持同步验签,只做异步通知验签
(3) 支付宝:新增公共API方法:gopay.VerifyAliPaySyncSign(),支付宝同步返回验签
(4) 支付宝:新增Client方法:client.SetAliPayPublicCertSN(),设置 支付宝公钥证书SN,通过 gopay.GetCertSN() 获取 alipay_cert_sn
(5) 支付宝:新增Client方法:client.SetAppCertSnByPath(),设置 app_cert_sn 通过应用公钥证书路径
(6) 支付宝:新增Client方法:client.SetAliPayPublicCertSnByPath(),设置 alipay_cert_sn 通过 支付宝公钥证书文件路径
(7) 支付宝:新增Client方法:client.SetAliPayRootCertSnByPath(),设置 alipay_root_cert_sn 通过支付宝CA根证书文件路径
版本号:Release 1.4.4
发布时间:2019/11/16 15:56
修改记录:
(1) 支付宝:新增公共API方法:gopay.ParseAliPayNotifyResultToBodyMap(),解析支付宝支付异步通知的参数到BodyMap
(2) 支付宝:修改公共API方法:gopay.VerifyAliPaySign(),支付宝异步验签支持传入 BodyMap
(3) 微信:新增Client方法:client.AddCertFileByte(),添加微信证书 Byte 数组
(4) 微信:新增Client方法:client.AddCertFilePath(),添加微信证书 Path 路径
(5) 微信:微信Client需要证书的方法,如已使用client.AddCertFilePath()或client.AddCertFileByte()添加过证书,参数certFilePath、keyFilePath、pkcs12FilePath全传空字符串 "",否则,3证书Path均不可空
(6) BodyMap 的Set方法去掉switch判断,直接赋值
(7) WeChatClient、AliPayClient 加锁
(8) 修改部分小问题和部分样式
版本号:Release 1.4.3
发布时间:2019/11/12 01:15
修改记录:
(1) 微信:新增公共API方法:gopay.ParseWeChatRefundNotifyResult(),解析微信退款异步通知的请求参数
(2) 微信:新增公共API方法:gopay.DecryptRefundNotifyReqInfo(),解密微信退款异步通知的加密数据
(3) 支付宝:修改Client方法:client.AliPayUserCertifyOpenCertify(),身份认证开始认证(获取认证链接)
(4) 修改部分小问题
版本号:Release 1.4.2
发布时间:2019/11/11 16:43
修改记录:
(1) 支付宝:新增Client方法:client.AliPayUserCertifyOpenInit(),身份认证初始化服务
(2) 支付宝:新增Client方法:client.AliPayUserCertifyOpenCertify(),身份认证开始认证
(3) 支付宝:新增Client方法:client.AliPayUserCertifyOpenQuery(),身份认证记录查询
(4) 支付宝:所有方法的返回结构体下的 XxxResponse 字段,统一修改为 Response,如:
type AliPayTradeCreateResponse struct {
Response createResponse `json:"alipay_trade_create_response,omitempty"`
SignData string `json:"-"`
Sign string `json:"sign"`
}
(5) 支付宝:修改一些代码格式问题
(6) 支付宝:client.AlipayOpenAuthTokenApp() 修改为 client.AliPayOpenAuthTokenApp()
版本号:Release 1.4.1
发布时间:2019/11/04 14:28
修改记录:
(1) 支付宝:修改公共API方法:GetCertSN(),修复获取支付宝根证书获取 sn 的问题(wziww)
(2) 支付宝:新增Client方法:client.SetAppCertSN(),可自行获取公钥 sn 并赋值
(3) 支付宝:新增Client方法:client.SetAliPayRootCertSN(),可自行获取根证书 sn 并赋值
版本号:Release 1.4.0
发布时间:2019/10/10 13:51
修改记录:
(1) AliPayNotifyRequest 结构体,新增加两个字段:method、timestamp,修复电脑网站支付,配置 return_url 支付成功后,支付宝请求该 return_url 返回参数验签失败的问题
(2) 去除支付宝老验签方法 VerifyAliPayResultSign()
(3) 去除微信老验签方法 VerifyWeChatResultSign()
版本号:Release 1.3.9
发布时间:2019/09/30 00:01
修改记录:
(1) 修复支付宝支付验签出错的问题!
版本号:Release 1.3.8
发布时间:2019/09/24 17:51
修改记录:
(1) 代码风格修改更新
版本号:Release 1.3.7
发布时间:2019/09/22 11:41
修改记录:
(1) README 增加 go mod 安装gopay的方法指导
版本号:Release 1.3.6
发布时间:2019/09/09 23:51
修改记录:
(1) 新增支付宝Client方法:client.AlipayUserInfoShare() => 支付宝会员授权信息查询接口(App支付宝登录)
版本号:Release 1.3.6
发布时间:2019/09/05 02:55
修改记录:
(1) 更改微信公共API方法名称:gopay.GetAccessToken() to gopay.GetWeChatAppletAccessToken() => 获取微信小程序全局唯一后台接口调用凭据
(2) 更改微信公共API方法名称:gopay.GetPaidUnionId() to gopay.GetWeChatAppletPaidUnionId() => 微信小程序用户支付完成后,获取该用户的 UnionId,无需用户授权
(3) 新增微信公共API方法:gopay.GetAppWeChatLoginAccessToken() => App应用微信第三方登录,code换取access_token
(4) 新增微信公共API方法:gopay.RefreshAppWeChatLoginAccessToken() => 刷新App应用微信第三方登录后,获取的 access_token
版本号:Release 1.3.5
发布时间:2019/09/05 02:10
修改记录:
(1) 支付宝、微信Client 由私有改为公有
版本号:Release 1.3.4
发布时间:2019/09/03 19:26
修改记录:
(1) 新增支付宝公共API方法:gopay.GetCertSN() => 获取证书SN号(app_cert_sn、alipay_root_cert_sn、alipay_cert_sn
(2) 新增支付宝Client方法:client.SetAliPayRootCertSN() => 设置支付宝根证书SN,通过 gopay.GetCertSN() 获取
(3) 新增支付宝Client方法:client.SetAppCertSN() => 设置应用公钥证书SN,通过 gopay.GetCertSN() 获取
+13
View File
@@ -0,0 +1,13 @@
赞助者皆为 微信 或 支付宝 用户名
平台 ---- 用户 ---- 金额
微信 ---- 燃烧的发丝 ---- 50
微信 ---- 三五五七ᴳᴼ ---- 100
微信 ---- 沐修 ---- 50
微信 ---- 深度学习 ---- 100
支付宝 ---- **旺 ---- 50
微信 ---- 开博黄工 ---- 88
微信 ---- 每天都有新旋律 ---- 100
微信 ---- *冉 ---- 50
微信 ---- *萧 ---- 80
+146
View File
@@ -0,0 +1,146 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 提交申请单API
// 注意:本接口会提交一些敏感信息,需调用 client.V3EncryptText() 进行加密
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_1.shtml
func (c *ClientV3) V3Apply4SubSubmit(ctx context.Context, bm gopay.BodyMap) (*Apply4SubSubmitRsp, error) {
if err := bm.CheckEmptyError("business_code", "contact_info", "subject_info", "business_info", "settlement_info", "bank_account_info"); err != nil {
return nil, err
}
authorization, err := c.authorization(MethodPost, v3Apply4SubSubmit, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3Apply4SubSubmit, authorization)
if err != nil {
return nil, err
}
wxRsp := &Apply4SubSubmitRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Apply4SubSubmit)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 通过业务申请编号查询申请状态API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_2.shtml
func (c *ClientV3) V3Apply4SubQueryByBusinessCode(ctx context.Context, businessCode string) (*Apply4SubQueryRsp, error) {
uri := fmt.Sprintf(v3Apply4SubQueryByBusinessCode, businessCode)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &Apply4SubQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Apply4SubQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 通过申请单号查询申请状态API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_2.shtml
func (c *ClientV3) V3Apply4SubQueryByApplyId(ctx context.Context, applyId string) (*Apply4SubQueryRsp, error) {
uri := fmt.Sprintf(v3Apply4SubQueryByApplyId, applyId)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &Apply4SubQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Apply4SubQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 修改结算账号 API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_3.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_4.shtml
func (c *ClientV3) V3Apply4SubModifySettlement(ctx context.Context, bm gopay.BodyMap) (*EmptyRsp, error) {
if err := bm.CheckEmptyError("sub_mchid", "account_type", "account_bank", "account_number"); err != nil {
return nil, err
}
postUrl := fmt.Sprintf(v3Apply4SubModifySettlement, bm["sub_mchid"])
bm.Remove("sub_mchid")
authorization, err := c.authorization(MethodPost, postUrl, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, postUrl, authorization)
if err != nil {
return nil, err
}
wxRsp := &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询结算账户 API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_4.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_5.shtml
func (c *ClientV3) V3Apply4SubQuerySettlement(ctx context.Context, subMchId string) (*Apply4SubQuerySettlementRsp, error) {
uri := fmt.Sprintf(v3Apply4SubQuerySettlement, subMchId)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &Apply4SubQuerySettlementRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Apply4SubQuerySettlement)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+177
View File
@@ -0,0 +1,177 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 获取对私银行卡号开户银行
// 注意:accountNo 需此方法加密:client.V3EncryptText()
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_1.shtml
func (c *ClientV3) V3BankSearchBank(ctx context.Context, accountNo string) (wxRsp *BankSearchBankRsp, err error) {
uri := v3BankSearchBank + "?account_number=" + url.QueryEscape(accountNo)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchBankRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchBank)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询支持个人业务的银行列表
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_2.shtml
func (c *ClientV3) V3BankSearchPersonalList(ctx context.Context, limit, offset int) (wxRsp *BankSearchPersonalListRsp, err error) {
if limit == 0 {
limit = 20
}
uri := v3BankSearchPersonalList + "?limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchPersonalListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchList)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询支持对公业务的银行列表
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_3.shtml
func (c *ClientV3) V3BankSearchCorporateList(ctx context.Context, limit, offset int) (wxRsp *BankSearchCorporateListRsp, err error) {
if limit == 0 {
limit = 20
}
uri := v3BankSearchCorporateList + "?limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchCorporateListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchList)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询省份列表
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_4.shtml
func (c *ClientV3) V3BankSearchProvinceList(ctx context.Context) (wxRsp *BankSearchProvinceListRsp, err error) {
authorization, err := c.authorization(MethodGet, v3BankSearchProvinceList, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, v3BankSearchProvinceList, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchProvinceListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchProvince)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询城市列表
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_5.shtml
func (c *ClientV3) V3BankSearchCityList(ctx context.Context, provinceCode int) (wxRsp *BankSearchCityListRsp, err error) {
url := fmt.Sprintf(v3BankSearchCityList, provinceCode)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchCityListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchCity)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询支行列表
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter11_2_6.shtml
func (c *ClientV3) V3BankSearchBranchList(ctx context.Context, bankAliasCode string, cityCode, limit, offset int) (wxRsp *BankSearchBranchListRsp, err error) {
if limit == 0 {
limit = 20
}
uri := fmt.Sprintf(v3BankSearchBranchList, bankAliasCode) + "?city_code=" + util.Int2String(cityCode) + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BankSearchBranchListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BankSearchBranch)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+188
View File
@@ -0,0 +1,188 @@
package wechat
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 申请交易账单API
// 注意:如 bill_date 为空,默认查前一天的
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_6.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_6.shtml
func (c *ClientV3) V3BillTradeBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
if bm != nil {
if bm.GetString("bill_date") == util.NULL {
now := time.Now()
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
bm.Set("bill_date", yesterday)
}
}
uri := v3TradeBill + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BillRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TradeBill)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请资金账单API
// 注意:如 bill_date 为空,默认查前一天的
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_7.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_7.shtml
func (c *ClientV3) V3BillFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
if bm != nil {
if bm.GetString("bill_date") == util.NULL {
now := time.Now()
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
bm.Set("bill_date", yesterday)
}
}
uri := v3FundFlowBill + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BillRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TradeBill)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请特约商户资金账单API
// 注意:如 bill_date 为空,默认查前一天的
// Code = 0 is success
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter7_2.shtml
func (c *ClientV3) V3BillEcommerceFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *EcommerceFundFlowBillRsp, err error) {
if bm != nil {
if bm.GetString("bill_date") == util.NULL {
now := time.Now()
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
bm.Set("bill_date", yesterday)
}
if bm.GetString("account_type") == util.NULL {
bm.Set("account_type", "ALL")
}
if bm.GetString("algorithm") == util.NULL {
bm.Set("algorithm", "AEAD_AES_256_GCM")
}
}
uri := v3EcommerceFundFlowBill + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceFundFlowBillRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(DownloadBill)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请单个子商户资金账单API
// 注意:如 bill_date 为空,默认查前一天的
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_12.shtml
func (c *ClientV3) V3BillSubFundFlowBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
if bm != nil {
if bm.GetString("bill_date") == util.NULL {
now := time.Now()
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
bm.Set("bill_date", yesterday)
}
}
uri := v3SubFundFlowBill + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BillRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TradeBill)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 下载账单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_8.shtml
func (c *ClientV3) V3BillDownLoadBill(ctx context.Context, downloadUrl string) (fileBytes []byte, err error) {
if downloadUrl == gopay.NULL {
return nil, errors.New("invalid download url")
}
split := strings.Split(downloadUrl, ".com")
if len(split) != 2 {
return nil, errors.New("invalid download url")
}
authorization, err := c.authorization(MethodGet, split[1], nil)
if err != nil {
return nil, err
}
res, _, bs, err := c.doProdGet(ctx, split[1], authorization)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, errors.New(string(bs))
}
return bs, nil
}
+59
View File
@@ -0,0 +1,59 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 商圈积分同步
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_6_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_6_2.shtml
func (c *ClientV3) V3BusinessPointsSync(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusinessPointsSync, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusinessPointsSync, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商圈积分授权查询
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_6_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_6_4.shtml
func (c *ClientV3) V3BusinessAuthPointsQuery(ctx context.Context, appid, openid string) (*BusinessAuthPointsQueryRsp, error) {
uri := fmt.Sprintf(v3BusinessAuthPointsQuery, openid) + "?appid=" + appid
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &BusinessAuthPointsQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusinessAuthPointsQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+284
View File
@@ -0,0 +1,284 @@
package wechat
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"runtime"
"sort"
"sync"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/aes"
"github.com/go-pay/gopay/pkg/errgroup"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xhttp"
"github.com/go-pay/gopay/pkg/xlog"
"github.com/go-pay/gopay/pkg/xpem"
"github.com/go-pay/gopay/pkg/xtime"
)
// 获取微信平台证书公钥(获取后自行保存使用,如需定期刷新功能,自行实现)
// 注意事项
// 如果自行实现验证平台签名逻辑的话,需要注意以下事项:
// - 程序实现定期更新平台证书的逻辑,不要硬编码验证应答消息签名的平台证书
// - 定期调用该接口,间隔时间小于12小时
// - 加密请求消息中的敏感信息时,使用最新的平台证书(即:证书启用时间较晚的证书)
// 文档说明:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
func GetPlatformCerts(ctx context.Context, mchid, apiV3Key, serialNo, privateKey string) (certs *PlatformCertRsp, err error) {
var (
eg = new(errgroup.Group)
mu sync.Mutex
jb = ""
)
// Prepare
priKey, err := xpem.DecodePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
timestamp := time.Now().Unix()
nonceStr := util.RandomString(32)
ts := util.Int642String(timestamp)
_str := MethodGet + "\n" + v3GetCerts + "\n" + ts + "\n" + nonceStr + "\n" + jb + "\n"
// Sign
h := sha256.New()
h.Write([]byte(_str))
result, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, h.Sum(nil))
if err != nil {
return nil, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err)
}
sign := base64.StdEncoding.EncodeToString(result)
// Authorization
authorization := Authorization + ` mchid="` + mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + serialNo + `",signature="` + sign + `"`
// Request
var url = v3BaseUrlCh + v3GetCerts
httpClient := xhttp.NewClient()
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, serialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err := httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx)
if err != nil {
return nil, err
}
certs = &PlatformCertRsp{Code: Success}
if res.StatusCode != http.StatusOK {
certs.Code = res.StatusCode
certs.Error = string(bs)
return certs, nil
}
// Parse
certRsp := new(PlatformCert)
if err = json.Unmarshal(bs, certRsp); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%+v", string(bs), err)
}
for _, v := range certRsp.Data {
cert := v
if cert.EncryptCertificate != nil {
ec := cert.EncryptCertificate
eg.Go(func(ctx context.Context) error {
cipherBytes, _ := base64.StdEncoding.DecodeString(ec.Ciphertext)
pubKeyBytes, err := aes.GCMDecrypt(cipherBytes, []byte(ec.Nonce), []byte(ec.AssociatedData), []byte(apiV3Key))
if err != nil {
return fmt.Errorf("aes.GCMDecrypt, err:%+v", err)
}
pci := &PlatformCertItem{
EffectiveTime: cert.EffectiveTime,
ExpireTime: cert.ExpireTime,
PublicKey: string(pubKeyBytes),
SerialNo: cert.SerialNo,
}
mu.Lock()
certs.Certs = append(certs.Certs, pci)
mu.Unlock()
return nil
})
}
}
if err = eg.Wait(); err != nil {
return nil, err
}
return certs, nil
}
// 设置 微信支付平台证书 和 证书序列号
// 注意1:如已开启自动验签功能 client.AutoVerifySign(),无需再调用此方法设置
// 注意2:请预先通过 wechat.GetPlatformCerts() 获取 微信平台公钥证书 和 证书序列号
// 部分接口请求参数中敏感信息加密,使用此 微信支付平台公钥 和 证书序列号
func (c *ClientV3) SetPlatformCert(wxPublicKeyContent []byte, wxSerialNo string) (client *ClientV3) {
pubKey, err := xpem.DecodePublicKey(wxPublicKeyContent)
if err != nil {
xlog.Errorf("SetPlatformCert(%s),err:%+v", wxPublicKeyContent, err)
}
if pubKey != nil {
c.wxPublicKey = pubKey
}
c.WxSerialNo = wxSerialNo
return c
}
// 获取 微信平台证书(readonly)
func (c *ClientV3) WxPublicKey() (wxPublicKey *rsa.PublicKey) {
wxPublicKey = c.wxPublicKey
return
}
// 获取并选择最新的有效证书
func (c *ClientV3) GetAndSelectNewestCert() (cert, serialNo string, err error) {
certs, err := c.getPlatformCerts()
if err != nil {
return gopay.NULL, gopay.NULL, err
}
if certs.Code == Success && len(certs.Certs) > 0 {
// only one
if len(certs.Certs) == 1 {
formatExpire := xtime.FormatDateTime(certs.Certs[0].ExpireTime)
expireTime, err := time.ParseInLocation(xtime.TimeLayout, formatExpire, time.Local)
if err != nil {
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatExpire, err)
}
if time.Now().Unix() >= expireTime.Unix() {
// 过期了
return gopay.NULL, gopay.NULL, fmt.Errorf("wechat platform API cert expired, expired time: %s", formatExpire)
}
return certs.Certs[0].PublicKey, certs.Certs[0].SerialNo, nil
}
// more one
var (
effectiveTs []int
certMap = make(map[int]*PlatformCertItem)
)
for _, v := range certs.Certs {
formatEffective := xtime.FormatDateTime(v.EffectiveTime)
effectiveTime, err := time.ParseInLocation(xtime.TimeLayout, formatEffective, time.Local)
if err != nil {
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatEffective, err)
}
eu := int(effectiveTime.Unix())
effectiveTs = append(effectiveTs, eu)
certMap[eu] = v
}
sort.Ints(effectiveTs)
// newest cert
newestCert := certMap[effectiveTs[len(effectiveTs)-1]]
formatExpire := xtime.FormatDateTime(newestCert.ExpireTime)
expireTime, err := time.ParseInLocation(xtime.TimeLayout, formatExpire, time.Local)
if err != nil {
return gopay.NULL, gopay.NULL, fmt.Errorf("time.ParseInLocation(%s, %s),err:%w", xtime.TimeLayout, formatExpire, err)
}
if time.Now().Unix() >= expireTime.Unix() {
// 过期了
return gopay.NULL, gopay.NULL, fmt.Errorf("wechat platform API cert expired, expired time: %s", formatExpire)
}
return newestCert.PublicKey, newestCert.SerialNo, nil
}
// failed
return gopay.NULL, gopay.NULL, fmt.Errorf("GetAndSelectNewestCert() failed or certs is nil: %+v", certs)
}
// 推荐直接使用 client.GetAndSelectNewestCert() 方法
// 获取微信平台证书公钥(获取后自行保存使用,如需定期刷新功能,自行实现)
// 注意事项
// 如果自行实现验证平台签名逻辑的话,需要注意以下事项:
// - 程序实现定期更新平台证书的逻辑,不要硬编码验证应答消息签名的平台证书
// - 定期调用该接口,间隔时间小于12小时
// - 加密请求消息中的敏感信息时,使用最新的平台证书(即:证书启用时间较晚的证书)
// 文档说明:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml
func (c *ClientV3) getPlatformCerts() (certs *PlatformCertRsp, err error) {
var (
eg = new(errgroup.Group)
mu sync.Mutex
)
authorization, err := c.authorization(MethodGet, v3GetCerts, nil)
if err != nil {
return nil, err
}
res, _, bs, err := c.doProdGet(c.ctx, v3GetCerts, authorization)
if err != nil {
return nil, err
}
certs = &PlatformCertRsp{Code: Success}
if res.StatusCode != http.StatusOK {
certs.Code = res.StatusCode
certs.Error = string(bs)
return certs, nil
}
certRsp := new(PlatformCert)
if err = json.Unmarshal(bs, certRsp); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%+v", string(bs), err)
}
for _, v := range certRsp.Data {
cert := v
if cert.EncryptCertificate != nil {
ec := cert.EncryptCertificate
eg.Go(func(ctx context.Context) error {
pubKey, err := c.decryptCerts(ec.Ciphertext, ec.Nonce, ec.AssociatedData)
if err != nil {
return err
}
pci := &PlatformCertItem{
EffectiveTime: cert.EffectiveTime,
ExpireTime: cert.ExpireTime,
PublicKey: pubKey,
SerialNo: cert.SerialNo,
}
mu.Lock()
certs.Certs = append(certs.Certs, pci)
mu.Unlock()
return nil
})
}
}
if err = eg.Wait(); err != nil {
return nil, err
}
return certs, nil
}
// 解密加密的证书
func (c *ClientV3) decryptCerts(ciphertext, nonce, additional string) (wxCerts string, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), c.ApiV3Key)
if err != nil {
return "", fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
return string(decrypt), nil
}
func (c *ClientV3) autoCheckCertProc() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)]
xlog.Errorf("autoCheckCertProc: panic recovered: %s\n%s", r, buf)
// 重启
c.autoCheckCertProc()
}
}()
for {
time.Sleep(time.Hour * 12)
wxPk, wxSerialNo, err := c.GetAndSelectNewestCert()
if err != nil {
xlog.Errorf("c.GetAndSelectNewestCert()err:%+v", err)
continue
}
// decode cert
pubKey, err := xpem.DecodePublicKey([]byte(wxPk))
if err != nil {
xlog.Errorf("xpem.DecodePublicKey(%s)err:%+v", wxPk, err)
continue
}
c.wxPublicKey = pubKey
c.WxSerialNo = wxSerialNo
}
}
+284
View File
@@ -0,0 +1,284 @@
package wechat
import (
"context"
"crypto/rsa"
"fmt"
"net/http"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xhttp"
"github.com/go-pay/gopay/pkg/xlog"
"github.com/go-pay/gopay/pkg/xpem"
)
// ClientV3 微信支付 V3
type ClientV3 struct {
Mchid string
ApiV3Key []byte
SerialNo string
WxSerialNo string
autoSign bool
privateKey *rsa.PrivateKey
wxPublicKey *rsa.PublicKey
ctx context.Context
DebugSwitch gopay.DebugSwitch
}
// NewClientV3 初始化微信客户端 V3
// mchid:商户ID 或者服务商模式的 sp_mchid
// serialNo:商户API证书的证书序列号
// apiV3KeyAPIv3Key,商户平台获取
// privateKey:商户API证书下载后,私钥 apiclient_key.pem 读取后的字符串内容
func NewClientV3(mchid, serialNo, apiV3Key, privateKey string) (client *ClientV3, err error) {
if mchid == util.NULL || serialNo == util.NULL || apiV3Key == util.NULL || privateKey == util.NULL {
return nil, gopay.MissWechatInitParamErr
}
priKey, err := xpem.DecodePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
client = &ClientV3{
Mchid: mchid,
SerialNo: serialNo,
ApiV3Key: []byte(apiV3Key),
privateKey: priKey,
ctx: context.Background(),
DebugSwitch: gopay.DebugOff,
}
return client, nil
}
// AutoVerifySign 开启请求完自动验签功能(默认不开启,推荐开启)
// 开启自动验签,自动开启每12小时一次轮询,请求最新证书操作
func (c *ClientV3) AutoVerifySign() (err error) {
wxPk, wxSerialNo, err := c.GetAndSelectNewestCert()
if err != nil {
return err
}
// decode cert
pubKey, err := xpem.DecodePublicKey([]byte(wxPk))
if err != nil {
return err
}
c.wxPublicKey = pubKey
c.WxSerialNo = wxSerialNo
c.autoSign = true
go c.autoCheckCertProc()
return
}
func (c *ClientV3) doProdPostWithHeader(ctx context.Context, headerMap map[string]string, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
for k, v := range headerMap {
httpClient.Header.Add(k, v)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdPost(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdGet(ctx context.Context, uri, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + uri
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_Url: %s", url)
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Get(url).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdPut(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Put(url).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdDelete(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Delete(url).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdPostFile(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.GetString("meta"))
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeMultipartFormData).Post(url).SendMultipartBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
func (c *ClientV3) doProdPatch(ctx context.Context, bm gopay.BodyMap, path, authorization string) (res *http.Response, si *SignInfo, bs []byte, err error) {
var url = v3BaseUrlCh + path
httpClient := xhttp.NewClient()
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_RequestBody: %s", bm.JsonBody())
xlog.Debugf("Wechat_V3_Authorization: %s", authorization)
}
httpClient.Header.Add(HeaderAuthorization, authorization)
httpClient.Header.Add(HeaderRequestID, fmt.Sprintf("%s-%d", util.RandomString(21), time.Now().Unix()))
httpClient.Header.Add(HeaderSerial, c.WxSerialNo)
httpClient.Header.Add("Accept", "*/*")
res, bs, err = httpClient.Type(xhttp.TypeJSON).Patch(url).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, nil, err
}
si = &SignInfo{
HeaderTimestamp: res.Header.Get(HeaderTimestamp),
HeaderNonce: res.Header.Get(HeaderNonce),
HeaderSignature: res.Header.Get(HeaderSignature),
HeaderSerial: res.Header.Get(HeaderSerial),
SignBody: string(bs),
}
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_Response: %d > %s", res.StatusCode, string(bs))
xlog.Debugf("Wechat_Headers: %#v", res.Header)
xlog.Debugf("Wechat_SignInfo: %#v", si)
}
return res, si, bs, nil
}
+276
View File
@@ -0,0 +1,276 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 创建投诉通知回调地址API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_2.shtml
func (c *ClientV3) V3ComplaintNotifyUrlCreate(ctx context.Context, url string) (wxRsp *ComplaintNotifyUrlRsp, err error) {
bm := make(gopay.BodyMap)
bm.Set("url", url)
authorization, err := c.authorization(MethodPost, v3ComplaintNotifyUrlCreate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ComplaintNotifyUrlCreate, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintNotifyUrl)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询投诉通知回调地址API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_3.shtml
func (c *ClientV3) V3ComplaintNotifyUrlQuery(ctx context.Context) (wxRsp *ComplaintNotifyUrlRsp, err error) {
authorization, err := c.authorization(MethodGet, v3ComplaintNotifyUrlQuery, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, v3ComplaintNotifyUrlQuery, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintNotifyUrl)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 更新投诉通知回调地址API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_4.shtml
func (c *ClientV3) V3ComplaintNotifyUrlUpdate(ctx context.Context, url string) (wxRsp *ComplaintNotifyUrlRsp, err error) {
bm := make(gopay.BodyMap)
bm.Set("url", url)
authorization, err := c.authorization(MethodPut, v3ComplaintNotifyUrlUpdate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPut(ctx, bm, v3ComplaintNotifyUrlUpdate, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintNotifyUrlRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintNotifyUrl)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 删除投诉通知回调地址API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_5.shtml
func (c *ClientV3) V3ComplaintNotifyUrlDelete(ctx context.Context) (wxRsp *EmptyRsp, err error) {
authorization, err := c.authorization(MethodDelete, v3ComplaintNotifyUrlDelete, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdDelete(ctx, nil, v3ComplaintNotifyUrlDelete, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商户上传反馈图片API
// 注意:图片不能超过2MB
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_10.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_10.shtml
func (c *ClientV3) V3ComplaintUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
bmFile := make(gopay.BodyMap)
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
authorization, err := c.authorization(MethodPost, v3ComplaintUploadImage, bmFile)
if err != nil {
return nil, err
}
bm := make(gopay.BodyMap)
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
bm.Set("filename", fileName).Set("sha256", fileSha256)
}).SetFormFile("file", img)
res, si, bs, err := c.doProdPostFile(ctx, bm, v3ComplaintUploadImage, authorization)
if err != nil {
return nil, err
}
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MediaUpload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询投诉单列表API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_11.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_11.shtml
func (c *ClientV3) V3ComplaintList(ctx context.Context, bm gopay.BodyMap) (wxRsp *ComplaintListRsp, err error) {
uri := v3ComplaintList + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintList)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询投诉协商历史API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_12.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_12.shtml
func (c *ClientV3) V3ComplaintNegotiationHistory(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *ComplaintNegotiationHistoryRsp, err error) {
uri := fmt.Sprintf(v3ComplaintNegotiationHistory, complaintId) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintNegotiationHistoryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintNegotiationHistory)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询投诉单详情API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_13.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_13.shtml
func (c *ClientV3) V3ComplaintDetail(ctx context.Context, complaintId string) (wxRsp *ComplaintDetailRsp, err error) {
url := fmt.Sprintf(v3ComplaintDetail, complaintId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ComplaintDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ComplaintDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 提交回复API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_14.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_14.shtml
func (c *ClientV3) V3ComplaintResponse(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3ComplaintResponse, complaintId)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 反馈处理完成API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter10_2_15.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter10_2_15.shtml
func (c *ClientV3) V3ComplaintComplete(ctx context.Context, complaintId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3ComplaintComplete, complaintId)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+281
View File
@@ -0,0 +1,281 @@
package wechat
const (
Success = 0
SignTypeRSA = "RSA"
MethodGet = "GET"
MethodPost = "POST"
MethodPut = "PUT"
MethodDelete = "DELETE"
MethodPATCH = "PATCH"
HeaderAuthorization = "Authorization"
HeaderRequestID = "Request-ID"
HeaderTimestamp = "Wechatpay-Timestamp"
HeaderNonce = "Wechatpay-Nonce"
HeaderSignature = "Wechatpay-Signature"
HeaderSerial = "Wechatpay-Serial"
Authorization = "WECHATPAY2-SHA256-RSA2048"
v3BaseUrlCh = "https://api.mch.weixin.qq.com" // 中国国内
v3GetCerts = "/v3/certificates"
// 基础支付(直连模式)
v3ApiApp = "/v3/pay/transactions/app" // APP 下单
v3ApiJsapi = "/v3/pay/transactions/jsapi" // JSAPI 下单
v3ApiNative = "/v3/pay/transactions/native" // Native 下单
v3ApiH5 = "/v3/pay/transactions/h5" // H5 下单
v3ApiQueryOrderTransactionId = "/v3/pay/transactions/id/%s" // transaction_id 查询订单
v3ApiQueryOrderOutTradeNo = "/v3/pay/transactions/out-trade-no/%s" // out_trade_no 查询订单
v3ApiCloseOrder = "/v3/pay/transactions/out-trade-no/%s/close" // out_trade_no 关闭订单
// 基础支付(服务商模式)
v3ApiPartnerPayApp = "/v3/pay/partner/transactions/app" // partner APP 下单
v3ApiPartnerJsapi = "/v3/pay/partner/transactions/jsapi" // partner JSAPI 下单
v3ApiPartnerNative = "/v3/pay/partner/transactions/native" // partner Native 下单
v3ApiPartnerH5 = "/v3/pay/partner/transactions/h5" // partner H5 下单
v3ApiPartnerQueryOrderTransactionId = "/v3/pay/partner/transactions/id/%s" // partner transaction_id 查询订单
v3ApiPartnerQueryOrderOutTradeNo = "/v3/pay/partner/transactions/out-trade-no/%s" // partner out_trade_no 查询订单
v3ApiPartnerCloseOrder = "/v3/pay/partner/transactions/out-trade-no/%s/close" // partner out_trade_no 关闭订单
// 基础支付(合单支付)
v3CombinePayApp = "/v3/combine-transactions/app"
v3CombinePayH5 = "/v3/combine-transactions/h5"
v3CombinePayJsapi = "/v3/combine-transactions/jsapi"
v3CombineNative = "/v3/combine-transactions/native"
v3CombineQuery = "/v3/combine-transactions/out-trade-no/%s"
v3CombineClose = "/v3/combine-transactions/out-trade-no/%s/close"
// 退款
v3DomesticRefund = "/v3/refund/domestic/refunds" // 申请退款
v3DomesticRefundQuery = "/v3/refund/domestic/refunds/%s" // 查询单笔退款
// 账单
v3TradeBill = "/v3/bill/tradebill" // 申请交易账单 GET
v3FundFlowBill = "/v3/bill/fundflowbill" // 申请资金账单 GET
v3EcommerceFundFlowBill = "/v3/ecommerce/bill/fundflowbill" // 申请特约商户资金账单 GET
v3SubFundFlowBill = "/v3/bill/sub-merchant-fundflowbill" // 申请单个子商户资金账单 GET
// 提现
v3Withdraw = "/v3/ecommerce/fund/withdraw" // 特约商户余额提现 POST
v3WithdrawStatusById = "/v3/ecommerce/fund/withdraw/%s" // withdraw_id 查询特约商户提现状态 GET
v3WithdrawStatusByNo = "/v3/ecommerce/fund/withdraw/out-request-no/%s" // out_request_no 查询特约商户提现状态 GET
v3EcommerceWithdraw = "/v3/merchant/fund/withdraw" // 电商平台预约提现 POST
v3EcommerceWithdrawStatusById = "/v3/merchant/fund/withdraw/withdraw-id/%s" // withdraw_id 电商平台查询预约提现状态 POST
v3EcommerceWithdrawStatusByNo = "/v3/merchant/fund/withdraw/out-request-no/%s" // out_request_no 电商平台查询预约提现状态 POST
v3WithdrawDownloadErrBill = "/v3/merchant/fund/withdraw/bill-type/%s" // bill_type 按日下载提现异常文件 GET
// 微信支付分(免确认模式)
v3ScoreDirectComplete = "/payscore/serviceorder/direct-complete" // 创单结单合并 POST
// 微信支付分(免确认预授权模式)
v3ScorePermission = "/v3/payscore/permissions" // 商户预授权 POST
v3ScorePermissionQuery = "/v3/payscore/permissions/authorization-code/%s" // authorization_code 查询用户授权记录(授权协议号) GET
v3ScorePermissionTerminate = "/v3/payscore/permissions/authorization-code/%s/terminate" // authorization_code 解除用户授权关系(授权协议号) POST
v3ScorePermissionOpenidQuery = "/v3/payscore/permissions/openid/%s" // openid 查询用户授权记录(openid GET
v3ScorePermissionOpenidTerminate = "/v3/payscore/permissions/openid/%s/terminate" // openid 解除用户授权记录(openid POST
// 微信支付分(公共API
v3ScoreOrderCreate = "/v3/payscore/serviceorder" // 创建支付分订单 POST
v3ScoreOrderQuery = "/v3/payscore/serviceorder" // 查询支付分订单 GET
v3ScoreOrderCancel = "/v3/payscore/serviceorder/%s/cancel" // out_trade_no 取消支付分订单 POST
v3ScoreOrderModify = "/v3/payscore/serviceorder/%s/modify" // out_trade_no 修改订单金额 POST
v3ScoreOrderComplete = "/v3/payscore/serviceorder/%s/complete" // out_trade_no 完结支付分订单 POST
v3ScoreOrderPay = "/v3/payscore/serviceorder/%s/pay" // out_trade_no 商户发起催收扣款 POST
v3ScoreOrderSync = "/v3/payscore/serviceorder/%s/sync" // out_trade_no 同步服务订单信息 POST
// 微信先享卡
v3CardPre = "/v3/discount-card/cards" // 预受理领卡请求 POST
v3CardAddUser = "/v3/discount-card/cards/%s/add-user-records" // out_card_code 增加用户记录 POST
v3CardQuery = "/v3/discount-card/cards/%s" // out_card_code 查询先享卡订单 GET
// 支付即服务
v3GuideReg = "/v3/smartguide/guides" // 服务人员注册 POST
v3GuideAssign = "/v3/smartguide/guides/%s/assign" // guide_id 服务人员分配 POST
v3GuideQuery = "/v3/smartguide/guides" // 服务人员查询 GET
v3GuideUpdate = "/v3/smartguide/guides/%s" // guide_id 服务人员信息更新 PATCH
// 智慧商圈
v3BusinessPointsSync = "/v3/businesscircle/points/notify" // 商圈积分同步 POST
v3BusinessAuthPointsQuery = "/v3/businesscircle/user-authorizations/%s" // openid 商圈积分授权查询 GET
// 代金券
v3FavorBatchCreate = "/v3/marketing/favor/coupon-stocks" // 创建代金券批次 POST
v3FavorBatchStart = "/v3/marketing/favor/stocks/%s/start" // stock_id 激活代金券批次 POST
v3FavorBatchGrant = "/v3/marketing/favor/users/%s/coupons" // openid 发放代金券批次 POST
v3FavorBatchPause = "/v3/marketing/favor/stocks/%s/pause" // stock_id 暂停代金券批次 POST
v3FavorBatchRestart = "/v3/marketing/favor/stocks/%s/restart" // stock_id 重启代金券批次 POST
v3FavorBatchList = "/v3/marketing/favor/stocks" // 条件查询批次列表 GET
v3FavorBatchDetail = "/v3/marketing/favor/stocks/%s" // stock_id 查询批次详情 GET
v3FavorDetail = "/v3/marketing/favor/users/%s/coupons/%s" // openid、coupon_id 查询代金券详情 GET
v3FavorMerchant = "/v3/marketing/favor/stocks/%s/merchants" // stock_id 查询代金券可用商户 GET
v3FavorItems = "/v3/marketing/favor/stocks/%s/items" // stock_id 查询代金券可用单品 GET
v3FavorUserCoupons = "/v3/marketing/favor/users/%s/coupons" // openid 根据商户号查用户的券 GET
v3FavorUseFlowDownload = "/v3/marketing/favor/stocks/%s/use-flow" // stock_id 下载批次核销明细 GET
v3FavorRefundFlowDownload = "/v3/marketing/favor/stocks/%s/refund-flow" // stock_id 下载批次退款明细 GET
v3FavorCallbackUrlSet = "/v3/marketing/favor/callbacks" // 设置消息通知地址 POST
v3FavorMediaUploadImage = "/v3/marketing/favor/media/image-upload" // 图片上传(营销专用) POST
// 商家券
v3BusiFavorBatchCreate = "/v3/marketing/busifavor/stocks" // 创建商家券 POST
v3BusiFavorBatchDetail = "/v3/marketing/busifavor/stocks/%s" // stock_id 查询商家券详情 GET
v3BusiFavorUse = "/v3/marketing/busifavor/coupons/use" // 核销用户券 POST
v3BusiFavorUserCoupons = "/v3/marketing/busifavor/users/%s/coupons" // openid 根据过滤条件查询用户券 GET
v3BusiFavorUserCouponDetail = "/v3/marketing/busifavor/users/%s/coupons/%s/appids/%s" // openid、coupon_code、appid 查询用户单张券详情 GET
v3BusiFavorCodeUpload = "/v3/marketing/busifavor/stocks/%s/couponcodes" // stock_id 上传预存code POST
v3BusiFavorCallbackUrlSet = "/v3/marketing/busifavor/callbacks" // 设置商家券事件通知地址 POST
v3BusiFavorCallbackUrl = "/v3/marketing/busifavor/callbacks" // 查询商家券事件通知地址 GET
v3BusiFavorAssociate = "/v3/marketing/busifavor/coupons/associate" // 关联订单信息 POST
v3BusiFavorDisassociate = "/v3/marketing/busifavor/coupons/disassociate" // 取消关联订单信息 POST
v3BusiFavorBatchUpdate = "/v3/marketing/busifavor/stocks/%s/budget" // stock_id 修改批次预算 PATCH
v3BusiFavorInfoUpdate = "/v3/marketing/busifavor/stocks/%s" // stock_id 修改商家券基本信息 PATCH
v3BusiFavorSend = "/v3/marketing/busifavor/coupons/%s/send" // card_id 发放消费卡 POST
v3BusiFavorReturn = "/v3/marketing/busifavor/coupons/return" // 申请退券 POST
v3BusiFavorDeactivate = "/v3/marketing/busifavor/coupons/deactivate" // 使券失效 POST
v3BusiFavorSubsidyPay = "/v3/marketing/busifavor/subsidy/pay-receipts" // 营销补差付款 POST
v3BusiFavorSubsidyPayDetail = "/v3/marketing/busifavor/subsidy/pay-receipts/%s" // subsidy_receipt_id 查询营销补差付款单详情 GET
// 委托营销(合作伙伴)
v3PartnershipsBuild = "/v3/marketing/partnerships/build" // 建立合作关系 POST
v3PartnershipsTerminate = "/v3/marketing/partnerships/terminate" // 终止合作关系 POST
v3PartnershipsList = "/v3/marketing/partnerships" // 查询合作关系列表 GET
// 点金计划(服务商)
v3GoldPlanManage = "/v3/goldplan/merchants/changegoldplanstatus" // 点金计划管理 POST
v3GoldPlanBillManage = "/v3/goldplan/merchants/changecustompagestatus" // 商家小票管理 POST
v3GoldPlanFilterManage = "/v3/goldplan/merchants/set-advertising-industry-filter" // 同业过滤标签管理 POST
v3GoldPlanOpenAdShow = "/v3/goldplan/merchants/open-advertising-show" // 开通广告展示 PATCH
v3GoldPlanCloseAdShow = "/v3/goldplan/merchants/close-advertising-show" // 关闭广告展示 POST
// 消费者投诉2.0
v3ComplaintList = "/v3/merchant-service/complaints-v2" // 查询投诉单列表 GET
v3ComplaintDetail = "/v3/merchant-service/complaints-v2/%s" // 查询投诉单详情 GET
v3ComplaintNegotiationHistory = "/v3/merchant-service/complaints-v2/%s/negotiation-historys" // 查询投诉协商历史 GET
v3ComplaintNotifyUrlCreate = "/v3/merchant-service/complaint-notifications" // 创建投诉通知回调地址 POST
v3ComplaintNotifyUrlQuery = "/v3/merchant-service/complaint-notifications" // 查询投诉通知回调地址 GET
v3ComplaintNotifyUrlUpdate = "/v3/merchant-service/complaint-notifications" // 查询投诉通知回调地址 PUT
v3ComplaintNotifyUrlDelete = "/v3/merchant-service/complaint-notifications" // 删除投诉通知回调地址 DELETE
v3ComplaintResponse = "/v3/merchant-service/complaints-v2/%s/response" // 提交回复 POST
v3ComplaintComplete = "/v3/merchant-service/complaints-v2/%s/complete" // 反馈处理完成 POST
v3ComplaintUploadImage = "/v3/merchant-service/images/upload" // 商户上传反馈图片 POST
// 分账(服务商)
v3ProfitShareOrder = "/v3/profitsharing/orders" // 请求分账 POST
v3ProfitShareQuery = "/v3/profitsharing/orders/%s" // 查询分账结果 GET
v3ProfitShareReturn = "/v3/profitsharing/return-orders" // 请求分账回退 POST
v3ProfitShareReturnResult = "/v3/profitsharing/return-orders/%s" // 查询分账回退结果 GET
v3ProfitShareUnfreeze = "/v3/profitsharing/orders/unfreeze" // 解冻剩余资金 POST
v3ProfitShareUnsplitAmount = "/v3/profitsharing/transactions/%s/amounts" // 查询剩余待分金额 GET
v3ProfitShareAddReceiver = "/v3/profitsharing/receivers/add" // 添加分账接收方 POST
v3ProfitShareDeleteReceiver = "/v3/profitsharing/receivers/delete" // 删除分账接收方 POST
v3ProfitShareMerchantConfigs = "/v3/profitsharing/merchant-configs/%s" // 查询最大分账比例API GET
v3ProfitShareBills = "/v3/profitsharing/bills" // 申请分账账单 GET
// 其他能力
v3MediaUploadImage = "/v3/merchant/media/upload" // 图片上传 POST
v3MediaUploadVideo = "/v3/merchant/media/video_upload" // 视频上传 POST
// 转账
v3Transfer = "/v3/transfer/batches" // 发起批量转账 POST
v3TransferQuery = "/v3/transfer/batches/batch-id/%s" // batch_id 微信批次单号查询批次单 GET
v3TransferDetail = "/v3/transfer/batches/batch-id/%s/details/detail-id/%s" // batch_id、detail_id 微信明细单号查询明细单 GET
v3TransferMerchantQuery = "/v3/transfer/batches/out-batch-no/%s" // out_batch_no 商家批次单号查询批次单 GET
v3TransferMerchantDetail = "/v3/transfer/batches/out-batch-no/%s/details/out-detail-no/%s" // out_batch_no、out_detail_no 商家明细单号查询明细单 GET
v3TransferReceipt = "/v3/transfer/bill-receipt" // 转账电子回单申请受理 POST
v3TransferReceiptQuery = "/v3/transfer/bill-receipt/%s" // out_batch_no 查询转账电子回单 GET
v3TransferDetailReceipt = "/v3/transfer-detail/electronic-receipts" // 转账明细电子回单受理 POST
v3TransferDetailReceiptQuery = "/v3/transfer-detail/electronic-receipts" // 查询转账明细电子回单受理结果 GET
// 转账(服务商)
v3PartnerTransfer = "/v3/partner-transfer/batches" // 发起批量转账 POST
v3PartnerTransferQuery = "/v3/partner-transfer/batches/batch-id/%s" // batch_id 微信批次单号查询批次单 GET
v3PartnerTransferDetail = "/v3/partner-transfer/batches/batch-id/%s/details/detail-id/%s" // batch_id、detail_id 微信明细单号查询明细单 GET
v3PartnerTransferMerchantQuery = "/v3/partner-transfer/batches/out-batch-no/%s" // out_batch_no 商家批次单号查询批次单 GET
v3PartnerTransferMerchantDetail = "/v3/partner-transfer/batches/out-batch-no/%s/details/out-detail-no/%s" // out_batch_no、out_detail_no 商家明细单号查询明细单 GET
// 余额
v3MerchantBalance = "/v3/merchant/fund/balance/%s" // account_type 查询账户实时余额 GET
v3MerchantDayBalance = "/v3/merchant/fund/dayendbalance/%s" // account_type 查询账户日终余额 GET
v3EcommerceBalance = "/v3/ecommerce/fund/balance/%s" // sub_mchid 查询特约商户账户实时余额 GET
v3EcommerceDayBalance = "/v3/ecommerce/fund/enddaybalance/%s" // sub_mchid 查询二级商户账户日终余额 GET
// 来账识别API
v3MerchantIncomeRecord = "/v3/merchantfund/merchant/income-records" // 商户银行来账查询 GET
v3EcommerceIncomeRecord = "/v3/merchantfund/partner/income-records" // 特约商户银行来账查询 GET
// 服务商-特约商户进件
v3Apply4SubSubmit = "/v3/applyment4sub/applyment/" // 提交申请单 POST
v3Apply4SubQueryByBusinessCode = "/v3/applyment4sub/applyment/business_code/%s" // business_code 通过业务申请编号查询申请状态 GET
v3Apply4SubQueryByApplyId = "/v3/applyment4sub/applyment/applyment_id/%s" // applyment_id 通过申请单号查询申请状态 GET
v3Apply4SubModifySettlement = "/v3/apply4sub/sub_merchants/%s/modify-settlement" // sub_mchid 修改结算账号 POST
v3Apply4SubQuerySettlement = "/v3/apply4sub/sub_merchants/%s/settlement" // sub_mchid 查询结算账户 GET
// 电商收付通(商户进件)
v3EcommerceApply = "/v3/ecommerce/applyments/" // 二级商户进件 POST
v3EcommerceApplyQueryById = "/v3/ecommerce/applyments/%d" // applyment_id 通过申请单ID查询申请状态 GET
v3EcommerceApplyQueryByNo = "/v3/ecommerce/applyments/out-request-no/%s" // out_request_no 通过业务申请编号查询申请状态 GET
// 电商收付通(分账)
v3EcommerceProfitShare = "/v3/ecommerce/profitsharing/orders" // 请求分账 POST
v3EcommerceProfitShareQuery = "/v3/ecommerce/profitsharing/orders" // 查询分账结果 GET
v3EcommerceProfitShareReturn = "/v3/ecommerce/profitsharing/returnorders" // 请求分账回退 POST
v3EcommerceProfitShareReturnResult = "/v3/ecommerce/profitsharing/returnorders" // 查询分账回退结果 GET
v3EcommerceProfitShareFinish = "/v3/ecommerce/profitsharing/finish-order" // 完结分账 POST
v3EcommerceProfitShareUnsplitAmount = "/v3/ecommerce/profitsharing/orders/%s/amounts" // transaction_id 查询订单剩余待分金额 GET
v3EcommerceProfitShareAddReceiver = "/v3/ecommerce/profitsharing/receivers/add" // 添加分账接收方 POST
v3EcommerceProfitShareDeleteReceiver = "/v3/ecommerce/profitsharing/receivers/delete" // 删除分账接收方 POST
// 电商收付通(补差)
v3EcommerceSubsidies = "/v3/ecommerce/subsidies/create" // 请求补差 POST
v3EcommerceSubsidiesReturn = "/v3/ecommerce/subsidies/return" // 请求补差回退 POST
v3EcommerceSubsidiesCancel = "/v3/ecommerce/subsidies/cancel" // 取消补差 POST
// 电商收付通(退款)
v3CommerceRefund = "/v3/ecommerce/refunds/apply" // 申请退款 POST
v3CommerceRefundQueryById = "/v3/ecommerce/refunds/id/%s" // refund_id 通过微信支付退款单号查询退款 GET
v3CommerceRefundQueryByNo = "/v3/ecommerce/refunds/out-refund-no/%s" // out_refund_no 通过商户退款单号查询退款 GET
v3CommerceRefundAdvance = "/v3/ecommerce/refunds/%s/return-advance" // refund_id 垫付退款回补 POST
v3CommerceRefundAdvanceResult = "/v3/ecommerce/refunds/%s/return-advance" // refund_id 查询垫付回补结果 GET
// 银行组件(服务商)
v3BankSearchBank = "/v3/capital/capitallhh/banks/search-banks-by-bank-account" // 获取对私银行卡号开户银行 GET
v3BankSearchPersonalList = "/v3/capital/capitallhh/banks/personal-banking" // 查询支持个人业务的银行列表 GET
v3BankSearchCorporateList = "/v3/capital/capitallhh/banks/corporate-banking" // 查询支持对公业务的银行列表 GET
v3BankSearchProvinceList = "/v3/capital/capitallhh/areas/provinces" // 查询省份列表 GET
v3BankSearchCityList = "/v3/capital/capitallhh/areas/provinces/%d/cities" // province_code 查询城市列表 GET
v3BankSearchBranchList = "/v3/capital/capitallhh/banks/%s/branches" // bank_alias_code 查询支行列表 GET
// 特约商户进件申请单状态
ApplyStateEditing = "APPLYMENT_STATE_EDITTING" // 编辑中
ApplyStateAuditing = "APPLYMENT_STATE_AUDITING" // 审核中
ApplyStateRejected = "APPLYMENT_STATE_REJECTED" // 已驳回
ApplyStateToBeConfirmed = "APPLYMENT_STATE_TO_BE_CONFIRMED" // 待账户验证
ApplyStateSigning = "APPLYMENT_STATE_SIGNING" // 开通权限中
ApplyStateFinished = "APPLYMENT_STATE_FINISHED" // 已完成
ApplyStateCanceled = "APPLYMENT_STATE_CANCELED" // 已作废
// 特约商户结算账号类型
ApplySettlementAccountTypeBusiness = "ACCOUNT_TYPE_BUSINESS" // 对公银行账户
ApplySettlementAccountTypePrivate = "ACCOUNT_TYPE_PRIVATE" // 经营者个人银行卡
// 特约商户结算账号汇款验证结果
ApplySettlementVerifying = "VERIFYING" // 系统汇款验证中,商户可发起提现尝试
ApplySettlementVerifySuccess = "VERIFY_SUCCESS" // 系统成功汇款,该账户可正常发起提现
ApplySettlementVerifyFail = "VERIFY_FAIL" // 系统汇款失败,该账户无法发起提现,请检查修改
// 订单号类型,1-微信订单号,2-商户订单号,3-微信侧回跳到商户前端时用于查单的单据查询id(查询支付分订单中会使用)
TransactionId OrderNoType = 1
OutTradeNo OrderNoType = 2
QueryId OrderNoType = 3
// v3 异步通知订单状态
TradeStateSuccess = "SUCCESS" // 支付成功
TradeStateRefund = "REFUND" // 转入退款
TradeStateNoPay = "NOTPAY" // 未支付
TradeStateClosed = "CLOSED" // 已关闭
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
TradeStatePaying = "USERPAYING" // 用户支付中(付款码支付)
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
)
+87
View File
@@ -0,0 +1,87 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 预受理领卡请求API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_1.shtml
func (c *ClientV3) V3DiscountCardApply(ctx context.Context, bm gopay.BodyMap) (wxRsp *DiscountCardApplyRsp, err error) {
authorization, err := c.authorization(MethodPost, v3CardPre, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CardPre, authorization)
if err != nil {
return nil, err
}
wxRsp = &DiscountCardApplyRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(DiscountCardApply)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 增加用户记录API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_2.shtml
func (c *ClientV3) V3DiscountCardAddUser(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
if err = bm.CheckEmptyError("out_card_code"); err != nil {
return nil, err
}
uri := fmt.Sprintf(v3CardAddUser, bm.GetString("out_card_code"))
bm.Remove("out_card_code")
authorization, err := c.authorization(MethodPost, uri, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询先享卡订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_3_3.shtml
func (c *ClientV3) V3DiscountCardQuery(ctx context.Context, outCardCode string) (wxRsp *DiscountCardQueryRsp, err error) {
url := fmt.Sprintf(v3CardQuery, outCardCode)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &DiscountCardQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(DiscountCardQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+360
View File
@@ -0,0 +1,360 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 二级商户进件API
// 注意:本接口会提交一些敏感信息,需调用 client.V3EncryptText() 进行加密。部分图片参数,请先调用 client.V3MediaUploadImage() 上传,获取MediaId
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_1.shtml
func (c *ClientV3) V3EcommerceApply(ctx context.Context, bm gopay.BodyMap) (*EcommerceApplyRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceApply, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceApply, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceApplyRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceApply)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询申请状态API
// 注意:applyId 和 outRequestNo 二选一
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_1_2.shtml
func (c *ClientV3) V3EcommerceApplyStatus(ctx context.Context, applyId int64, outRequestNo string) (*EcommerceApplyStatusRsp, error) {
if applyId == 0 && outRequestNo == gopay.NULL {
return nil, fmt.Errorf("applyId[%d] and outRequestNo[%s] empty at the same time", applyId, outRequestNo)
}
var url string
if applyId != 0 {
url = fmt.Sprintf(v3EcommerceApplyQueryById, applyId)
} else {
url = fmt.Sprintf(v3EcommerceApplyQueryByNo, outRequestNo)
}
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceApplyStatusRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceApplyStatus)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 请求分账API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_1.shtml
func (c *ClientV3) V3EcommerceProfitShare(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShare, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShare, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShare)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询分账结果API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_2.shtml
func (c *ClientV3) V3EcommerceProfitShareQuery(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareQueryRsp, error) {
uri := v3EcommerceProfitShareQuery + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 请求分账回退API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_3.shtml
func (c *ClientV3) V3EcommerceProfitShareReturn(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareReturnRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareReturn, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareReturn, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareReturnRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareReturn)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询分账回退结果API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_4.shtml
func (c *ClientV3) V3EcommerceProfitShareReturnResult(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareReturnResultRsp, error) {
uri := v3EcommerceProfitShareReturnResult + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareReturnResultRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareReturn)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 完结分账API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_5.shtml
func (c *ClientV3) V3EcommerceProfitShareFinish(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareFinishRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareFinish, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareFinish, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareFinishRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareFinish)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询订单剩余待分金额API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_9.shtml
func (c *ClientV3) V3EcommerceProfitShareUnsplitAmount(ctx context.Context, transactionId string) (*EcommerceProfitShareUnsplitAmountRsp, error) {
url := fmt.Sprintf(v3EcommerceProfitShareUnsplitAmount, transactionId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareUnsplitAmountRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareUnsplitAmount)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 添加分账接收方API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_7.shtml
func (c *ClientV3) V3EcommerceProfitShareAddReceiver(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareAddReceiverRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareAddReceiver, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareAddReceiver, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareAddReceiverRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareReceiver)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 删除分账接收方API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_4_8.shtml
func (c *ClientV3) V3EcommerceProfitShareDeleteReceiver(ctx context.Context, bm gopay.BodyMap) (*EcommerceProfitShareDeleteReceiverRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceProfitShareDeleteReceiver, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceProfitShareDeleteReceiver, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceProfitShareDeleteReceiverRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceProfitShareReceiver)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 请求补差API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_1.shtml
func (c *ClientV3) V3EcommerceSubsidies(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidies, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidies, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceSubsidiesRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceSubsidies)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 请求补差回退API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_2.shtml
func (c *ClientV3) V3EcommerceSubsidiesReturn(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesReturnRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidiesReturn, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidiesReturn, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceSubsidiesReturnRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceSubsidiesReturn)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 取消补差API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_5_3.shtml
func (c *ClientV3) V3EcommerceSubsidiesCancel(ctx context.Context, bm gopay.BodyMap) (*EcommerceSubsidiesCancelRsp, error) {
authorization, err := c.authorization(MethodPost, v3EcommerceSubsidiesCancel, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceSubsidiesCancel, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceSubsidiesCancelRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceSubsidiesCancel)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+179
View File
@@ -0,0 +1,179 @@
package wechat
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/aes"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xpem"
)
// 敏感信息加密
func (c *ClientV3) V3EncryptText(text string) (cipherText string, err error) {
if c.wxPublicKey == nil || c.WxSerialNo == "" {
return util.NULL, errors.New("WxPublicKey or WxSerialNo is null")
}
cipherByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, c.wxPublicKey, []byte(text), nil)
if err != nil {
return "", fmt.Errorf("rsa.EncryptOAEP%w", err)
}
return base64.StdEncoding.EncodeToString(cipherByte), nil
}
// 敏感信息解密
func (c *ClientV3) V3DecryptText(cipherText string) (text string, err error) {
cipherByte, _ := base64.StdEncoding.DecodeString(cipherText)
textByte, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, c.privateKey, cipherByte, nil)
if err != nil {
return "", fmt.Errorf("rsa.DecryptOAEP%w", err)
}
return string(textByte), nil
}
// 敏感参数信息加密
// wxPublicKeyContent:微信平台证书内容
func V3EncryptText(text string, wxPublicKeyContent []byte) (cipherText string, err error) {
publicKey, err := xpem.DecodePublicKey(wxPublicKeyContent)
if err != nil {
return gopay.NULL, err
}
cipherByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(text), nil)
if err != nil {
return "", fmt.Errorf("rsa.EncryptOAEP%w", err)
}
return base64.StdEncoding.EncodeToString(cipherByte), nil
}
// 敏感参数信息解密
// privateKeyContent:私钥 apiclient_key.pem 读取后的字符串内容
func V3DecryptText(cipherText string, privateKeyContent []byte) (text string, err error) {
privateKey, err := xpem.DecodePrivateKey(privateKeyContent)
if err != nil {
return gopay.NULL, err
}
cipherByte, _ := base64.StdEncoding.DecodeString(cipherText)
textByte, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, privateKey, cipherByte, nil)
if err != nil {
return "", fmt.Errorf("rsa.DecryptOAEP%w", err)
}
return string(textByte), nil
}
// 解密 普通支付 回调中的加密信息
func V3DecryptNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密 服务商支付 回调中的加密信息
func V3DecryptPartnerNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptPartnerResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptPartnerResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密 普通退款 回调中的加密信息
func V3DecryptRefundNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptRefundResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptRefundResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密 服务商退款 回调中的加密信息
func V3DecryptPartnerRefundNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptPartnerRefundResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptPartnerRefundResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密 合单支付 回调中的加密信息
func V3DecryptCombineNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptCombineResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptCombineResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密分账动账回调中的加密信息
func V3DecryptProfitShareNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptProfitShareResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptProfitShareResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密 支付分 回调中的加密信息
func V3DecryptScoreNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptScoreResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptScoreResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
// 解密商家券回调中的加密信息
func V3DecryptBusifavorNotifyCipherText(ciphertext, nonce, additional, apiV3Key string) (result *V3DecryptBusifavorResult, err error) {
cipherBytes, _ := base64.StdEncoding.DecodeString(ciphertext)
decrypt, err := aes.GCMDecrypt(cipherBytes, []byte(nonce), []byte(additional), []byte(apiV3Key))
if err != nil {
return nil, fmt.Errorf("aes.GCMDecrypt, err:%w", err)
}
result = &V3DecryptBusifavorResult{}
if err = json.Unmarshal(decrypt, result); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s), err:%w", string(decrypt), err)
}
return result, nil
}
+123
View File
@@ -0,0 +1,123 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 点金计划管理API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_1.shtml
func (c *ClientV3) V3GoldPlanManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *GoldPlanManageRsp, err error) {
authorization, err := c.authorization(MethodPost, v3GoldPlanManage, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanManage, authorization)
if err != nil {
return nil, err
}
wxRsp = &GoldPlanManageRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(GoldPlanManage)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商家小票管理API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_2.shtml
func (c *ClientV3) V3GoldPlanBillManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *GoldPlanManageRsp, err error) {
authorization, err := c.authorization(MethodPost, v3GoldPlanBillManage, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanBillManage, authorization)
if err != nil {
return nil, err
}
wxRsp = &GoldPlanManageRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(GoldPlanManage)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 同业过滤标签管理API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_3.shtml
func (c *ClientV3) V3GoldPlanFilterManage(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
authorization, err := c.authorization(MethodPost, v3GoldPlanFilterManage, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanFilterManage, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 开通广告展示API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_4.shtml
func (c *ClientV3) V3GoldPlanOpenAdShow(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
authorization, err := c.authorization(MethodPATCH, v3GoldPlanOpenAdShow, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPatch(ctx, bm, v3GoldPlanOpenAdShow, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 关闭广告展示API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_5_5.shtml
func (c *ClientV3) V3GoldPlanCloseAdShow(ctx context.Context, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
authorization, err := c.authorization(MethodPATCH, v3GoldPlanCloseAdShow, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3GoldPlanCloseAdShow, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+3
View File
@@ -0,0 +1,3 @@
package wechat
// 支付有礼
+456
View File
@@ -0,0 +1,456 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 创建商家券
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_1.shtml
func (c *ClientV3) V3BusiFavorBatchCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorCreateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorBatchCreate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorBatchCreate, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorCreateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchCreate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询商家券详情
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_2.shtml
func (c *ClientV3) V3BusiFavorBatchDetail(ctx context.Context, stockId string) (wxRsp *BusiFavorBatchDetailRsp, err error) {
uri := fmt.Sprintf(v3BusiFavorBatchDetail, stockId)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorBatchDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorBatchDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 核销用户券
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_3.shtml
func (c *ClientV3) V3BusiFavorUse(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorUseRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorUse, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorUse, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorUseRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorUse)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 根据过滤条件查询用户券
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_4.shtml
func (c *ClientV3) V3BusiFavorUserCoupons(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *BusiFavorUserCouponsRsp, err error) {
uri := fmt.Sprintf(v3BusiFavorUserCoupons, openid) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorUserCouponsRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorUserCoupons)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询用户单张券详情
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_5.shtml
func (c *ClientV3) V3BusiFavorUserCouponDetail(ctx context.Context, openid, couponCode, appid string) (wxRsp *BusiFavorUserCouponDetailRsp, err error) {
uri := fmt.Sprintf(v3BusiFavorUserCouponDetail, openid, couponCode, appid)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorUserCouponDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiUserCoupon)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 上传预存code
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_6.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_6.shtml
func (c *ClientV3) V3BusiFavorCodeUpload(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *BusiFavorCodeUploadRsp, err error) {
url := fmt.Sprintf(v3BusiFavorCodeUpload, stockId)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorCodeUploadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorCodeUpload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 设置商家券事件通知地址
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_7.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_7.shtml
func (c *ClientV3) V3BusiFavorCallbackUrlSet(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorCallbackUrlSetRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorCallbackUrlSet, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorCallbackUrlSet, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorCallbackUrlSetRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorCallbackUrlSet)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询商家券事件通知地址
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_8.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_8.shtml
func (c *ClientV3) V3BusiFavorCallbackUrl(ctx context.Context, mchid string) (wxRsp *BusiFavorCallbackUrlRsp, err error) {
uri := v3BusiFavorCallbackUrl + "?mchid=" + mchid
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorCallbackUrlRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorCallbackUrl)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 关联订单信息
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_9.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_9.shtml
func (c *ClientV3) V3BusiFavorAssociate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorAssociateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorAssociate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorAssociate, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorAssociateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorAssociate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 取消关联订单信息
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_10.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_10.shtml
func (c *ClientV3) V3BusiFavorDisassociate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorDisassociateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorDisassociate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorDisassociate, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorDisassociateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorDisassociate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 修改批次预算
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_11.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_11.shtml
func (c *ClientV3) V3BusiFavorBatchUpdate(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *BusiFavorBatchUpdateRsp, err error) {
url := fmt.Sprintf(v3BusiFavorBatchUpdate, stockId)
authorization, err := c.authorization(MethodPATCH, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorBatchUpdateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorBatchUpdate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 修改商家券基本信息
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_12.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_12.shtml
func (c *ClientV3) V3BusiFavorInfoUpdate(ctx context.Context, stockId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3BusiFavorInfoUpdate, stockId)
authorization, err := c.authorization(MethodPATCH, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 发放消费卡
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_6_1.shtml
func (c *ClientV3) V3BusiFavorSend(ctx context.Context, cardId string, bm gopay.BodyMap) (wxRsp *BusiFavorSendRsp, err error) {
url := fmt.Sprintf(v3BusiFavorSend, cardId)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorSendRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorSend)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请退券
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_13.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_13.shtml
func (c *ClientV3) V3BusiFavorReturn(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorReturnRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorReturn, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorReturn, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorReturnRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorReturn)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 使券失效
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_14.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_14.shtml
func (c *ClientV3) V3BusiFavorDeactivate(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorDeactivateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorDeactivate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorDeactivate, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorDeactivateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorDeactivate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 营销补差付款
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_16.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_16.shtml
func (c *ClientV3) V3BusiFavorSubsidyPay(ctx context.Context, bm gopay.BodyMap) (wxRsp *BusiFavorSubsidyPayRsp, err error) {
authorization, err := c.authorization(MethodPost, v3BusiFavorSubsidyPay, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3BusiFavorSubsidyPay, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorSubsidyPayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorSubsidyPay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询营销补差付款单详情
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_18.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_2_18.shtml
func (c *ClientV3) V3BusiFavorSubsidyPayDetail(ctx context.Context, subsidyReceiptId string) (wxRsp *BusiFavorSubsidyPayDetailRsp, err error) {
url := fmt.Sprintf(v3BusiFavorSubsidyPayDetail, subsidyReceiptId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &BusiFavorSubsidyPayDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(BusiFavorSubsidyPay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+399
View File
@@ -0,0 +1,399 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 创建代金券批次
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_1.shtml
func (c *ClientV3) V3FavorBatchCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorBatchCreateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3FavorBatchCreate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3FavorBatchCreate, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchCreateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchCreate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 发放代金券批次
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_2.shtml
func (c *ClientV3) V3FavorBatchGrant(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *FavorBatchGrantRsp, err error) {
url := fmt.Sprintf(v3FavorBatchGrant, openid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchGrantRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchGrant)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 激活代金券批次
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_3.shtml
func (c *ClientV3) V3FavorBatchStart(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchStartRsp, err error) {
url := fmt.Sprintf(v3FavorBatchStart, stockId)
bm := make(gopay.BodyMap)
bm.Set("stock_creator_mchid", stockCreatorMchid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchStartRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchStart)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 条件查询批次列表
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_4.shtml
func (c *ClientV3) V3FavorBatchList(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorBatchListRsp, err error) {
uri := v3FavorBatchList + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchList)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询批次详情
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_5.shtml
func (c *ClientV3) V3FavorBatchDetail(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchDetailRsp, err error) {
uri := fmt.Sprintf(v3FavorBatchDetail, stockId) + "?stock_creator_mchid=" + stockCreatorMchid
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatch)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询代金券详情
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_6.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_6.shtml
func (c *ClientV3) V3FavorDetail(ctx context.Context, appid, couponId, openid string) (wxRsp *FavorDetailRsp, err error) {
uri := fmt.Sprintf(v3FavorDetail, openid, couponId) + "?appid=" + appid
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询代金券可用商户
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_7.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_7.shtml
func (c *ClientV3) V3FavorMerchant(ctx context.Context, stockId, stockCreatorMchid string, limit, offset int) (wxRsp *FavorMerchantRsp, err error) {
if limit == 0 {
limit = 20
}
uri := fmt.Sprintf(v3FavorMerchant, stockId) + "?stock_creator_mchid=" + stockCreatorMchid + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorMerchantRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorMerchant)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询代金券可用单品
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_8.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_8.shtml
func (c *ClientV3) V3FavorItems(ctx context.Context, stockId, stockCreatorMchid string, limit, offset int) (wxRsp *FavorItemsRsp, err error) {
if limit == 0 {
limit = 20
}
uri := fmt.Sprintf(v3FavorItems, stockId) + "?stock_creator_mchid=" + stockCreatorMchid + "&limit=" + util.Int2String(limit) + "&offset=" + util.Int2String(offset)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorItemsRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorItems)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 根据商户号查用户的券
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_9.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_9.shtml
func (c *ClientV3) V3FavorUserCoupons(ctx context.Context, openid string, bm gopay.BodyMap) (wxRsp *FavorUserCouponsRsp, err error) {
uri := fmt.Sprintf(v3FavorUserCoupons, openid) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorUserCouponsRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorUserCoupons)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 下载批次核销明细
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_10.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_10.shtml
func (c *ClientV3) V3FavorUseFlowDownload(ctx context.Context, stockId string) (wxRsp *FavorUseFlowDownloadRsp, err error) {
url := fmt.Sprintf(v3FavorUseFlowDownload, stockId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorUseFlowDownloadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorFlowDownload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 下载批次退款明细
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_11.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_11.shtml
func (c *ClientV3) V3FavorRefundFlowDownload(ctx context.Context, stockId string) (wxRsp *FavorRefundFlowDownloadRsp, err error) {
url := fmt.Sprintf(v3FavorRefundFlowDownload, stockId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorRefundFlowDownloadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorFlowDownload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 设置消息通知地址
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_12.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_12.shtml
func (c *ClientV3) V3FavorCallbackUrlSet(ctx context.Context, bm gopay.BodyMap) (wxRsp *FavorCallbackUrlSetRsp, err error) {
authorization, err := c.authorization(MethodPost, v3FavorCallbackUrlSet, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3FavorCallbackUrlSet, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorCallbackUrlSetRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorCallbackUrl)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 暂停代金券批次
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_13.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_13.shtml
func (c *ClientV3) V3FavorBatchPause(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchPauseRsp, err error) {
url := fmt.Sprintf(v3FavorBatchPause, stockId)
bm := make(gopay.BodyMap)
bm.Set("stock_creator_mchid", stockCreatorMchid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchPauseRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchPause)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 重启代金券批次
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_14.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_1_14.shtml
func (c *ClientV3) V3FavorBatchRestart(ctx context.Context, stockId, stockCreatorMchid string) (wxRsp *FavorBatchRestartRsp, err error) {
url := fmt.Sprintf(v3FavorBatchRestart, stockId)
bm := make(gopay.BodyMap)
bm.Set("stock_creator_mchid", stockCreatorMchid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &FavorBatchRestartRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(FavorBatchRestart)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+45
View File
@@ -0,0 +1,45 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 图片上传(营销专用)
// 注意:图片不能超过2MB
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_0_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_0_1.shtml
func (c *ClientV3) V3FavorMediaUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MarketMediaUploadRsp, err error) {
bmFile := make(gopay.BodyMap)
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
authorization, err := c.authorization(MethodPost, v3FavorMediaUploadImage, bmFile)
if err != nil {
return nil, err
}
bm := make(gopay.BodyMap)
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
bm.Set("filename", fileName).Set("sha256", fileSha256)
}).SetFormFile("file", img)
res, si, bs, err := c.doProdPostFile(ctx, bm, v3FavorMediaUploadImage, authorization)
if err != nil {
return nil, err
}
wxRsp = &MarketMediaUploadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MarketMediaUpload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+89
View File
@@ -0,0 +1,89 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 建立合作关系
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_1.shtml
func (c *ClientV3) V3PartnershipsBuild(ctx context.Context, idempotencyKey string, bm gopay.BodyMap) (wxRsp *PartnershipsBuildRsp, err error) {
authorization, err := c.authorization(MethodPost, v3PartnershipsBuild, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPostWithHeader(ctx, map[string]string{"Idempotency-Key": idempotencyKey}, bm, v3PartnershipsBuild, authorization)
if err != nil {
return nil, err
}
wxRsp = &PartnershipsBuildRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnershipsBuild)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 终止合作关系
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_2.shtml
func (c *ClientV3) V3PartnershipsTerminate(ctx context.Context, idempotencyKey string, bm gopay.BodyMap) (wxRsp *PartnershipsTerminateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3PartnershipsTerminate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPostWithHeader(ctx, map[string]string{"Idempotency-Key": idempotencyKey}, bm, v3PartnershipsTerminate, authorization)
if err != nil {
return nil, err
}
wxRsp = &PartnershipsTerminateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnershipsTerminate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询合作关系列表
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_5_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter9_5_3.shtml
func (c *ClientV3) V3PartnershipsList(ctx context.Context, bm gopay.BodyMap) (wxRsp *PartnershipsListRsp, err error) {
uri := v3PartnershipsList + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &PartnershipsListRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnershipsList)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+79
View File
@@ -0,0 +1,79 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 图片上传API
// 注意:图片不能超过2MB
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter2_1_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter2_1_1.shtml
func (c *ClientV3) V3MediaUploadImage(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
bmFile := make(gopay.BodyMap)
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
authorization, err := c.authorization(MethodPost, v3MediaUploadImage, bmFile)
if err != nil {
return nil, err
}
bm := make(gopay.BodyMap)
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
bm.Set("filename", fileName).Set("sha256", fileSha256)
}).SetFormFile("file", img)
res, si, bs, err := c.doProdPostFile(ctx, bm, v3MediaUploadImage, authorization)
if err != nil {
return nil, err
}
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MediaUpload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 视频上传API
// 注意:媒体视频只支持avi、wmv、mpeg、mp4、mov、mkv、flv、f4v、m4v、rmvb格式,文件大小不能超过5M。
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter2_1_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter2_1_2.shtml
func (c *ClientV3) V3MediaUploadVideo(ctx context.Context, fileName, fileSha256 string, img *util.File) (wxRsp *MediaUploadRsp, err error) {
bmFile := make(gopay.BodyMap)
bmFile.Set("filename", fileName).Set("sha256", fileSha256)
authorization, err := c.authorization(MethodPost, v3MediaUploadVideo, bmFile)
if err != nil {
return nil, err
}
bm := make(gopay.BodyMap)
bm.SetBodyMap("meta", func(bm gopay.BodyMap) {
bm.Set("filename", fileName).Set("sha256", fileSha256)
}).SetFormFile("file", img)
res, si, bs, err := c.doProdPostFile(ctx, bm, v3MediaUploadVideo, authorization)
if err != nil {
return nil, err
}
wxRsp = &MediaUploadRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MediaUpload)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+181
View File
@@ -0,0 +1,181 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 查询特约商户账户实时余额、查询二级商户账户实时余额
// Code = 0 is success
// 注意:服务商时,bm参数传 nil
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_1.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_1.shtml
func (c *ClientV3) V3EcommerceBalance(ctx context.Context, subMchid string, bm gopay.BodyMap) (*EcommerceBalanceRsp, error) {
url := fmt.Sprintf(v3EcommerceBalance, subMchid) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceBalanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceBalance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询二级商户账户日终余额
// date示例值:2019-08-17
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_2.shtml
func (c *ClientV3) V3EcommerceDayBalance(ctx context.Context, subMchid, date string) (*EcommerceBalanceRsp, error) {
uri := fmt.Sprintf(v3EcommerceDayBalance, subMchid) + "?date=" + date
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceBalanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceBalance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询账户实时余额
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_2.shtml
// 电商平台:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_3.shtml
func (c *ClientV3) V3MerchantBalance(ctx context.Context, accountType string) (*MerchantBalanceRsp, error) {
url := fmt.Sprintf(v3MerchantBalance, accountType)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &MerchantBalanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MerchantBalance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询账户日终余额
// date示例值:2019-08-17
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter5_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter5_3.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_7_4.shtml
func (c *ClientV3) V3MerchantDayBalance(ctx context.Context, accountType, date string) (*MerchantBalanceRsp, error) {
uri := fmt.Sprintf(v3MerchantDayBalance, accountType) + "?date=" + date
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &MerchantBalanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MerchantBalance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 特约商户银行来账查询API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_6.shtml
func (c *ClientV3) V3EcommerceIncomeRecord(ctx context.Context, bm gopay.BodyMap) (*PartnerIncomeRecordRsp, error) {
uri := v3EcommerceIncomeRecord + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &PartnerIncomeRecordRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerIncomeRecord)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商户/服务商银行来账查询API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_7.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_7.shtml
func (c *ClientV3) V3MerchantIncomeRecord(ctx context.Context, bm gopay.BodyMap) (*MerchantIncomeRecordRsp, error) {
uri := v3MerchantIncomeRecord + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &MerchantIncomeRecordRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(MerchantIncomeRecord)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+2743
View File
File diff suppressed because it is too large Load Diff
+328
View File
@@ -0,0 +1,328 @@
package wechat
import (
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/xlog"
)
type Resource struct {
OriginalType string `json:"original_type"`
Algorithm string `json:"algorithm"`
Ciphertext string `json:"ciphertext"`
AssociatedData string `json:"associated_data"`
Nonce string `json:"nonce"`
}
type V3DecryptResult struct {
Appid string `json:"appid"`
Mchid string `json:"mchid"`
OutTradeNo string `json:"out_trade_no"`
TransactionId string `json:"transaction_id"`
TradeType string `json:"trade_type"`
TradeState string `json:"trade_state"`
TradeStateDesc string `json:"trade_state_desc"`
BankType string `json:"bank_type"`
Attach string `json:"attach"`
SuccessTime string `json:"success_time"`
Payer *Payer `json:"payer"`
Amount *Amount `json:"amount"`
SceneInfo *SceneInfo `json:"scene_info"`
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
}
type V3DecryptPartnerResult struct {
SpAppid string `json:"sp_appid"`
SpMchid string `json:"sp_mchid"`
SubAppid string `json:"sub_appid"`
SubMchid string `json:"sub_mchid"`
OutTradeNo string `json:"out_trade_no"`
TransactionId string `json:"transaction_id"`
TradeType string `json:"trade_type"`
TradeState string `json:"trade_state"`
TradeStateDesc string `json:"trade_state_desc"`
BankType string `json:"bank_type"`
Attach string `json:"attach"`
SuccessTime string `json:"success_time"`
Payer *PartnerPayer `json:"payer"`
Amount *Amount `json:"amount"`
SceneInfo *SceneInfo `json:"scene_info"`
PromotionDetail []*PromotionDetail `json:"promotion_detail"`
}
type V3DecryptRefundResult struct {
Mchid string `json:"mchid"`
OutTradeNo string `json:"out_trade_no"`
TransactionId string `json:"transaction_id"`
OutRefundNo string `json:"out_refund_no"`
RefundId string `json:"refund_id"`
RefundStatus string `json:"refund_status"`
SuccessTime string `json:"success_time"`
UserReceivedAccount string `json:"user_received_account"`
Amount *RefundAmount `json:"amount"`
}
type V3DecryptPartnerRefundResult struct {
SpMchid string `json:"sp_mchid"`
SubMchid string `json:"sub_mchid"`
OutTradeNo string `json:"out_trade_no"`
TransactionId string `json:"transaction_id"`
OutRefundNo string `json:"out_refund_no"`
RefundId string `json:"refund_id"`
RefundStatus string `json:"refund_status"`
SuccessTime string `json:"success_time"`
UserReceivedAccount string `json:"user_received_account"`
Amount *RefundAmount `json:"amount"`
}
type V3DecryptCombineResult struct {
CombineAppid string `json:"combine_appid"`
CombineMchid string `json:"combine_mchid"`
CombineOutTradeNo string `json:"combine_out_trade_no"`
SceneInfo *SceneInfo `json:"scene_info"`
SubOrders []*SubOrders `json:"sub_orders"` // 最多支持子单条数:50
CombinePayerInfo *Payer `json:"combine_payer_info"` // 支付者信息
}
type V3DecryptScoreResult struct {
Appid string `json:"appid"`
Mchid string `json:"mchid"`
OutOrderNo string `json:"out_order_no"`
ServiceId string `json:"service_id"`
Openid string `json:"openid"`
State string `json:"state"`
StateDescription string `json:"state_description"`
TotalAmount int `json:"total_amount"`
ServiceIntroduction string `json:"service_introduction"`
PostPayments []*PostPayments `json:"post_payments"`
PostDiscounts []*PostDiscounts `json:"post_discounts"`
RiskFund *RiskFund `json:"risk_fund"`
TimeRange *TimeRange `json:"time_range"`
Location *Location `json:"location"`
Attach string `json:"attach"`
NotifyUrl string `json:"notify_url"`
OrderId string `json:"order_id"`
NeedCollection bool `json:"need_collection"`
Collection *Collection `json:"collection"`
}
type V3DecryptProfitShareResult struct {
SpMchid string `json:"sp_mchid"` // 服务商商户号
SubMchid string `json:"sub_mchid"` // 子商户号
TransactionId string `json:"transaction_id"` // 微信订单号
OrderId string `json:"order_id"` // 微信分账/回退单号
OutOrderNo string `json:"out_order_no"` // 商户分账/回退单号
Receiver *Receiver `json:"receiver"`
SuccessTime string `json:"success_time"` // 成功时间
}
type Receiver struct {
Type string `json:"type"` // 分账接收方类型
Account string `json:"account"` // 分账接收方账号
Amount int `json:"amount"` // 分账动账金额
Description string `json:"description"` // 分账/回退描述
}
type V3DecryptBusifavorResult struct {
EventType string `json:"event_type"` // 事件类型
CouponCode string `json:"coupon_code"` // 券code
StockId string `json:"stock_id"` // 批次号
SendTime string `json:"send_time"` // 发放时间
Openid string `json:"openid"` // 用户标识
Unionid string `json:"unionid"` // 用户统一标识
SendChannel string `json:"send_channel"` // 发放渠道
SendMerchant string `json:"send_merchant"` // 发券商户号
AttachInfo *BusifavorAttachInfo `json:"attach_info"` // 发券附加信息
}
type BusifavorAttachInfo struct {
TransactionId string `json:"transaction_id"` // 交易订单编号
ActCode string `json:"act_code"` // 支付有礼活动编号/营销馆活动ID
HallCode string `json:"hall_code"` // 营销馆ID
HallBelongMchID int `json:"hall_belong_mch_id"` // 营销馆所属商户号
CardID string `json:"card_id"` // 会员卡ID
Code string `json:"code"` // 会员卡code
ActivityID string `json:"activity_id"` // 会员活动ID
}
type V3NotifyReq struct {
Id string `json:"id"`
CreateTime string `json:"create_time"`
ResourceType string `json:"resource_type"`
EventType string `json:"event_type"`
Summary string `json:"summary"`
Resource *Resource `json:"resource"`
SignInfo *SignInfo `json:"-"`
}
type V3NotifyRsp struct {
Code string `json:"code"`
Message string `json:"message"`
}
// 解析微信回调请求的参数到 V3NotifyReq 结构体
func V3ParseNotify(req *http.Request) (notifyReq *V3NotifyReq, err error) {
bs, err := ioutil.ReadAll(io.LimitReader(req.Body, int64(3<<20))) // default 3MB change the size you want;
defer req.Body.Close()
if err != nil {
return nil, fmt.Errorf("read request body error:%w", err)
}
si := &SignInfo{
HeaderTimestamp: req.Header.Get(HeaderTimestamp),
HeaderNonce: req.Header.Get(HeaderNonce),
HeaderSignature: req.Header.Get(HeaderSignature),
HeaderSerial: req.Header.Get(HeaderSerial),
SignBody: string(bs),
}
notifyReq = &V3NotifyReq{SignInfo: si}
if err = json.Unmarshal(bs, notifyReq); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s, %+v)%w", string(bs), notifyReq, err)
}
return notifyReq, nil
}
// Deprecated
// 推荐使用 VerifySignByPK()
func (v *V3NotifyReq) VerifySign(wxPkContent string) (err error) {
if v.SignInfo != nil {
return V3VerifySign(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPkContent)
}
return errors.New("verify notify sign, bug SignInfo is nil")
}
// 异步通知验签
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKey() 获取
func (v *V3NotifyReq) VerifySignByPK(wxPublicKey *rsa.PublicKey) (err error) {
if v.SignInfo != nil {
return V3VerifySignByPK(v.SignInfo.HeaderTimestamp, v.SignInfo.HeaderNonce, v.SignInfo.SignBody, v.SignInfo.HeaderSignature, wxPublicKey)
}
return errors.New("verify notify sign, bug SignInfo is nil")
}
// 解密 普通支付 回调中的加密信息
func (v *V3NotifyReq) DecryptCipherText(apiV3Key string) (result *V3DecryptResult, err error) {
if v.Resource != nil {
result, err = V3DecryptNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密 服务商支付 回调中的加密信息
func (v *V3NotifyReq) DecryptPartnerCipherText(apiV3Key string) (result *V3DecryptPartnerResult, err error) {
if v.Resource != nil {
result, err = V3DecryptPartnerNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密 普通退款 回调中的加密信息
func (v *V3NotifyReq) DecryptRefundCipherText(apiV3Key string) (result *V3DecryptRefundResult, err error) {
if v.Resource != nil {
result, err = V3DecryptRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密 服务商退款 回调中的加密信息
func (v *V3NotifyReq) DecryptPartnerRefundCipherText(apiV3Key string) (result *V3DecryptPartnerRefundResult, err error) {
if v.Resource != nil {
result, err = V3DecryptPartnerRefundNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密 合单支付 回调中的加密信息
func (v *V3NotifyReq) DecryptCombineCipherText(apiV3Key string) (result *V3DecryptCombineResult, err error) {
if v.Resource != nil {
result, err = V3DecryptCombineNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密 支付分 回调中的加密信息
func (v *V3NotifyReq) DecryptScoreCipherText(apiV3Key string) (result *V3DecryptScoreResult, err error) {
if v.Resource != nil {
result, err = V3DecryptScoreNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密分账动账回调中的加密信息
func (v *V3NotifyReq) DecryptProfitShareCipherText(apiV3Key string) (result *V3DecryptProfitShareResult, err error) {
if v.Resource != nil {
result, err = V3DecryptProfitShareNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// 解密商家券回调中的加密信息
func (v *V3NotifyReq) DecryptBusifavorCipherText(apiV3Key string) (result *V3DecryptBusifavorResult, err error) {
if v.Resource != nil {
result, err = V3DecryptBusifavorNotifyCipherText(v.Resource.Ciphertext, v.Resource.Nonce, v.Resource.AssociatedData, apiV3Key)
if err != nil {
bytes, _ := json.Marshal(v)
return nil, fmt.Errorf("V3NotifyReq(%s) decrypt cipher text error(%w)", string(bytes), err)
}
return result, nil
}
return nil, errors.New("notify data Resource is nil")
}
// Deprecated
// 暂时不推荐此方法,请使用 wechat.V3ParseNotify()
// 解析微信回调请求的参数到 gopay.BodyMap
func V3ParseNotifyToBodyMap(req *http.Request) (bm gopay.BodyMap, err error) {
bs, err := ioutil.ReadAll(io.LimitReader(req.Body, int64(3<<20))) // default 3MB change the size you want;
defer req.Body.Close()
if err != nil {
xlog.Error("err:", err)
return
}
bm = make(gopay.BodyMap)
if err = json.Unmarshal(bs, &bm); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%s)%w", string(bs), err)
}
return bm, nil
}
+185
View File
@@ -0,0 +1,185 @@
package wechat
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// APP下单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
func (c *ClientV3) V3TransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("mchid") == util.NULL {
bm.Set("mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiApp, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiApp, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// JSAPI/小程序下单API
// Code = 0 is success
// 商户JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
// 商户小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
func (c *ClientV3) V3TransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("mchid") == util.NULL {
bm.Set("mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiJsapi, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiJsapi, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// Native下单API
// Code = 0 is success
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
func (c *ClientV3) V3TransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
if bm.GetString("mchid") == util.NULL {
bm.Set("mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiNative, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiNative, authorization)
if err != nil {
return nil, err
}
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Native)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// H5下单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml
func (c *ClientV3) V3TransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
if bm.GetString("mchid") == util.NULL {
bm.Set("mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiH5, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiH5, authorization)
if err != nil {
return nil, err
}
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
wxRsp.Response = new(H5Url)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_2.shtml
func (c *ClientV3) V3TransactionQueryOrder(ctx context.Context, orderNoType OrderNoType, orderNo string) (wxRsp *QueryOrderRsp, err error) {
var uri string
switch orderNoType {
case TransactionId:
uri = fmt.Sprintf(v3ApiQueryOrderTransactionId, orderNo) + "?mchid=" + c.Mchid
case OutTradeNo:
uri = fmt.Sprintf(v3ApiQueryOrderOutTradeNo, orderNo) + "?mchid=" + c.Mchid
default:
return nil, errors.New("unsupported order number type")
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &QueryOrderRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(QueryOrder)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 关闭订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_3.shtml
func (c *ClientV3) V3TransactionCloseOrder(ctx context.Context, tradeNo string) (wxRsp *CloseOrderRsp, err error) {
url := fmt.Sprintf(v3ApiCloseOrder, tradeNo)
bm := make(gopay.BodyMap)
bm.Set("mchid", c.Mchid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+186
View File
@@ -0,0 +1,186 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 合单APP下单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_1.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_1.shtml
func (c *ClientV3) V3CombineTransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("combine_mchid") == util.NULL {
bm.Set("combine_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3CombinePayApp, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayApp, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 合单JSAPI/小程序下单API
// Code = 0 is success
// 商户JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml
// 商户小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_4.shtml
// 服务商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_3.shtml
// 服务商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_4.shtml
// 电商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_3.shtml
// 电商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_4.shtml
func (c *ClientV3) V3CombineTransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("combine_mchid") == util.NULL {
bm.Set("combine_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3CombinePayJsapi, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayJsapi, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 合单Native下单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_5.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_5.shtml
func (c *ClientV3) V3CombineTransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
if bm.GetString("combine_mchid") == util.NULL {
bm.Set("combine_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3CombineNative, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CombineNative, authorization)
if err != nil {
return nil, err
}
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Native)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 合单H5下单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml
func (c *ClientV3) V3CombineTransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
if bm.GetString("combine_mchid") == util.NULL {
bm.Set("combine_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3CombinePayH5, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CombinePayH5, authorization)
if err != nil {
return nil, err
}
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
wxRsp.Response = new(H5Url)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 合单查询订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_11.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_11.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_11.shtml
func (c *ClientV3) V3CombineQueryOrder(ctx context.Context, traderNo string) (wxRsp *CombineQueryOrderRsp, err error) {
uri := fmt.Sprintf(v3CombineQuery, traderNo)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &CombineQueryOrderRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(CombineQueryOrder)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 合单关闭订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_12.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_12.shtml
func (c *ClientV3) V3CombineCloseOrder(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *CloseOrderRsp, err error) {
url := fmt.Sprintf(v3CombineClose, tradeNo)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+195
View File
@@ -0,0 +1,195 @@
package wechat
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// (服务商、电商模式)APP下单API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_2_1.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_1.shtml
func (c *ClientV3) V3PartnerTransactionApp(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("sp_mchid") == util.NULL {
bm.Set("sp_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiPartnerPayApp, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerPayApp, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// (服务商、电商模式)JSAPI/小程序下单API
// Code = 0 is success
// 服务商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_1.shtml
// 服务商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_5_1.shtml
// 电商JSAPI文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_2.shtml
// 电商小程序文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_3.shtml
func (c *ClientV3) V3PartnerTransactionJsapi(ctx context.Context, bm gopay.BodyMap) (wxRsp *PrepayRsp, err error) {
if bm.GetString("sp_mchid") == util.NULL {
bm.Set("sp_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiPartnerJsapi, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerJsapi, authorization)
if err != nil {
return nil, err
}
wxRsp = &PrepayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Prepay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// (服务商、电商模式)Native下单API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_4_1.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_12.shtml
func (c *ClientV3) V3PartnerTransactionNative(ctx context.Context, bm gopay.BodyMap) (wxRsp *NativeRsp, err error) {
if bm.GetString("sp_mchid") == util.NULL {
bm.Set("sp_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiPartnerNative, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerNative, authorization)
if err != nil {
return nil, err
}
wxRsp = &NativeRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Native)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// (服务商模式)H5下单API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_3_1.shtml
func (c *ClientV3) V3PartnerTransactionH5(ctx context.Context, bm gopay.BodyMap) (wxRsp *H5Rsp, err error) {
if bm.GetString("sp_mchid") == util.NULL {
bm.Set("sp_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, v3ApiPartnerH5, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ApiPartnerH5, authorization)
if err != nil {
return nil, err
}
wxRsp = &H5Rsp{Code: Success, SignInfo: si}
wxRsp.Response = new(H5Url)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// (服务商、电商模式)查询订单API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_2.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_5.shtml
func (c *ClientV3) V3PartnerQueryOrder(ctx context.Context, orderNoType OrderNoType, orderNo string, bm gopay.BodyMap) (wxRsp *PartnerQueryOrderRsp, err error) {
var uri string
if bm.GetString("sp_mchid") == gopay.NULL {
bm.Set("sp_mchid", c.Mchid)
}
switch orderNoType {
case TransactionId:
uri = fmt.Sprintf(v3ApiPartnerQueryOrderTransactionId, orderNo) + "?" + bm.EncodeURLParams()
case OutTradeNo:
uri = fmt.Sprintf(v3ApiPartnerQueryOrderOutTradeNo, orderNo) + "?" + bm.EncodeURLParams()
default:
return nil, errors.New("unsupported order number type")
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &PartnerQueryOrderRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerQueryOrder)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// (服务商、电商模式)关单API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_3.shtml
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_6.shtml
func (c *ClientV3) V3PartnerCloseOrder(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *CloseOrderRsp, err error) {
url := fmt.Sprintf(v3ApiPartnerCloseOrder, tradeNo)
if bm.GetString("sp_mchid") == gopay.NULL {
bm.Set("sp_mchid", c.Mchid)
}
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &CloseOrderRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+285
View File
@@ -0,0 +1,285 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 请求分账API
// 微信会在接到请求后立刻返回请求接收结果,分账结果需要自行调用查询接口来获取
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_1.shtml
func (c *ClientV3) V3ProfitShareOrder(ctx context.Context, bm gopay.BodyMap) (*ProfitShareOrderRsp, error) {
authorization, err := c.authorization(MethodPost, v3ProfitShareOrder, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareOrder, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareOrderRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareOrder)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询分账结果API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_2.shtml
func (c *ClientV3) V3ProfitShareOrderQuery(ctx context.Context, orderNo string, bm gopay.BodyMap) (*ProfitShareOrderQueryRsp, error) {
uri := fmt.Sprintf(v3ProfitShareQuery, orderNo) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareOrderQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareOrderQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 请求分账回退API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_3.shtml
func (c *ClientV3) V3ProfitShareReturn(ctx context.Context, bm gopay.BodyMap) (*ProfitShareReturnRsp, error) {
authorization, err := c.authorization(MethodPost, v3ProfitShareReturn, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareReturn, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareReturnRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareReturn)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询分账回退结果API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_4.shtml
func (c *ClientV3) V3ProfitShareReturnResult(ctx context.Context, returnNo string, bm gopay.BodyMap) (*ProfitShareReturnResultRsp, error) {
uri := fmt.Sprintf(v3ProfitShareReturnResult, returnNo) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareReturnResultRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareReturnResult)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 解冻剩余资金API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_5.shtml
func (c *ClientV3) V3ProfitShareOrderUnfreeze(ctx context.Context, bm gopay.BodyMap) (*ProfitShareOrderUnfreezeRsp, error) {
authorization, err := c.authorization(MethodPost, v3ProfitShareUnfreeze, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareUnfreeze, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareOrderUnfreezeRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareOrderUnfreeze)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询剩余待分金额API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_6.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_6.shtml
func (c *ClientV3) V3ProfitShareUnsplitAmount(ctx context.Context, transId string) (*ProfitShareUnsplitAmountRsp, error) {
url := fmt.Sprintf(v3ProfitShareUnsplitAmount, transId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareUnsplitAmountRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareUnsplitAmount)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询最大分账比例API
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_7.shtml
func (c *ClientV3) V3ProfitShareMerchantConfigs(ctx context.Context, subMchId string) (*ProfitShareMerchantConfigsRsp, error) {
uri := fmt.Sprintf(v3ProfitShareMerchantConfigs, subMchId)
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareMerchantConfigsRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareMerchantConfigs)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 新增分账接收方API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_8.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_8.shtml
func (c *ClientV3) V3ProfitShareAddReceiver(ctx context.Context, bm gopay.BodyMap) (*ProfitShareAddReceiverRsp, error) {
authorization, err := c.authorization(MethodPost, v3ProfitShareAddReceiver, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareAddReceiver, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareAddReceiverRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareAddReceiver)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 删除分账接收方API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_9.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_9.shtml
func (c *ClientV3) V3ProfitShareDeleteReceiver(ctx context.Context, bm gopay.BodyMap) (*ProfitShareDeleteReceiverRsp, error) {
authorization, err := c.authorization(MethodPost, v3ProfitShareDeleteReceiver, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ProfitShareDeleteReceiver, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareDeleteReceiverRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareDeleteReceiver)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请分账账单
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_11.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_1_11.shtml
func (c *ClientV3) V3ProfitShareBills(ctx context.Context, bm gopay.BodyMap) (*ProfitShareBillsRsp, error) {
uri := v3ProfitShareBills + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &ProfitShareBillsRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ProfitShareBills)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+203
View File
@@ -0,0 +1,203 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 申请退款API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml
func (c *ClientV3) V3Refund(ctx context.Context, bm gopay.BodyMap) (wxRsp *RefundRsp, err error) {
authorization, err := c.authorization(MethodPost, v3DomesticRefund, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3DomesticRefund, authorization)
if err != nil {
return nil, err
}
wxRsp = &RefundRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(RefundOrderResponse)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询单笔退款API
// 注意:商户查询时,bm 可传 nil;服务商时,传相应query参数
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_10.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_10.shtml
func (c *ClientV3) V3RefundQuery(ctx context.Context, outRefundNo string, bm gopay.BodyMap) (wxRsp *RefundQueryRsp, err error) {
uri := fmt.Sprintf(v3DomesticRefundQuery, outRefundNo)
if bm != nil {
uri = fmt.Sprintf(v3DomesticRefundQuery, outRefundNo) + "?" + bm.EncodeURLParams()
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &RefundQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(RefundQueryResponse)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 申请退款API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_1.shtml
func (c *ClientV3) V3EcommerceRefund(ctx context.Context, bm gopay.BodyMap) (wxRsp *EcommerceRefundRsp, err error) {
authorization, err := c.authorization(MethodPost, v3CommerceRefund, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3CommerceRefund, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceRefundRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceRefund)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 通过微信支付退款单号查询退款API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_2.shtml
func (c *ClientV3) V3EcommerceRefundQueryById(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundQueryRsp, err error) {
uri := fmt.Sprintf(v3CommerceRefundQueryById, refundId) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceRefundQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceRefundQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 通过商户退款单号查询退款API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_2.shtml
func (c *ClientV3) V3EcommerceRefundQueryByNo(ctx context.Context, outRefundNo string, bm gopay.BodyMap) (wxRsp *EcommerceRefundQueryRsp, err error) {
uri := fmt.Sprintf(v3CommerceRefundQueryByNo, outRefundNo) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceRefundQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceRefundQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 垫付退款回补API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_4.shtml
func (c *ClientV3) V3EcommerceRefundAdvance(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundAdvanceRsp, err error) {
url := fmt.Sprintf(v3CommerceRefundAdvance, refundId)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceRefundAdvanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceRefundAdvance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询垫付回补结果API
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_5.shtml
func (c *ClientV3) V3EcommerceRefundAdvanceResult(ctx context.Context, refundId string, bm gopay.BodyMap) (wxRsp *EcommerceRefundAdvanceRsp, err error) {
uri := fmt.Sprintf(v3CommerceRefundAdvanceResult, refundId) + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &EcommerceRefundAdvanceRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceRefundAdvance)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+361
View File
@@ -0,0 +1,361 @@
package wechat
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 创单结单合并API
// Code = 0 is success
// 注意:限制条件:【免确认订单模式】,用户已授权状态下,可调用该接口。
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_1.shtml
func (c *ClientV3) V3ScoreDirectComplete(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScoreDirectCompleteRsp, err error) {
authorization, err := c.authorization(MethodPost, v3ScoreDirectComplete, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ScoreDirectComplete, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreDirectCompleteRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreDirectComplete)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商户预授权API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_2.shtml
func (c *ClientV3) V3ScorePermission(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScorePermissionRsp, err error) {
authorization, err := c.authorization(MethodPost, v3ScorePermission, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ScorePermission, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScorePermissionRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScorePermission)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询用户授权记录(授权协议号)API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_3.shtml
func (c *ClientV3) V3ScorePermissionQuery(ctx context.Context, authCode, serviceId string) (wxRsp *ScorePermissionQueryRsp, err error) {
uri := fmt.Sprintf(v3ScorePermissionQuery, authCode) + "?service_id=" + serviceId
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScorePermissionQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScorePermissionQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 解除用户授权关系(授权协议号)API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_4.shtml
func (c *ClientV3) V3ScorePermissionTerminate(ctx context.Context, authCode, serviceId, reason string) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3ScorePermissionTerminate, authCode)
bm := make(gopay.BodyMap)
bm.Set("service_id", serviceId).
Set("reason", reason)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询用户授权记录(openidAPI
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_5.shtml
func (c *ClientV3) V3ScorePermissionOpenidQuery(ctx context.Context, appid, openid, serviceid string) (wxRsp *ScorePermissionOpenidQueryRsp, err error) {
uri := fmt.Sprintf(v3ScorePermissionOpenidQuery, openid) + "?appid=" + appid + "&service_id=" + serviceid
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScorePermissionOpenidQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScorePermissionOpenidQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 解除用户授权关系(openidAPI
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_6.shtml
func (c *ClientV3) V3ScorePermissionOpenidTerminate(ctx context.Context, appid, openid, serviceid, reason string) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3ScorePermissionOpenidTerminate, openid)
bm := make(gopay.BodyMap)
bm.Set("service_id", serviceid).
Set("appid", appid).
Set("reason", reason)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 创建支付分订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_14.shtml
func (c *ClientV3) V3ScoreOrderCreate(ctx context.Context, bm gopay.BodyMap) (wxRsp *ScoreOrderCreateRsp, err error) {
authorization, err := c.authorization(MethodPost, v3ScoreOrderCreate, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3ScoreOrderCreate, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderCreateRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderCreate)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询支付分订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_15.shtml
func (c *ClientV3) V3ScoreOrderQuery(ctx context.Context, orderNoType OrderNoType, appid, orderNo, serviceid string) (wxRsp *ScoreOrderQueryRsp, err error) {
var uri string
switch orderNoType {
case OutTradeNo:
uri = v3ScoreOrderQuery + "?appid=" + appid + "&out_order_no=" + orderNo + "&service_id=" + serviceid
case QueryId:
uri = v3ScoreOrderQuery + "?appid=" + appid + "&query_id=" + orderNo + "&service_id=" + serviceid
default:
return nil, errors.New("unsupported order number type")
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 取消支付分订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_16.shtml
func (c *ClientV3) V3ScoreOrderCancel(ctx context.Context, appid, tradeNo, serviceid, reason string) (wxRsp *ScoreOrderCancelRsp, err error) {
url := fmt.Sprintf(v3ScoreOrderCancel, tradeNo)
bm := make(gopay.BodyMap)
bm.Set("appid", appid).
Set("service_id", serviceid).
Set("reason", reason)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderCancelRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderCancel)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 修改订单金额API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_17.shtml
func (c *ClientV3) V3ScoreOrderModify(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderModifyRsp, err error) {
url := fmt.Sprintf(v3ScoreOrderModify, tradeNo)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderModifyRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderModify)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 完结支付分订单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_18.shtml
func (c *ClientV3) V3ScoreOrderComplete(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderCompleteRsp, err error) {
url := fmt.Sprintf(v3ScoreOrderComplete, tradeNo)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderCompleteRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderComplete)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商户发起催收扣款API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_19.shtml
func (c *ClientV3) V3ScoreOrderPay(ctx context.Context, appid, tradeNo, serviceid string) (wxRsp *ScoreOrderPayRsp, err error) {
url := fmt.Sprintf(v3ScoreOrderPay, tradeNo)
bm := make(gopay.BodyMap)
bm.Set("appid", appid).
Set("service_id", serviceid)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderPayRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderPay)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 同步服务订单信息API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_20.shtml
func (c *ClientV3) V3ScoreOrderSync(ctx context.Context, tradeNo string, bm gopay.BodyMap) (wxRsp *ScoreOrderSyncRsp, err error) {
url := fmt.Sprintf(v3ScoreOrderSync, tradeNo)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &ScoreOrderSyncRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(ScoreOrderSync)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+169
View File
@@ -0,0 +1,169 @@
package wechat
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xlog"
"github.com/go-pay/gopay/pkg/xpem"
)
// Deprecated
// 推荐使用 wechat.V3VerifySignByPK()
func V3VerifySign(timestamp, nonce, signBody, sign, wxPubKeyContent string) (err error) {
publicKey, err := xpem.DecodePublicKey([]byte(wxPubKeyContent))
if err != nil {
return err
}
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
signBytes, _ := base64.StdEncoding.DecodeString(sign)
h := sha256.New()
h.Write([]byte(str))
if err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
}
return nil
}
// 微信V3 版本验签(同步/异步)
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKey() 获取
func V3VerifySignByPK(timestamp, nonce, signBody, sign string, wxPublicKey *rsa.PublicKey) (err error) {
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
signBytes, _ := base64.StdEncoding.DecodeString(sign)
h := sha256.New()
h.Write([]byte(str))
if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
}
return nil
}
// PaySignOfJSAPI 获取 JSAPI paySign
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
func (c *ClientV3) PaySignOfJSAPI(appid, prepayid string) (jsapi *JSAPIPayParams, err error) {
ts := util.Int642String(time.Now().Unix())
nonceStr := util.RandomString(32)
pkg := "prepay_id=" + prepayid
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + pkg + "\n"
sign, err := c.rsaSign(_str)
if err != nil {
return nil, err
}
jsapi = &JSAPIPayParams{
AppId: appid,
TimeStamp: ts,
NonceStr: nonceStr,
Package: pkg,
SignType: SignTypeRSA,
PaySign: sign,
}
return jsapi, nil
}
// PaySignOfApp 获取 App sign
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
func (c *ClientV3) PaySignOfApp(appid, prepayid string) (app *AppPayParams, err error) {
ts := util.Int642String(time.Now().Unix())
nonceStr := util.RandomString(32)
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayid + "\n"
sign, err := c.rsaSign(_str)
if err != nil {
return nil, err
}
app = &AppPayParams{
Appid: appid,
Partnerid: c.Mchid,
Prepayid: prepayid,
Package: "Sign=WXPay",
Noncestr: nonceStr,
Timestamp: ts,
Sign: sign,
}
return app, nil
}
// PaySignOfApplet 获取 小程序 paySign
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
func (c *ClientV3) PaySignOfApplet(appid, prepayid string) (applet *AppletParams, err error) {
jsapi, err := c.PaySignOfJSAPI(appid, prepayid)
if err != nil {
return nil, err
}
applet = &AppletParams{
AppId: jsapi.AppId,
TimeStamp: jsapi.TimeStamp,
NonceStr: jsapi.NonceStr,
Package: jsapi.Package,
SignType: jsapi.SignType,
PaySign: jsapi.PaySign,
}
return applet, nil
}
// v3 鉴权请求Header
func (c *ClientV3) authorization(method, path string, bm gopay.BodyMap) (string, error) {
var (
jb = ""
timestamp = time.Now().Unix()
nonceStr = util.RandomString(32)
)
if bm != nil {
jb = bm.JsonBody()
}
ts := util.Int642String(timestamp)
_str := method + "\n" + path + "\n" + ts + "\n" + nonceStr + "\n" + jb + "\n"
if c.DebugSwitch == gopay.DebugOn {
xlog.Debugf("Wechat_V3_SignString:\n%s", _str)
}
sign, err := c.rsaSign(_str)
if err != nil {
return "", err
}
return Authorization + ` mchid="` + c.Mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + c.SerialNo + `",signature="` + sign + `"`, nil
}
func (c *ClientV3) rsaSign(str string) (string, error) {
if c.privateKey == nil {
return "", errors.New("privateKey can't be nil")
}
h := sha256.New()
h.Write([]byte(str))
result, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, h.Sum(nil))
if err != nil {
return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err)
}
return base64.StdEncoding.EncodeToString(result), nil
}
// 自动同步请求验签
func (c *ClientV3) verifySyncSign(si *SignInfo) (err error) {
if c.autoSign && c.wxPublicKey != nil {
if si != nil {
str := si.HeaderTimestamp + "\n" + si.HeaderNonce + "\n" + si.SignBody + "\n"
signBytes, _ := base64.StdEncoding.DecodeString(si.HeaderSignature)
h := sha256.New()
h.Write([]byte(str))
if err = rsa.VerifyPKCS1v15(c.wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
}
return nil
}
return errors.New("auto verify sign, bug SignInfo is nil")
}
return nil
}
+117
View File
@@ -0,0 +1,117 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 服务人员注册API
// 注意:入参加密字段数据加密:client.V3EncryptText()
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_1.shtml
func (c *ClientV3) V3SmartGuideReg(ctx context.Context, bm gopay.BodyMap) (wxRsp *SmartGuideRegRsp, err error) {
authorization, err := c.authorization(MethodPost, v3GuideReg, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3GuideReg, authorization)
if err != nil {
return nil, err
}
wxRsp = &SmartGuideRegRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(SmartGuideReg)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 服务人员分配API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_2.shtml
func (c *ClientV3) V3SmartGuideAssign(ctx context.Context, guideId, tradeNo string) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3GuideAssign, guideId)
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", tradeNo)
authorization, err := c.authorization(MethodPost, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 服务人员查询API
// 注意:入参加密字段数据加密:client.V3EncryptText(),返回参数加密字段解密:client.V3DecryptText()
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_3.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_3.shtml
func (c *ClientV3) V3SmartGuideQuery(ctx context.Context, bm gopay.BodyMap) (wxRsp *SmartGuideQueryRsp, err error) {
if err = bm.CheckEmptyError("store_id"); err != nil {
return nil, err
}
uri := v3GuideQuery + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &SmartGuideQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(SmartGuideQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 服务人员信息更新API
// 注意:入参加密字段数据加密:client.V3EncryptText()
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter8_4_4.shtml
func (c *ClientV3) V3SmartGuideUpdate(ctx context.Context, guideId string, bm gopay.BodyMap) (wxRsp *EmptyRsp, err error) {
url := fmt.Sprintf(v3GuideUpdate, guideId)
authorization, err := c.authorization(MethodPATCH, url, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPatch(ctx, bm, url, authorization)
if err != nil {
return nil, err
}
wxRsp = &EmptyRsp{Code: Success, SignInfo: si}
if res.StatusCode != http.StatusNoContent {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+413
View File
@@ -0,0 +1,413 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-pay/gopay"
)
// 发起批量转账API
// 注意:入参加密字段数据加密:client.V3EncryptText()
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_1.shtml
func (c *ClientV3) V3Transfer(ctx context.Context, bm gopay.BodyMap) (*TransferRsp, error) {
authorization, err := c.authorization(MethodPost, v3Transfer, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3Transfer, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Transfer)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 发起批量转账API(服务商)
// 注意:入参加密字段数据加密:client.V3EncryptText()
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_1.shtml
func (c *ClientV3) V3PartnerTransfer(ctx context.Context, bm gopay.BodyMap) (*TransferRsp, error) {
authorization, err := c.authorization(MethodPost, v3PartnerTransfer, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3PartnerTransfer, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Transfer)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 微信批次单号查询批次单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_2.shtml
func (c *ClientV3) V3TransferQuery(ctx context.Context, batchId string, bm gopay.BodyMap) (*TransferQueryRsp, error) {
url := fmt.Sprintf(v3TransferQuery, batchId)
bm.Remove("batch_id")
uri := url + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 微信批次单号查询批次单API(服务商)
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_2.shtml
func (c *ClientV3) V3PartnerTransferQuery(ctx context.Context, batchId string, bm gopay.BodyMap) (*PartnerTransferQueryRsp, error) {
url := fmt.Sprintf(v3PartnerTransferQuery, batchId)
bm.Remove("batch_id")
uri := url + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &PartnerTransferQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerTransferQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 微信明细单号查询明细单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_3.shtml
func (c *ClientV3) V3TransferDetail(ctx context.Context, batchId, detailId string) (*TransferDetailRsp, error) {
url := fmt.Sprintf(v3TransferDetail, batchId, detailId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferDetailQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 微信明细单号查询明细单API(服务商)
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_3.shtml
func (c *ClientV3) V3PartnerTransferDetail(ctx context.Context, batchId, detailId string) (*PartnerTransferDetailRsp, error) {
url := fmt.Sprintf(v3PartnerTransferDetail, batchId, detailId)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &PartnerTransferDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerTransferDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// Deprecated
// 推荐直接使用 client.V3TransferDetail() 方法
func (c *ClientV3) V3TransferDetailQuery(ctx context.Context, batchId, detailId string) (*TransferDetailRsp, error) {
return c.V3TransferDetail(ctx, batchId, detailId)
}
// 商家批次单号查询批次单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_4.shtml
func (c *ClientV3) V3TransferMerchantQuery(ctx context.Context, outBatchNo string, bm gopay.BodyMap) (*TransferMerchantQueryRsp, error) {
url := fmt.Sprintf(v3TransferMerchantQuery, outBatchNo)
bm.Remove("out_batch_no")
uri := url + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferMerchantQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferMerchantQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商家批次单号查询批次单API(服务商)
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_4.shtml
func (c *ClientV3) V3PartnerTransferMerchantQuery(ctx context.Context, outBatchNo string, bm gopay.BodyMap) (*PartnerTransferMerchantQueryRsp, error) {
url := fmt.Sprintf(v3PartnerTransferMerchantQuery, outBatchNo)
bm.Remove("out_batch_no")
uri := url + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &PartnerTransferMerchantQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerTransferMerchantQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商家明细单号查询明细单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter3_5.shtml
func (c *ClientV3) V3TransferMerchantDetail(ctx context.Context, outBatchNo, outDetailNo string) (*TransferMerchantDetailRsp, error) {
url := fmt.Sprintf(v3TransferMerchantDetail, outBatchNo, outDetailNo)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferMerchantDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferMerchantDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 商家明细单号查询明细单API(服务商)
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter3_5.shtml
func (c *ClientV3) V3PartnerTransferMerchantDetail(ctx context.Context, outBatchNo, outDetailNo string) (*PartnerTransferMerchantDetailRsp, error) {
url := fmt.Sprintf(v3PartnerTransferMerchantDetail, outBatchNo, outDetailNo)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &PartnerTransferMerchantDetailRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(PartnerTransferMerchantDetail)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// Deprecated
// 推荐直接使用 client.V3TransferMerchantDetail() 方法
func (c *ClientV3) V3TransferMerchantDetailQuery(ctx context.Context, outBatchNo, outDetailNo string) (*TransferMerchantDetailRsp, error) {
return c.V3TransferMerchantDetail(ctx, outBatchNo, outDetailNo)
}
// 转账电子回单申请受理API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_1.shtml
func (c *ClientV3) V3TransferReceipt(ctx context.Context, outBatchNo string) (*TransferReceiptRsp, error) {
bm := make(gopay.BodyMap)
bm.Set("out_batch_no", outBatchNo)
authorization, err := c.authorization(MethodPost, v3TransferReceipt, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3TransferReceipt, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferReceiptRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferReceipt)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询转账电子回单API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_2.shtml
func (c *ClientV3) V3TransferReceiptQuery(ctx context.Context, outBatchNo string) (*TransferReceiptQueryRsp, error) {
url := fmt.Sprintf(v3TransferReceiptQuery, outBatchNo)
authorization, err := c.authorization(MethodGet, url, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, url, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferReceiptQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferReceiptQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 转账明细电子回单受理API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_4.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_4.shtml
func (c *ClientV3) V3TransferDetailReceipt(ctx context.Context, bm gopay.BodyMap) (*TransferDetailReceiptRsp, error) {
authorization, err := c.authorization(MethodPost, v3TransferDetailReceipt, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3TransferDetailReceipt, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferDetailReceiptRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferDetailReceipt)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询转账明细电子回单受理结果API
// Code = 0 is success
// 商户文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_5.shtml
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter4_5.shtml
func (c *ClientV3) V3TransferDetailReceiptQuery(ctx context.Context, bm gopay.BodyMap) (*TransferDetailReceiptQueryRsp, error) {
uri := v3TransferDetailReceiptQuery + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &TransferDetailReceiptQueryRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TransferDetailReceiptQuery)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
+176
View File
@@ -0,0 +1,176 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
)
// 特约商户余额提现、二级商户预约提现
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_1.shtml
func (c *ClientV3) V3Withdraw(ctx context.Context, bm gopay.BodyMap) (*WithdrawRsp, error) {
if err := bm.CheckEmptyError("sub_mchid", "out_request_no", "amount"); err != nil {
return nil, err
}
authorization, err := c.authorization(MethodPost, v3Withdraw, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3Withdraw, authorization)
if err != nil {
return nil, err
}
wxRsp := &WithdrawRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(Withdraw)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 查询特约商户提现状态、二级商户查询预约提现状态
// 注意:withdrawId 和 outRequestNo 二选一
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_2.shtml
func (c *ClientV3) V3WithdrawStatus(ctx context.Context, withdrawId, outRequestNo string, bm gopay.BodyMap) (*WithdrawStatusRsp, error) {
if withdrawId == gopay.NULL && outRequestNo == gopay.NULL {
return nil, fmt.Errorf("[%w]: withdrawId[%s] and outRequestNo[%s] empty at the same time", gopay.MissParamErr, withdrawId, outRequestNo)
}
var uri string
if withdrawId != gopay.NULL {
uri = fmt.Sprintf(v3WithdrawStatusById, withdrawId) + "?" + bm.EncodeURLParams()
} else {
uri = fmt.Sprintf(v3WithdrawStatusByNo, outRequestNo) + "?" + bm.EncodeURLParams()
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &WithdrawStatusRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(WithdrawStatus)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 电商平台预约提现
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_2.shtml
func (c *ClientV3) V3EcommerceWithdraw(ctx context.Context, bm gopay.BodyMap) (*EcommerceWithdrawRsp, error) {
if err := bm.CheckEmptyError("out_request_no", "amount", "account_type"); err != nil {
return nil, err
}
authorization, err := c.authorization(MethodPost, v3EcommerceWithdraw, bm)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdPost(ctx, bm, v3EcommerceWithdraw, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceWithdrawRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceWithdraw)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 电商平台查询预约提现状态
// 注意:withdrawId 和 outRequestNo 二选一
// Code = 0 is success
// 电商文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_8_6.shtml
func (c *ClientV3) V3EcommerceWithdrawStatus(ctx context.Context, withdrawId, outRequestNo string) (*EcommerceWithdrawStatusRsp, error) {
if withdrawId == gopay.NULL && outRequestNo == gopay.NULL {
return nil, fmt.Errorf("[%w]: withdrawId[%s] and outRequestNo[%s] empty at the same time", gopay.MissParamErr, withdrawId, outRequestNo)
}
var uri string
if withdrawId != gopay.NULL {
uri = fmt.Sprintf(v3EcommerceWithdrawStatusById, withdrawId)
} else {
uri = fmt.Sprintf(v3EcommerceWithdrawStatusByNo, outRequestNo)
}
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp := &EcommerceWithdrawStatusRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(EcommerceWithdrawStatus)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
// 按日下载提现异常文件
// 注意:如 bill_date 为空,默认查前一天的
// Code = 0 is success
// 服务商文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer_partner/chapter6_3.shtml
func (c *ClientV3) V3WithdrawDownloadErrBill(ctx context.Context, bm gopay.BodyMap) (wxRsp *BillRsp, err error) {
if bm != nil {
if bm.GetString("bill_date") == util.NULL {
now := time.Now()
yesterday := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Format(util.DateLayout)
bm.Set("bill_date", yesterday)
}
bm.Remove("bill_type")
}
uri := fmt.Sprintf(v3WithdrawDownloadErrBill, "NO_SUCC") + "?" + bm.EncodeURLParams()
authorization, err := c.authorization(MethodGet, uri, nil)
if err != nil {
return nil, err
}
res, si, bs, err := c.doProdGet(ctx, uri, authorization)
if err != nil {
return nil, err
}
wxRsp = &BillRsp{Code: Success, SignInfo: si}
wxRsp.Response = new(TradeBill)
if err = json.Unmarshal(bs, wxRsp.Response); err != nil {
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs))
}
if res.StatusCode != http.StatusOK {
wxRsp.Code = res.StatusCode
wxRsp.Error = string(bs)
return wxRsp, nil
}
return wxRsp, c.verifySyncSign(si)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB