Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/andrewpillar/stripeutil

Utility library for building SaaS platforms in Go - WIP
https://github.com/andrewpillar/stripeutil

go stripe stripe-api

Last synced: about 2 months ago
JSON representation

Utility library for building SaaS platforms in Go - WIP

Awesome Lists containing this project

README

        

# stripeutil

* [Overview](#overview)
* [Store](#store)
* [Stripe](#stripe)
* [Params](#params)
* [Client](#client)
* [Testing](#testing)

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.