go-wiki/vendor/github.com/stripe/stripe-go/stripe.go
2020-03-20 00:19:27 +01:00

1139 lines
34 KiB
Go

// Package stripe provides the binding for Stripe REST APIs.
package stripe
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os/exec"
"reflect"
"runtime"
"strings"
"sync"
"time"
"github.com/stripe/stripe-go/form"
)
//
// Public constants
//
const (
// APIVersion is the currently supported API version
APIVersion string = "2020-03-02"
// APIBackend is a constant representing the API service backend.
APIBackend SupportedBackend = "api"
// APIURL is the URL of the API service backend.
APIURL string = "https://api.stripe.com"
// ConnectURL is the URL for OAuth.
ConnectURL string = "https://connect.stripe.com"
// ConnectBackend is a constant representing the connect service backend for
// OAuth.
ConnectBackend SupportedBackend = "connect"
// UnknownPlatform is the string returned as the system name if we couldn't get
// one from `uname`.
UnknownPlatform string = "unknown platform"
// UploadsBackend is a constant representing the uploads service backend.
UploadsBackend SupportedBackend = "uploads"
// UploadsURL is the URL of the uploads service backend.
UploadsURL string = "https://files.stripe.com"
)
//
// Public variables
//
// EnableTelemetry is a global override for enabling client telemetry, which
// sends request performance metrics to Stripe via the `X-Stripe-Client-Telemetry`
// header. If set to true, all clients will send telemetry metrics. Defaults to
// true.
//
// Telemetry can also be disabled on a per-client basis by instead creating a
// `BackendConfig` with `EnableTelemetry: false`.
var EnableTelemetry = true
// Key is the Stripe API key used globally in the binding.
var Key string
//
// Public types
//
// AppInfo contains information about the "app" which this integration belongs
// to. This should be reserved for plugins that wish to identify themselves
// with Stripe.
type AppInfo struct {
Name string `json:"name"`
PartnerID string `json:"partner_id"`
URL string `json:"url"`
Version string `json:"version"`
}
// formatUserAgent formats an AppInfo in a way that's suitable to be appended
// to a User-Agent string. Note that this format is shared between all
// libraries so if it's changed, it should be changed everywhere.
func (a *AppInfo) formatUserAgent() string {
str := a.Name
if a.Version != "" {
str += "/" + a.Version
}
if a.URL != "" {
str += " (" + a.URL + ")"
}
return str
}
// Backend is an interface for making calls against a Stripe service.
// This interface exists to enable mocking for during testing if needed.
type Backend interface {
Call(method, path, key string, params ParamsContainer, v interface{}) error
CallRaw(method, path, key string, body *form.Values, params *Params, v interface{}) error
CallMultipart(method, path, key, boundary string, body *bytes.Buffer, params *Params, v interface{}) error
SetMaxNetworkRetries(maxNetworkRetries int)
}
// BackendConfig is used to configure a new Stripe backend.
type BackendConfig struct {
// EnableTelemetry allows request metrics (request id and duration) to be sent
// to Stripe in subsequent requests via the `X-Stripe-Client-Telemetry` header.
//
// Defaults to false.
EnableTelemetry bool
// HTTPClient is an HTTP client instance to use when making API requests.
//
// If left unset, it'll be set to a default HTTP client for the package.
HTTPClient *http.Client
// LeveledLogger is the logger that the backend will use to log errors,
// warnings, and informational messages.
//
// LeveledLoggerInterface is implemented by LeveledLogger, and one can be
// initialized at the desired level of logging. LeveledLoggerInterface
// also provides out-of-the-box compatibility with a Logrus Logger, but may
// require a thin shim for use with other logging libraries that use less
// standard conventions like Zap.
LeveledLogger LeveledLoggerInterface
// LogLevel is the logging level of the library and defined by:
//
// 0: no logging
// 1: errors only
// 2: errors + informational (default)
// 3: errors + informational + debug
//
// Defaults to 0 (no logging), so please make sure to set this if you want
// to see logging output in your custom configuration.
//
// Deprecated: Logging should be configured with LeveledLogger instead.
LogLevel int
// Logger is where this backend will write its logs.
//
// If left unset, it'll be set to Logger.
//
// Deprecated: Logging should be configured with LeveledLogger instead.
Logger Printfer
// MaxNetworkRetries sets maximum number of times that the library will
// retry requests that appear to have failed due to an intermittent
// problem.
//
// Defaults to 0.
MaxNetworkRetries int
// URL is the base URL to use for API paths.
//
// If left empty, it'll be set to the default for the SupportedBackend.
URL string
}
// BackendImplementation is the internal implementation for making HTTP calls
// to Stripe.
//
// The public use of this struct is deprecated. It will be unexported in a
// future version.
type BackendImplementation struct {
Type SupportedBackend
URL string
HTTPClient *http.Client
LeveledLogger LeveledLoggerInterface
MaxNetworkRetries int
enableTelemetry bool
// networkRetriesSleep indicates whether the backend should use the normal
// sleep between retries.
//
// See also SetNetworkRetriesSleep.
networkRetriesSleep bool
requestMetricsBuffer chan requestMetrics
}
// Call is the Backend.Call implementation for invoking Stripe APIs.
func (s *BackendImplementation) Call(method, path, key string, params ParamsContainer, v interface{}) error {
var body *form.Values
var commonParams *Params
if params != nil {
// This is a little unfortunate, but Go makes it impossible to compare
// an interface value to nil without the use of the reflect package and
// its true disciples insist that this is a feature and not a bug.
//
// Here we do invoke reflect because (1) we have to reflect anyway to
// use encode with the form package, and (2) the corresponding removal
// of boilerplate that this enables makes the small performance penalty
// worth it.
reflectValue := reflect.ValueOf(params)
if reflectValue.Kind() == reflect.Ptr && !reflectValue.IsNil() {
commonParams = params.GetParams()
body = &form.Values{}
form.AppendTo(body, params)
}
}
return s.CallRaw(method, path, key, body, commonParams, v)
}
// CallMultipart is the Backend.CallMultipart implementation for invoking Stripe APIs.
func (s *BackendImplementation) CallMultipart(method, path, key, boundary string, body *bytes.Buffer, params *Params, v interface{}) error {
contentType := "multipart/form-data; boundary=" + boundary
req, err := s.NewRequest(method, path, key, contentType, params)
if err != nil {
return err
}
if err := s.Do(req, body, v); err != nil {
return err
}
return nil
}
// CallRaw is the implementation for invoking Stripe APIs internally without a backend.
func (s *BackendImplementation) CallRaw(method, path, key string, form *form.Values, params *Params, v interface{}) error {
var body string
if form != nil && !form.Empty() {
body = form.Encode()
// On `GET`, move the payload into the URL
if method == http.MethodGet {
path += "?" + body
body = ""
}
}
bodyBuffer := bytes.NewBufferString(body)
req, err := s.NewRequest(method, path, key, "application/x-www-form-urlencoded", params)
if err != nil {
return err
}
if err := s.Do(req, bodyBuffer, v); err != nil {
return err
}
return nil
}
// NewRequest is used by Call to generate an http.Request. It handles encoding
// parameters and attaching the appropriate headers.
func (s *BackendImplementation) NewRequest(method, path, key, contentType string, params *Params) (*http.Request, error) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
path = s.URL + path
// Body is set later by `Do`.
req, err := http.NewRequest(method, path, nil)
if err != nil {
s.LeveledLogger.Errorf("Cannot create Stripe request: %v", err)
return nil, err
}
authorization := "Bearer " + key
req.Header.Add("Authorization", authorization)
req.Header.Add("Content-Type", contentType)
req.Header.Add("Stripe-Version", APIVersion)
req.Header.Add("User-Agent", encodedUserAgent)
req.Header.Add("X-Stripe-Client-User-Agent", encodedStripeUserAgent)
if params != nil {
if params.Context != nil {
req = req.WithContext(params.Context)
}
if params.IdempotencyKey != nil {
idempotencyKey := strings.TrimSpace(*params.IdempotencyKey)
if len(idempotencyKey) > 255 {
return nil, errors.New("cannot use an idempotency key longer than 255 characters")
}
req.Header.Add("Idempotency-Key", idempotencyKey)
} else if isHTTPWriteMethod(method) {
req.Header.Add("Idempotency-Key", NewIdempotencyKey())
}
if params.StripeAccount != nil {
req.Header.Add("Stripe-Account", strings.TrimSpace(*params.StripeAccount))
}
for k, v := range params.Headers {
for _, line := range v {
// Use Set to override the default value possibly set before
req.Header.Set(k, line)
}
}
}
return req, nil
}
// Do is used by Call to execute an API request and parse the response. It uses
// the backend's HTTP client to execute the request and unmarshals the response
// into v. It also handles unmarshaling errors returned by the API.
func (s *BackendImplementation) Do(req *http.Request, body *bytes.Buffer, v interface{}) error {
s.LeveledLogger.Infof("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path)
if s.enableTelemetry {
select {
case metrics := <-s.requestMetricsBuffer:
metricsJSON, err := json.Marshal(&requestTelemetry{LastRequestMetrics: metrics})
if err == nil {
req.Header.Set("X-Stripe-Client-Telemetry", string(metricsJSON))
} else {
s.LeveledLogger.Warnf("Unable to encode client telemetry: %v", err)
}
default:
// There are no metrics available, so don't send any.
// This default case needs to be here to prevent Do from blocking on an
// empty requestMetricsBuffer.
}
}
var res *http.Response
var err error
var requestDuration time.Duration
var resBody []byte
for retry := 0; ; {
start := time.Now()
// This might look a little strange, but we set the request's body
// outside of `NewRequest` so that we can get a fresh version every
// time.
//
// The background is that back in the era of old style HTTP, it was
// safe to reuse `Request` objects, but with the addition of HTTP/2,
// it's now only sometimes safe. Reusing a `Request` with a body will
// break.
//
// See some details here:
//
// https://github.com/golang/go/issues/19653#issuecomment-341539160
//
// And our original bug report here:
//
// https://github.com/stripe/stripe-go/issues/642
//
// To workaround the problem, we put a fresh `Body` onto the `Request`
// every time we execute it, and this seems to empirically resolve the
// problem.
if body != nil {
// We can safely reuse the same buffer that we used to encode our body,
// but return a new reader to it everytime so that each read is from
// the beginning.
reader := bytes.NewReader(body.Bytes())
req.Body = nopReadCloser{reader}
// And also add the same thing to `Request.GetBody`, which allows
// `net/http` to get a new body in cases like a redirect. This is
// usually not used, but it doesn't hurt to set it in case it's
// needed. See:
//
// https://github.com/stripe/stripe-go/issues/710
//
req.GetBody = func() (io.ReadCloser, error) {
reader := bytes.NewReader(body.Bytes())
return nopReadCloser{reader}, nil
}
}
res, err = s.HTTPClient.Do(req)
requestDuration = time.Since(start)
s.LeveledLogger.Infof("Request completed in %v (retry: %v)", requestDuration, retry)
if err == nil {
resBody, err = ioutil.ReadAll(res.Body)
res.Body.Close()
}
if err != nil {
s.LeveledLogger.Errorf("Request failed with error: %v", err)
} else if res.StatusCode >= 400 {
err = s.ResponseToError(res, resBody)
if stripeErr, ok := err.(*Error); ok {
// The Stripe API makes a distinction between errors that were
// caused by invalid parameters or something else versus those
// that occurred *despite* valid parameters, the latter coming
// back with status 402.
//
// On a 402, log to info so as to not make an integration's log
// noisy with error messages that they don't have much control
// over.
//
// Note I use the constant 402 instead of an `http.Status*`
// constant because technically 402 is "Payment required". The
// Stripe API doesn't comply to the letter of the specification
// and uses it in a broader sense.
if res.StatusCode == 402 {
s.LeveledLogger.Infof("User-compelled request error from Stripe (status %v): %v",
res.StatusCode, stripeErr)
} else {
s.LeveledLogger.Errorf("Request error from Stripe (status %v): %v",
res.StatusCode, stripeErr)
}
} else {
s.LeveledLogger.Errorf("Error decoding error from Stripe: %v", err)
}
}
// If the response was okay, or an error that shouldn't be retried,
// we're done, and it's safe to leave the retry loop.
if !s.shouldRetry(err, req, res, retry) {
break
}
sleepDuration := s.sleepTime(retry)
retry++
s.LeveledLogger.Warnf("Initiating retry %v for request %v %v%v after sleeping %v",
retry, req.Method, req.URL.Host, req.URL.Path, sleepDuration)
time.Sleep(sleepDuration)
}
if s.enableTelemetry && res != nil {
reqID := res.Header.Get("Request-Id")
if len(reqID) > 0 {
metrics := requestMetrics{
RequestDurationMS: int(requestDuration / time.Millisecond),
RequestID: reqID,
}
// If the metrics buffer is full, discard the new metrics. Otherwise, add
// them to the buffer.
select {
case s.requestMetricsBuffer <- metrics:
default:
}
}
}
if err != nil {
return err
}
s.LeveledLogger.Debugf("Response: %s\n", string(resBody))
if v != nil {
return s.UnmarshalJSONVerbose(res.StatusCode, resBody, v)
}
return nil
}
// ResponseToError converts a stripe response to an Error.
func (s *BackendImplementation) ResponseToError(res *http.Response, resBody []byte) error {
var raw rawError
if s.Type == ConnectBackend {
// If this is an OAuth request, deserialize as Error because OAuth errors
// are a different shape from the standard API errors.
var topLevelError rawErrorInternal
if err := s.UnmarshalJSONVerbose(res.StatusCode, resBody, &topLevelError); err != nil {
return err
}
raw.E = &topLevelError
} else {
if err := s.UnmarshalJSONVerbose(res.StatusCode, resBody, &raw); err != nil {
return err
}
}
// no error in resBody
if raw.E == nil {
err := errors.New(string(resBody))
return err
}
raw.E.HTTPStatusCode = res.StatusCode
raw.E.RequestID = res.Header.Get("Request-Id")
var typedError error
switch raw.E.Type {
case ErrorTypeAPI:
typedError = &APIError{stripeErr: raw.E.Error}
case ErrorTypeAPIConnection:
typedError = &APIConnectionError{stripeErr: raw.E.Error}
case ErrorTypeAuthentication:
typedError = &AuthenticationError{stripeErr: raw.E.Error}
case ErrorTypeCard:
cardErr := &CardError{stripeErr: raw.E.Error}
if raw.E.DeclineCode != nil {
cardErr.DeclineCode = *raw.E.DeclineCode
}
typedError = cardErr
case ErrorTypeInvalidRequest:
typedError = &InvalidRequestError{stripeErr: raw.E.Error}
case ErrorTypePermission:
typedError = &PermissionError{stripeErr: raw.E.Error}
case ErrorTypeRateLimit:
typedError = &RateLimitError{stripeErr: raw.E.Error}
}
raw.E.Err = typedError
return raw.E.Error
}
// SetMaxNetworkRetries sets max number of retries on failed requests
//
// This function is deprecated. Please use GetBackendWithConfig instead.
func (s *BackendImplementation) SetMaxNetworkRetries(maxNetworkRetries int) {
s.MaxNetworkRetries = maxNetworkRetries
}
// SetNetworkRetriesSleep allows the normal sleep between network retries to be
// enabled or disabled.
//
// This function is available for internal testing only and should never be
// used in production.
func (s *BackendImplementation) SetNetworkRetriesSleep(sleep bool) {
s.networkRetriesSleep = sleep
}
// UnmarshalJSONVerbose unmarshals JSON, but in case of a failure logs and
// produces a more descriptive error.
func (s *BackendImplementation) UnmarshalJSONVerbose(statusCode int, body []byte, v interface{}) error {
err := json.Unmarshal(body, v)
if err != nil {
// If we got invalid JSON back then something totally unexpected is
// happening (caused by a bug on the server side). Put a sample of the
// response body into the error message so we can get a better feel for
// what the problem was.
bodySample := string(body)
if len(bodySample) > 500 {
bodySample = bodySample[0:500] + " ..."
}
// Make sure a multi-line response ends up all on one line
bodySample = strings.Replace(bodySample, "\n", "\\n", -1)
newErr := fmt.Errorf("Couldn't deserialize JSON (response status: %v, body sample: '%s'): %v",
statusCode, bodySample, err)
s.LeveledLogger.Errorf("%s", newErr.Error())
return newErr
}
return nil
}
// Checks if an error is a problem that we should retry on. This includes both
// socket errors that may represent an intermittent problem and some special
// HTTP statuses.
func (s *BackendImplementation) shouldRetry(err error, req *http.Request, resp *http.Response, numRetries int) bool {
if numRetries >= s.MaxNetworkRetries {
return false
}
stripeErr, _ := err.(*Error)
// TODO: This retries any non-Stripe errors produced as part of the
// communication process. It generally works, but there are many errors
// that should *not* be retried. Try to make this more granular by
// including only connection errors, timeout errors, etc.
if stripeErr == nil && err != nil {
return true
}
// The API may ask us not to retry (e.g. if doing so would be a no-op), or
// advise us to retry (e.g. in cases of lock timeouts). Defer to those
// instructions if given.
if resp.Header.Get("Stripe-Should-Retry") == "false" {
return false
}
if resp.Header.Get("Stripe-Should-Retry") == "true" {
return true
}
// 409 Conflict
if resp.StatusCode == http.StatusConflict {
return true
}
// 429 Too Many Requests
//
// There are a few different problems that can lead to a 429. The most
// common is rate limiting, on which we *don't* want to retry because
// that'd likely contribute to more contention problems. However, some 429s
// are lock timeouts, which is when a request conflicted with another
// request or an internal process on some particular object. These 429s are
// safe to retry.
if resp.StatusCode == http.StatusTooManyRequests {
if stripeErr != nil && stripeErr.Code == ErrorCodeLockTimeout {
return true
}
}
// 500 Internal Server Error
//
// We only bother retrying these for non-POST requests. POSTs end up being
// cached by the idempotency layer so there's no purpose in retrying them.
if resp.StatusCode >= http.StatusInternalServerError && req.Method != http.MethodPost {
return true
}
// 503 Service Unavailable
if resp.StatusCode == http.StatusServiceUnavailable {
return true
}
return false
}
// sleepTime calculates sleeping/delay time in milliseconds between failure and a new one request.
func (s *BackendImplementation) sleepTime(numRetries int) time.Duration {
// We disable sleeping in some cases for tests.
if !s.networkRetriesSleep {
return 0 * time.Second
}
// Apply exponential backoff with minNetworkRetriesDelay on the
// number of num_retries so far as inputs.
delay := minNetworkRetriesDelay + minNetworkRetriesDelay*time.Duration(numRetries*numRetries)
// Do not allow the number to exceed maxNetworkRetriesDelay.
if delay > maxNetworkRetriesDelay {
delay = maxNetworkRetriesDelay
}
// Apply some jitter by randomizing the value in the range of 75%-100%.
jitter := rand.Int63n(int64(delay / 4))
delay -= time.Duration(jitter)
// But never sleep less than the base sleep seconds.
if delay < minNetworkRetriesDelay {
delay = minNetworkRetriesDelay
}
return delay
}
// Backends are the currently supported endpoints.
type Backends struct {
API, Connect, Uploads Backend
mu sync.RWMutex
}
// SupportedBackend is an enumeration of supported Stripe endpoints.
// Currently supported values are "api" and "uploads".
type SupportedBackend string
//
// Public functions
//
// Bool returns a pointer to the bool value passed in.
func Bool(v bool) *bool {
return &v
}
// BoolValue returns the value of the bool pointer passed in or
// false if the pointer is nil.
func BoolValue(v *bool) bool {
if v != nil {
return *v
}
return false
}
// BoolSlice returns a slice of bool pointers given a slice of bools.
func BoolSlice(v []bool) []*bool {
out := make([]*bool, len(v))
for i := range v {
out[i] = &v[i]
}
return out
}
// Float64 returns a pointer to the float64 value passed in.
func Float64(v float64) *float64 {
return &v
}
// Float64Value returns the value of the float64 pointer passed in or
// 0 if the pointer is nil.
func Float64Value(v *float64) float64 {
if v != nil {
return *v
}
return 0
}
// Float64Slice returns a slice of float64 pointers given a slice of float64s.
func Float64Slice(v []float64) []*float64 {
out := make([]*float64, len(v))
for i := range v {
out[i] = &v[i]
}
return out
}
// FormatURLPath takes a format string (of the kind used in the fmt package)
// representing a URL path with a number of parameters that belong in the path
// and returns a formatted string.
//
// This is mostly a pass through to Sprintf. It exists to make it
// it impossible to accidentally provide a parameter type that would be
// formatted improperly; for example, a string pointer instead of a string.
//
// It also URL-escapes every given parameter. This usually isn't necessary for
// a standard Stripe ID, but is needed in places where user-provided IDs are
// allowed, like in coupons or plans. We apply it broadly for extra safety.
func FormatURLPath(format string, params ...string) string {
// Convert parameters to interface{} and URL-escape them
untypedParams := make([]interface{}, len(params))
for i, param := range params {
untypedParams[i] = interface{}(url.QueryEscape(param))
}
return fmt.Sprintf(format, untypedParams...)
}
// GetBackend returns one of the library's supported backends based off of the
// given argument.
//
// It returns an existing default backend if one's already been created.
func GetBackend(backendType SupportedBackend) Backend {
var backend Backend
backends.mu.RLock()
switch backendType {
case APIBackend:
backend = backends.API
case ConnectBackend:
backend = backends.Connect
case UploadsBackend:
backend = backends.Uploads
}
backends.mu.RUnlock()
if backend != nil {
return backend
}
backend = GetBackendWithConfig(
backendType,
&BackendConfig{
HTTPClient: httpClient,
LeveledLogger: DefaultLeveledLogger,
LogLevel: LogLevel,
Logger: Logger,
MaxNetworkRetries: 0,
URL: "", // Set by GetBackendWithConfiguation when empty
},
)
SetBackend(backendType, backend)
return backend
}
// GetBackendWithConfig is the same as GetBackend except that it can be given a
// configuration struct that will configure certain aspects of the backend
// that's return.
func GetBackendWithConfig(backendType SupportedBackend, config *BackendConfig) Backend {
if config.HTTPClient == nil {
config.HTTPClient = httpClient
}
if config.LeveledLogger == nil {
if config.Logger == nil {
config.Logger = Logger
}
config.LeveledLogger = &leveledLoggerPrintferShim{
level: printferLevel(config.LogLevel),
logger: config.Logger,
}
}
switch backendType {
case APIBackend:
if config.URL == "" {
config.URL = apiURL
}
config.URL = normalizeURL(config.URL)
return newBackendImplementation(backendType, config)
case UploadsBackend:
if config.URL == "" {
config.URL = uploadsURL
}
config.URL = normalizeURL(config.URL)
return newBackendImplementation(backendType, config)
case ConnectBackend:
if config.URL == "" {
config.URL = ConnectURL
}
config.URL = normalizeURL(config.URL)
return newBackendImplementation(backendType, config)
}
return nil
}
// Int64 returns a pointer to the int64 value passed in.
func Int64(v int64) *int64 {
return &v
}
// Int64Value returns the value of the int64 pointer passed in or
// 0 if the pointer is nil.
func Int64Value(v *int64) int64 {
if v != nil {
return *v
}
return 0
}
// Int64Slice returns a slice of int64 pointers given a slice of int64s.
func Int64Slice(v []int64) []*int64 {
out := make([]*int64, len(v))
for i := range v {
out[i] = &v[i]
}
return out
}
// NewBackends creates a new set of backends with the given HTTP client. You
// should only need to use this for testing purposes or on App Engine.
func NewBackends(httpClient *http.Client) *Backends {
apiConfig := &BackendConfig{HTTPClient: httpClient}
connectConfig := &BackendConfig{HTTPClient: httpClient}
uploadConfig := &BackendConfig{HTTPClient: httpClient}
return &Backends{
API: GetBackendWithConfig(APIBackend, apiConfig),
Connect: GetBackendWithConfig(ConnectBackend, connectConfig),
Uploads: GetBackendWithConfig(UploadsBackend, uploadConfig),
}
}
// ParseID attempts to parse a string scalar from a given JSON value which is
// still encoded as []byte. If the value was a string, it returns the string
// along with true as the second return value. If not, false is returned as the
// second return value.
//
// The purpose of this function is to detect whether a given value in a
// response from the Stripe API is a string ID or an expanded object.
func ParseID(data []byte) (string, bool) {
s := string(data)
if !strings.HasPrefix(s, "\"") {
return "", false
}
if !strings.HasSuffix(s, "\"") {
return "", false
}
return s[1 : len(s)-1], true
}
// SetAppInfo sets app information. See AppInfo.
func SetAppInfo(info *AppInfo) {
if info != nil && info.Name == "" {
panic(fmt.Errorf("App info name cannot be empty"))
}
appInfo = info
// This is run in init, but we need to reinitialize it now that we have
// some app info.
initUserAgent()
}
// SetBackend sets the backend used in the binding.
func SetBackend(backend SupportedBackend, b Backend) {
backends.mu.Lock()
defer backends.mu.Unlock()
switch backend {
case APIBackend:
backends.API = b
case ConnectBackend:
backends.Connect = b
case UploadsBackend:
backends.Uploads = b
}
}
// SetHTTPClient overrides the default HTTP client.
// This is useful if you're running in a Google AppEngine environment
// where the http.DefaultClient is not available.
func SetHTTPClient(client *http.Client) {
httpClient = client
}
// String returns a pointer to the string value passed in.
func String(v string) *string {
return &v
}
// StringValue returns the value of the string pointer passed in or
// "" if the pointer is nil.
func StringValue(v *string) string {
if v != nil {
return *v
}
return ""
}
// StringSlice returns a slice of string pointers given a slice of strings.
func StringSlice(v []string) []*string {
out := make([]*string, len(v))
for i := range v {
out[i] = &v[i]
}
return out
}
//
// Private constants
//
const apiURL = "https://api.stripe.com"
// clientversion is the binding version
const clientversion = "70.5.0"
// defaultHTTPTimeout is the default timeout on the http.Client used by the library.
// This is chosen to be consistent with the other Stripe language libraries and
// to coordinate with other timeouts configured in the Stripe infrastructure.
const defaultHTTPTimeout = 80 * time.Second
// maxNetworkRetriesDelay and minNetworkRetriesDelay defines sleep time in milliseconds between
// tries to send HTTP request again after network failure.
const maxNetworkRetriesDelay = 5000 * time.Millisecond
const minNetworkRetriesDelay = 500 * time.Millisecond
// The number of requestMetric objects to buffer for client telemetry. When the
// buffer is full, new requestMetrics are dropped.
const telemetryBufferSize = 16
const uploadsURL = "https://uploads.stripe.com"
//
// Private types
//
// nopReadCloser's sole purpose is to give us a way to turn an `io.Reader` into
// an `io.ReadCloser` by adding a no-op implementation of the `Closer`
// interface. We need this because `http.Request`'s `Body` takes an
// `io.ReadCloser` instead of a `io.Reader`.
type nopReadCloser struct {
io.Reader
}
func (nopReadCloser) Close() error { return nil }
// stripeClientUserAgent contains information about the current runtime which
// is serialized and sent in the `X-Stripe-Client-User-Agent` as additional
// debugging information.
type stripeClientUserAgent struct {
Application *AppInfo `json:"application"`
BindingsVersion string `json:"bindings_version"`
Language string `json:"lang"`
LanguageVersion string `json:"lang_version"`
Publisher string `json:"publisher"`
Uname string `json:"uname"`
}
// requestMetrics contains the id and duration of the last request sent
type requestMetrics struct {
RequestDurationMS int `json:"request_duration_ms"`
RequestID string `json:"request_id"`
}
// requestTelemetry contains the payload sent in the
// `X-Stripe-Client-Telemetry` header when BackendConfig.EnableTelemetry = true.
type requestTelemetry struct {
LastRequestMetrics requestMetrics `json:"last_request_metrics"`
}
//
// Private variables
//
var appInfo *AppInfo
var backends Backends
var encodedStripeUserAgent string
var encodedUserAgent string
// The default HTTP client used for communication with any of Stripe's
// backends.
//
// Can be overridden with the function `SetHTTPClient` or by setting the
// `HTTPClient` value when using `BackendConfig`.
var httpClient = &http.Client{
Timeout: defaultHTTPTimeout,
// There is a bug in Go's HTTP/2 implementation that occasionally causes it
// to send an empty body when it receives a `GOAWAY` message from a server:
//
// https://github.com/golang/go/issues/32441
//
// This is particularly problematic for this library because the empty body
// results in no parameters being sent, which usually results in a 400,
// which is a status code expressly not covered by retry logic.
//
// The bug seems to be somewhat tricky to fix and hasn't seen any traction
// lately, so for now we're mitigating by disabling HTTP/2 in stripe-go by
// default. Users who like to live dangerously can still re-enable it by
// specifying a custom HTTP client. When the bug above is fixed, we can
// turn it back on.
//
// The particular methodology here for disabling HTTP/2 is a little
// confusing at first glance, but is recommended by the `net/http`
// documentation ("Programs that must disable HTTP/2 can do so by setting
// Transport.TLSNextProto (for clients) ... to a non-nil, empty map.")
//
// Note that the test suite still uses HTTP/2 to run as it specifies its
// own HTTP client with it enabled. See `testing/testing.go`.
//
// (Written 2019/07/24.)
Transport: &http.Transport{
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
},
}
//
// Private functions
//
// getUname tries to get a uname from the system, but not that hard. It tries
// to execute `uname -a`, but swallows any errors in case that didn't work
// (i.e. non-Unix non-Mac system or some other reason).
func getUname() string {
path, err := exec.LookPath("uname")
if err != nil {
return UnknownPlatform
}
cmd := exec.Command(path, "-a")
var out bytes.Buffer
cmd.Stderr = nil // goes to os.DevNull
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
return UnknownPlatform
}
return out.String()
}
func init() {
initUserAgent()
}
func initUserAgent() {
encodedUserAgent = "Stripe/v1 GoBindings/" + clientversion
if appInfo != nil {
encodedUserAgent += " " + appInfo.formatUserAgent()
}
stripeUserAgent := &stripeClientUserAgent{
Application: appInfo,
BindingsVersion: clientversion,
Language: "go",
LanguageVersion: runtime.Version(),
Publisher: "stripe",
Uname: getUname(),
}
marshaled, err := json.Marshal(stripeUserAgent)
// Encoding this struct should never be a problem, so we're okay to panic
// in case it is for some reason.
if err != nil {
panic(err)
}
encodedStripeUserAgent = string(marshaled)
}
func isHTTPWriteMethod(method string) bool {
return method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch || method == http.MethodDelete
}
// newBackendImplementation returns a new Backend based off a given type and
// fully initialized BackendConfig struct.
//
// The vast majority of the time you should be calling GetBackendWithConfig
// instead of this function.
func newBackendImplementation(backendType SupportedBackend, config *BackendConfig) Backend {
var requestMetricsBuffer chan requestMetrics
enableTelemetry := config.EnableTelemetry || EnableTelemetry
// only allocate the requestMetrics buffer if client telemetry is enabled.
if enableTelemetry {
requestMetricsBuffer = make(chan requestMetrics, telemetryBufferSize)
}
return &BackendImplementation{
HTTPClient: config.HTTPClient,
LeveledLogger: config.LeveledLogger,
MaxNetworkRetries: config.MaxNetworkRetries,
Type: backendType,
URL: config.URL,
enableTelemetry: enableTelemetry,
networkRetriesSleep: true,
requestMetricsBuffer: requestMetricsBuffer,
}
}
func normalizeURL(url string) string {
// All paths include a leading slash, so to keep logs pretty, trim a
// trailing slash on the URL.
url = strings.TrimSuffix(url, "/")
// For a long time we had the `/v1` suffix as part of a configured URL
// rather than in the per-package URLs throughout the library. Continue
// to support this for the time being by stripping one that's been
// passed for better backwards compatibility.
url = strings.TrimSuffix(url, "/v1")
return url
}