Skip to content

Commit

Permalink
Make API response accessible on returned API structs (#1054)
Browse files Browse the repository at this point in the history
* Make API response accessible on returned API structs

Makes an API response struct containing niceties like the raw response
body, status, and request ID accessible via API resource structs
returned from client functions. For example:

    customer, err := customer.New(params)
    fmt.Printf("request ID = %s\n", customer.LastResponse.RequestID)

This is a feature that already exists in other language API libraries
and which is requested occasionally here, usually for various situations
involving more complex usage or desire for better observability.

-- Implementation

We introduce a few new types to make this work:

* `APIResponse`: Represents a response from the Stripe API and includes
  things like request ID, status, and headers. I elected to create my
  own object instead of reusing `http.Response` because it gives us a
  little more flexibility, and hides many of myriad of fields exposed by
  the `http` version, which will hopefully give us a little more API
  stability/forward compatibility.

* `APIResource`: A struct that contains `LastResponse` and is meant to
  represent any type that can we returned from a Stripe API endpoint. A
  coupon is an `APIResource` and so is a list object. This struct is
  embedded in response structs where appropriate across the whole API
  surface area (e.g. `Coupon`, `ListMeta`, etc.).

* `LastResponseGetter`: A very basic interface to an object that looks
  like an `APIResource`. This isn't strictly necessary, but gives us
  slightly more flexibility around the API and makes backward
  compatibility a little bit better for non-standard use cases (see the
  section on that below).

`stripe.Do` and other backend calls all start taking objects which are
`LastResponseGetter` instead of `interface{}`. This provides us with some
type safety around forgetting to include an embedded `APIResource` on
structs that should have it by making the compiler balk.

As `stripe.Do` finishes running a request, it generates an `APIResponse`
object and sets it onto the API resource type it's deserializing and
returning (e.g. a `Coupon`).

Errors also embed `APIResource` and similarly get access to the same set
of fields as response resources, although in their case some of the
fields provided in `APIResponse` are duplicates of what they had
already (see "Caveats" below).

-- Backwards compatibility

This is a minor breaking change in that backend implementations methods
like `Do` now take `LastResponseGetter` instead of `interface{}`, which
is more strict.

The good news though is that:

* Very few users should be using any of these even if they're
  technically public. The resource-specific clients packages tend to do
  all the work.

* Users who are broken should have a very easy time updating code.
  Mostly this will just involve adding `APIResource` to structs that were
  being passed in.

-- Naming

* `APIResponse`: Went with this instead of `StripeResponse` as we see in
  some other libraries because the linter will complain that it
  "stutters" when used outside of the package (meaning, uses the same
  word twice in a row), i.e. `stripe.StripeResponse`. `APIResponse`
  sorts nicely with `APIResource` though, so I think it's okay.

* `LastResponse`: Copied the "last" convention from other API libraries
  like stripe-python.

* `LastResponseGetter`: Given an "-er" name per Go convention around
  small interfaces that are basically one liners -- e.g. `Reader`,
  `Writer, `Formatter`, `CloseNotifier`, etc. I can see the argument
  that this maybe should just be `APIResourceInterface` or something
  like that in case we start adding new things, but I figure at that
  point we can either rename it, or create a parent interface that
  encapsulates it:

    ``` go
    type APIResourceInterface interface {
        LastResponseGetter
    }
    ```

-- Caveats

* We only set the last response for top-level returned objects. For
  example, an `InvoiceItem` is an API resource, but if it's returned
  under an `Invoice`, only `Invoice` has a non-nil `LastResponse`. The
  same applies for all resources under list objects. I figure that doing
  it this way is more performant and makes a little bit more intuitive
  sense. Users should be able to work around it if they need to.

* There is some duplication between `LastResponse` and some other fields
  that already existed on `stripe.Error` because the latter was already
  exposing some of this information, e.g. `RequestID`. I figure this is
  okay: it's nice that `stripe.Error` is a `LastResponseGetter` for
  consistency with other API resources. The duplication is a little
  unfortunate, but not that big of a deal.

* Rename `LastResponseGetter` to `LastResponseSetter` and remove a function

* Update stripe.go

Co-Authored-By: Olivier Bellone <ob@stripe.com>

* Move `APIResource` onto individual list structs instead of having it in `ListMeta`

Co-authored-by: Brandur <brandur@stripe.com>
Co-authored-by: Olivier Bellone <ob@stripe.com>
  • Loading branch information
3 people authored Apr 3, 2020
1 parent 22dbae4 commit 8d70710
Show file tree
Hide file tree
Showing 77 changed files with 275 additions and 7 deletions.
3 changes: 3 additions & 0 deletions account.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ type AccountTOSAcceptance struct {
// Account is the resource representing your Stripe account.
// For more details see https://stripe.com/docs/api/#account.
type Account struct {
APIResource
BusinessProfile *AccountBusinessProfile `json:"business_profile"`
BusinessType AccountBusinessType `json:"business_type"`
Capabilities *AccountCapabilities `json:"capabilities"`
Expand Down Expand Up @@ -503,13 +504,15 @@ func (a *Account) UnmarshalJSON(data []byte) error {

// AccountList is a list of accounts as returned from a list endpoint.
type AccountList struct {
APIResource
ListMeta
Data []*Account `json:"data"`
}

// ExternalAccountList is a list of external accounts that may be either bank
// accounts or cards.
type ExternalAccountList struct {
APIResource
ListMeta

// Values contains any external accounts (bank accounts and/or cards)
Expand Down
1 change: 1 addition & 0 deletions accountlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type AccountLinkParams struct {
// AccountLink is the resource representing an account link.
// For more details see https://stripe.com/docs/api/#account_links.
type AccountLink struct {
APIResource
Created int64 `json:"created"`
ExpiresAt int64 `json:"expires_at"`
Object string `json:"object"`
Expand Down
2 changes: 2 additions & 0 deletions applepaydomain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ApplePayDomainParams struct {

// ApplePayDomain is the resource representing a Stripe ApplePayDomain object
type ApplePayDomain struct {
APIResource
Created int64 `json:"created"`
Deleted bool `json:"deleted"`
DomainName string `json:"domain_name"`
Expand All @@ -22,6 +23,7 @@ type ApplePayDomainListParams struct {

// ApplePayDomainList is a list of ApplePayDomains as returned from a list endpoint.
type ApplePayDomainList struct {
APIResource
ListMeta
Data []*ApplePayDomain `json:"data"`
}
1 change: 1 addition & 0 deletions balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type BalanceParams struct {
// Balance is the resource representing your Stripe balance.
// For more details see https://stripe.com/docs/api/#balance.
type Balance struct {
APIResource
Available []*Amount `json:"available"`
ConnectReserved []*Amount `json:"connect_reserved"`
Livemode bool `json:"livemode"`
Expand Down
2 changes: 2 additions & 0 deletions balancetransaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ type BalanceTransactionListParams struct {
// BalanceTransaction is the resource representing the balance transaction.
// For more details see https://stripe.com/docs/api/#balance.
type BalanceTransaction struct {
APIResource
Amount int64 `json:"amount"`
AvailableOn int64 `json:"available_on"`
Created int64 `json:"created"`
Expand All @@ -145,6 +146,7 @@ type BalanceTransaction struct {

// BalanceTransactionList is a list of transactions as returned from a list endpoint.
type BalanceTransactionList struct {
APIResource
ListMeta
Data []*BalanceTransaction `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions bankaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (p *BankAccountListParams) AppendTo(body *form.Values, keyParts []string) {

// BankAccount represents a Stripe bank account.
type BankAccount struct {
APIResource
Account *Account `json:"account"`
AccountHolderName string `json:"account_holder_name"`
AccountHolderType BankAccountAccountHolderType `json:"account_holder_type"`
Expand All @@ -160,6 +161,7 @@ type BankAccount struct {

// BankAccountList is a list object for bank accounts.
type BankAccountList struct {
APIResource
ListMeta
Data []*BankAccount `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions bitcoinreceiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type BitcoinReceiverListParams struct {
// BitcoinReceiver is the resource representing a Stripe bitcoin receiver.
// For more details see https://stripe.com/docs/api/#bitcoin_receivers
type BitcoinReceiver struct {
APIResource
Active bool `json:"active"`
Amount int64 `json:"amount"`
AmountReceived int64 `json:"amount_received"`
Expand All @@ -39,6 +40,7 @@ type BitcoinReceiver struct {

// BitcoinReceiverList is a list of bitcoin receivers as retrieved from a list endpoint.
type BitcoinReceiverList struct {
APIResource
ListMeta
Data []*BitcoinReceiver `json:"data"`
}
Expand Down
1 change: 1 addition & 0 deletions bitcointransaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type BitcoinTransactionListParams struct {
// It is a child object of BitcoinReceivers
// For more details see https://stripe.com/docs/api/#retrieve_bitcoin_receiver
type BitcoinTransactionList struct {
APIResource
ListMeta
Data []*BitcoinTransaction `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type CapabilityRequirements struct {
// Capability is the resource representing a Stripe capability.
// For more details see https://stripe.com/docs/api/capabilities
type Capability struct {
APIResource
Account *Account `json:"account"`
ID string `json:"id"`
Object string `json:"object"`
Expand All @@ -66,6 +67,7 @@ type Capability struct {

// CapabilityList is a list of capabilities as retrieved from a list endpoint.
type CapabilityList struct {
APIResource
ListMeta
Data []*Capability `json:"data"`
}
Expand Down
3 changes: 3 additions & 0 deletions card.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ func (p *CardListParams) AppendTo(body *form.Values, keyParts []string) {
// Card is the resource representing a Stripe credit/debit card.
// For more details see https://stripe.com/docs/api#cards.
type Card struct {
APIResource

AddressCity string `json:"address_city"`
AddressCountry string `json:"address_country"`
AddressLine1 string `json:"address_line1"`
Expand Down Expand Up @@ -249,6 +251,7 @@ type Card struct {

// CardList is a list object for cards.
type CardList struct {
APIResource
ListMeta
Data []*Card `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions charge.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ type ChargeTransferData struct {
// Charge is the resource representing a Stripe charge.
// For more details see https://stripe.com/docs/api#charges.
type Charge struct {
APIResource
Amount int64 `json:"amount"`
AmountRefunded int64 `json:"amount_refunded"`
Application *Application `json:"application"`
Expand Down Expand Up @@ -526,6 +527,7 @@ func (c *Charge) UnmarshalJSON(data []byte) error {

// ChargeList is a list of charges as retrieved from a list endpoint.
type ChargeList struct {
APIResource
ListMeta
Data []*Charge `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions checkout_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ type CheckoutSessionShippingAddressCollection struct {
// CheckoutSession is the resource representing a Stripe checkout session.
// For more details see https://stripe.com/docs/api/checkout/sessions/object
type CheckoutSession struct {
APIResource
CancelURL string `json:"cancel_url"`
ClientReferenceID string `json:"client_reference_id"`
Customer *Customer `json:"customer"`
Expand All @@ -187,6 +188,7 @@ type CheckoutSession struct {

// CheckoutSessionList is a list of sessions as retrieved from a list endpoint.
type CheckoutSessionList struct {
APIResource
ListMeta
Data []*CheckoutSession `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions countryspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type VerificationFieldsList struct {
// CountrySpec is the resource representing the rules required for a Stripe account.
// For more details see https://stripe.com/docs/api/#country_specs.
type CountrySpec struct {
APIResource
DefaultCurrency Currency `json:"default_currency"`
ID string `json:"id"`
SupportedBankAccountCurrencies map[Currency][]Country `json:"supported_bank_account_currencies"`
Expand All @@ -29,6 +30,7 @@ type CountrySpecParams struct {

// CountrySpecList is a list of country specs as retrieved from a list endpoint.
type CountrySpecList struct {
APIResource
ListMeta
Data []*CountrySpec `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions coupon.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type CouponListParams struct {
// Coupon is the resource representing a Stripe coupon.
// For more details see https://stripe.com/docs/api#coupons.
type Coupon struct {
APIResource
AmountOff int64 `json:"amount_off"`
Created int64 `json:"created"`
Currency Currency `json:"currency"`
Expand All @@ -57,6 +58,7 @@ type Coupon struct {

// CouponList is a list of coupons as retrieved from a list endpoint.
type CouponList struct {
APIResource
ListMeta
Data []*Coupon `json:"data"`
}
Expand Down
3 changes: 3 additions & 0 deletions creditnote.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type CreditNoteTaxAmount struct {
// CreditNote is the resource representing a Stripe credit note.
// For more details see https://stripe.com/docs/api/credit_notes/object.
type CreditNote struct {
APIResource
Amount int64 `json:"amount"`
Created int64 `json:"created"`
Currency Currency `json:"currency"`
Expand Down Expand Up @@ -174,12 +175,14 @@ type CreditNoteLineItem struct {

// CreditNoteList is a list of credit notes as retrieved from a list endpoint.
type CreditNoteList struct {
APIResource
ListMeta
Data []*CreditNote `json:"data"`
}

// CreditNoteLineItemList is a list of credit note line items as retrieved from a list endpoint.
type CreditNoteLineItemList struct {
APIResource
ListMeta
Data []*CreditNoteLineItem `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type CustomerListParams struct {
// Customer is the resource representing a Stripe customer.
// For more details see https://stripe.com/docs/api#customers.
type Customer struct {
APIResource
Address Address `json:"address"`
Balance int64 `json:"balance"`
Created int64 `json:"created"`
Expand Down Expand Up @@ -134,6 +135,7 @@ type CustomerInvoiceSettings struct {

// CustomerList is a list of customers as retrieved from a list endpoint.
type CustomerList struct {
APIResource
ListMeta
Data []*Customer `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions customerbalancetransaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type CustomerBalanceTransactionListParams struct {
// CustomerBalanceTransaction is the resource representing a customer balance transaction.
// For more details see https://stripe.com/docs/api/customers/customer_balance_transaction_object
type CustomerBalanceTransaction struct {
APIResource
Amount int64 `json:"amount"`
Created int64 `json:"created"`
CreditNote *CreditNote `json:"credit_note"`
Expand All @@ -57,6 +58,7 @@ type CustomerBalanceTransaction struct {
// CustomerBalanceTransactionList is a list of customer balance transactions as retrieved from a
// list endpoint.
type CustomerBalanceTransactionList struct {
APIResource
ListMeta
Data []*CustomerBalanceTransaction `json:"data"`
}
Expand Down
1 change: 1 addition & 0 deletions discount.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type DiscountParams struct {
// Discount is the resource representing a Stripe discount.
// For more details see https://stripe.com/docs/api#discounts.
type Discount struct {
APIResource
Coupon *Coupon `json:"coupon"`
Customer string `json:"customer"`
Deleted bool `json:"deleted"`
Expand Down
2 changes: 2 additions & 0 deletions dispute.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type DisputeListParams struct {
// Dispute is the resource representing a Stripe dispute.
// For more details see https://stripe.com/docs/api#disputes.
type Dispute struct {
APIResource
Amount int64 `json:"amount"`
BalanceTransactions []*BalanceTransaction `json:"balance_transactions"`
Charge *Charge `json:"charge"`
Expand All @@ -105,6 +106,7 @@ type Dispute struct {

// DisputeList is a list of disputes as retrieved from a list endpoint.
type DisputeList struct {
APIResource
ListMeta
Data []*Dispute `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions ephemeralkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type EphemeralKeyParams struct {
// EphemeralKey is the resource representing a Stripe ephemeral key. This is used by Mobile SDKs
// to for example manage a Customer's payment methods.
type EphemeralKey struct {
APIResource

AssociatedObjects []struct {
ID string `json:"id"`
Type string `json:"type"`
Expand Down
2 changes: 2 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ const (
// Error is the response returned when a call is unsuccessful.
// For more details see https://stripe.com/docs/api#errors.
type Error struct {
APIResource

ChargeID string `json:"charge,omitempty"`
Code ErrorCode `json:"code,omitempty"`
DeclineCode DeclineCode `json:"decline_code,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
// Event is the resource representing a Stripe event.
// For more details see https://stripe.com/docs/api#events.
type Event struct {
APIResource
Account string `json:"account"`
Created int64 `json:"created"`
Data *EventData `json:"data"`
Expand Down Expand Up @@ -49,6 +50,7 @@ type EventParams struct {

// EventList is a list of events as retrieved from a list endpoint.
type EventList struct {
APIResource
ListMeta
Data []*Event `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions exchangerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package stripe
// ExchangeRate is the resource representing the currency exchange rates at
// a given time.
type ExchangeRate struct {
APIResource
ID string `json:"id"`
Rates map[Currency]float64 `json:"rates"`
}
Expand All @@ -15,6 +16,7 @@ type ExchangeRateParams struct {

// ExchangeRateList is a list of exchange rates as retrieved from a list endpoint.
type ExchangeRateList struct {
APIResource
ListMeta
Data []*ExchangeRate `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type ApplicationFeeListParams struct {
// ApplicationFee is the resource representing a Stripe application fee.
// For more details see https://stripe.com/docs/api#application_fees.
type ApplicationFee struct {
APIResource
Account *Account `json:"account"`
Amount int64 `json:"amount"`
AmountRefunded int64 `json:"amount_refunded"`
Expand All @@ -37,6 +38,7 @@ type ApplicationFee struct {

//ApplicationFeeList is a list of application fees as retrieved from a list endpoint.
type ApplicationFeeList struct {
APIResource
ListMeta
Data []*ApplicationFee `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions feerefund.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type FeeRefundListParams struct {
// FeeRefund is the resource representing a Stripe application fee refund.
// For more details see https://stripe.com/docs/api#fee_refunds.
type FeeRefund struct {
APIResource
Amount int64 `json:"amount"`
BalanceTransaction *BalanceTransaction `json:"balance_transaction"`
Created int64 `json:"created"`
Expand All @@ -33,6 +34,7 @@ type FeeRefund struct {

// FeeRefundList is a list object for application fee refunds.
type FeeRefundList struct {
APIResource
ListMeta
Data []*FeeRefund `json:"data"`
}
Expand Down
2 changes: 2 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type FileListParams struct {
// File is the resource representing a Stripe file.
// For more details see https://stripe.com/docs/api#file_object.
type File struct {
APIResource
Created int64 `json:"created"`
ID string `json:"id"`
Filename string `json:"filename"`
Expand All @@ -77,6 +78,7 @@ type File struct {

// FileList is a list of files as retrieved from a list endpoint.
type FileList struct {
APIResource
ListMeta
Data []*File `json:"data"`
}
Expand Down
Loading

0 comments on commit 8d70710

Please sign in to comment.