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