-
Notifications
You must be signed in to change notification settings - Fork 44
v1 Migration Guide
If you have been using this library and recently run into a set of breaking changes, this guide will help you migrate your code to v1 where things will be stable. We highly recommend upgrading your code to this version as several bugs have been fixed and your own code will be much simpler and more maintainable after migrating.
The reason for the upgrade? Design and maintenance issues causing too much complexity for users of the library, particularly around error handling and return values. v1 addresses this and simplifies usage of the library.
Additionally, the internals of the library have been completely rewritten with several bugs fixed and more stable code. The library is now easier to use, easier to maintain, and simpler for the community to add contributions.
This guide will walk you through migrating to the new v1 version of this library step-by-step.
NOTE: If you do not want to (or are not ready to) upgrade, you can pin this library to
v0.1
which was released from the latestmaster
beforev1
was released.
NOTE: This guide does not cover any changes to internals as they are not applicable for end users. Most of the changes should be straightforward, however, if you are a contributor and have any questions/comments, please open an issue.
Major changes:
- Remove third argument from
recurly.NewClient()
- Support the
context
package - Simplify error handling
- Eliminate helper methods that increased the surface area of the library
- Improve method names
- Remove deprecated functions and fields
- Standardize and simplify pagination
- Add additional fields and functions that were previously not implemented
- Rework
NullInt
,NullBool
, andNullTime
to reduce common implementation bugs - Return
nil, nil
for allGet()
methods when the item is not found
This migration guide will walk you through updating your code to address all of these changes. We will first start out with high-level, library-wide changes and then explore changes by service.
The third parameter previously was an optional HTTP Client. If you need to set your own HTTP client, you can set directly:
client := recurly.NewClient("your-subdomain", "APIKEY")
client.Client = &http.Client{}
Virtually every method now takes context.Context
as the first parameter. You'll either need to pass your own context
into the function, or use context.Background()
if you do not have one.
Previously each function returned *recurly.Response
, along with the concrete type and error. For example, creating an account previously looked like this:
resp, account, err := client.Accounts.Create(recurly.Account{})
if err != nil {
return err
} else if resp.IsClientError() {
// bad request
} else if resp.IsServerError() {
// server error
} else {
// account was successfully created
}
This is unnecessarily complex and error prone: there are too many return values and just determining if the request succeeded is overly complex.
So what changed?
First, the resp
parameter is dropped. Next, you can determine if the request succeeded simply by checking the error message. So you can change your code to this:
account, err := client.Accounts.Create(recurly.Account{})
if err != nil {
return err
}
// account was successfully created
In the majority of cases, this is all you need to do!
If you had any code that generated transactions, you might previously look for a transaction error like this:
resp, purchase, err := client.Purchases.Create(recurly.Purchase{})
if err != nil {
return err
} else if resp != nil && resp.IsClientError() && resp.StatusCode == http.StatusUnprocessableEntity {
// Attempt to access the transaction error on resp
if resp.Transaction != nil && resp.Transaction.TransactionError != nil {
// handle transaction error
}
} else if err := wrapError(resp); err != nil { // look for client/server errors in resp
return err
}
// succeeded
That can now be simplified to:
purchase, err := client.Purchases.Create(context.Background(), recurly.Purchase{})
if e, ok := err.(*recurly.TransactionFailedError); ok {
// Transaction failed
// e.Transaction may be set
// e.TransactionError holds the details of why the transaction failed
} else if err != nil {
// Catch all for other errors
return err
}
This is the standard way to check for transaction errors -- regardless of the specific API call!
If you need to inspect validation errors, you can look for *recurly.ClientError
. See the README or godoc for details.
Pagination is much simpler now, and it is consistent for all methods that are named List()
or List*()
. Here is an example of how to paginate accounts:
// Initialize a pager with any pagination options needed.
pager := client.Accounts.List(&recurly.PagerOptions{
State: recurly.AccountStateActive,
})
// Count the records (if desired)
count, err := pager.Count(ctx)
if err != nil {
return err
}
// Or iterate through each of the pages
for pager.Next() {
var accounts []recurly.Account
if err := pager.Fetch(ctx, &accounts); err != nil {
return err
}
}
You can also let the library paginate for you and return all of the results at once:
pager := client.Accounts.List(nil)
var accounts []recurly.Account
if err := pager.FetchAll(ctx, &accounts); err != nil {
return err
}
Many users (including myself) have done this thinking they would get a valid value of 0
to be sent to recurly: recurly.NullInt{Int: 0}
without realizing that it should have been recurly.NullInt{Int: 0, Valid: true}
.
In order to simplify, the actual value and valid bool on all Null
types has been made unexported. You can create any of the types now like so:
recurly.NewInt(0)
recurly.NewBool(true)
recurly.NewTime(time.Now())
// Or if you have a pointer: validity is determined on if the pointer is non-nil
recurly.NewIntPtr(v)
recurly.NewBoolPtr(v)
recurly.NewTimePtr(v) // v must be non-nil and v.IsZero() must return false to be considered valid
In addition, each has an Int()
Bool()
or Time()
function (respectively) to access the underlying value:
recurly.NewInt(0).Int() // 0
recurly.NewBool(true).Bool() // true
recurly.NewTime(time.Now()).Time() // time.Time, previously *time.Time
Or to retrieve pointer values (where nil is returned if the value is invalid)
recurly.NewInt(0).IntPtr() // int ptr with a value of 0
recurly.NewBool(true).BoolPtr() // bool ptr with a value of true
recurly.NewTime(time.Now()).TimePtr() // time ptr with a value of time.Now()
If you need to also know if the valid held is valid, you can use the Value()
function:
value, ok := recurly.NewInt(0).Value() // 0, true
value, ok = recurly.NewBool(false).Value() // false, true
value, ok = recurly.NewTime(time.Now()).Value() // time.Time, true
Here is what the return values look like for invalid null values (we'll use a zero value to illustrate)
var nullInt recurly.NullInt
nullInt.Int() // returns 0
nullInt.Value() // returns 0, false
nullInt.IntPtr() // returns nil
When retrieving an individual item (such as an account, invoice, or subscription): if the item is not found, a nil item and nil error will be returned. This is standard for all functions named Get()
. All other functions would return a ClientError
when encountering a 404 Not Found
status code.
a, err := client.Accounts.Get(ctx, "1")
if err != nil {
return err
} else if a == nil {
// Account not found
}
Now we'll look at the specific changes that occurred for each service. The godoc for each of the services will give you more details than this section, but this will call out any important changes.
Updated from v2.18
to v2.20
-
client.Accounts.LookupAccountBalance()
renamed toclient.Accounts.Balance()
- The following changes have been made to the
Account
struct:-
Address
changed fromrecurly.Address
to*recurly.Address
- Added
CCEmails
- Added
HasLiveSubscription
- Added
HasActiveSubscription
- Added
HasFutureSubscription
- Added
HasCanceledsubscription
- Added
HasPastDueInvoice
-
-
AccountCode
field was removed from theAccountBalance
struct
No significant changes.
-
client.Adjustments.List()
renamed toclient.Adjustments.ListAccount()
- The following changes have been made to the
Adjustments
struct-
UnitAmountInCents
changed fromint
toNullInt
-
-
ExternalHPPType
added toBilling
struct -
client.Billing.CreateWithToken()
removed. Please useclient.Billing.Create(context.Background(), recurly.Billing{Token: "TOKEN"}
-
client.Billing.UpdateWithToken()
removed. Please useclient.Billing.Update(context.Background(), recurly.Billing{Token: "TOKEN"}
- Added new methods:
client.Coupons.Update()
client.Coupons.Restore()
client.Coupons.Generate()
- The following changes have been made to the
Coupon
struct:-
ID
changed fromuint64
toint64
-
SingleUse
field was removed from theCoupon
struct (deprecated by Recurly)
-
No significant changes.
-
client.Invoices.RefundVoidOpenAmount
changed to accept anInvoiceRefund
struct with parameters needed to refund. MoveamountInCents
andrefundMethod
moved toInvoiceRefund
struct.InvoiceRefund
struct allows you to send additional parameters previously not allowed. - Added
client.Invoices.RefundVoidLineItems
to refund specific line items
No significant changes.
- Added the following methods:
client.Purchases.Authorize()
client.Purchases.Pending()
client.Purchases.Capture()
client.Purchases.Cancel()
- Modified the
PurchaseSubscription
struct:- Added
ShippingAddress
- Added
ShippingAddressID
- Added
ShippingMethodCode
- Added
ShippingAmountInCents
- Added
- Renamed
client.Redemptions.GetForAccount
toclient.Redemptions.ListForAccount()
- Renamed
client.Redemptions.GetForInvoice()
toclient.Redemptions.ListForInvoice()
- Added the following methods:
client.Redemptions.ListSubscription()
-
client.Redemptions.Redeem()
now accepts aCouponRedemption
instead of individual parameters foraccountCode
andcurrency
. -
client.Redemptions.RedeemToSubscription()
has been removed. UseRedeem()
and set theSubscriptionUUID
field in theCouponRedemption
struct.
- Removed
client.ShippingAddresses.GetSubscriptions()
. This method does not exist in the Recurly API per their documentation.
New API added for v2.20.
- in
client.Subscriptions
:TerminateWithPartialRefund()
,TerminateWithFullRefund()
andTerminateWithoutRefund()
have all been consolidated intoclient.Subscriptions.Terminate()
. TherefundType
parameter acceptspartial
,full
, ornone
respectively. - In the
Subscription
struct:- Removed the
MakeUpdate()
method
- Removed the
- In the
NewSubscription
struct:- Changed
UnitAmountInCents
fromint
toNullInt
- Added
RevenueScheduleType
- Added
ShippingAddress
- Added
ShippingAddressID
- Added
ImportedTrial
- Added
ShippingMethodCode
- Added
ShippingAmountInCents
- Changed
- In the
UpdateSubscription
struct:- Changed
UnitAmountInCents
fromint
toNullInt
- Changed
AutoRenew
frombool
toNullBool
- Added
ImportedTrial
- Added
RevenueScheduleType
- Changed
- Removed
client.Transactions.Create()
(deprecated per Recurly). Use the Purchases API instead. - Removed the following helper methods from
CVVResult
IsMatch()
IsNoMatch()
NotProcessed()
ShouldHaveBeenPresent()
UnableToProcess()
- Removed
TransactionResult
embedded type fromCVVResult
andAVSResult
. All previous fields are accessible directly on the struct now.
- Changed
NewClient
- Previous:
func NewClient(httpClient *http.Client) *recurly.Client
- New:
func NewClient(subdomain, apiKey string) *Client
- Previous:
- Added examples and Godoc documentation for testing the package using mocks
If you run into any issues with migrating to v1, or find any issues in this documentation, please open an issue.