增加vendor
202
vendor/github.com/bradfitz/gomemcache/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
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 [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
718
vendor/github.com/bradfitz/gomemcache/memcache/memcache.go
generated
vendored
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011 Google Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package memcache provides a client for the memcached cache server.
|
||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Similar to:
|
||||||
|
// https://godoc.org/google.golang.org/appengine/memcache
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCacheMiss means that a Get failed because the item wasn't present.
|
||||||
|
ErrCacheMiss = errors.New("memcache: cache miss")
|
||||||
|
|
||||||
|
// ErrCASConflict means that a CompareAndSwap call failed due to the
|
||||||
|
// cached value being modified between the Get and the CompareAndSwap.
|
||||||
|
// If the cached value was simply evicted rather than replaced,
|
||||||
|
// ErrNotStored will be returned instead.
|
||||||
|
ErrCASConflict = errors.New("memcache: compare-and-swap conflict")
|
||||||
|
|
||||||
|
// ErrNotStored means that a conditional write operation (i.e. Add or
|
||||||
|
// CompareAndSwap) failed because the condition was not satisfied.
|
||||||
|
ErrNotStored = errors.New("memcache: item not stored")
|
||||||
|
|
||||||
|
// ErrServer means that a server error occurred.
|
||||||
|
ErrServerError = errors.New("memcache: server error")
|
||||||
|
|
||||||
|
// ErrNoStats means that no statistics were available.
|
||||||
|
ErrNoStats = errors.New("memcache: no statistics available")
|
||||||
|
|
||||||
|
// ErrMalformedKey is returned when an invalid key is used.
|
||||||
|
// Keys must be at maximum 250 bytes long and not
|
||||||
|
// contain whitespace or control characters.
|
||||||
|
ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters")
|
||||||
|
|
||||||
|
// ErrNoServers is returned when no servers are configured or available.
|
||||||
|
ErrNoServers = errors.New("memcache: no servers configured or available")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultTimeout is the default socket read/write timeout.
|
||||||
|
DefaultTimeout = 100 * time.Millisecond
|
||||||
|
|
||||||
|
// DefaultMaxIdleConns is the default maximum number of idle connections
|
||||||
|
// kept for any single address.
|
||||||
|
DefaultMaxIdleConns = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const buffered = 8 // arbitrary buffered channel size, for readability
|
||||||
|
|
||||||
|
// resumableError returns true if err is only a protocol-level cache error.
|
||||||
|
// This is used to determine whether or not a server connection should
|
||||||
|
// be re-used or not. If an error occurs, by default we don't reuse the
|
||||||
|
// connection, unless it was just a cache error.
|
||||||
|
func resumableError(err error) bool {
|
||||||
|
switch err {
|
||||||
|
case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func legalKey(key string) bool {
|
||||||
|
if len(key) > 250 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
if key[i] <= ' ' || key[i] == 0x7f {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
crlf = []byte("\r\n")
|
||||||
|
space = []byte(" ")
|
||||||
|
resultOK = []byte("OK\r\n")
|
||||||
|
resultStored = []byte("STORED\r\n")
|
||||||
|
resultNotStored = []byte("NOT_STORED\r\n")
|
||||||
|
resultExists = []byte("EXISTS\r\n")
|
||||||
|
resultNotFound = []byte("NOT_FOUND\r\n")
|
||||||
|
resultDeleted = []byte("DELETED\r\n")
|
||||||
|
resultEnd = []byte("END\r\n")
|
||||||
|
resultOk = []byte("OK\r\n")
|
||||||
|
resultTouched = []byte("TOUCHED\r\n")
|
||||||
|
|
||||||
|
resultClientErrorPrefix = []byte("CLIENT_ERROR ")
|
||||||
|
versionPrefix = []byte("VERSION")
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a memcache client using the provided server(s)
|
||||||
|
// with equal weight. If a server is listed multiple times,
|
||||||
|
// it gets a proportional amount of weight.
|
||||||
|
func New(server ...string) *Client {
|
||||||
|
ss := new(ServerList)
|
||||||
|
ss.SetServers(server...)
|
||||||
|
return NewFromSelector(ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromSelector returns a new Client using the provided ServerSelector.
|
||||||
|
func NewFromSelector(ss ServerSelector) *Client {
|
||||||
|
return &Client{selector: ss}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is a memcache client.
|
||||||
|
// It is safe for unlocked use by multiple concurrent goroutines.
|
||||||
|
type Client struct {
|
||||||
|
// Timeout specifies the socket read/write timeout.
|
||||||
|
// If zero, DefaultTimeout is used.
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// MaxIdleConns specifies the maximum number of idle connections that will
|
||||||
|
// be maintained per address. If less than one, DefaultMaxIdleConns will be
|
||||||
|
// used.
|
||||||
|
//
|
||||||
|
// Consider your expected traffic rates and latency carefully. This should
|
||||||
|
// be set to a number higher than your peak parallel requests.
|
||||||
|
MaxIdleConns int
|
||||||
|
|
||||||
|
selector ServerSelector
|
||||||
|
|
||||||
|
lk sync.Mutex
|
||||||
|
freeconn map[string][]*conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item is an item to be got or stored in a memcached server.
|
||||||
|
type Item struct {
|
||||||
|
// Key is the Item's key (250 bytes maximum).
|
||||||
|
Key string
|
||||||
|
|
||||||
|
// Value is the Item's value.
|
||||||
|
Value []byte
|
||||||
|
|
||||||
|
// Flags are server-opaque flags whose semantics are entirely
|
||||||
|
// up to the app.
|
||||||
|
Flags uint32
|
||||||
|
|
||||||
|
// Expiration is the cache expiration time, in seconds: either a relative
|
||||||
|
// time from now (up to 1 month), or an absolute Unix epoch time.
|
||||||
|
// Zero means the Item has no expiration time.
|
||||||
|
Expiration int32
|
||||||
|
|
||||||
|
// Compare and swap ID.
|
||||||
|
casid uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// conn is a connection to a server.
|
||||||
|
type conn struct {
|
||||||
|
nc net.Conn
|
||||||
|
rw *bufio.ReadWriter
|
||||||
|
addr net.Addr
|
||||||
|
c *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// release returns this connection back to the client's free pool
|
||||||
|
func (cn *conn) release() {
|
||||||
|
cn.c.putFreeConn(cn.addr, cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) extendDeadline() {
|
||||||
|
cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// condRelease releases this connection if the error pointed to by err
|
||||||
|
// is nil (not an error) or is only a protocol level error (e.g. a
|
||||||
|
// cache miss). The purpose is to not recycle TCP connections that
|
||||||
|
// are bad.
|
||||||
|
func (cn *conn) condRelease(err *error) {
|
||||||
|
if *err == nil || resumableError(*err) {
|
||||||
|
cn.release()
|
||||||
|
} else {
|
||||||
|
cn.nc.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) putFreeConn(addr net.Addr, cn *conn) {
|
||||||
|
c.lk.Lock()
|
||||||
|
defer c.lk.Unlock()
|
||||||
|
if c.freeconn == nil {
|
||||||
|
c.freeconn = make(map[string][]*conn)
|
||||||
|
}
|
||||||
|
freelist := c.freeconn[addr.String()]
|
||||||
|
if len(freelist) >= c.maxIdleConns() {
|
||||||
|
cn.nc.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.freeconn[addr.String()] = append(freelist, cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) {
|
||||||
|
c.lk.Lock()
|
||||||
|
defer c.lk.Unlock()
|
||||||
|
if c.freeconn == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
freelist, ok := c.freeconn[addr.String()]
|
||||||
|
if !ok || len(freelist) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
cn = freelist[len(freelist)-1]
|
||||||
|
c.freeconn[addr.String()] = freelist[:len(freelist)-1]
|
||||||
|
return cn, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) netTimeout() time.Duration {
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
return c.Timeout
|
||||||
|
}
|
||||||
|
return DefaultTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) maxIdleConns() int {
|
||||||
|
if c.MaxIdleConns > 0 {
|
||||||
|
return c.MaxIdleConns
|
||||||
|
}
|
||||||
|
return DefaultMaxIdleConns
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectTimeoutError is the error type used when it takes
|
||||||
|
// too long to connect to the desired host. This level of
|
||||||
|
// detail can generally be ignored.
|
||||||
|
type ConnectTimeoutError struct {
|
||||||
|
Addr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cte *ConnectTimeoutError) Error() string {
|
||||||
|
return "memcache: connect timeout to " + cte.Addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) dial(addr net.Addr) (net.Conn, error) {
|
||||||
|
type connError struct {
|
||||||
|
cn net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout())
|
||||||
|
if err == nil {
|
||||||
|
return nc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||||
|
return nil, &ConnectTimeoutError{addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getConn(addr net.Addr) (*conn, error) {
|
||||||
|
cn, ok := c.getFreeConn(addr)
|
||||||
|
if ok {
|
||||||
|
cn.extendDeadline()
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
nc, err := c.dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn = &conn{
|
||||||
|
nc: nc,
|
||||||
|
addr: addr,
|
||||||
|
rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)),
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
cn.extendDeadline()
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error {
|
||||||
|
addr, err := c.selector.PickServer(item.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cn, err := c.getConn(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cn.condRelease(&err)
|
||||||
|
if err = fn(c, cn.rw, item); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FlushAll() error {
|
||||||
|
return c.selector.Each(c.flushAllFromAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the item for the given key. ErrCacheMiss is returned for a
|
||||||
|
// memcache cache miss. The key must be at most 250 bytes in length.
|
||||||
|
func (c *Client) Get(key string) (item *Item, err error) {
|
||||||
|
err = c.withKeyAddr(key, func(addr net.Addr) error {
|
||||||
|
return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it })
|
||||||
|
})
|
||||||
|
if err == nil && item == nil {
|
||||||
|
err = ErrCacheMiss
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch updates the expiry for the given key. The seconds parameter is either
|
||||||
|
// a Unix timestamp or, if seconds is less than 1 month, the number of seconds
|
||||||
|
// into the future at which time the item will expire. Zero means the item has
|
||||||
|
// no expiration time. ErrCacheMiss is returned if the key is not in the cache.
|
||||||
|
// The key must be at most 250 bytes in length.
|
||||||
|
func (c *Client) Touch(key string, seconds int32) (err error) {
|
||||||
|
return c.withKeyAddr(key, func(addr net.Addr) error {
|
||||||
|
return c.touchFromAddr(addr, []string{key}, seconds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) {
|
||||||
|
if !legalKey(key) {
|
||||||
|
return ErrMalformedKey
|
||||||
|
}
|
||||||
|
addr, err := c.selector.PickServer(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) {
|
||||||
|
cn, err := c.getConn(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cn.condRelease(&err)
|
||||||
|
return fn(cn.rw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error {
|
||||||
|
return c.withKeyAddr(key, func(addr net.Addr) error {
|
||||||
|
return c.withAddrRw(addr, fn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error {
|
||||||
|
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||||
|
if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := parseGetResponse(rw.Reader, cb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushAllFromAddr send the flush_all command to the given addr
|
||||||
|
func (c *Client) flushAllFromAddr(addr net.Addr) error {
|
||||||
|
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||||
|
if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := rw.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line, resultOk):
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping sends the version command to the given addr
|
||||||
|
func (c *Client) ping(addr net.Addr) error {
|
||||||
|
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||||
|
if _, err := fmt.Fprintf(rw, "version\r\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := rw.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(line, versionPrefix):
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("memcache: unexpected response line from ping: %q", string(line))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error {
|
||||||
|
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||||
|
for _, key := range keys {
|
||||||
|
if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := rw.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line, resultTouched):
|
||||||
|
break
|
||||||
|
case bytes.Equal(line, resultNotFound):
|
||||||
|
return ErrCacheMiss
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti is a batch version of Get. The returned map from keys to
|
||||||
|
// items may have fewer elements than the input slice, due to memcache
|
||||||
|
// cache misses. Each key must be at most 250 bytes in length.
|
||||||
|
// If no error is returned, the returned map will also be non-nil.
|
||||||
|
func (c *Client) GetMulti(keys []string) (map[string]*Item, error) {
|
||||||
|
var lk sync.Mutex
|
||||||
|
m := make(map[string]*Item)
|
||||||
|
addItemToMap := func(it *Item) {
|
||||||
|
lk.Lock()
|
||||||
|
defer lk.Unlock()
|
||||||
|
m[it.Key] = it
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMap := make(map[net.Addr][]string)
|
||||||
|
for _, key := range keys {
|
||||||
|
if !legalKey(key) {
|
||||||
|
return nil, ErrMalformedKey
|
||||||
|
}
|
||||||
|
addr, err := c.selector.PickServer(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyMap[addr] = append(keyMap[addr], key)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan error, buffered)
|
||||||
|
for addr, keys := range keyMap {
|
||||||
|
go func(addr net.Addr, keys []string) {
|
||||||
|
ch <- c.getFromAddr(addr, keys, addItemToMap)
|
||||||
|
}(addr, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _ = range keyMap {
|
||||||
|
if ge := <-ch; ge != nil {
|
||||||
|
err = ge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGetResponse reads a GET response from r and calls cb for each
|
||||||
|
// read and allocated Item
|
||||||
|
func parseGetResponse(r *bufio.Reader, cb func(*Item)) error {
|
||||||
|
for {
|
||||||
|
line, err := r.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bytes.Equal(line, resultEnd) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
it := new(Item)
|
||||||
|
size, err := scanGetResponseLine(line, it)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
it.Value = make([]byte, size+2)
|
||||||
|
_, err = io.ReadFull(r, it.Value)
|
||||||
|
if err != nil {
|
||||||
|
it.Value = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !bytes.HasSuffix(it.Value, crlf) {
|
||||||
|
it.Value = nil
|
||||||
|
return fmt.Errorf("memcache: corrupt get result read")
|
||||||
|
}
|
||||||
|
it.Value = it.Value[:size]
|
||||||
|
cb(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanGetResponseLine populates it and returns the declared size of the item.
|
||||||
|
// It does not read the bytes of the item.
|
||||||
|
func scanGetResponseLine(line []byte, it *Item) (size int, err error) {
|
||||||
|
pattern := "VALUE %s %d %d %d\r\n"
|
||||||
|
dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid}
|
||||||
|
if bytes.Count(line, space) == 3 {
|
||||||
|
pattern = "VALUE %s %d %d\r\n"
|
||||||
|
dest = dest[:3]
|
||||||
|
}
|
||||||
|
n, err := fmt.Sscanf(string(line), pattern, dest...)
|
||||||
|
if err != nil || n != len(dest) {
|
||||||
|
return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line)
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes the given item, unconditionally.
|
||||||
|
func (c *Client) Set(item *Item) error {
|
||||||
|
return c.onItem(item, (*Client).set)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) set(rw *bufio.ReadWriter, item *Item) error {
|
||||||
|
return c.populateOne(rw, "set", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add writes the given item, if no value already exists for its
|
||||||
|
// key. ErrNotStored is returned if that condition is not met.
|
||||||
|
func (c *Client) Add(item *Item) error {
|
||||||
|
return c.onItem(item, (*Client).add)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) add(rw *bufio.ReadWriter, item *Item) error {
|
||||||
|
return c.populateOne(rw, "add", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace writes the given item, but only if the server *does*
|
||||||
|
// already hold data for this key
|
||||||
|
func (c *Client) Replace(item *Item) error {
|
||||||
|
return c.onItem(item, (*Client).replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error {
|
||||||
|
return c.populateOne(rw, "replace", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndSwap writes the given item that was previously returned
|
||||||
|
// by Get, if the value was neither modified or evicted between the
|
||||||
|
// Get and the CompareAndSwap calls. The item's Key should not change
|
||||||
|
// between calls but all other item fields may differ. ErrCASConflict
|
||||||
|
// is returned if the value was modified in between the
|
||||||
|
// calls. ErrNotStored is returned if the value was evicted in between
|
||||||
|
// the calls.
|
||||||
|
func (c *Client) CompareAndSwap(item *Item) error {
|
||||||
|
return c.onItem(item, (*Client).cas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error {
|
||||||
|
return c.populateOne(rw, "cas", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error {
|
||||||
|
if !legalKey(item.Key) {
|
||||||
|
return ErrMalformedKey
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if verb == "cas" {
|
||||||
|
_, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n",
|
||||||
|
verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n",
|
||||||
|
verb, item.Key, item.Flags, item.Expiration, len(item.Value))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = rw.Write(item.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := rw.Write(crlf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := rw.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line, resultStored):
|
||||||
|
return nil
|
||||||
|
case bytes.Equal(line, resultNotStored):
|
||||||
|
return ErrNotStored
|
||||||
|
case bytes.Equal(line, resultExists):
|
||||||
|
return ErrCASConflict
|
||||||
|
case bytes.Equal(line, resultNotFound):
|
||||||
|
return ErrCacheMiss
|
||||||
|
}
|
||||||
|
return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) {
|
||||||
|
_, err := fmt.Fprintf(rw, format, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rw.Flush(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line, err := rw.ReadSlice('\n')
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error {
|
||||||
|
line, err := writeReadLine(rw, format, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line, resultOK):
|
||||||
|
return nil
|
||||||
|
case bytes.Equal(line, expect):
|
||||||
|
return nil
|
||||||
|
case bytes.Equal(line, resultNotStored):
|
||||||
|
return ErrNotStored
|
||||||
|
case bytes.Equal(line, resultExists):
|
||||||
|
return ErrCASConflict
|
||||||
|
case bytes.Equal(line, resultNotFound):
|
||||||
|
return ErrCacheMiss
|
||||||
|
}
|
||||||
|
return fmt.Errorf("memcache: unexpected response line: %q", string(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the item with the provided key. The error ErrCacheMiss is
|
||||||
|
// returned if the item didn't already exist in the cache.
|
||||||
|
func (c *Client) Delete(key string) error {
|
||||||
|
return c.withKeyRw(key, func(rw *bufio.ReadWriter) error {
|
||||||
|
return writeExpectf(rw, resultDeleted, "delete %s\r\n", key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAll deletes all items in the cache.
|
||||||
|
func (c *Client) DeleteAll() error {
|
||||||
|
return c.withKeyRw("", func(rw *bufio.ReadWriter) error {
|
||||||
|
return writeExpectf(rw, resultDeleted, "flush_all\r\n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping checks all instances if they are alive. Returns error if any
|
||||||
|
// of them is down.
|
||||||
|
func (c *Client) Ping() error {
|
||||||
|
return c.selector.Each(c.ping)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment atomically increments key by delta. The return value is
|
||||||
|
// the new value after being incremented or an error. If the value
|
||||||
|
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||||
|
// memcached must be an decimal number, or an error will be returned.
|
||||||
|
// On 64-bit overflow, the new value wraps around.
|
||||||
|
func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) {
|
||||||
|
return c.incrDecr("incr", key, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement atomically decrements key by delta. The return value is
|
||||||
|
// the new value after being decremented or an error. If the value
|
||||||
|
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||||
|
// memcached must be an decimal number, or an error will be returned.
|
||||||
|
// On underflow, the new value is capped at zero and does not wrap
|
||||||
|
// around.
|
||||||
|
func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) {
|
||||||
|
return c.incrDecr("decr", key, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) {
|
||||||
|
var val uint64
|
||||||
|
err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error {
|
||||||
|
line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line, resultNotFound):
|
||||||
|
return ErrCacheMiss
|
||||||
|
case bytes.HasPrefix(line, resultClientErrorPrefix):
|
||||||
|
errMsg := line[len(resultClientErrorPrefix) : len(line)-2]
|
||||||
|
return errors.New("memcache: client error: " + string(errMsg))
|
||||||
|
}
|
||||||
|
val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
129
vendor/github.com/bradfitz/gomemcache/memcache/selector.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011 Google Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/crc32"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerSelector is the interface that selects a memcache server
|
||||||
|
// as a function of the item's key.
|
||||||
|
//
|
||||||
|
// All ServerSelector implementations must be safe for concurrent use
|
||||||
|
// by multiple goroutines.
|
||||||
|
type ServerSelector interface {
|
||||||
|
// PickServer returns the server address that a given item
|
||||||
|
// should be shared onto.
|
||||||
|
PickServer(key string) (net.Addr, error)
|
||||||
|
Each(func(net.Addr) error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerList is a simple ServerSelector. Its zero value is usable.
|
||||||
|
type ServerList struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
addrs []net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticAddr caches the Network() and String() values from any net.Addr.
|
||||||
|
type staticAddr struct {
|
||||||
|
ntw, str string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStaticAddr(a net.Addr) net.Addr {
|
||||||
|
return &staticAddr{
|
||||||
|
ntw: a.Network(),
|
||||||
|
str: a.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticAddr) Network() string { return s.ntw }
|
||||||
|
func (s *staticAddr) String() string { return s.str }
|
||||||
|
|
||||||
|
// SetServers changes a ServerList's set of servers at runtime and is
|
||||||
|
// safe for concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// Each server is given equal weight. A server is given more weight
|
||||||
|
// if it's listed multiple times.
|
||||||
|
//
|
||||||
|
// SetServers returns an error if any of the server names fail to
|
||||||
|
// resolve. No attempt is made to connect to the server. If any error
|
||||||
|
// is returned, no changes are made to the ServerList.
|
||||||
|
func (ss *ServerList) SetServers(servers ...string) error {
|
||||||
|
naddr := make([]net.Addr, len(servers))
|
||||||
|
for i, server := range servers {
|
||||||
|
if strings.Contains(server, "/") {
|
||||||
|
addr, err := net.ResolveUnixAddr("unix", server)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
naddr[i] = newStaticAddr(addr)
|
||||||
|
} else {
|
||||||
|
tcpaddr, err := net.ResolveTCPAddr("tcp", server)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
naddr[i] = newStaticAddr(tcpaddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.mu.Lock()
|
||||||
|
defer ss.mu.Unlock()
|
||||||
|
ss.addrs = naddr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each iterates over each server calling the given function
|
||||||
|
func (ss *ServerList) Each(f func(net.Addr) error) error {
|
||||||
|
ss.mu.RLock()
|
||||||
|
defer ss.mu.RUnlock()
|
||||||
|
for _, a := range ss.addrs {
|
||||||
|
if err := f(a); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyBufPool returns []byte buffers for use by PickServer's call to
|
||||||
|
// crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the
|
||||||
|
// copies, which at least are bounded in size and small)
|
||||||
|
var keyBufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]byte, 256)
|
||||||
|
return &b
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *ServerList) PickServer(key string) (net.Addr, error) {
|
||||||
|
ss.mu.RLock()
|
||||||
|
defer ss.mu.RUnlock()
|
||||||
|
if len(ss.addrs) == 0 {
|
||||||
|
return nil, ErrNoServers
|
||||||
|
}
|
||||||
|
if len(ss.addrs) == 1 {
|
||||||
|
return ss.addrs[0], nil
|
||||||
|
}
|
||||||
|
bufp := keyBufPool.Get().(*[]byte)
|
||||||
|
n := copy(*bufp, key)
|
||||||
|
cs := crc32.ChecksumIEEE((*bufp)[:n])
|
||||||
|
keyBufPool.Put(bufp)
|
||||||
|
|
||||||
|
return ss.addrs[cs%uint32(len(ss.addrs))], nil
|
||||||
|
}
|
||||||
23
vendor/github.com/fatih/structs/.gitignore
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
13
vendor/github.com/fatih/structs/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- tip
|
||||||
|
sudo: false
|
||||||
|
before_install:
|
||||||
|
- go get github.com/axw/gocov/gocov
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||||
21
vendor/github.com/fatih/structs/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Fatih Arslan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
163
vendor/github.com/fatih/structs/README.md
generated
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Structs [](http://godoc.org/github.com/fatih/structs) [](https://travis-ci.org/fatih/structs) [](https://coveralls.io/r/fatih/structs)
|
||||||
|
|
||||||
|
Structs contains various utilities to work with Go (Golang) structs. It was
|
||||||
|
initially used by me to convert a struct into a `map[string]interface{}`. With
|
||||||
|
time I've added other utilities for structs. It's basically a high level
|
||||||
|
package based on primitives from the reflect package. Feel free to add new
|
||||||
|
functions or improve the existing code.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/fatih/structs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage and Examples
|
||||||
|
|
||||||
|
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
|
||||||
|
many global functions to manipulate or organize your struct data. Lets define
|
||||||
|
and declare a struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Server struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ID int
|
||||||
|
Enabled bool
|
||||||
|
users []string // not exported
|
||||||
|
http.Server // embedded
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &Server{
|
||||||
|
Name: "gopher",
|
||||||
|
ID: 123456,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Convert a struct to a map[string]interface{}
|
||||||
|
// => {"Name":"gopher", "ID":123456, "Enabled":true}
|
||||||
|
m := structs.Map(server)
|
||||||
|
|
||||||
|
// Convert the values of a struct to a []interface{}
|
||||||
|
// => ["gopher", 123456, true]
|
||||||
|
v := structs.Values(server)
|
||||||
|
|
||||||
|
// Convert the names of a struct to a []string
|
||||||
|
// (see "Names methods" for more info about fields)
|
||||||
|
n := structs.Names(server)
|
||||||
|
|
||||||
|
// Convert the values of a struct to a []*Field
|
||||||
|
// (see "Field methods" for more info about fields)
|
||||||
|
f := structs.Fields(server)
|
||||||
|
|
||||||
|
// Return the struct name => "Server"
|
||||||
|
n := structs.Name(server)
|
||||||
|
|
||||||
|
// Check if any field of a struct is initialized or not.
|
||||||
|
h := structs.HasZero(server)
|
||||||
|
|
||||||
|
// Check if all fields of a struct is initialized or not.
|
||||||
|
z := structs.IsZero(server)
|
||||||
|
|
||||||
|
// Check if server is a struct or a pointer to struct
|
||||||
|
i := structs.IsStruct(server)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Struct methods
|
||||||
|
|
||||||
|
The structs functions can be also used as independent methods by creating a new
|
||||||
|
`*structs.Struct`. This is handy if you want to have more control over the
|
||||||
|
structs (such as retrieving a single Field).
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a new struct type:
|
||||||
|
s := structs.New(server)
|
||||||
|
|
||||||
|
m := s.Map() // Get a map[string]interface{}
|
||||||
|
v := s.Values() // Get a []interface{}
|
||||||
|
f := s.Fields() // Get a []*Field
|
||||||
|
n := s.Names() // Get a []string
|
||||||
|
f := s.Field(name) // Get a *Field based on the given field name
|
||||||
|
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
|
||||||
|
n := s.Name() // Get the struct name
|
||||||
|
h := s.HasZero() // Check if any field is uninitialized
|
||||||
|
z := s.IsZero() // Check if all fields are uninitialized
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field methods
|
||||||
|
|
||||||
|
We can easily examine a single Field for more detail. Below you can see how we
|
||||||
|
get and interact with various field methods:
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
s := structs.New(server)
|
||||||
|
|
||||||
|
// Get the Field struct for the "Name" field
|
||||||
|
name := s.Field("Name")
|
||||||
|
|
||||||
|
// Get the underlying value, value => "gopher"
|
||||||
|
value := name.Value().(string)
|
||||||
|
|
||||||
|
// Set the field's value
|
||||||
|
name.Set("another gopher")
|
||||||
|
|
||||||
|
// Get the field's kind, kind => "string"
|
||||||
|
name.Kind()
|
||||||
|
|
||||||
|
// Check if the field is exported or not
|
||||||
|
if name.IsExported() {
|
||||||
|
fmt.Println("Name field is exported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the value is a zero value, such as "" for string, 0 for int
|
||||||
|
if !name.IsZero() {
|
||||||
|
fmt.Println("Name is initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the field is an anonymous (embedded) field
|
||||||
|
if !name.IsEmbedded() {
|
||||||
|
fmt.Println("Name is not an embedded field")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
|
||||||
|
tagValue := name.Tag("json")
|
||||||
|
```
|
||||||
|
|
||||||
|
Nested structs are supported too:
|
||||||
|
|
||||||
|
```go
|
||||||
|
addrField := s.Field("Server").Field("Addr")
|
||||||
|
|
||||||
|
// Get the value for addr
|
||||||
|
a := addrField.Value().(string)
|
||||||
|
|
||||||
|
// Or get all fields
|
||||||
|
httpServer := s.Field("Server").Fields()
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also get a slice of Fields from the Struct type to iterate over all
|
||||||
|
fields. This is handy if you wish to examine all fields:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s := structs.New(server)
|
||||||
|
|
||||||
|
for _, f := range s.Fields() {
|
||||||
|
fmt.Printf("field name: %+v\n", f.Name())
|
||||||
|
|
||||||
|
if f.IsExported() {
|
||||||
|
fmt.Printf("value : %+v\n", f.Value())
|
||||||
|
fmt.Printf("is zero : %+v\n", f.IsZero())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Fatih Arslan](https://github.com/fatih)
|
||||||
|
* [Cihangir Savas](https://github.com/cihangir)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT) - see LICENSE.md for more details
|
||||||
141
vendor/github.com/fatih/structs/field.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotExported = errors.New("field is not exported")
|
||||||
|
errNotSettable = errors.New("field is not settable")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Field represents a single struct field that encapsulates high level
|
||||||
|
// functions around the field.
|
||||||
|
type Field struct {
|
||||||
|
value reflect.Value
|
||||||
|
field reflect.StructField
|
||||||
|
defaultTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag returns the value associated with key in the tag string. If there is no
|
||||||
|
// such key in the tag, Tag returns the empty string.
|
||||||
|
func (f *Field) Tag(key string) string {
|
||||||
|
return f.field.Tag.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the underlying value of the field. It panics if the field
|
||||||
|
// is not exported.
|
||||||
|
func (f *Field) Value() interface{} {
|
||||||
|
return f.value.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||||
|
func (f *Field) IsEmbedded() bool {
|
||||||
|
return f.field.Anonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExported returns true if the given field is exported.
|
||||||
|
func (f *Field) IsExported() bool {
|
||||||
|
return f.field.PkgPath == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the given field is not initialized (has a zero value).
|
||||||
|
// It panics if the field is not exported.
|
||||||
|
func (f *Field) IsZero() bool {
|
||||||
|
zero := reflect.Zero(f.value.Type()).Interface()
|
||||||
|
current := f.Value()
|
||||||
|
|
||||||
|
return reflect.DeepEqual(current, zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the given field
|
||||||
|
func (f *Field) Name() string {
|
||||||
|
return f.field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||||
|
func (f *Field) Kind() reflect.Kind {
|
||||||
|
return f.value.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the field to given value v. It returns an error if the field is not
|
||||||
|
// settable (not addressable or not exported) or if the given value's type
|
||||||
|
// doesn't match the fields type.
|
||||||
|
func (f *Field) Set(val interface{}) error {
|
||||||
|
// we can't set unexported fields, so be sure this field is exported
|
||||||
|
if !f.IsExported() {
|
||||||
|
return errNotExported
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we get here? not sure...
|
||||||
|
if !f.value.CanSet() {
|
||||||
|
return errNotSettable
|
||||||
|
}
|
||||||
|
|
||||||
|
given := reflect.ValueOf(val)
|
||||||
|
|
||||||
|
if f.value.Kind() != given.Kind() {
|
||||||
|
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
f.value.Set(given)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero sets the field to its zero value. It returns an error if the field is not
|
||||||
|
// settable (not addressable or not exported).
|
||||||
|
func (f *Field) Zero() error {
|
||||||
|
zero := reflect.Zero(f.value.Type()).Interface()
|
||||||
|
return f.Set(zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||||
|
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||||
|
// checking of that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field *http.Request `structs:"-"`
|
||||||
|
//
|
||||||
|
// It panics if field is not exported or if field's kind is not struct
|
||||||
|
func (f *Field) Fields() []*Field {
|
||||||
|
return getFields(f.value, f.defaultTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field returns the field from a nested struct. It panics if the nested struct
|
||||||
|
// is not exported or if the field was not found.
|
||||||
|
func (f *Field) Field(name string) *Field {
|
||||||
|
field, ok := f.FieldOk(name)
|
||||||
|
if !ok {
|
||||||
|
panic("field not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldOk returns the field from a nested struct. The boolean returns whether
|
||||||
|
// the field was found (true) or not (false).
|
||||||
|
func (f *Field) FieldOk(name string) (*Field, bool) {
|
||||||
|
value := &f.value
|
||||||
|
// value must be settable so we need to make sure it holds the address of the
|
||||||
|
// variable and not a copy, so we can pass the pointer to strctVal instead of a
|
||||||
|
// copy (which is not assigned to any variable, hence not settable).
|
||||||
|
// see "https://blog.golang.org/laws-of-reflection#TOC_8."
|
||||||
|
if f.value.Kind() != reflect.Ptr {
|
||||||
|
a := f.value.Addr()
|
||||||
|
value = &a
|
||||||
|
}
|
||||||
|
v := strctVal(value.Interface())
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
field, ok := t.FieldByName(name)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Field{
|
||||||
|
field: field,
|
||||||
|
value: v.FieldByName(name),
|
||||||
|
}, true
|
||||||
|
}
|
||||||
584
vendor/github.com/fatih/structs/structs.go
generated
vendored
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
// Package structs contains various utilities functions to work with structs.
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultTagName is the default tag name for struct fields which provides
|
||||||
|
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||||
|
// for more info.
|
||||||
|
DefaultTagName = "structs" // struct's field default tag name
|
||||||
|
)
|
||||||
|
|
||||||
|
// Struct encapsulates a struct type to provide several high level functions
|
||||||
|
// around the struct.
|
||||||
|
type Struct struct {
|
||||||
|
raw interface{}
|
||||||
|
value reflect.Value
|
||||||
|
TagName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||||
|
// not struct.
|
||||||
|
func New(s interface{}) *Struct {
|
||||||
|
return &Struct{
|
||||||
|
raw: s,
|
||||||
|
value: strctVal(s),
|
||||||
|
TagName: DefaultTagName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||||
|
// of the map are the field names and the values of the map the associated
|
||||||
|
// values of the fields. The default key string is the struct field name but
|
||||||
|
// can be changed in the struct field's tag value. The "structs" key in the
|
||||||
|
// struct's field tag value is the key name. Example:
|
||||||
|
//
|
||||||
|
// // Field appears in map as key "myName".
|
||||||
|
// Name string `structs:"myName"`
|
||||||
|
//
|
||||||
|
// A tag value with the content of "-" ignores that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structs:"-"`
|
||||||
|
//
|
||||||
|
// A tag value with the content of "string" uses the stringer to get the value. Example:
|
||||||
|
//
|
||||||
|
// // The value will be output of Animal's String() func.
|
||||||
|
// // Map will panic if Animal does not implement String().
|
||||||
|
// Field *Animal `structs:"field,string"`
|
||||||
|
//
|
||||||
|
// A tag value with the option of "flatten" used in a struct field is to flatten its fields
|
||||||
|
// in the output map. Example:
|
||||||
|
//
|
||||||
|
// // The FieldStruct's fields will be flattened into the output map.
|
||||||
|
// FieldStruct time.Time `structs:",flatten"`
|
||||||
|
//
|
||||||
|
// A tag value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structs:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structs:",omitnested"`
|
||||||
|
//
|
||||||
|
// A tag value with the option of "omitempty" ignores that particular field if
|
||||||
|
// the field value is empty. Example:
|
||||||
|
//
|
||||||
|
// // Field appears in map as key "myName", but the field is
|
||||||
|
// // skipped if empty.
|
||||||
|
// Field string `structs:"myName,omitempty"`
|
||||||
|
//
|
||||||
|
// // Field appears in map as key "Field" (the default), but
|
||||||
|
// // the field is skipped if empty.
|
||||||
|
// Field string `structs:",omitempty"`
|
||||||
|
//
|
||||||
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
|
// fields will be neglected.
|
||||||
|
func (s *Struct) Map() map[string]interface{} {
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
s.FillMap(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||||
|
// given map.
|
||||||
|
func (s *Struct) FillMap(out map[string]interface{}) {
|
||||||
|
if out == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := s.structFields()
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
name := field.Name
|
||||||
|
val := s.value.FieldByName(name)
|
||||||
|
isSubStruct := false
|
||||||
|
var finalVal interface{}
|
||||||
|
|
||||||
|
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||||
|
if tagName != "" {
|
||||||
|
name = tagName
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the value is a zero value and the field is marked as omitempty do
|
||||||
|
// not include
|
||||||
|
if tagOpts.Has("omitempty") {
|
||||||
|
zero := reflect.Zero(val.Type()).Interface()
|
||||||
|
current := val.Interface()
|
||||||
|
|
||||||
|
if reflect.DeepEqual(current, zero) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tagOpts.Has("omitnested") {
|
||||||
|
finalVal = s.nested(val)
|
||||||
|
|
||||||
|
v := reflect.ValueOf(val.Interface())
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Map, reflect.Struct:
|
||||||
|
isSubStruct = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalVal = val.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagOpts.Has("string") {
|
||||||
|
s, ok := val.Interface().(fmt.Stringer)
|
||||||
|
if ok {
|
||||||
|
out[name] = s.String()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSubStruct && (tagOpts.Has("flatten")) {
|
||||||
|
for k := range finalVal.(map[string]interface{}) {
|
||||||
|
out[k] = finalVal.(map[string]interface{})[k]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out[name] = finalVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values converts the given s struct's field values to a []interface{}. A
|
||||||
|
// struct tag with the content of "-" ignores the that particular field.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field int `structs:"-"`
|
||||||
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Fields is not processed further by this package.
|
||||||
|
// Field time.Time `structs:",omitnested"`
|
||||||
|
// Field *http.Request `structs:",omitnested"`
|
||||||
|
//
|
||||||
|
// A tag value with the option of "omitempty" ignores that particular field and
|
||||||
|
// is not added to the values if the field value is empty. Example:
|
||||||
|
//
|
||||||
|
// // Field is skipped if empty
|
||||||
|
// Field string `structs:",omitempty"`
|
||||||
|
//
|
||||||
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
|
// fields will be neglected.
|
||||||
|
func (s *Struct) Values() []interface{} {
|
||||||
|
fields := s.structFields()
|
||||||
|
|
||||||
|
var t []interface{}
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
val := s.value.FieldByName(field.Name)
|
||||||
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||||
|
|
||||||
|
// if the value is a zero value and the field is marked as omitempty do
|
||||||
|
// not include
|
||||||
|
if tagOpts.Has("omitempty") {
|
||||||
|
zero := reflect.Zero(val.Type()).Interface()
|
||||||
|
current := val.Interface()
|
||||||
|
|
||||||
|
if reflect.DeepEqual(current, zero) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagOpts.Has("string") {
|
||||||
|
s, ok := val.Interface().(fmt.Stringer)
|
||||||
|
if ok {
|
||||||
|
t = append(t, s.String())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
|
// look out for embedded structs, and convert them to a
|
||||||
|
// []interface{} to be added to the final values slice
|
||||||
|
t = append(t, Values(val.Interface())...)
|
||||||
|
} else {
|
||||||
|
t = append(t, val.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||||
|
// ignores the checking of that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structs:"-"`
|
||||||
|
//
|
||||||
|
// It panics if s's kind is not struct.
|
||||||
|
func (s *Struct) Fields() []*Field {
|
||||||
|
return getFields(s.value, s.TagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns a slice of field names. A struct tag with the content of "-"
|
||||||
|
// ignores the checking of that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structs:"-"`
|
||||||
|
//
|
||||||
|
// It panics if s's kind is not struct.
|
||||||
|
func (s *Struct) Names() []string {
|
||||||
|
fields := getFields(s.value, s.TagName)
|
||||||
|
|
||||||
|
names := make([]string, len(fields))
|
||||||
|
|
||||||
|
for i, field := range fields {
|
||||||
|
names[i] = field.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFields(v reflect.Value, tagName string) []*Field {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
var fields []*Field
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
|
||||||
|
if tag := field.Tag.Get(tagName); tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &Field{
|
||||||
|
field: field,
|
||||||
|
value: v.FieldByName(field.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = append(fields, f)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field returns a new Field struct that provides several high level functions
|
||||||
|
// around a single struct field entity. It panics if the field is not found.
|
||||||
|
func (s *Struct) Field(name string) *Field {
|
||||||
|
f, ok := s.FieldOk(name)
|
||||||
|
if !ok {
|
||||||
|
panic("field not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldOk returns a new Field struct that provides several high level functions
|
||||||
|
// around a single struct field entity. The boolean returns true if the field
|
||||||
|
// was found.
|
||||||
|
func (s *Struct) FieldOk(name string) (*Field, bool) {
|
||||||
|
t := s.value.Type()
|
||||||
|
|
||||||
|
field, ok := t.FieldByName(name)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Field{
|
||||||
|
field: field,
|
||||||
|
value: s.value.FieldByName(name),
|
||||||
|
defaultTag: s.TagName,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if all fields in a struct is a zero value (not
|
||||||
|
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||||
|
// that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structs:"-"`
|
||||||
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structs:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structs:",omitnested"`
|
||||||
|
//
|
||||||
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
|
func (s *Struct) IsZero() bool {
|
||||||
|
fields := s.structFields()
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
val := s.value.FieldByName(field.Name)
|
||||||
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
|
ok := IsZero(val.Interface())
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero value of the given field, such as "" for string, 0 for int
|
||||||
|
zero := reflect.Zero(val.Type()).Interface()
|
||||||
|
|
||||||
|
// current value of the given field
|
||||||
|
current := val.Interface()
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(current, zero) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||||
|
// A struct tag with the content of "-" ignores the checking of that particular
|
||||||
|
// field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structs:"-"`
|
||||||
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structs:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structs:",omitnested"`
|
||||||
|
//
|
||||||
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
|
func (s *Struct) HasZero() bool {
|
||||||
|
fields := s.structFields()
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
val := s.value.FieldByName(field.Name)
|
||||||
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
|
ok := HasZero(val.Interface())
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero value of the given field, such as "" for string, 0 for int
|
||||||
|
zero := reflect.Zero(val.Type()).Interface()
|
||||||
|
|
||||||
|
// current value of the given field
|
||||||
|
current := val.Interface()
|
||||||
|
|
||||||
|
if reflect.DeepEqual(current, zero) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the structs's type name within its package. For more info refer
|
||||||
|
// to Name() function.
|
||||||
|
func (s *Struct) Name() string {
|
||||||
|
return s.value.Type().Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// structFields returns the exported struct fields for a given s struct. This
|
||||||
|
// is a convenient helper method to avoid duplicate code in some of the
|
||||||
|
// functions.
|
||||||
|
func (s *Struct) structFields() []reflect.StructField {
|
||||||
|
t := s.value.Type()
|
||||||
|
|
||||||
|
var f []reflect.StructField
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
// we can't access the value of unexported fields
|
||||||
|
if field.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't check if it's omitted
|
||||||
|
if tag := field.Tag.Get(s.TagName); tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f = append(f, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func strctVal(s interface{}) reflect.Value {
|
||||||
|
v := reflect.ValueOf(s)
|
||||||
|
|
||||||
|
// if pointer get the underlying element≤
|
||||||
|
for v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
panic("not struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map converts the given struct to a map[string]interface{}. For more info
|
||||||
|
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||||
|
func Map(s interface{}) map[string]interface{} {
|
||||||
|
return New(s).Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||||
|
// given map.
|
||||||
|
func FillMap(s interface{}, out map[string]interface{}) {
|
||||||
|
New(s).FillMap(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values converts the given struct to a []interface{}. For more info refer to
|
||||||
|
// Struct types Values() method. It panics if s's kind is not struct.
|
||||||
|
func Values(s interface{}) []interface{} {
|
||||||
|
return New(s).Values()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||||
|
// Fields() method. It panics if s's kind is not struct.
|
||||||
|
func Fields(s interface{}) []*Field {
|
||||||
|
return New(s).Fields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns a slice of field names. For more info refer to Struct types
|
||||||
|
// Names() method. It panics if s's kind is not struct.
|
||||||
|
func Names(s interface{}) []string {
|
||||||
|
return New(s).Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||||
|
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||||
|
func IsZero(s interface{}) bool {
|
||||||
|
return New(s).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasZero returns true if any field is equal to a zero value. For more info
|
||||||
|
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||||
|
func HasZero(s interface{}) bool {
|
||||||
|
return New(s).HasZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||||
|
// struct.
|
||||||
|
func IsStruct(s interface{}) bool {
|
||||||
|
v := reflect.ValueOf(s)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// uninitialized zero value of a struct
|
||||||
|
if v.Kind() == reflect.Invalid {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the structs's type name within its package. It returns an
|
||||||
|
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||||
|
func Name(s interface{}) string {
|
||||||
|
return New(s).Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested retrieves recursively all types for the given value and returns the
|
||||||
|
// nested value.
|
||||||
|
func (s *Struct) nested(val reflect.Value) interface{} {
|
||||||
|
var finalVal interface{}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(val.Interface())
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
n := New(val.Interface())
|
||||||
|
n.TagName = s.TagName
|
||||||
|
m := n.Map()
|
||||||
|
|
||||||
|
// do not add the converted value if there are no exported fields, ie:
|
||||||
|
// time.Time
|
||||||
|
if len(m) == 0 {
|
||||||
|
finalVal = val.Interface()
|
||||||
|
} else {
|
||||||
|
finalVal = m
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
// get the element type of the map
|
||||||
|
mapElem := val.Type()
|
||||||
|
switch val.Type().Kind() {
|
||||||
|
case reflect.Ptr, reflect.Array, reflect.Map,
|
||||||
|
reflect.Slice, reflect.Chan:
|
||||||
|
mapElem = val.Type().Elem()
|
||||||
|
if mapElem.Kind() == reflect.Ptr {
|
||||||
|
mapElem = mapElem.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only iterate over struct types, ie: map[string]StructType,
|
||||||
|
// map[string][]StructType,
|
||||||
|
if mapElem.Kind() == reflect.Struct ||
|
||||||
|
(mapElem.Kind() == reflect.Slice &&
|
||||||
|
mapElem.Elem().Kind() == reflect.Struct) {
|
||||||
|
m := make(map[string]interface{}, val.Len())
|
||||||
|
for _, k := range val.MapKeys() {
|
||||||
|
m[k.String()] = s.nested(val.MapIndex(k))
|
||||||
|
}
|
||||||
|
finalVal = m
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(arslan): should this be optional?
|
||||||
|
finalVal = val.Interface()
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if val.Type().Kind() == reflect.Interface {
|
||||||
|
finalVal = val.Interface()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(arslan): should this be optional?
|
||||||
|
// do not iterate of non struct types, just pass the value. Ie: []int,
|
||||||
|
// []string, co... We only iterate further if it's a struct.
|
||||||
|
// i.e []foo or []*foo
|
||||||
|
if val.Type().Elem().Kind() != reflect.Struct &&
|
||||||
|
!(val.Type().Elem().Kind() == reflect.Ptr &&
|
||||||
|
val.Type().Elem().Elem().Kind() == reflect.Struct) {
|
||||||
|
finalVal = val.Interface()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
slices := make([]interface{}, val.Len())
|
||||||
|
for x := 0; x < val.Len(); x++ {
|
||||||
|
slices[x] = s.nested(val.Index(x))
|
||||||
|
}
|
||||||
|
finalVal = slices
|
||||||
|
default:
|
||||||
|
finalVal = val.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalVal
|
||||||
|
}
|
||||||
32
vendor/github.com/fatih/structs/tags.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package structs
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// tagOptions contains a slice of tag options
|
||||||
|
type tagOptions []string
|
||||||
|
|
||||||
|
// Has returns true if the given option is available in tagOptions
|
||||||
|
func (t tagOptions) Has(opt string) bool {
|
||||||
|
for _, tagOpt := range t {
|
||||||
|
if tagOpt == opt {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTag splits a struct field's tag into its name and a list of options
|
||||||
|
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||||
|
// The name can be neglectected.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
// tag is one of followings:
|
||||||
|
// ""
|
||||||
|
// "name"
|
||||||
|
// "name,opt"
|
||||||
|
// "name,opt,opt2"
|
||||||
|
// ",opt"
|
||||||
|
|
||||||
|
res := strings.Split(tag, ",")
|
||||||
|
return res[0], res[1:]
|
||||||
|
}
|
||||||
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
|
||||||
|
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.
|
||||||
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2014 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package internal // import "github.com/garyburd/redigo/internal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WatchState = 1 << iota
|
||||||
|
MultiState
|
||||||
|
SubscribeState
|
||||||
|
MonitorState
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandInfo struct {
|
||||||
|
Set, Clear int
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandInfos = map[string]CommandInfo{
|
||||||
|
"WATCH": {Set: WatchState},
|
||||||
|
"UNWATCH": {Clear: WatchState},
|
||||||
|
"MULTI": {Set: MultiState},
|
||||||
|
"EXEC": {Clear: WatchState | MultiState},
|
||||||
|
"DISCARD": {Clear: WatchState | MultiState},
|
||||||
|
"PSUBSCRIBE": {Set: SubscribeState},
|
||||||
|
"SUBSCRIBE": {Set: SubscribeState},
|
||||||
|
"MONITOR": {Set: MonitorState},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for n, ci := range commandInfos {
|
||||||
|
commandInfos[strings.ToLower(n)] = ci
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupCommandInfo(commandName string) CommandInfo {
|
||||||
|
if ci, ok := commandInfos[commandName]; ok {
|
||||||
|
return ci
|
||||||
|
}
|
||||||
|
return commandInfos[strings.ToUpper(commandName)]
|
||||||
|
}
|
||||||
673
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ ConnWithTimeout = (*conn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// conn is the low-level implementation of Conn
|
||||||
|
type conn struct {
|
||||||
|
// Shared
|
||||||
|
mu sync.Mutex
|
||||||
|
pending int
|
||||||
|
err error
|
||||||
|
conn net.Conn
|
||||||
|
|
||||||
|
// Read
|
||||||
|
readTimeout time.Duration
|
||||||
|
br *bufio.Reader
|
||||||
|
|
||||||
|
// Write
|
||||||
|
writeTimeout time.Duration
|
||||||
|
bw *bufio.Writer
|
||||||
|
|
||||||
|
// Scratch space for formatting argument length.
|
||||||
|
// '*' or '$', length, "\r\n"
|
||||||
|
lenScratch [32]byte
|
||||||
|
|
||||||
|
// Scratch space for formatting integers and floats.
|
||||||
|
numScratch [40]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout acts like Dial but takes timeouts for establishing the
|
||||||
|
// connection to the server, writing a command and reading a reply.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Dial with options instead.
|
||||||
|
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
||||||
|
return Dial(network, address,
|
||||||
|
DialConnectTimeout(connectTimeout),
|
||||||
|
DialReadTimeout(readTimeout),
|
||||||
|
DialWriteTimeout(writeTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialOption specifies an option for dialing a Redis server.
|
||||||
|
type DialOption struct {
|
||||||
|
f func(*dialOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialOptions struct {
|
||||||
|
readTimeout time.Duration
|
||||||
|
writeTimeout time.Duration
|
||||||
|
dialer *net.Dialer
|
||||||
|
dial func(network, addr string) (net.Conn, error)
|
||||||
|
db int
|
||||||
|
password string
|
||||||
|
useTLS bool
|
||||||
|
skipVerify bool
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialReadTimeout specifies the timeout for reading a single command reply.
|
||||||
|
func DialReadTimeout(d time.Duration) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.readTimeout = d
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWriteTimeout specifies the timeout for writing a single command.
|
||||||
|
func DialWriteTimeout(d time.Duration) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.writeTimeout = d
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialConnectTimeout specifies the timeout for connecting to the Redis server when
|
||||||
|
// no DialNetDial option is specified.
|
||||||
|
func DialConnectTimeout(d time.Duration) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.dialer.Timeout = d
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
|
||||||
|
// when no DialNetDial option is specified.
|
||||||
|
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
|
||||||
|
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
|
||||||
|
func DialKeepAlive(d time.Duration) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.dialer.KeepAlive = d
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialNetDial specifies a custom dial function for creating TCP
|
||||||
|
// connections, otherwise a net.Dialer customized via the other options is used.
|
||||||
|
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
|
||||||
|
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.dial = dial
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialDatabase specifies the database to select when dialing a connection.
|
||||||
|
func DialDatabase(db int) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.db = db
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPassword specifies the password to use when connecting to
|
||||||
|
// the Redis server.
|
||||||
|
func DialPassword(password string) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.password = password
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTLSConfig specifies the config to use when a TLS connection is dialed.
|
||||||
|
// Has no effect when not dialing a TLS connection.
|
||||||
|
func DialTLSConfig(c *tls.Config) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.tlsConfig = c
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTLSSkipVerify disables server name verification when connecting over
|
||||||
|
// TLS. Has no effect when not dialing a TLS connection.
|
||||||
|
func DialTLSSkipVerify(skip bool) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.skipVerify = skip
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUseTLS specifies whether TLS should be used when connecting to the
|
||||||
|
// server. This option is ignore by DialURL.
|
||||||
|
func DialUseTLS(useTLS bool) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.useTLS = useTLS
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the Redis server at the given network and
|
||||||
|
// address using the specified options.
|
||||||
|
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||||
|
do := dialOptions{
|
||||||
|
dialer: &net.Dialer{
|
||||||
|
KeepAlive: time.Minute * 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
option.f(&do)
|
||||||
|
}
|
||||||
|
if do.dial == nil {
|
||||||
|
do.dial = do.dialer.Dial
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn, err := do.dial(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if do.useTLS {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if do.tlsConfig == nil {
|
||||||
|
tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}
|
||||||
|
} else {
|
||||||
|
tlsConfig = cloneTLSConfig(do.tlsConfig)
|
||||||
|
}
|
||||||
|
if tlsConfig.ServerName == "" {
|
||||||
|
host, _, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.ServerName = host
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConn := tls.Client(netConn, tlsConfig)
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netConn = tlsConn
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &conn{
|
||||||
|
conn: netConn,
|
||||||
|
bw: bufio.NewWriter(netConn),
|
||||||
|
br: bufio.NewReader(netConn),
|
||||||
|
readTimeout: do.readTimeout,
|
||||||
|
writeTimeout: do.writeTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if do.password != "" {
|
||||||
|
if _, err := c.Do("AUTH", do.password); err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if do.db != 0 {
|
||||||
|
if _, err := c.Do("SELECT", do.db); err != nil {
|
||||||
|
netConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||||
|
|
||||||
|
// DialURL connects to a Redis server at the given URL using the Redis
|
||||||
|
// URI scheme. URLs should follow the draft IANA specification for the
|
||||||
|
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||||
|
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
||||||
|
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per the IANA draft spec, the host defaults to localhost and
|
||||||
|
// the port defaults to 6379.
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
// assume port is missing
|
||||||
|
host = u.Host
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
address := net.JoinHostPort(host, port)
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
password, isSet := u.User.Password()
|
||||||
|
if isSet {
|
||||||
|
options = append(options, DialPassword(password))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match := pathDBRegexp.FindStringSubmatch(u.Path)
|
||||||
|
if len(match) == 2 {
|
||||||
|
db := 0
|
||||||
|
if len(match[1]) > 0 {
|
||||||
|
db, err = strconv.Atoi(match[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if db != 0 {
|
||||||
|
options = append(options, DialDatabase(db))
|
||||||
|
}
|
||||||
|
} else if u.Path != "" {
|
||||||
|
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(options, DialUseTLS(u.Scheme == "rediss"))
|
||||||
|
|
||||||
|
return Dial("tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn returns a new Redigo connection for the given net connection.
|
||||||
|
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
|
||||||
|
return &conn{
|
||||||
|
conn: netConn,
|
||||||
|
bw: bufio.NewWriter(netConn),
|
||||||
|
br: bufio.NewReader(netConn),
|
||||||
|
readTimeout: readTimeout,
|
||||||
|
writeTimeout: writeTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.err
|
||||||
|
if c.err == nil {
|
||||||
|
c.err = errors.New("redigo: closed")
|
||||||
|
err = c.conn.Close()
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) fatal(err error) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.err == nil {
|
||||||
|
c.err = err
|
||||||
|
// Close connection to force errors on subsequent calls and to unblock
|
||||||
|
// other reader or writer.
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Err() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.err
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeLen(prefix byte, n int) error {
|
||||||
|
c.lenScratch[len(c.lenScratch)-1] = '\n'
|
||||||
|
c.lenScratch[len(c.lenScratch)-2] = '\r'
|
||||||
|
i := len(c.lenScratch) - 3
|
||||||
|
for {
|
||||||
|
c.lenScratch[i] = byte('0' + n%10)
|
||||||
|
i -= 1
|
||||||
|
n = n / 10
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.lenScratch[i] = prefix
|
||||||
|
_, err := c.bw.Write(c.lenScratch[i:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeString(s string) error {
|
||||||
|
c.writeLen('$', len(s))
|
||||||
|
c.bw.WriteString(s)
|
||||||
|
_, err := c.bw.WriteString("\r\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeBytes(p []byte) error {
|
||||||
|
c.writeLen('$', len(p))
|
||||||
|
c.bw.Write(p)
|
||||||
|
_, err := c.bw.WriteString("\r\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeInt64(n int64) error {
|
||||||
|
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeFloat64(n float64) error {
|
||||||
|
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeCommand(cmd string, args []interface{}) error {
|
||||||
|
c.writeLen('*', 1+len(args))
|
||||||
|
if err := c.writeString(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
if err := c.writeArg(arg, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case string:
|
||||||
|
return c.writeString(arg)
|
||||||
|
case []byte:
|
||||||
|
return c.writeBytes(arg)
|
||||||
|
case int:
|
||||||
|
return c.writeInt64(int64(arg))
|
||||||
|
case int64:
|
||||||
|
return c.writeInt64(arg)
|
||||||
|
case float64:
|
||||||
|
return c.writeFloat64(arg)
|
||||||
|
case bool:
|
||||||
|
if arg {
|
||||||
|
return c.writeString("1")
|
||||||
|
} else {
|
||||||
|
return c.writeString("0")
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
return c.writeString("")
|
||||||
|
case Argument:
|
||||||
|
if argumentTypeOK {
|
||||||
|
return c.writeArg(arg.RedisArg(), false)
|
||||||
|
}
|
||||||
|
// See comment in default clause below.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, arg)
|
||||||
|
return c.writeBytes(buf.Bytes())
|
||||||
|
default:
|
||||||
|
// This default clause is intended to handle builtin numeric types.
|
||||||
|
// The function should return an error for other types, but this is not
|
||||||
|
// done for compatibility with previous versions of the package.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, arg)
|
||||||
|
return c.writeBytes(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolError string
|
||||||
|
|
||||||
|
func (pe protocolError) Error() string {
|
||||||
|
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) readLine() ([]byte, error) {
|
||||||
|
p, err := c.br.ReadSlice('\n')
|
||||||
|
if err == bufio.ErrBufferFull {
|
||||||
|
return nil, protocolError("long response line")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i := len(p) - 2
|
||||||
|
if i < 0 || p[i] != '\r' {
|
||||||
|
return nil, protocolError("bad response line terminator")
|
||||||
|
}
|
||||||
|
return p[:i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLen parses bulk string and array lengths.
|
||||||
|
func parseLen(p []byte) (int, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return -1, protocolError("malformed length")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
|
||||||
|
// handle $-1 and $-1 null replies.
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
for _, b := range p {
|
||||||
|
n *= 10
|
||||||
|
if b < '0' || b > '9' {
|
||||||
|
return -1, protocolError("illegal bytes in length")
|
||||||
|
}
|
||||||
|
n += int(b - '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInt parses an integer reply.
|
||||||
|
func parseInt(p []byte) (interface{}, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, protocolError("malformed integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var negate bool
|
||||||
|
if p[0] == '-' {
|
||||||
|
negate = true
|
||||||
|
p = p[1:]
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, protocolError("malformed integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int64
|
||||||
|
for _, b := range p {
|
||||||
|
n *= 10
|
||||||
|
if b < '0' || b > '9' {
|
||||||
|
return 0, protocolError("illegal bytes in length")
|
||||||
|
}
|
||||||
|
n += int64(b - '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
if negate {
|
||||||
|
n = -n
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
okReply interface{} = "OK"
|
||||||
|
pongReply interface{} = "PONG"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *conn) readReply() (interface{}, error) {
|
||||||
|
line, err := c.readLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, protocolError("short response line")
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case '+':
|
||||||
|
switch {
|
||||||
|
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
|
||||||
|
// Avoid allocation for frequent "+OK" response.
|
||||||
|
return okReply, nil
|
||||||
|
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
|
||||||
|
// Avoid allocation in PING command benchmarks :)
|
||||||
|
return pongReply, nil
|
||||||
|
default:
|
||||||
|
return string(line[1:]), nil
|
||||||
|
}
|
||||||
|
case '-':
|
||||||
|
return Error(string(line[1:])), nil
|
||||||
|
case ':':
|
||||||
|
return parseInt(line[1:])
|
||||||
|
case '$':
|
||||||
|
n, err := parseLen(line[1:])
|
||||||
|
if n < 0 || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := make([]byte, n)
|
||||||
|
_, err = io.ReadFull(c.br, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if line, err := c.readLine(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if len(line) != 0 {
|
||||||
|
return nil, protocolError("bad bulk string format")
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
case '*':
|
||||||
|
n, err := parseLen(line[1:])
|
||||||
|
if n < 0 || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := make([]interface{}, n)
|
||||||
|
for i := range r {
|
||||||
|
r[i], err = c.readReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
return nil, protocolError("unexpected response line")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Send(cmd string, args ...interface{}) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.pending += 1
|
||||||
|
c.mu.Unlock()
|
||||||
|
if c.writeTimeout != 0 {
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
|
}
|
||||||
|
if err := c.writeCommand(cmd, args); err != nil {
|
||||||
|
return c.fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Flush() error {
|
||||||
|
if c.writeTimeout != 0 {
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
|
}
|
||||||
|
if err := c.bw.Flush(); err != nil {
|
||||||
|
return c.fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Receive() (interface{}, error) {
|
||||||
|
return c.ReceiveWithTimeout(c.readTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
|
||||||
|
var deadline time.Time
|
||||||
|
if timeout != 0 {
|
||||||
|
deadline = time.Now().Add(timeout)
|
||||||
|
}
|
||||||
|
c.conn.SetReadDeadline(deadline)
|
||||||
|
|
||||||
|
if reply, err = c.readReply(); err != nil {
|
||||||
|
return nil, c.fatal(err)
|
||||||
|
}
|
||||||
|
// When using pub/sub, the number of receives can be greater than the
|
||||||
|
// number of sends. To enable normal use of the connection after
|
||||||
|
// unsubscribing from all channels, we do not decrement pending to a
|
||||||
|
// negative value.
|
||||||
|
//
|
||||||
|
// The pending field is decremented after the reply is read to handle the
|
||||||
|
// case where Receive is called before Send.
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.pending > 0 {
|
||||||
|
c.pending -= 1
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
if err, ok := reply.(Error); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||||
|
return c.DoWithTimeout(c.readTimeout, cmd, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
pending := c.pending
|
||||||
|
c.pending = 0
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if cmd == "" && pending == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.writeTimeout != 0 {
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != "" {
|
||||||
|
if err := c.writeCommand(cmd, args); err != nil {
|
||||||
|
return nil, c.fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.bw.Flush(); err != nil {
|
||||||
|
return nil, c.fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if readTimeout != 0 {
|
||||||
|
deadline = time.Now().Add(readTimeout)
|
||||||
|
}
|
||||||
|
c.conn.SetReadDeadline(deadline)
|
||||||
|
|
||||||
|
if cmd == "" {
|
||||||
|
reply := make([]interface{}, pending)
|
||||||
|
for i := range reply {
|
||||||
|
r, e := c.readReply()
|
||||||
|
if e != nil {
|
||||||
|
return nil, c.fatal(e)
|
||||||
|
}
|
||||||
|
reply[i] = r
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var reply interface{}
|
||||||
|
for i := 0; i <= pending; i++ {
|
||||||
|
var e error
|
||||||
|
if reply, e = c.readReply(); e != nil {
|
||||||
|
return nil, c.fatal(e)
|
||||||
|
}
|
||||||
|
if e, ok := reply.(Error); ok && err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
177
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package redis is a client for the Redis database.
|
||||||
|
//
|
||||||
|
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
|
||||||
|
// documentation about this package.
|
||||||
|
//
|
||||||
|
// Connections
|
||||||
|
//
|
||||||
|
// The Conn interface is the primary interface for working with Redis.
|
||||||
|
// Applications create connections by calling the Dial, DialWithTimeout or
|
||||||
|
// NewConn functions. In the future, functions will be added for creating
|
||||||
|
// sharded and other types of connections.
|
||||||
|
//
|
||||||
|
// The application must call the connection Close method when the application
|
||||||
|
// is done with the connection.
|
||||||
|
//
|
||||||
|
// Executing Commands
|
||||||
|
//
|
||||||
|
// The Conn interface has a generic method for executing Redis commands:
|
||||||
|
//
|
||||||
|
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||||
|
//
|
||||||
|
// The Redis command reference (http://redis.io/commands) lists the available
|
||||||
|
// commands. An example of using the Redis APPEND command is:
|
||||||
|
//
|
||||||
|
// n, err := conn.Do("APPEND", "key", "value")
|
||||||
|
//
|
||||||
|
// The Do method converts command arguments to bulk strings for transmission
|
||||||
|
// to the server as follows:
|
||||||
|
//
|
||||||
|
// Go Type Conversion
|
||||||
|
// []byte Sent as is
|
||||||
|
// string Sent as is
|
||||||
|
// int, int64 strconv.FormatInt(v)
|
||||||
|
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
|
// bool true -> "1", false -> "0"
|
||||||
|
// nil ""
|
||||||
|
// all other types fmt.Fprint(w, v)
|
||||||
|
//
|
||||||
|
// Redis command reply types are represented using the following Go types:
|
||||||
|
//
|
||||||
|
// Redis type Go type
|
||||||
|
// error redis.Error
|
||||||
|
// integer int64
|
||||||
|
// simple string string
|
||||||
|
// bulk string []byte or nil if value not present.
|
||||||
|
// array []interface{} or nil if value not present.
|
||||||
|
//
|
||||||
|
// Use type assertions or the reply helper functions to convert from
|
||||||
|
// interface{} to the specific Go type for the command result.
|
||||||
|
//
|
||||||
|
// Pipelining
|
||||||
|
//
|
||||||
|
// Connections support pipelining using the Send, Flush and Receive methods.
|
||||||
|
//
|
||||||
|
// Send(commandName string, args ...interface{}) error
|
||||||
|
// Flush() error
|
||||||
|
// Receive() (reply interface{}, err error)
|
||||||
|
//
|
||||||
|
// Send writes the command to the connection's output buffer. Flush flushes the
|
||||||
|
// connection's output buffer to the server. Receive reads a single reply from
|
||||||
|
// the server. The following example shows a simple pipeline.
|
||||||
|
//
|
||||||
|
// c.Send("SET", "foo", "bar")
|
||||||
|
// c.Send("GET", "foo")
|
||||||
|
// c.Flush()
|
||||||
|
// c.Receive() // reply from SET
|
||||||
|
// v, err = c.Receive() // reply from GET
|
||||||
|
//
|
||||||
|
// The Do method combines the functionality of the Send, Flush and Receive
|
||||||
|
// methods. The Do method starts by writing the command and flushing the output
|
||||||
|
// buffer. Next, the Do method receives all pending replies including the reply
|
||||||
|
// for the command just sent by Do. If any of the received replies is an error,
|
||||||
|
// then Do returns the error. If there are no errors, then Do returns the last
|
||||||
|
// reply. If the command argument to the Do method is "", then the Do method
|
||||||
|
// will flush the output buffer and receive pending replies without sending a
|
||||||
|
// command.
|
||||||
|
//
|
||||||
|
// Use the Send and Do methods to implement pipelined transactions.
|
||||||
|
//
|
||||||
|
// c.Send("MULTI")
|
||||||
|
// c.Send("INCR", "foo")
|
||||||
|
// c.Send("INCR", "bar")
|
||||||
|
// r, err := c.Do("EXEC")
|
||||||
|
// fmt.Println(r) // prints [1, 1]
|
||||||
|
//
|
||||||
|
// Concurrency
|
||||||
|
//
|
||||||
|
// Connections support one concurrent caller to the Receive method and one
|
||||||
|
// concurrent caller to the Send and Flush methods. No other concurrency is
|
||||||
|
// supported including concurrent calls to the Do method.
|
||||||
|
//
|
||||||
|
// For full concurrent access to Redis, use the thread-safe Pool to get, use
|
||||||
|
// and release a connection from within a goroutine. Connections returned from
|
||||||
|
// a Pool have the concurrency restrictions described in the previous
|
||||||
|
// paragraph.
|
||||||
|
//
|
||||||
|
// Publish and Subscribe
|
||||||
|
//
|
||||||
|
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
|
||||||
|
//
|
||||||
|
// c.Send("SUBSCRIBE", "example")
|
||||||
|
// c.Flush()
|
||||||
|
// for {
|
||||||
|
// reply, err := c.Receive()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// // process pushed message
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The PubSubConn type wraps a Conn with convenience methods for implementing
|
||||||
|
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
|
||||||
|
// send and flush a subscription management command. The receive method
|
||||||
|
// converts a pushed message to convenient types for use in a type switch.
|
||||||
|
//
|
||||||
|
// psc := redis.PubSubConn{Conn: c}
|
||||||
|
// psc.Subscribe("example")
|
||||||
|
// for {
|
||||||
|
// switch v := psc.Receive().(type) {
|
||||||
|
// case redis.Message:
|
||||||
|
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
|
||||||
|
// case redis.Subscription:
|
||||||
|
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
|
||||||
|
// case error:
|
||||||
|
// return v
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Reply Helpers
|
||||||
|
//
|
||||||
|
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
|
||||||
|
// to a value of a specific type. To allow convenient wrapping of calls to the
|
||||||
|
// connection Do and Receive methods, the functions take a second argument of
|
||||||
|
// type error. If the error is non-nil, then the helper function returns the
|
||||||
|
// error. If the error is nil, the function converts the reply to the specified
|
||||||
|
// type:
|
||||||
|
//
|
||||||
|
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error return from c.Do or type conversion error.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The Scan function converts elements of a array reply to Go types:
|
||||||
|
//
|
||||||
|
// var value1 int
|
||||||
|
// var value2 string
|
||||||
|
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Errors
|
||||||
|
//
|
||||||
|
// Connection methods return error replies from the server as type redis.Error.
|
||||||
|
//
|
||||||
|
// Call the connection Err() method to determine if the connection encountered
|
||||||
|
// non-recoverable error such as a network error or protocol parsing error. If
|
||||||
|
// Err() returns a non-nil value, then the connection is not usable and should
|
||||||
|
// be closed.
|
||||||
|
package redis // import "github.com/garyburd/redigo/redis"
|
||||||
27
vendor/github.com/garyburd/redigo/redis/go16.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: cfg.Rand,
|
||||||
|
Time: cfg.Time,
|
||||||
|
Certificates: cfg.Certificates,
|
||||||
|
NameToCertificate: cfg.NameToCertificate,
|
||||||
|
GetCertificate: cfg.GetCertificate,
|
||||||
|
RootCAs: cfg.RootCAs,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
|
ServerName: cfg.ServerName,
|
||||||
|
ClientAuth: cfg.ClientAuth,
|
||||||
|
ClientCAs: cfg.ClientCAs,
|
||||||
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||||
|
CipherSuites: cfg.CipherSuites,
|
||||||
|
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||||
|
ClientSessionCache: cfg.ClientSessionCache,
|
||||||
|
MinVersion: cfg.MinVersion,
|
||||||
|
MaxVersion: cfg.MaxVersion,
|
||||||
|
CurvePreferences: cfg.CurvePreferences,
|
||||||
|
}
|
||||||
|
}
|
||||||
29
vendor/github.com/garyburd/redigo/redis/go17.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// +build go1.7,!go1.8
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: cfg.Rand,
|
||||||
|
Time: cfg.Time,
|
||||||
|
Certificates: cfg.Certificates,
|
||||||
|
NameToCertificate: cfg.NameToCertificate,
|
||||||
|
GetCertificate: cfg.GetCertificate,
|
||||||
|
RootCAs: cfg.RootCAs,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
|
ServerName: cfg.ServerName,
|
||||||
|
ClientAuth: cfg.ClientAuth,
|
||||||
|
ClientCAs: cfg.ClientCAs,
|
||||||
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||||
|
CipherSuites: cfg.CipherSuites,
|
||||||
|
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||||
|
ClientSessionCache: cfg.ClientSessionCache,
|
||||||
|
MinVersion: cfg.MinVersion,
|
||||||
|
MaxVersion: cfg.MaxVersion,
|
||||||
|
CurvePreferences: cfg.CurvePreferences,
|
||||||
|
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
|
||||||
|
Renegotiation: cfg.Renegotiation,
|
||||||
|
}
|
||||||
|
}
|
||||||
9
vendor/github.com/garyburd/redigo/redis/go18.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||||
|
return cfg.Clone()
|
||||||
|
}
|
||||||
134
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ ConnWithTimeout = (*loggingConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLoggingConn returns a logging wrapper around a connection.
|
||||||
|
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
||||||
|
if prefix != "" {
|
||||||
|
prefix = prefix + "."
|
||||||
|
}
|
||||||
|
return &loggingConn{conn, logger, prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggingConn struct {
|
||||||
|
Conn
|
||||||
|
logger *log.Logger
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) Close() error {
|
||||||
|
err := c.Conn.Close()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
||||||
|
c.logger.Output(2, buf.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
||||||
|
const chop = 32
|
||||||
|
switch v := v.(type) {
|
||||||
|
case []byte:
|
||||||
|
if len(v) > chop {
|
||||||
|
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buf, "%q", v)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
if len(v) > chop {
|
||||||
|
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buf, "%q", v)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if len(v) == 0 {
|
||||||
|
buf.WriteString("[]")
|
||||||
|
} else {
|
||||||
|
sep := "["
|
||||||
|
fin := "]"
|
||||||
|
if len(v) > chop {
|
||||||
|
v = v[:chop]
|
||||||
|
fin = "...]"
|
||||||
|
}
|
||||||
|
for _, vv := range v {
|
||||||
|
buf.WriteString(sep)
|
||||||
|
c.printValue(buf, vv)
|
||||||
|
sep = ", "
|
||||||
|
}
|
||||||
|
buf.WriteString(fin)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Fprint(buf, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
||||||
|
if method != "Receive" {
|
||||||
|
buf.WriteString(commandName)
|
||||||
|
for _, arg := range args {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
c.printValue(&buf, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString(") -> (")
|
||||||
|
if method != "Send" {
|
||||||
|
c.printValue(&buf, reply)
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "%v)", err)
|
||||||
|
c.logger.Output(3, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||||
|
reply, err := c.Conn.Do(commandName, args...)
|
||||||
|
c.print("Do", commandName, args, reply, err)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
|
||||||
|
reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...)
|
||||||
|
c.print("DoWithTimeout", commandName, args, reply, err)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
||||||
|
err := c.Conn.Send(commandName, args...)
|
||||||
|
c.print("Send", commandName, args, nil, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) Receive() (interface{}, error) {
|
||||||
|
reply, err := c.Conn.Receive()
|
||||||
|
c.print("Receive", "", nil, reply, err)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
|
||||||
|
reply, err := ReceiveWithTimeout(c.Conn, timeout)
|
||||||
|
c.print("ReceiveWithTimeout", "", nil, reply, err)
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
562
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ ConnWithTimeout = (*activeConn)(nil)
|
||||||
|
_ ConnWithTimeout = (*errorConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nowFunc = time.Now // for testing
|
||||||
|
|
||||||
|
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
|
||||||
|
// Receive, Flush, Err) when the maximum number of database connections in the
|
||||||
|
// pool has been reached.
|
||||||
|
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
|
||||||
|
|
||||||
|
var (
|
||||||
|
errPoolClosed = errors.New("redigo: connection pool closed")
|
||||||
|
errConnClosed = errors.New("redigo: connection closed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pool maintains a pool of connections. The application calls the Get method
|
||||||
|
// to get a connection from the pool and the connection's Close method to
|
||||||
|
// return the connection's resources to the pool.
|
||||||
|
//
|
||||||
|
// The following example shows how to use a pool in a web application. The
|
||||||
|
// application creates a pool at application startup and makes it available to
|
||||||
|
// request handlers using a package level variable. The pool configuration used
|
||||||
|
// here is an example, not a recommendation.
|
||||||
|
//
|
||||||
|
// func newPool(addr string) *redis.Pool {
|
||||||
|
// return &redis.Pool{
|
||||||
|
// MaxIdle: 3,
|
||||||
|
// IdleTimeout: 240 * time.Second,
|
||||||
|
// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var (
|
||||||
|
// pool *redis.Pool
|
||||||
|
// redisServer = flag.String("redisServer", ":6379", "")
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// flag.Parse()
|
||||||
|
// pool = newPool(*redisServer)
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// A request handler gets a connection from the pool and closes the connection
|
||||||
|
// when the handler is done:
|
||||||
|
//
|
||||||
|
// func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// conn := pool.Get()
|
||||||
|
// defer conn.Close()
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Use the Dial function to authenticate connections with the AUTH command or
|
||||||
|
// select a database with the SELECT command:
|
||||||
|
//
|
||||||
|
// pool := &redis.Pool{
|
||||||
|
// // Other pool configuration not shown in this example.
|
||||||
|
// Dial: func () (redis.Conn, error) {
|
||||||
|
// c, err := redis.Dial("tcp", server)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// if _, err := c.Do("AUTH", password); err != nil {
|
||||||
|
// c.Close()
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// if _, err := c.Do("SELECT", db); err != nil {
|
||||||
|
// c.Close()
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return c, nil
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Use the TestOnBorrow function to check the health of an idle connection
|
||||||
|
// before the connection is returned to the application. This example PINGs
|
||||||
|
// connections that have been idle more than a minute:
|
||||||
|
//
|
||||||
|
// pool := &redis.Pool{
|
||||||
|
// // Other pool configuration not shown in this example.
|
||||||
|
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||||
|
// if time.Since(t) < time.Minute {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// _, err := c.Do("PING")
|
||||||
|
// return err
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Pool struct {
|
||||||
|
// Dial is an application supplied function for creating and configuring a
|
||||||
|
// connection.
|
||||||
|
//
|
||||||
|
// The connection returned from Dial must not be in a special state
|
||||||
|
// (subscribed to pubsub channel, transaction started, ...).
|
||||||
|
Dial func() (Conn, error)
|
||||||
|
|
||||||
|
// TestOnBorrow is an optional application supplied function for checking
|
||||||
|
// the health of an idle connection before the connection is used again by
|
||||||
|
// the application. Argument t is the time that the connection was returned
|
||||||
|
// to the pool. If the function returns an error, then the connection is
|
||||||
|
// closed.
|
||||||
|
TestOnBorrow func(c Conn, t time.Time) error
|
||||||
|
|
||||||
|
// Maximum number of idle connections in the pool.
|
||||||
|
MaxIdle int
|
||||||
|
|
||||||
|
// Maximum number of connections allocated by the pool at a given time.
|
||||||
|
// When zero, there is no limit on the number of connections in the pool.
|
||||||
|
MaxActive int
|
||||||
|
|
||||||
|
// Close connections after remaining idle for this duration. If the value
|
||||||
|
// is zero, then idle connections are not closed. Applications should set
|
||||||
|
// the timeout to a value less than the server's timeout.
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
|
||||||
|
// If Wait is true and the pool is at the MaxActive limit, then Get() waits
|
||||||
|
// for a connection to be returned to the pool before returning.
|
||||||
|
Wait bool
|
||||||
|
|
||||||
|
// Close connections older than this duration. If the value is zero, then
|
||||||
|
// the pool does not close connections based on age.
|
||||||
|
MaxConnLifetime time.Duration
|
||||||
|
|
||||||
|
chInitialized uint32 // set to 1 when field ch is initialized
|
||||||
|
|
||||||
|
mu sync.Mutex // mu protects the following fields
|
||||||
|
closed bool // set to true when the pool is closed.
|
||||||
|
active int // the number of open connections in the pool
|
||||||
|
ch chan struct{} // limits open connections when p.Wait is true
|
||||||
|
idle idleList // idle connections
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool creates a new pool.
|
||||||
|
//
|
||||||
|
// Deprecated: Initialize the Pool directory as shown in the example.
|
||||||
|
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
|
||||||
|
return &Pool{Dial: newFn, MaxIdle: maxIdle}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a connection. The application must close the returned connection.
|
||||||
|
// This method always returns a valid connection so that applications can defer
|
||||||
|
// error handling to the first use of the connection. If there is an error
|
||||||
|
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||||
|
// and Receive methods return that error.
|
||||||
|
func (p *Pool) Get() Conn {
|
||||||
|
pc, err := p.get(nil)
|
||||||
|
if err != nil {
|
||||||
|
return errorConn{err}
|
||||||
|
}
|
||||||
|
return &activeConn{p: p, pc: pc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolStats contains pool statistics.
|
||||||
|
type PoolStats struct {
|
||||||
|
// ActiveCount is the number of connections in the pool. The count includes
|
||||||
|
// idle connections and connections in use.
|
||||||
|
ActiveCount int
|
||||||
|
// IdleCount is the number of idle connections in the pool.
|
||||||
|
IdleCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns pool's statistics.
|
||||||
|
func (p *Pool) Stats() PoolStats {
|
||||||
|
p.mu.Lock()
|
||||||
|
stats := PoolStats{
|
||||||
|
ActiveCount: p.active,
|
||||||
|
IdleCount: p.idle.count,
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveCount returns the number of connections in the pool. The count
|
||||||
|
// includes idle connections and connections in use.
|
||||||
|
func (p *Pool) ActiveCount() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
active := p.active
|
||||||
|
p.mu.Unlock()
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleCount returns the number of idle connections in the pool.
|
||||||
|
func (p *Pool) IdleCount() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
idle := p.idle.count
|
||||||
|
p.mu.Unlock()
|
||||||
|
return idle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases the resources used by the pool.
|
||||||
|
func (p *Pool) Close() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.closed {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.closed = true
|
||||||
|
p.active -= p.idle.count
|
||||||
|
pc := p.idle.front
|
||||||
|
p.idle.count = 0
|
||||||
|
p.idle.front, p.idle.back = nil, nil
|
||||||
|
if p.ch != nil {
|
||||||
|
close(p.ch)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
for ; pc != nil; pc = pc.next {
|
||||||
|
pc.c.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) lazyInit() {
|
||||||
|
// Fast path.
|
||||||
|
if atomic.LoadUint32(&p.chInitialized) == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Slow path.
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.chInitialized == 0 {
|
||||||
|
p.ch = make(chan struct{}, p.MaxActive)
|
||||||
|
if p.closed {
|
||||||
|
close(p.ch)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < p.MaxActive; i++ {
|
||||||
|
p.ch <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&p.chInitialized, 1)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// get prunes stale connections and returns a connection from the idle list or
|
||||||
|
// creates a new connection.
|
||||||
|
func (p *Pool) get(ctx interface {
|
||||||
|
Done() <-chan struct{}
|
||||||
|
Err() error
|
||||||
|
}) (*poolConn, error) {
|
||||||
|
|
||||||
|
// Handle limit for p.Wait == true.
|
||||||
|
if p.Wait && p.MaxActive > 0 {
|
||||||
|
p.lazyInit()
|
||||||
|
if ctx == nil {
|
||||||
|
<-p.ch
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case <-p.ch:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
|
||||||
|
// Prune stale connections at the back of the idle list.
|
||||||
|
if p.IdleTimeout > 0 {
|
||||||
|
n := p.idle.count
|
||||||
|
for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ {
|
||||||
|
pc := p.idle.back
|
||||||
|
p.idle.popBack()
|
||||||
|
p.mu.Unlock()
|
||||||
|
pc.c.Close()
|
||||||
|
p.mu.Lock()
|
||||||
|
p.active--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get idle connection from the front of idle list.
|
||||||
|
for p.idle.front != nil {
|
||||||
|
pc := p.idle.front
|
||||||
|
p.idle.popFront()
|
||||||
|
p.mu.Unlock()
|
||||||
|
if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) &&
|
||||||
|
(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
pc.c.Close()
|
||||||
|
p.mu.Lock()
|
||||||
|
p.active--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pool closed before dialing a new connection.
|
||||||
|
if p.closed {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil, errors.New("redigo: get on closed pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle limit for p.Wait == false.
|
||||||
|
if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil, ErrPoolExhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
p.active++
|
||||||
|
p.mu.Unlock()
|
||||||
|
c, err := p.Dial()
|
||||||
|
if err != nil {
|
||||||
|
c = nil
|
||||||
|
p.mu.Lock()
|
||||||
|
p.active--
|
||||||
|
if p.ch != nil && !p.closed {
|
||||||
|
p.ch <- struct{}{}
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
return &poolConn{c: c, created: nowFunc()}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) put(pc *poolConn, forceClose bool) error {
|
||||||
|
p.mu.Lock()
|
||||||
|
if !p.closed && !forceClose {
|
||||||
|
pc.t = nowFunc()
|
||||||
|
p.idle.pushFront(pc)
|
||||||
|
if p.idle.count > p.MaxIdle {
|
||||||
|
pc = p.idle.back
|
||||||
|
p.idle.popBack()
|
||||||
|
} else {
|
||||||
|
pc = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc != nil {
|
||||||
|
p.mu.Unlock()
|
||||||
|
pc.c.Close()
|
||||||
|
p.mu.Lock()
|
||||||
|
p.active--
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ch != nil && !p.closed {
|
||||||
|
p.ch <- struct{}{}
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type activeConn struct {
|
||||||
|
p *Pool
|
||||||
|
pc *poolConn
|
||||||
|
state int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
sentinel []byte
|
||||||
|
sentinelOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func initSentinel() {
|
||||||
|
p := make([]byte, 64)
|
||||||
|
if _, err := rand.Read(p); err == nil {
|
||||||
|
sentinel = p
|
||||||
|
} else {
|
||||||
|
h := sha1.New()
|
||||||
|
io.WriteString(h, "Oops, rand failed. Use time instead.")
|
||||||
|
io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||||
|
sentinel = h.Sum(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Close() error {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ac.pc = nil
|
||||||
|
|
||||||
|
if ac.state&internal.MultiState != 0 {
|
||||||
|
pc.c.Send("DISCARD")
|
||||||
|
ac.state &^= (internal.MultiState | internal.WatchState)
|
||||||
|
} else if ac.state&internal.WatchState != 0 {
|
||||||
|
pc.c.Send("UNWATCH")
|
||||||
|
ac.state &^= internal.WatchState
|
||||||
|
}
|
||||||
|
if ac.state&internal.SubscribeState != 0 {
|
||||||
|
pc.c.Send("UNSUBSCRIBE")
|
||||||
|
pc.c.Send("PUNSUBSCRIBE")
|
||||||
|
// To detect the end of the message stream, ask the server to echo
|
||||||
|
// a sentinel value and read until we see that value.
|
||||||
|
sentinelOnce.Do(initSentinel)
|
||||||
|
pc.c.Send("ECHO", sentinel)
|
||||||
|
pc.c.Flush()
|
||||||
|
for {
|
||||||
|
p, err := pc.c.Receive()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
|
||||||
|
ac.state &^= internal.SubscribeState
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pc.c.Do("")
|
||||||
|
ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Err() error {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return errConnClosed
|
||||||
|
}
|
||||||
|
return pc.c.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return nil, errConnClosed
|
||||||
|
}
|
||||||
|
ci := internal.LookupCommandInfo(commandName)
|
||||||
|
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||||
|
return pc.c.Do(commandName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return nil, errConnClosed
|
||||||
|
}
|
||||||
|
cwt, ok := pc.c.(ConnWithTimeout)
|
||||||
|
if !ok {
|
||||||
|
return nil, errTimeoutNotSupported
|
||||||
|
}
|
||||||
|
ci := internal.LookupCommandInfo(commandName)
|
||||||
|
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||||
|
return cwt.DoWithTimeout(timeout, commandName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Send(commandName string, args ...interface{}) error {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return errConnClosed
|
||||||
|
}
|
||||||
|
ci := internal.LookupCommandInfo(commandName)
|
||||||
|
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||||
|
return pc.c.Send(commandName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Flush() error {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return errConnClosed
|
||||||
|
}
|
||||||
|
return pc.c.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) Receive() (reply interface{}, err error) {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return nil, errConnClosed
|
||||||
|
}
|
||||||
|
return pc.c.Receive()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
|
||||||
|
pc := ac.pc
|
||||||
|
if pc == nil {
|
||||||
|
return nil, errConnClosed
|
||||||
|
}
|
||||||
|
cwt, ok := pc.c.(ConnWithTimeout)
|
||||||
|
if !ok {
|
||||||
|
return nil, errTimeoutNotSupported
|
||||||
|
}
|
||||||
|
return cwt.ReceiveWithTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorConn struct{ err error }
|
||||||
|
|
||||||
|
func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
|
||||||
|
func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {
|
||||||
|
return nil, ec.err
|
||||||
|
}
|
||||||
|
func (ec errorConn) Send(string, ...interface{}) error { return ec.err }
|
||||||
|
func (ec errorConn) Err() error { return ec.err }
|
||||||
|
func (ec errorConn) Close() error { return nil }
|
||||||
|
func (ec errorConn) Flush() error { return ec.err }
|
||||||
|
func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err }
|
||||||
|
func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err }
|
||||||
|
|
||||||
|
type idleList struct {
|
||||||
|
count int
|
||||||
|
front, back *poolConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolConn struct {
|
||||||
|
c Conn
|
||||||
|
t time.Time
|
||||||
|
created time.Time
|
||||||
|
next, prev *poolConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *idleList) pushFront(pc *poolConn) {
|
||||||
|
pc.next = l.front
|
||||||
|
pc.prev = nil
|
||||||
|
if l.count == 0 {
|
||||||
|
l.back = pc
|
||||||
|
} else {
|
||||||
|
l.front.prev = pc
|
||||||
|
}
|
||||||
|
l.front = pc
|
||||||
|
l.count++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *idleList) popFront() {
|
||||||
|
pc := l.front
|
||||||
|
l.count--
|
||||||
|
if l.count == 0 {
|
||||||
|
l.front, l.back = nil, nil
|
||||||
|
} else {
|
||||||
|
pc.next.prev = nil
|
||||||
|
l.front = pc.next
|
||||||
|
}
|
||||||
|
pc.next, pc.prev = nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *idleList) popBack() {
|
||||||
|
pc := l.back
|
||||||
|
l.count--
|
||||||
|
if l.count == 0 {
|
||||||
|
l.front, l.back = nil, nil
|
||||||
|
} else {
|
||||||
|
pc.prev.next = nil
|
||||||
|
l.back = pc.prev
|
||||||
|
}
|
||||||
|
pc.next, pc.prev = nil, nil
|
||||||
|
}
|
||||||
35
vendor/github.com/garyburd/redigo/redis/pool17.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2018 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// GetContext gets a connection using the provided context.
|
||||||
|
//
|
||||||
|
// The provided Context must be non-nil. If the context expires before the
|
||||||
|
// connection is complete, an error is returned. Any expiration on the context
|
||||||
|
// will not affect the returned connection.
|
||||||
|
//
|
||||||
|
// If the function completes without error, then the application must close the
|
||||||
|
// returned connection.
|
||||||
|
func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
|
||||||
|
pc, err := p.get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errorConn{err}, err
|
||||||
|
}
|
||||||
|
return &activeConn{p: p, pc: pc}, nil
|
||||||
|
}
|
||||||
157
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subscription represents a subscribe or unsubscribe notification.
|
||||||
|
type Subscription struct {
|
||||||
|
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||||
|
Kind string
|
||||||
|
|
||||||
|
// The channel that was changed.
|
||||||
|
Channel string
|
||||||
|
|
||||||
|
// The current number of subscriptions for connection.
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message represents a message notification.
|
||||||
|
type Message struct {
|
||||||
|
// The originating channel.
|
||||||
|
Channel string
|
||||||
|
|
||||||
|
// The message data.
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// PMessage represents a pmessage notification.
|
||||||
|
type PMessage struct {
|
||||||
|
// The matched pattern.
|
||||||
|
Pattern string
|
||||||
|
|
||||||
|
// The originating channel.
|
||||||
|
Channel string
|
||||||
|
|
||||||
|
// The message data.
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pong represents a pubsub pong notification.
|
||||||
|
type Pong struct {
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubSubConn wraps a Conn with convenience methods for subscribers.
|
||||||
|
type PubSubConn struct {
|
||||||
|
Conn Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (c PubSubConn) Close() error {
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the connection to the specified channels.
|
||||||
|
func (c PubSubConn) Subscribe(channel ...interface{}) error {
|
||||||
|
c.Conn.Send("SUBSCRIBE", channel...)
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe subscribes the connection to the given patterns.
|
||||||
|
func (c PubSubConn) PSubscribe(channel ...interface{}) error {
|
||||||
|
c.Conn.Send("PSUBSCRIBE", channel...)
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe unsubscribes the connection from the given channels, or from all
|
||||||
|
// of them if none is given.
|
||||||
|
func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
|
||||||
|
c.Conn.Send("UNSUBSCRIBE", channel...)
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUnsubscribe unsubscribes the connection from the given patterns, or from all
|
||||||
|
// of them if none is given.
|
||||||
|
func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
||||||
|
c.Conn.Send("PUNSUBSCRIBE", channel...)
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping sends a PING to the server with the specified data.
|
||||||
|
//
|
||||||
|
// The connection must be subscribed to at least one channel or pattern when
|
||||||
|
// calling this method.
|
||||||
|
func (c PubSubConn) Ping(data string) error {
|
||||||
|
c.Conn.Send("PING", data)
|
||||||
|
return c.Conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
|
||||||
|
// or error. The return value is intended to be used directly in a type switch
|
||||||
|
// as illustrated in the PubSubConn example.
|
||||||
|
func (c PubSubConn) Receive() interface{} {
|
||||||
|
return c.receiveInternal(c.Conn.Receive())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveWithTimeout is like Receive, but it allows the application to
|
||||||
|
// override the connection's default timeout.
|
||||||
|
func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} {
|
||||||
|
return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} {
|
||||||
|
reply, err := Values(replyArg, errArg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kind string
|
||||||
|
reply, err = Scan(reply, &kind)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case "message":
|
||||||
|
var m Message
|
||||||
|
if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
case "pmessage":
|
||||||
|
var pm PMessage
|
||||||
|
if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pm
|
||||||
|
case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
|
||||||
|
s := Subscription{Kind: kind}
|
||||||
|
if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
case "pong":
|
||||||
|
var p Pong
|
||||||
|
if _, err := Scan(reply, &p.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return errors.New("redigo: unknown pubsub notification")
|
||||||
|
}
|
||||||
117
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an error returned in a command reply.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
func (err Error) Error() string { return string(err) }
|
||||||
|
|
||||||
|
// Conn represents a connection to a Redis server.
|
||||||
|
type Conn interface {
|
||||||
|
// Close closes the connection.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// Err returns a non-nil value when the connection is not usable.
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// Do sends a command to the server and returns the received reply.
|
||||||
|
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||||
|
|
||||||
|
// Send writes the command to the client's output buffer.
|
||||||
|
Send(commandName string, args ...interface{}) error
|
||||||
|
|
||||||
|
// Flush flushes the output buffer to the Redis server.
|
||||||
|
Flush() error
|
||||||
|
|
||||||
|
// Receive receives a single reply from the Redis server
|
||||||
|
Receive() (reply interface{}, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Argument is the interface implemented by an object which wants to control how
|
||||||
|
// the object is converted to Redis bulk strings.
|
||||||
|
type Argument interface {
|
||||||
|
// RedisArg returns a value to be encoded as a bulk string per the
|
||||||
|
// conversions listed in the section 'Executing Commands'.
|
||||||
|
// Implementations should typically return a []byte or string.
|
||||||
|
RedisArg() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner is implemented by an object which wants to control its value is
|
||||||
|
// interpreted when read from Redis.
|
||||||
|
type Scanner interface {
|
||||||
|
// RedisScan assigns a value from a Redis value. The argument src is one of
|
||||||
|
// the reply types listed in the section `Executing Commands`.
|
||||||
|
//
|
||||||
|
// An error should be returned if the value cannot be stored without
|
||||||
|
// loss of information.
|
||||||
|
RedisScan(src interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnWithTimeout is an optional interface that allows the caller to override
|
||||||
|
// a connection's default read timeout. This interface is useful for executing
|
||||||
|
// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the
|
||||||
|
// server.
|
||||||
|
//
|
||||||
|
// A connection's default read timeout is set with the DialReadTimeout dial
|
||||||
|
// option. Applications should rely on the default timeout for commands that do
|
||||||
|
// not block at the server.
|
||||||
|
//
|
||||||
|
// All of the Conn implementations in this package satisfy the ConnWithTimeout
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify
|
||||||
|
// use of this interface.
|
||||||
|
type ConnWithTimeout interface {
|
||||||
|
Conn
|
||||||
|
|
||||||
|
// Do sends a command to the server and returns the received reply.
|
||||||
|
// The timeout overrides the read timeout set when dialing the
|
||||||
|
// connection.
|
||||||
|
DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error)
|
||||||
|
|
||||||
|
// Receive receives a single reply from the Redis server. The timeout
|
||||||
|
// overrides the read timeout set when dialing the connection.
|
||||||
|
ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout")
|
||||||
|
|
||||||
|
// DoWithTimeout executes a Redis command with the specified read timeout. If
|
||||||
|
// the connection does not satisfy the ConnWithTimeout interface, then an error
|
||||||
|
// is returned.
|
||||||
|
func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
|
||||||
|
cwt, ok := c.(ConnWithTimeout)
|
||||||
|
if !ok {
|
||||||
|
return nil, errTimeoutNotSupported
|
||||||
|
}
|
||||||
|
return cwt.DoWithTimeout(timeout, cmd, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveWithTimeout receives a reply with the specified read timeout. If the
|
||||||
|
// connection does not satisfy the ConnWithTimeout interface, then an error is
|
||||||
|
// returned.
|
||||||
|
func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {
|
||||||
|
cwt, ok := c.(ConnWithTimeout)
|
||||||
|
if !ok {
|
||||||
|
return nil, errTimeoutNotSupported
|
||||||
|
}
|
||||||
|
return cwt.ReceiveWithTimeout(timeout)
|
||||||
|
}
|
||||||
479
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNil indicates that a reply value is nil.
|
||||||
|
var ErrNil = errors.New("redigo: nil returned")
|
||||||
|
|
||||||
|
// Int is a helper that converts a command reply to an integer. If err is not
|
||||||
|
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
|
||||||
|
// reply to an int as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// integer int(reply), nil
|
||||||
|
// bulk string parsed reply, nil
|
||||||
|
// nil 0, ErrNil
|
||||||
|
// other 0, error
|
||||||
|
func Int(reply interface{}, err error) (int, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case int64:
|
||||||
|
x := int(reply)
|
||||||
|
if int64(x) != reply {
|
||||||
|
return 0, strconv.ErrRange
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseInt(string(reply), 10, 0)
|
||||||
|
return int(n), err
|
||||||
|
case nil:
|
||||||
|
return 0, ErrNil
|
||||||
|
case Error:
|
||||||
|
return 0, reply
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||||
|
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||||
|
// reply to an int64 as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// integer reply, nil
|
||||||
|
// bulk string parsed reply, nil
|
||||||
|
// nil 0, ErrNil
|
||||||
|
// other 0, error
|
||||||
|
func Int64(reply interface{}, err error) (int64, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case int64:
|
||||||
|
return reply, nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseInt(string(reply), 10, 64)
|
||||||
|
return n, err
|
||||||
|
case nil:
|
||||||
|
return 0, ErrNil
|
||||||
|
case Error:
|
||||||
|
return 0, reply
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
|
||||||
|
|
||||||
|
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||||
|
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||||
|
// reply to an int64 as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// integer reply, nil
|
||||||
|
// bulk string parsed reply, nil
|
||||||
|
// nil 0, ErrNil
|
||||||
|
// other 0, error
|
||||||
|
func Uint64(reply interface{}, err error) (uint64, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case int64:
|
||||||
|
if reply < 0 {
|
||||||
|
return 0, errNegativeInt
|
||||||
|
}
|
||||||
|
return uint64(reply), nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseUint(string(reply), 10, 64)
|
||||||
|
return n, err
|
||||||
|
case nil:
|
||||||
|
return 0, ErrNil
|
||||||
|
case Error:
|
||||||
|
return 0, reply
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 is a helper that converts a command reply to 64 bit float. If err is
|
||||||
|
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
|
||||||
|
// the reply to an int as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// bulk string parsed reply, nil
|
||||||
|
// nil 0, ErrNil
|
||||||
|
// other 0, error
|
||||||
|
func Float64(reply interface{}, err error) (float64, error) {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseFloat(string(reply), 64)
|
||||||
|
return n, err
|
||||||
|
case nil:
|
||||||
|
return 0, ErrNil
|
||||||
|
case Error:
|
||||||
|
return 0, reply
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a helper that converts a command reply to a string. If err is not
|
||||||
|
// equal to nil, then String returns "", err. Otherwise String converts the
|
||||||
|
// reply to a string as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// bulk string string(reply), nil
|
||||||
|
// simple string reply, nil
|
||||||
|
// nil "", ErrNil
|
||||||
|
// other "", error
|
||||||
|
func String(reply interface{}, err error) (string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []byte:
|
||||||
|
return string(reply), nil
|
||||||
|
case string:
|
||||||
|
return reply, nil
|
||||||
|
case nil:
|
||||||
|
return "", ErrNil
|
||||||
|
case Error:
|
||||||
|
return "", reply
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes is a helper that converts a command reply to a slice of bytes. If err
|
||||||
|
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
|
||||||
|
// the reply to a slice of bytes as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// bulk string reply, nil
|
||||||
|
// simple string []byte(reply), nil
|
||||||
|
// nil nil, ErrNil
|
||||||
|
// other nil, error
|
||||||
|
func Bytes(reply interface{}, err error) ([]byte, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []byte:
|
||||||
|
return reply, nil
|
||||||
|
case string:
|
||||||
|
return []byte(reply), nil
|
||||||
|
case nil:
|
||||||
|
return nil, ErrNil
|
||||||
|
case Error:
|
||||||
|
return nil, reply
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool is a helper that converts a command reply to a boolean. If err is not
|
||||||
|
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
|
||||||
|
// reply to boolean as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// integer value != 0, nil
|
||||||
|
// bulk string strconv.ParseBool(reply)
|
||||||
|
// nil false, ErrNil
|
||||||
|
// other false, error
|
||||||
|
func Bool(reply interface{}, err error) (bool, error) {
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case int64:
|
||||||
|
return reply != 0, nil
|
||||||
|
case []byte:
|
||||||
|
return strconv.ParseBool(string(reply))
|
||||||
|
case nil:
|
||||||
|
return false, ErrNil
|
||||||
|
case Error:
|
||||||
|
return false, reply
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiBulk is a helper that converts an array command reply to a []interface{}.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Values instead.
|
||||||
|
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
|
||||||
|
|
||||||
|
// Values is a helper that converts an array command reply to a []interface{}.
|
||||||
|
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
|
||||||
|
// converts the reply as follows:
|
||||||
|
//
|
||||||
|
// Reply type Result
|
||||||
|
// array reply, nil
|
||||||
|
// nil nil, ErrNil
|
||||||
|
// other nil, error
|
||||||
|
func Values(reply interface{}, err error) ([]interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
return reply, nil
|
||||||
|
case nil:
|
||||||
|
return nil, ErrNil
|
||||||
|
case Error:
|
||||||
|
return nil, reply
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
makeSlice(len(reply))
|
||||||
|
for i := range reply {
|
||||||
|
if reply[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := assign(i, reply[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case nil:
|
||||||
|
return ErrNil
|
||||||
|
case Error:
|
||||||
|
return reply
|
||||||
|
}
|
||||||
|
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s is a helper that converts an array command reply to a []float64. If
|
||||||
|
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
|
||||||
|
// converted to 0 in the output slice. Floats64 returns an error if an array
|
||||||
|
// item is not a bulk string or nil.
|
||||||
|
func Float64s(reply interface{}, err error) ([]float64, error) {
|
||||||
|
var result []float64
|
||||||
|
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
|
||||||
|
p, ok := v.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(string(p), 64)
|
||||||
|
result[i] = f
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings is a helper that converts an array command reply to a []string. If
|
||||||
|
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||||
|
// converted to "" in the output slice. Strings returns an error if an array
|
||||||
|
// item is not a bulk string or nil.
|
||||||
|
func Strings(reply interface{}, err error) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
result[i] = v
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
result[i] = string(v)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||||
|
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
|
||||||
|
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||||
|
// bulk string or nil.
|
||||||
|
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||||
|
var result [][]byte
|
||||||
|
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
|
||||||
|
p, ok := v.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
|
||||||
|
}
|
||||||
|
result[i] = p
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64s is a helper that converts an array command reply to a []int64.
|
||||||
|
// If err is not equal to nil, then Int64s returns nil, err. Nil array
|
||||||
|
// items are stay nil. Int64s returns an error if an array item is not a
|
||||||
|
// bulk string or nil.
|
||||||
|
func Int64s(reply interface{}, err error) ([]int64, error) {
|
||||||
|
var result []int64
|
||||||
|
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case int64:
|
||||||
|
result[i] = v
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseInt(string(v), 10, 64)
|
||||||
|
result[i] = n
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints is a helper that converts an array command reply to a []in.
|
||||||
|
// If err is not equal to nil, then Ints returns nil, err. Nil array
|
||||||
|
// items are stay nil. Ints returns an error if an array item is not a
|
||||||
|
// bulk string or nil.
|
||||||
|
func Ints(reply interface{}, err error) ([]int, error) {
|
||||||
|
var result []int
|
||||||
|
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case int64:
|
||||||
|
n := int(v)
|
||||||
|
if int64(n) != v {
|
||||||
|
return strconv.ErrRange
|
||||||
|
}
|
||||||
|
result[i] = n
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.Atoi(string(v))
|
||||||
|
result[i] = n
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||||
|
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
|
||||||
|
// Requires an even number of values in result.
|
||||||
|
func StringMap(result interface{}, err error) (map[string]string, error) {
|
||||||
|
values, err := Values(result, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(values)%2 != 0 {
|
||||||
|
return nil, errors.New("redigo: StringMap expects even number of values result")
|
||||||
|
}
|
||||||
|
m := make(map[string]string, len(values)/2)
|
||||||
|
for i := 0; i < len(values); i += 2 {
|
||||||
|
key, okKey := values[i].([]byte)
|
||||||
|
value, okValue := values[i+1].([]byte)
|
||||||
|
if !okKey || !okValue {
|
||||||
|
return nil, errors.New("redigo: StringMap key not a bulk string value")
|
||||||
|
}
|
||||||
|
m[string(key)] = string(value)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntMap is a helper that converts an array of strings (alternating key, value)
|
||||||
|
// into a map[string]int. The HGETALL commands return replies in this format.
|
||||||
|
// Requires an even number of values in result.
|
||||||
|
func IntMap(result interface{}, err error) (map[string]int, error) {
|
||||||
|
values, err := Values(result, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(values)%2 != 0 {
|
||||||
|
return nil, errors.New("redigo: IntMap expects even number of values result")
|
||||||
|
}
|
||||||
|
m := make(map[string]int, len(values)/2)
|
||||||
|
for i := 0; i < len(values); i += 2 {
|
||||||
|
key, ok := values[i].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("redigo: IntMap key not a bulk string value")
|
||||||
|
}
|
||||||
|
value, err := Int(values[i+1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[string(key)] = value
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Map is a helper that converts an array of strings (alternating key, value)
|
||||||
|
// into a map[string]int64. The HGETALL commands return replies in this format.
|
||||||
|
// Requires an even number of values in result.
|
||||||
|
func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
||||||
|
values, err := Values(result, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(values)%2 != 0 {
|
||||||
|
return nil, errors.New("redigo: Int64Map expects even number of values result")
|
||||||
|
}
|
||||||
|
m := make(map[string]int64, len(values)/2)
|
||||||
|
for i := 0; i < len(values); i += 2 {
|
||||||
|
key, ok := values[i].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("redigo: Int64Map key not a bulk string value")
|
||||||
|
}
|
||||||
|
value, err := Int64(values[i+1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[string(key)] = value
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positions is a helper that converts an array of positions (lat, long)
|
||||||
|
// into a [][2]float64. The GEOPOS command returns replies in this format.
|
||||||
|
func Positions(result interface{}, err error) ([]*[2]float64, error) {
|
||||||
|
values, err := Values(result, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
positions := make([]*[2]float64, len(values))
|
||||||
|
for i := range values {
|
||||||
|
if values[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, ok := values[i].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i])
|
||||||
|
}
|
||||||
|
if len(p) != 2 {
|
||||||
|
return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p))
|
||||||
|
}
|
||||||
|
lat, err := Float64(p[0], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
long, err := Float64(p[1], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
positions[i] = &[2]float64{lat, long}
|
||||||
|
}
|
||||||
|
return positions, nil
|
||||||
|
}
|
||||||
585
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
Normal file
@ -0,0 +1,585 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensureLen(d reflect.Value, n int) {
|
||||||
|
if n > d.Cap() {
|
||||||
|
d.Set(reflect.MakeSlice(d.Type(), n, n))
|
||||||
|
} else {
|
||||||
|
d.SetLen(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cannotConvert(d reflect.Value, s interface{}) error {
|
||||||
|
var sname string
|
||||||
|
switch s.(type) {
|
||||||
|
case string:
|
||||||
|
sname = "Redis simple string"
|
||||||
|
case Error:
|
||||||
|
sname = "Redis error"
|
||||||
|
case int64:
|
||||||
|
sname = "Redis integer"
|
||||||
|
case []byte:
|
||||||
|
sname = "Redis bulk string"
|
||||||
|
case []interface{}:
|
||||||
|
sname = "Redis array"
|
||||||
|
default:
|
||||||
|
sname = reflect.TypeOf(s).String()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
|
||||||
|
switch d.Type().Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
var x float64
|
||||||
|
x, err = strconv.ParseFloat(string(s), d.Type().Bits())
|
||||||
|
d.SetFloat(x)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
var x int64
|
||||||
|
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
|
||||||
|
d.SetInt(x)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
var x uint64
|
||||||
|
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
|
||||||
|
d.SetUint(x)
|
||||||
|
case reflect.Bool:
|
||||||
|
var x bool
|
||||||
|
x, err = strconv.ParseBool(string(s))
|
||||||
|
d.SetBool(x)
|
||||||
|
case reflect.String:
|
||||||
|
d.SetString(string(s))
|
||||||
|
case reflect.Slice:
|
||||||
|
if d.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
} else {
|
||||||
|
d.SetBytes(s)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAssignInt(d reflect.Value, s int64) (err error) {
|
||||||
|
switch d.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
d.SetInt(s)
|
||||||
|
if d.Int() != s {
|
||||||
|
err = strconv.ErrRange
|
||||||
|
d.SetInt(0)
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
if s < 0 {
|
||||||
|
err = strconv.ErrRange
|
||||||
|
} else {
|
||||||
|
x := uint64(s)
|
||||||
|
d.SetUint(x)
|
||||||
|
if d.Uint() != x {
|
||||||
|
err = strconv.ErrRange
|
||||||
|
d.SetUint(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
d.SetBool(s != 0)
|
||||||
|
default:
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
||||||
|
if d.Kind() != reflect.Ptr {
|
||||||
|
if d.CanAddr() {
|
||||||
|
d2 := d.Addr()
|
||||||
|
if d2.CanInterface() {
|
||||||
|
if scanner, ok := d2.Interface().(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if d.CanInterface() {
|
||||||
|
// Already a reflect.Ptr
|
||||||
|
if d.IsNil() {
|
||||||
|
d.Set(reflect.New(d.Type().Elem()))
|
||||||
|
}
|
||||||
|
if scanner, ok := d.Interface().(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s := s.(type) {
|
||||||
|
case []byte:
|
||||||
|
err = convertAssignBulkString(d, s)
|
||||||
|
case int64:
|
||||||
|
err = convertAssignInt(d, s)
|
||||||
|
default:
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAssignArray(d reflect.Value, s []interface{}) error {
|
||||||
|
if d.Type().Kind() != reflect.Slice {
|
||||||
|
return cannotConvert(d, s)
|
||||||
|
}
|
||||||
|
ensureLen(d, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if err := convertAssignValue(d.Index(i), s[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAssign(d interface{}, s interface{}) (err error) {
|
||||||
|
if scanner, ok := d.(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the most common destination types using type switches and
|
||||||
|
// fall back to reflection for all other types.
|
||||||
|
switch s := s.(type) {
|
||||||
|
case nil:
|
||||||
|
// ignore
|
||||||
|
case []byte:
|
||||||
|
switch d := d.(type) {
|
||||||
|
case *string:
|
||||||
|
*d = string(s)
|
||||||
|
case *int:
|
||||||
|
*d, err = strconv.Atoi(string(s))
|
||||||
|
case *bool:
|
||||||
|
*d, err = strconv.ParseBool(string(s))
|
||||||
|
case *[]byte:
|
||||||
|
*d = s
|
||||||
|
case *interface{}:
|
||||||
|
*d = s
|
||||||
|
case nil:
|
||||||
|
// skip value
|
||||||
|
default:
|
||||||
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
} else {
|
||||||
|
err = convertAssignBulkString(d.Elem(), s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case int64:
|
||||||
|
switch d := d.(type) {
|
||||||
|
case *int:
|
||||||
|
x := int(s)
|
||||||
|
if int64(x) != s {
|
||||||
|
err = strconv.ErrRange
|
||||||
|
x = 0
|
||||||
|
}
|
||||||
|
*d = x
|
||||||
|
case *bool:
|
||||||
|
*d = s != 0
|
||||||
|
case *interface{}:
|
||||||
|
*d = s
|
||||||
|
case nil:
|
||||||
|
// skip value
|
||||||
|
default:
|
||||||
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
} else {
|
||||||
|
err = convertAssignInt(d.Elem(), s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
switch d := d.(type) {
|
||||||
|
case *string:
|
||||||
|
*d = s
|
||||||
|
case *interface{}:
|
||||||
|
*d = s
|
||||||
|
case nil:
|
||||||
|
// skip value
|
||||||
|
default:
|
||||||
|
err = cannotConvert(reflect.ValueOf(d), s)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
switch d := d.(type) {
|
||||||
|
case *[]interface{}:
|
||||||
|
*d = s
|
||||||
|
case *interface{}:
|
||||||
|
*d = s
|
||||||
|
case nil:
|
||||||
|
// skip value
|
||||||
|
default:
|
||||||
|
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||||
|
err = cannotConvert(d, s)
|
||||||
|
} else {
|
||||||
|
err = convertAssignArray(d.Elem(), s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Error:
|
||||||
|
err = s
|
||||||
|
default:
|
||||||
|
err = cannotConvert(reflect.ValueOf(d), s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan copies from src to the values pointed at by dest.
|
||||||
|
//
|
||||||
|
// Scan uses RedisScan if available otherwise:
|
||||||
|
//
|
||||||
|
// The values pointed at by dest must be an integer, float, boolean, string,
|
||||||
|
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
||||||
|
// package to convert bulk strings to numeric and boolean types.
|
||||||
|
//
|
||||||
|
// If a dest value is nil, then the corresponding src value is skipped.
|
||||||
|
//
|
||||||
|
// If a src element is nil, then the corresponding dest value is not modified.
|
||||||
|
//
|
||||||
|
// To enable easy use of Scan in a loop, Scan returns the slice of src
|
||||||
|
// following the copied values.
|
||||||
|
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
|
||||||
|
if len(src) < len(dest) {
|
||||||
|
return nil, errors.New("redigo.Scan: array short")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for i, d := range dest {
|
||||||
|
err = convertAssign(d, src[i])
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src[len(dest):], err
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldSpec struct {
|
||||||
|
name string
|
||||||
|
index []int
|
||||||
|
omitEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type structSpec struct {
|
||||||
|
m map[string]*fieldSpec
|
||||||
|
l []*fieldSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
|
||||||
|
return ss.m[string(name)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
switch {
|
||||||
|
case f.PkgPath != "" && !f.Anonymous:
|
||||||
|
// Ignore unexported fields.
|
||||||
|
case f.Anonymous:
|
||||||
|
// TODO: Handle pointers. Requires change to decoder and
|
||||||
|
// protection against infinite recursion.
|
||||||
|
if f.Type.Kind() == reflect.Struct {
|
||||||
|
compileStructSpec(f.Type, depth, append(index, i), ss)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fs := &fieldSpec{name: f.Name}
|
||||||
|
tag := f.Tag.Get("redis")
|
||||||
|
p := strings.Split(tag, ",")
|
||||||
|
if len(p) > 0 {
|
||||||
|
if p[0] == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(p[0]) > 0 {
|
||||||
|
fs.name = p[0]
|
||||||
|
}
|
||||||
|
for _, s := range p[1:] {
|
||||||
|
switch s {
|
||||||
|
case "omitempty":
|
||||||
|
fs.omitEmpty = true
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d, found := depth[fs.name]
|
||||||
|
if !found {
|
||||||
|
d = 1 << 30
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case len(index) == d:
|
||||||
|
// At same depth, remove from result.
|
||||||
|
delete(ss.m, fs.name)
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(ss.l); i++ {
|
||||||
|
if fs.name != ss.l[i].name {
|
||||||
|
ss.l[j] = ss.l[i]
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ss.l = ss.l[:j]
|
||||||
|
case len(index) < d:
|
||||||
|
fs.index = make([]int, len(index)+1)
|
||||||
|
copy(fs.index, index)
|
||||||
|
fs.index[len(index)] = i
|
||||||
|
depth[fs.name] = len(index)
|
||||||
|
ss.m[fs.name] = fs
|
||||||
|
ss.l = append(ss.l, fs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
structSpecMutex sync.RWMutex
|
||||||
|
structSpecCache = make(map[reflect.Type]*structSpec)
|
||||||
|
defaultFieldSpec = &fieldSpec{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func structSpecForType(t reflect.Type) *structSpec {
|
||||||
|
|
||||||
|
structSpecMutex.RLock()
|
||||||
|
ss, found := structSpecCache[t]
|
||||||
|
structSpecMutex.RUnlock()
|
||||||
|
if found {
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
structSpecMutex.Lock()
|
||||||
|
defer structSpecMutex.Unlock()
|
||||||
|
ss, found = structSpecCache[t]
|
||||||
|
if found {
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
ss = &structSpec{m: make(map[string]*fieldSpec)}
|
||||||
|
compileStructSpec(t, make(map[string]int), nil, ss)
|
||||||
|
structSpecCache[t] = ss
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct")
|
||||||
|
|
||||||
|
// ScanStruct scans alternating names and values from src to a struct. The
|
||||||
|
// HGETALL and CONFIG GET commands return replies in this format.
|
||||||
|
//
|
||||||
|
// ScanStruct uses exported field names to match values in the response. Use
|
||||||
|
// 'redis' field tag to override the name:
|
||||||
|
//
|
||||||
|
// Field int `redis:"myName"`
|
||||||
|
//
|
||||||
|
// Fields with the tag redis:"-" are ignored.
|
||||||
|
//
|
||||||
|
// Each field uses RedisScan if available otherwise:
|
||||||
|
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
||||||
|
// standard strconv package to convert bulk string values to numeric and
|
||||||
|
// boolean types.
|
||||||
|
//
|
||||||
|
// If a src element is nil, then the corresponding field is not modified.
|
||||||
|
func ScanStruct(src []interface{}, dest interface{}) error {
|
||||||
|
d := reflect.ValueOf(dest)
|
||||||
|
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||||
|
return errScanStructValue
|
||||||
|
}
|
||||||
|
d = d.Elem()
|
||||||
|
if d.Kind() != reflect.Struct {
|
||||||
|
return errScanStructValue
|
||||||
|
}
|
||||||
|
ss := structSpecForType(d.Type())
|
||||||
|
|
||||||
|
if len(src)%2 != 0 {
|
||||||
|
return errors.New("redigo.ScanStruct: number of values not a multiple of 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(src); i += 2 {
|
||||||
|
s := src[i+1]
|
||||||
|
if s == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, ok := src[i].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
|
||||||
|
}
|
||||||
|
fs := ss.fieldSpec(name)
|
||||||
|
if fs == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||||
|
return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScanSlice scans src to the slice pointed to by dest. The elements the dest
|
||||||
|
// slice must be integer, float, boolean, string, struct or pointer to struct
|
||||||
|
// values.
|
||||||
|
//
|
||||||
|
// Struct fields must be integer, float, boolean or string values. All struct
|
||||||
|
// fields are used unless a subset is specified using fieldNames.
|
||||||
|
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
|
||||||
|
d := reflect.ValueOf(dest)
|
||||||
|
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||||
|
return errScanSliceValue
|
||||||
|
}
|
||||||
|
d = d.Elem()
|
||||||
|
if d.Kind() != reflect.Slice {
|
||||||
|
return errScanSliceValue
|
||||||
|
}
|
||||||
|
|
||||||
|
isPtr := false
|
||||||
|
t := d.Type().Elem()
|
||||||
|
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||||
|
isPtr = true
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
ensureLen(d, len(src))
|
||||||
|
for i, s := range src {
|
||||||
|
if s == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := convertAssignValue(d.Index(i), s); err != nil {
|
||||||
|
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := structSpecForType(t)
|
||||||
|
fss := ss.l
|
||||||
|
if len(fieldNames) > 0 {
|
||||||
|
fss = make([]*fieldSpec, len(fieldNames))
|
||||||
|
for i, name := range fieldNames {
|
||||||
|
fss[i] = ss.m[name]
|
||||||
|
if fss[i] == nil {
|
||||||
|
return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fss) == 0 {
|
||||||
|
return errors.New("redigo.ScanSlice: no struct fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(src) / len(fss)
|
||||||
|
if n*len(fss) != len(src) {
|
||||||
|
return errors.New("redigo.ScanSlice: length not a multiple of struct field count")
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureLen(d, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
d := d.Index(i)
|
||||||
|
if isPtr {
|
||||||
|
if d.IsNil() {
|
||||||
|
d.Set(reflect.New(t))
|
||||||
|
}
|
||||||
|
d = d.Elem()
|
||||||
|
}
|
||||||
|
for j, fs := range fss {
|
||||||
|
s := src[i*len(fss)+j]
|
||||||
|
if s == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||||
|
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args is a helper for constructing command arguments from structured values.
|
||||||
|
type Args []interface{}
|
||||||
|
|
||||||
|
// Add returns the result of appending value to args.
|
||||||
|
func (args Args) Add(value ...interface{}) Args {
|
||||||
|
return append(args, value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFlat returns the result of appending the flattened value of v to args.
|
||||||
|
//
|
||||||
|
// Maps are flattened by appending the alternating keys and map values to args.
|
||||||
|
//
|
||||||
|
// Slices are flattened by appending the slice elements to args.
|
||||||
|
//
|
||||||
|
// Structs are flattened by appending the alternating names and values of
|
||||||
|
// exported fields to args. If v is a nil struct pointer, then nothing is
|
||||||
|
// appended. The 'redis' field tag overrides struct field names. See ScanStruct
|
||||||
|
// for more information on the use of the 'redis' field tag.
|
||||||
|
//
|
||||||
|
// Other types are appended to args as is.
|
||||||
|
func (args Args) AddFlat(v interface{}) Args {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
args = flattenStruct(args, rv)
|
||||||
|
case reflect.Slice:
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
args = append(args, rv.Index(i).Interface())
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
for _, k := range rv.MapKeys() {
|
||||||
|
args = append(args, k.Interface(), rv.MapIndex(k).Interface())
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if rv.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
if !rv.IsNil() {
|
||||||
|
args = flattenStruct(args, rv.Elem())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = append(args, v)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
args = append(args, v)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenStruct(args Args, v reflect.Value) Args {
|
||||||
|
ss := structSpecForType(v.Type())
|
||||||
|
for _, fs := range ss.l {
|
||||||
|
fv := v.FieldByIndex(fs.index)
|
||||||
|
if fs.omitEmpty {
|
||||||
|
var empty = false
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
empty = fv.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
empty = !fv.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
empty = fv.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
empty = fv.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
empty = fv.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
empty = fv.IsNil()
|
||||||
|
}
|
||||||
|
if empty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = append(args, fs.name, fv.Interface())
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
91
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2012 Gary Burd
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Script encapsulates the source, hash and key count for a Lua script. See
|
||||||
|
// http://redis.io/commands/eval for information on scripts in Redis.
|
||||||
|
type Script struct {
|
||||||
|
keyCount int
|
||||||
|
src string
|
||||||
|
hash string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScript returns a new script object. If keyCount is greater than or equal
|
||||||
|
// to zero, then the count is automatically inserted in the EVAL command
|
||||||
|
// argument list. If keyCount is less than zero, then the application supplies
|
||||||
|
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
||||||
|
// SendHash methods.
|
||||||
|
func NewScript(keyCount int, src string) *Script {
|
||||||
|
h := sha1.New()
|
||||||
|
io.WriteString(h, src)
|
||||||
|
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
||||||
|
var args []interface{}
|
||||||
|
if s.keyCount < 0 {
|
||||||
|
args = make([]interface{}, 1+len(keysAndArgs))
|
||||||
|
args[0] = spec
|
||||||
|
copy(args[1:], keysAndArgs)
|
||||||
|
} else {
|
||||||
|
args = make([]interface{}, 2+len(keysAndArgs))
|
||||||
|
args[0] = spec
|
||||||
|
args[1] = s.keyCount
|
||||||
|
copy(args[2:], keysAndArgs)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the script hash.
|
||||||
|
func (s *Script) Hash() string {
|
||||||
|
return s.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
||||||
|
// script using the EVALSHA command. If the command fails because the script is
|
||||||
|
// not loaded, then Do evaluates the script using the EVAL command (thus
|
||||||
|
// causing the script to load).
|
||||||
|
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
||||||
|
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||||
|
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
||||||
|
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHash evaluates the script without waiting for the reply. The script is
|
||||||
|
// evaluated with the EVALSHA command. The application must ensure that the
|
||||||
|
// script is loaded by a previous call to Send, Do or Load methods.
|
||||||
|
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
||||||
|
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send evaluates the script without waiting for the reply.
|
||||||
|
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
||||||
|
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the script without evaluating it.
|
||||||
|
func (s *Script) Load(c Conn) error {
|
||||||
|
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
40
vendor/github.com/go-pay/gopay/.drone.yml
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/.gitignore
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/.idea
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
settings.json
|
||||||
|
|
||||||
|
vendor
|
||||||
201
vendor/github.com/go-pay/gopay/LICENSE
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/README.md
generated
vendored
Normal 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
|
||||||
|
|
||||||
|
[](https://github.com/iGoogle-ink)
|
||||||
|
[](https://github.com/go-pay/gopay/fork)
|
||||||
|
|
||||||
|
[](https://golang.google.cn)
|
||||||
|
[](https://pkg.go.dev/github.com/go-pay/gopay)
|
||||||
|
[](https://cloud.drone.io/go-pay/gopay)
|
||||||
|
[](https://github.com/go-pay/gopay/releases)
|
||||||
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
[](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
vendor/github.com/go-pay/gopay/alipay.jpg
generated
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
242
vendor/github.com/go-pay/gopay/body_map.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/constant.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/error.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/go.mod
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/go.sum
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/logo.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
43
vendor/github.com/go-pay/gopay/pkg/aes/aes_cbc.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/aes/aes_ecb.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/aes/aes_gcm.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/aes/pkcs_padding.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/errgroup/errgroup.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/util/common.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/util/convert.go
generated
vendored
Normal 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转字符串
|
||||||
|
// floatNum:float64数字
|
||||||
|
// 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转字符串
|
||||||
|
// floatNum:float32数字
|
||||||
|
// 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
vendor/github.com/go-pay/gopay/pkg/util/random.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/util/string.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xhttp/client.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xhttp/model.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/color.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/debug_logger.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/error_logger.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/info_logger.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/log.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xlog/warn_logger.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xpem/decode.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xtime/constants.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xtime/data_time.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xtime/parse_format.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/pkg/xtime/xtime.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/qq_gopay.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
670
vendor/github.com/go-pay/gopay/release_note.txt
generated
vendored
Normal file
@ -0,0 +1,670 @@
|
|||||||
|
版本号:Release 1.5.78
|
||||||
|
修改记录:
|
||||||
|
(1) 微信V3:V3EcommerceBalance() 缺失参数补充
|
||||||
|
(2) 微信V3:新增 client.V3EcommerceDayBalance() 方法,电商平台预约提现
|
||||||
|
(3) 微信V3:修复 银行列表获取相关接口,路由修正
|
||||||
|
(4) 微信V3:修复 client.V3BankSearchBank() 接口,私钥解密出错的问题
|
||||||
|
|
||||||
|
版本号:Release 1.5.77
|
||||||
|
修改记录:
|
||||||
|
(1) 微信V3:V3EcommerceRefundQueryById()、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) 微信V3:client.V3Apply4SubModifySettlement(),sub_mchid 问题处理
|
||||||
|
(2) 微信V3:微信分账接收方,model参数补充添加 detail_id
|
||||||
|
(3) PayPal:注释中接口文档地址更新
|
||||||
|
(4) PayPal:新增 client.OrderConfirm(),订单确认
|
||||||
|
(5) PayPal:OrderDetail、Capture、Payer、Name 等结构体,遗漏参数补充
|
||||||
|
(6) gopay:pkg/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) gopay:BodyMap 新增 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) 微信V3:fix 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) xhttp:xhttp client 优化,支持自定义client,默认还是使用标准 http.Client
|
||||||
|
|
||||||
|
版本号:Release 1.5.62
|
||||||
|
修改记录:
|
||||||
|
(1) 微信V3:client 内 WxSerialNo、ApiV3Key 公有化
|
||||||
|
(2) 微信V3:client 提供新方法 client.WxPublicKey() 直接获取 微信平台公钥
|
||||||
|
(3) 微信V3:wechat 提供新方法 wechat.V3VerifySignByPK(),不再推荐使用 wechat.V3VerifySign()
|
||||||
|
(4) 微信V3:V3NotifyReq 提供新方法 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) gopay:BodyMap,EncodeURLParams 方法稍作调整
|
||||||
|
(3) PayPal:PayPal支付能力接入(订单、支付)
|
||||||
|
|
||||||
|
版本号: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) 微信V3:wechat.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) pkg:xhttp.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) xhttp:Fix 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) QQ:client 添加 DebugSwitch 开关,默认关闭,不输出 请求参数和返回参数,通过 client.DebugSwitch = gopay.DebugOn 打开
|
||||||
|
(4) 更新 Gotil
|
||||||
|
|
||||||
|
版本号:Release 1.5.19
|
||||||
|
发布时间:2020/09/20 23:58
|
||||||
|
修改记录:
|
||||||
|
(1) 微信:修复 client.ProfitSharingQuery() 接口的Bug,https://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.13,fmt.Errorf() 使用 %w 格式化 error,1.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
vendor/github.com/go-pay/gopay/support_note.txt
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
赞助者皆为 微信 或 支付宝 用户名
|
||||||
|
|
||||||
|
平台 ---- 用户 ---- 金额
|
||||||
|
|
||||||
|
微信 ---- 燃烧的发丝 ---- 50
|
||||||
|
微信 ---- 三五五七ᴳᴼ ---- 100
|
||||||
|
微信 ---- 沐修 ---- 50
|
||||||
|
微信 ---- 深度学习 ---- 100
|
||||||
|
支付宝 ---- **旺 ---- 50
|
||||||
|
微信 ---- 开博黄工 ---- 88
|
||||||
|
微信 ---- 每天都有新旋律 ---- 100
|
||||||
|
微信 ---- *冉 ---- 50
|
||||||
|
微信 ---- *萧 ---- 80
|
||||||
146
vendor/github.com/go-pay/gopay/wechat/v3/applyment_for_sub.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/bank.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/bill.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/business_circle.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/cert.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/client.go
generated
vendored
Normal 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证书的证书序列号
|
||||||
|
// apiV3Key:APIv3Key,商户平台获取
|
||||||
|
// 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
vendor/github.com/go-pay/gopay/wechat/v3/complaint.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/constant.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/discount_card.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/ecommerce.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/encrypt_decrypt.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/gold_plan.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/market_activity.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
// 支付有礼
|
||||||
456
vendor/github.com/go-pay/gopay/wechat/v3/market_busifavor.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/market_favor.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/market_media.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/market_partner.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/media.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/merchant.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/model.go
generated
vendored
Normal file
328
vendor/github.com/go-pay/gopay/wechat/v3/notify.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/pay.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/pay_combine.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/pay_partner.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/profit_share.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/refund.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/score.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户授权记录(openid)API
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解除用户授权关系(openid)API
|
||||||
|
// 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
vendor/github.com/go-pay/gopay/wechat/v3/sign.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/smart_guide.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/transfer.go
generated
vendored
Normal 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
vendor/github.com/go-pay/gopay/wechat/v3/withdraw.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
BIN
vendor/github.com/go-pay/gopay/wechat_jerry.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
vendor/github.com/go-pay/gopay/zanshang.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
vendor/github.com/go-pay/gopay/zanshang_wx.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
vendor/github.com/go-pay/gopay/zanshang_zfb.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 31 KiB |
9
vendor/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
Icon?
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
.idea
|
||||||
117
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||||
|
|
||||||
|
# If you are submitting a patch, please add your name or the name of the
|
||||||
|
# organization which holds the copyright to this list in alphabetical order.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
|
||||||
|
# Individual Persons
|
||||||
|
|
||||||
|
Aaron Hopkins <go-sql-driver at die.net>
|
||||||
|
Achille Roussel <achille.roussel at gmail.com>
|
||||||
|
Alex Snast <alexsn at fb.com>
|
||||||
|
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
|
||||||
|
Andrew Reid <andrew.reid at tixtrack.com>
|
||||||
|
Animesh Ray <mail.rayanimesh at gmail.com>
|
||||||
|
Arne Hormann <arnehormann at gmail.com>
|
||||||
|
Ariel Mashraki <ariel at mashraki.co.il>
|
||||||
|
Asta Xie <xiemengjun at gmail.com>
|
||||||
|
Bulat Gaifullin <gaifullinbf at gmail.com>
|
||||||
|
Caine Jette <jette at alum.mit.edu>
|
||||||
|
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||||
|
Chris Moos <chris at tech9computers.com>
|
||||||
|
Craig Wilson <craiggwilson at gmail.com>
|
||||||
|
Daniel Montoya <dsmontoyam at gmail.com>
|
||||||
|
Daniel Nichter <nil at codenode.com>
|
||||||
|
Daniël van Eeden <git at myname.nl>
|
||||||
|
Dave Protasowski <dprotaso at gmail.com>
|
||||||
|
DisposaBoy <disposaboy at dby.me>
|
||||||
|
Egor Smolyakov <egorsmkv at gmail.com>
|
||||||
|
Erwan Martin <hello at erwan.io>
|
||||||
|
Evan Shaw <evan at vendhq.com>
|
||||||
|
Frederick Mayle <frederickmayle at gmail.com>
|
||||||
|
Gustavo Kristic <gkristic at gmail.com>
|
||||||
|
Hajime Nakagami <nakagami at gmail.com>
|
||||||
|
Hanno Braun <mail at hannobraun.com>
|
||||||
|
Henri Yandell <flamefew at gmail.com>
|
||||||
|
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||||
|
Huyiguang <hyg at webterren.com>
|
||||||
|
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||||
|
Ilia Cimpoes <ichimpoesh at gmail.com>
|
||||||
|
INADA Naoki <songofacandy at gmail.com>
|
||||||
|
Jacek Szwec <szwec.jacek at gmail.com>
|
||||||
|
James Harr <james.harr at gmail.com>
|
||||||
|
Jeff Hodges <jeff at somethingsimilar.com>
|
||||||
|
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||||
|
Jerome Meyer <jxmeyer at gmail.com>
|
||||||
|
Jiajia Zhong <zhong2plus at gmail.com>
|
||||||
|
Jian Zhen <zhenjl at gmail.com>
|
||||||
|
Joshua Prunier <joshua.prunier at gmail.com>
|
||||||
|
Julien Lefevre <julien.lefevr at gmail.com>
|
||||||
|
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||||
|
Justin Li <jli at j-li.net>
|
||||||
|
Justin Nuß <nuss.justin at gmail.com>
|
||||||
|
Kamil Dziedzic <kamil at klecza.pl>
|
||||||
|
Kei Kamikawa <x00.x7f.x86 at gmail.com>
|
||||||
|
Kevin Malachowski <kevin at chowski.com>
|
||||||
|
Kieron Woodhouse <kieron.woodhouse at infosum.com>
|
||||||
|
Lennart Rudolph <lrudolph at hmc.edu>
|
||||||
|
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||||
|
Linh Tran Tuan <linhduonggnu at gmail.com>
|
||||||
|
Lion Yang <lion at aosc.xyz>
|
||||||
|
Luca Looz <luca.looz92 at gmail.com>
|
||||||
|
Lucas Liu <extrafliu at gmail.com>
|
||||||
|
Luke Scott <luke at webconnex.com>
|
||||||
|
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
||||||
|
Michael Woolnough <michael.woolnough at gmail.com>
|
||||||
|
Nathanial Murphy <nathanial.murphy at gmail.com>
|
||||||
|
Nicola Peduzzi <thenikso at gmail.com>
|
||||||
|
Olivier Mengué <dolmen at cpan.org>
|
||||||
|
oscarzhao <oscarzhaosl at gmail.com>
|
||||||
|
Paul Bonser <misterpib at gmail.com>
|
||||||
|
Peter Schultz <peter.schultz at classmarkets.com>
|
||||||
|
Rebecca Chin <rchin at pivotal.io>
|
||||||
|
Reed Allman <rdallman10 at gmail.com>
|
||||||
|
Richard Wilkes <wilkes at me.com>
|
||||||
|
Robert Russell <robert at rrbrussell.com>
|
||||||
|
Runrioter Wung <runrioter at gmail.com>
|
||||||
|
Sho Iizuka <sho.i518 at gmail.com>
|
||||||
|
Sho Ikeda <suicaicoca at gmail.com>
|
||||||
|
Shuode Li <elemount at qq.com>
|
||||||
|
Simon J Mudd <sjmudd at pobox.com>
|
||||||
|
Soroush Pour <me at soroushjp.com>
|
||||||
|
Stan Putrya <root.vagner at gmail.com>
|
||||||
|
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||||
|
Steven Hartland <steven.hartland at multiplay.co.uk>
|
||||||
|
Tan Jinhua <312841925 at qq.com>
|
||||||
|
Thomas Wodarek <wodarekwebpage at gmail.com>
|
||||||
|
Tim Ruffles <timruffles at gmail.com>
|
||||||
|
Tom Jenkinson <tom at tjenkinson.me>
|
||||||
|
Vladimir Kovpak <cn007b at gmail.com>
|
||||||
|
Vladyslav Zhelezniak <zhvladi at gmail.com>
|
||||||
|
Xiangyu Hu <xiangyu.hu at outlook.com>
|
||||||
|
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||||
|
Xiuming Chen <cc at cxm.cc>
|
||||||
|
Xuehong Chan <chanxuehong at gmail.com>
|
||||||
|
Zhenye Xie <xiezhenye at gmail.com>
|
||||||
|
Zhixin Wen <john.wenzhixin at gmail.com>
|
||||||
|
|
||||||
|
# Organizations
|
||||||
|
|
||||||
|
Barracuda Networks, Inc.
|
||||||
|
Counting Ltd.
|
||||||
|
DigitalOcean Inc.
|
||||||
|
Facebook Inc.
|
||||||
|
GitHub Inc.
|
||||||
|
Google Inc.
|
||||||
|
InfoSum Ltd.
|
||||||
|
Keybase Inc.
|
||||||
|
Multiplay Ltd.
|
||||||
|
Percona LLC
|
||||||
|
Pivotal Inc.
|
||||||
|
Stripe Inc.
|
||||||
|
Zendesk Inc.
|
||||||
232
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
## Version 1.6 (2021-04-01)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190)
|
||||||
|
- `NullTime` is deprecated (#960, #1144)
|
||||||
|
- Reduce allocations when building SET command (#1111)
|
||||||
|
- Performance improvement for time formatting (#1118)
|
||||||
|
- Performance improvement for time parsing (#1098, #1113)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Implement `driver.Validator` interface (#1106, #1174)
|
||||||
|
- Support returning `uint64` from `Valuer` in `ConvertValue` (#1143)
|
||||||
|
- Add `json.RawMessage` for converter and prepared statement (#1059)
|
||||||
|
- Interpolate `json.RawMessage` as `string` (#1058)
|
||||||
|
- Implements `CheckNamedValue` (#1090)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Stop rounding times (#1121, #1172)
|
||||||
|
- Put zero filler into the SSL handshake packet (#1066)
|
||||||
|
- Fix checking cancelled connections back into the connection pool (#1095)
|
||||||
|
- Fix remove last 0 byte for mysql_old_password when password is empty (#1133)
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.5 (2020-01-07)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Dropped support Go 1.9 and lower (#823, #829, #886, #1016, #1017)
|
||||||
|
- Improve buffer handling (#890)
|
||||||
|
- Document potentially insecure TLS configs (#901)
|
||||||
|
- Use a double-buffering scheme to prevent data races (#943)
|
||||||
|
- Pass uint64 values without converting them to string (#838, #955)
|
||||||
|
- Update collations and make utf8mb4 default (#877, #1054)
|
||||||
|
- Make NullTime compatible with sql.NullTime in Go 1.13+ (#995)
|
||||||
|
- Removed CloudSQL support (#993, #1007)
|
||||||
|
- Add Go Module support (#1003)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Implement support of optional TLS (#900)
|
||||||
|
- Check connection liveness (#934, #964, #997, #1048, #1051, #1052)
|
||||||
|
- Implement Connector Interface (#941, #958, #1020, #1035)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Mark connections as bad on error during ping (#875)
|
||||||
|
- Mark connections as bad on error during dial (#867)
|
||||||
|
- Fix connection leak caused by rapid context cancellation (#1024)
|
||||||
|
- Mark connections as bad on error during Conn.Prepare (#1030)
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.4.1 (2018-11-14)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Fix TIME format for binary columns (#818)
|
||||||
|
- Fix handling of empty auth plugin names (#835)
|
||||||
|
- Fix caching_sha2_password with empty password (#826)
|
||||||
|
- Fix canceled context broke mysqlConn (#862)
|
||||||
|
- Fix OldAuthSwitchRequest support (#870)
|
||||||
|
- Fix Auth Response packet for cleartext password (#887)
|
||||||
|
|
||||||
|
## Version 1.4 (2018-06-03)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Documentation fixes (#530, #535, #567)
|
||||||
|
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
|
||||||
|
- Cache column names (#444)
|
||||||
|
- Sort the DSN parameters in DSNs generated from a config (#637)
|
||||||
|
- Allow native password authentication by default (#644)
|
||||||
|
- Use the default port if it is missing in the DSN (#668)
|
||||||
|
- Removed the `strict` mode (#676)
|
||||||
|
- Do not query `max_allowed_packet` by default (#680)
|
||||||
|
- Dropped support Go 1.6 and lower (#696)
|
||||||
|
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
|
||||||
|
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
|
||||||
|
- Improved the compatibility of the authentication system (#807)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Multi-Results support (#537)
|
||||||
|
- `rejectReadOnly` DSN option (#604)
|
||||||
|
- `context.Context` support (#608, #612, #627, #761)
|
||||||
|
- Transaction isolation level support (#619, #744)
|
||||||
|
- Read-Only transactions support (#618, #634)
|
||||||
|
- `NewConfig` function which initializes a config with default values (#679)
|
||||||
|
- Implemented the `ColumnType` interfaces (#667, #724)
|
||||||
|
- Support for custom string types in `ConvertValue` (#623)
|
||||||
|
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
|
||||||
|
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
|
||||||
|
- Implemented `driver.SessionResetter` (#779)
|
||||||
|
- `sha256_password` authentication plugin support (#808)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
|
||||||
|
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
|
||||||
|
- Removed columns definition cache since it sometimes cached invalid data (#592)
|
||||||
|
- Don't mutate registered TLS configs (#600)
|
||||||
|
- Make RegisterTLSConfig concurrency-safe (#613)
|
||||||
|
- Handle missing auth data in the handshake packet correctly (#646)
|
||||||
|
- Do not retry queries when data was written to avoid data corruption (#302, #736)
|
||||||
|
- Cache the connection pointer for error handling before invalidating it (#678)
|
||||||
|
- Fixed imports for appengine/cloudsql (#700)
|
||||||
|
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
|
||||||
|
- Set correct capacity for []bytes read from length-encoded strings (#766)
|
||||||
|
- Make RegisterDial concurrency-safe (#773)
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.3 (2016-12-01)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Go 1.1 is no longer supported
|
||||||
|
- Use decimals fields in MySQL to format time types (#249)
|
||||||
|
- Buffer optimizations (#269)
|
||||||
|
- TLS ServerName defaults to the host (#283)
|
||||||
|
- Refactoring (#400, #410, #437)
|
||||||
|
- Adjusted documentation for second generation CloudSQL (#485)
|
||||||
|
- Documented DSN system var quoting rules (#502)
|
||||||
|
- Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
|
||||||
|
- Support for returning table alias on Columns() (#289, #359, #382)
|
||||||
|
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
|
||||||
|
- Support for uint64 parameters with high bit set (#332, #345)
|
||||||
|
- Cleartext authentication plugin support (#327)
|
||||||
|
- Exported ParseDSN function and the Config struct (#403, #419, #429)
|
||||||
|
- Read / Write timeouts (#401)
|
||||||
|
- Support for JSON field type (#414)
|
||||||
|
- Support for multi-statements and multi-results (#411, #431)
|
||||||
|
- DSN parameter to set the driver-side max_allowed_packet value manually (#489)
|
||||||
|
- Native password authentication plugin support (#494, #524)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Fixed handling of queries without columns and rows (#255)
|
||||||
|
- Fixed a panic when SetKeepAlive() failed (#298)
|
||||||
|
- Handle ERR packets while reading rows (#321)
|
||||||
|
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
|
||||||
|
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
|
||||||
|
- Actually zero out bytes in handshake response (#378)
|
||||||
|
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
|
||||||
|
- Fixed tests with MySQL 5.7.9+ (#380)
|
||||||
|
- QueryUnescape TLS config names (#397)
|
||||||
|
- Fixed "broken pipe" error by writing to closed socket (#390)
|
||||||
|
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
|
||||||
|
- Fixed parsing of floats into float64 when placeholders are used (#434)
|
||||||
|
- Fixed DSN tests with Go 1.7+ (#459)
|
||||||
|
- Handle ERR packets while waiting for EOF (#473)
|
||||||
|
- Invalidate connection on error while discarding additional results (#513)
|
||||||
|
- Allow terminating packets of length 0 (#516)
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.2 (2014-06-03)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- We switched back to a "rolling release". `go get` installs the current master branch again
|
||||||
|
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
|
||||||
|
- Exported errors to allow easy checking from application code
|
||||||
|
- Enabled TCP Keepalives on TCP connections
|
||||||
|
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
|
||||||
|
- The DSN parser also checks for a missing separating slash
|
||||||
|
- Faster binary date / datetime to string formatting
|
||||||
|
- Also exported the MySQLWarning type
|
||||||
|
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
|
||||||
|
- writePacket() automatically writes the packet size to the header
|
||||||
|
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
|
||||||
|
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
|
||||||
|
- Logging of critical errors is configurable with `SetLogger`
|
||||||
|
- Google CloudSQL support
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Allow more than 32 parameters in prepared statements
|
||||||
|
- Various old_password fixes
|
||||||
|
- Fixed TestConcurrent test to pass Go's race detection
|
||||||
|
- Fixed appendLengthEncodedInteger for large numbers
|
||||||
|
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.1 (2013-11-02)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Go-MySQL-Driver now requires Go 1.1
|
||||||
|
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
|
||||||
|
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
|
||||||
|
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
|
||||||
|
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
|
||||||
|
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
|
||||||
|
- Optimized the buffer for reading
|
||||||
|
- stmt.Query now caches column metadata
|
||||||
|
- New Logo
|
||||||
|
- Changed the copyright header to include all contributors
|
||||||
|
- Improved the LOAD INFILE documentation
|
||||||
|
- The driver struct is now exported to make the driver directly accessible
|
||||||
|
- Refactored the driver tests
|
||||||
|
- Added more benchmarks and moved all to a separate file
|
||||||
|
- Other small refactoring
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
|
||||||
|
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
|
||||||
|
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
|
||||||
|
- Convert to DB timezone when inserting `time.Time`
|
||||||
|
- Splitted packets (more than 16MB) are now merged correctly
|
||||||
|
- Fixed false positive `io.EOF` errors when the data was fully read
|
||||||
|
- Avoid panics on reuse of closed connections
|
||||||
|
- Fixed empty string producing false nil values
|
||||||
|
- Fixed sign byte for positive TIME fields
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.0 (2013-05-14)
|
||||||
|
|
||||||
|
Initial Release
|
||||||
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||