Skip to content

Utility library for building SaaS platforms in Go - WIP

License

Notifications You must be signed in to change notification settings

andrewpillar/stripeutil

Repository files navigation

stripeutil

stripeutil provides some utility functions and data structures for working with the Stripe API for builing a SaaS application. This provides simple ways of creating Customers and Subscriptions, and storing them in a user defined data store along with their PaymentMethods and Invoices. This also provides a simple way of handling webhook events that are emitted from Stripe.

Overview

stripeutil.Stripe is the main way to interact with the Stripe API. This is supposed to be used along with the stripeutil.Store interface which allows for storing the resources retrieved from Stripe. Below is a brief example as to how this library would be used to implement a subscription flow,

stripe := stripeutil.New(os.Getenv("STRIPE_SECRET"), store)

c, err := stripe.Customer("[email protected]")

if err != nil {
    panic(err) // Don't actually do this.
}

// Assume the payment method ID has been passed through the client, as
// opposed to being hardcoded.
pm, err := stripeutil.RetrievePaymentMethod(stripe, "pm_123456")

if err != nil {
    panic(err) // Handle error properly.
}

// Create a subscription for the given customer with the given payment
// method.
sub, err := stripe.Subscribe(c, pm, stripeutil.Params{
    "items": []stripeutil.Params{
        {"price": "price_123456"},
    },
})

if err != nil {
    panic(err) // Be more graceful when you do this.
}

the above code will first lookup the customer via the given stripeutil.Store implementation we pass. If a customer cannot be found then one is created in Stripe with the given email address and subsequently stored, before being returned. After this, we then retrieve the given payment method from Stripe, and pass this, along with the customer to the Subscribe call. We also specify the request parameters we wish to have set when creating a subscription in Stripe. Under the hood, stripeutil.Stripe will do the following when Subscribe is called,

  • Retrieve the subscription for the given customer from the underlying store
  • Attach the given payment method to the given customer
  • Update the customer's default payment method to what was given
  • Store the given payment method in the underlying store
  • Return the subscription for the given customer, if one was found otherwise...
  • ...a new subscription is created for the customer, and returned if the invoice status is valid

And below is how a cancellation flow of a subscription would work with this library,

c, err := stripe.Customer("[email protected]")

if err != nil {
    panic(err) // If a recover is used then a panic is fine right?
}

if err := stripe.Unsubscribe(c); err != nil {
    panic(err) // Let's hope a recover is somewhere...
}

with the above, we lookup the customer similar to how we did before, and pass them to the Unsubscribe call. This will update the customer's subscription to cancel at the end period, and update the subscription in the underlying store. However, if the customer's subscription cannot be found in the underlying store, or is not valid then nothing happens and nil is returned.

Store

stripeutil.Store is an interface that allows for storing the resources retrieved from Stripe. An implementation of this interface for PostgreSQL comes with this library out of the box. stripeutil.Stripe, depends on this interface for storing the customer, invoice, and subscription invoices during the Subscribe flow.

Stripe

stripeutil.Stripe is what is primarily used for interfacing with the Stripe API. This depends on the stripeutil.Store interface, as previously mentioned, for storing the resources retrieved from Stripe.

Params

stripeutil.Params allows for specifying the request parameters to set in the body of the request sent to Stripe. This is encoded to x-www-url-formencoded, when sent in a request, for example,

stripeutil.Params{
    "invoice_settings": stripeutil.Params{
        "default_payment_method": "pm_123456",
    },
}

would be encoded to,

invoice_settings[default_payment_method]=pm_123456

stripeutil.Stripe has a Post method that accepts a stripeutil.Params argument, this can be used for making more explicit calls to Stripe,

resp, err := stripe.Post("/v1/payment_intents", stripeutil.Params{
    "amount":               2000,
    "currency":             "gbp",
    "payment_method_types": []string{"card"},
})

the returned *http.Response can be used as usual.

Client

stripeutil.Client is a thin HTTP client for the Stripe API. All HTTP requests made through this client will be configured to talk to Stripe. This is embedded inside of stripeutil.Stripe so you can do stuff like,

resp, err := stripe.Get("/v1/customers")

for example, to get the customers you have. A new client can be created via stripeutil.NewClient, and through this you can configured which version of the Stripe API to use,

client := stripeutil.NewClient("2006-01-02", os.Getenv("STRIPE_SECRET"))

Note: If using an older/newer version of the Stripe API this way then it is highly recommended that you do not use stripeutil.Stripe and instead perform all interactions via stripeutil.Client. This is because stripeutil.Stripe relies on the stripe/stripe-go SDK, so the versions may not match if you do this.

Testing

The tests includes an integration test Test_Stripe, to have this invoked simply set the STRIPE_SECRET and STRIPE_PRICE environment variables when running go test.

$ STRIPE_SECRET=sk_test_123456 STRIPE_PRICE=price_123456 go test

STRIPE_PRICE here is used for setting the price of the subscription that is temporarily created in Stripe.

About

Utility library for building SaaS platforms in Go - WIP

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages