Documentation
¶
Overview ¶
Package stripe is payforge's Stripe adapter. Client implements payforge.Billing against the Stripe API using the official stripe-go library.
Everything written to Stripe carries the spec's app in metadata (payforge.MetaApp), so several products can share one account, and every creating call carries an idempotency key. A secret key is passed to New and is never logged or returned in an error.
Index ¶
- Variables
- func ProductID(key string) string
- type Client
- func (c *Client) Account(ctx context.Context, accountID string) (payforge.AccountState, error)
- func (c *Client) CheckoutPlan(ctx context.Context, in payforge.PlanCheckout) (payforge.Session, error)
- func (c *Client) CheckoutProduct(ctx context.Context, in payforge.ProductCheckout) (payforge.Session, error)
- func (c *Client) CheckoutSale(ctx context.Context, in payforge.Sale) (payforge.Session, error)
- func (c *Client) CreateAccount(ctx context.Context, seller payforge.Subject, country string) (string, error)
- func (*Client) Enabled() bool
- func (c *Client) EnsureCustomer(ctx context.Context, s payforge.Subject) (string, error)
- func (c *Client) Mode() Mode
- func (c *Client) OnboardingLink(ctx context.Context, accountID, refreshURL, returnURL string) (string, error)
- func (c *Client) Plan(stored string) pricingspec.Plan
- func (c *Client) Portal(ctx context.Context, customerID, returnURL string) (string, error)
- func (c *Client) Subscriptions(ctx context.Context, customerID string) ([]payforge.SubscriptionState, error)
- func (c *Client) Sync(ctx context.Context, dryRun bool) (SyncReport, error)
- type Config
- type Mode
- type SyncAction
- type SyncKind
- type SyncReport
- type Webhook
Constants ¶
This section is empty.
Variables ¶
var ErrArchived = errors.New("stripe: product is archived")
ErrArchived means a product the spec needs is archived at the account. Sync does not bring archived objects back; unarchive it or choose another plan ID.
var ErrDeleted = errors.New("stripe: product was deleted")
ErrDeleted means a product was deleted at Stripe. A deleted product's ID cannot be used again, so the plan needs a new ID in the spec.
var ErrForeign = errors.New("stripe: object belongs to another app")
ErrForeign means an object at the account holds a name payforge uses but belongs to another product, so sync leaves it alone and stops.
Functions ¶
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client implements payforge.Billing for one spec on one Stripe account.
func New ¶
func New(spec *pricingspec.Spec, cfg Config) (*Client, error)
New returns a Client. It fails with payforge.ErrNotConfigured when there is no secret key, so a host that reaches this point without one is in free mode and should use payforge.Disabled, and with the spec's errors when the spec is invalid.
func (*Client) CheckoutPlan ¶
func (c *Client) CheckoutPlan(ctx context.Context, in payforge.PlanCheckout) (payforge.Session, error)
CheckoutPlan starts a subscription checkout for a self-serve plan.
The price is found by lookup key, so the catalog must have been synced to the account. A subject that already holds a granting subscription is refused with payforge.ErrAlreadySubscribed. Any plan checkout of this app still open for the subject is expired first, so only the newest can be paid and two tabs cannot both subscribe. Two checkouts made at the same moment can still both miss each other unless Config.Locker is set, which serializes them per subject. A plan with a trial starts one without asking for a card, and the subscription is canceled at the end if the buyer added none.
func (*Client) CheckoutProduct ¶
func (c *Client) CheckoutProduct(ctx context.Context, in payforge.ProductCheckout) (payforge.Session, error)
CheckoutProduct starts a one-time purchase checkout.
A fixed-price product is sold at its synced price. A seller-set product is sold at the amount given, which must reach the product's minimum. A Reference makes a retry return the same session.
func (*Client) CheckoutSale ¶
CheckoutSale starts a checkout whose proceeds go to the seller.
It is a destination charge made on the seller's behalf: the seller is the merchant on the buyer's statement, the money is transferred to the seller's account, and the platform's fee is taken as the application fee. The fee is the seller plan's rate when it sets one, else the marketplace's, rounded down to a cent. A seller whose account cannot take charges is refused with payforge.ErrSellerNotReady before any checkout is made, and an account this app did not create with payforge.ErrForeignAccount. The fee follows Sale.SellerPlan, so take it from your own record of the seller, never from a request. For a fixed-price product the fee is computed from the spec's amount: keep the catalog synced, or Stripe refuses a fee larger than a lower price.
func (*Client) CreateAccount ¶
func (c *Client) CreateAccount(ctx context.Context, seller payforge.Subject, country string) (string, error)
CreateAccount returns the Express account for a seller, creating it once.
An idempotency key from the app and the seller makes a second call return the account the first created. Stripe has no search for accounts, so a host that loses the ID cannot look it up again and should store it. The country is the seller's two-letter code and cannot be changed later.
func (*Client) EnsureCustomer ¶
EnsureCustomer returns the Stripe customer for a subject, creating it once.
It searches by the subject's metadata first, then creates with an idempotency key. Stripe's search can lag a creation by up to a minute, so the key covers that gap: a second call in the meantime gets the customer the first created. The key covers the name and email too, because Stripe refuses a key reused with different parameters; a changed email inside that gap can therefore create a second customer instead of failing. A host that stores the customer ID need not call this again.
func (*Client) Mode ¶
Mode is the mode of the client's key. Give it to the Webhook, so events of the other mode are not acted on.
func (*Client) OnboardingLink ¶
func (c *Client) OnboardingLink(ctx context.Context, accountID, refreshURL, returnURL string) (string, error)
OnboardingLink returns a one-use page where the seller gives Stripe the details it needs. The links expire in minutes; refreshURL is where Stripe sends the seller when one has, and it should call this again.
func (*Client) Plan ¶
func (c *Client) Plan(stored string) pricingspec.Plan
Plan resolves a stored plan; an unknown plan is the spec's default plan.
func (*Client) Subscriptions ¶
func (c *Client) Subscriptions(ctx context.Context, customerID string) ([]payforge.SubscriptionState, error)
Subscriptions returns a customer's subscriptions that have not ended. It is what the lifecycle reads to decide an account's plan from Stripe's state, not from the order webhooks arrived in.
func (*Client) Sync ¶
Sync makes the Stripe account hold what the spec describes: a product per priced plan and per fixed one-time product, and a price per interval found by its lookup key.
Sync is idempotent. It never deletes and never edits an amount: a price is immutable at Stripe, so a changed amount creates a new price, moves the lookup key to it and leaves the old price for existing subscribers. With dryRun it reads the account and reports what it would do without writing.
If a step fails, the report holds the steps taken so far along with the error.
type Config ¶
type Config struct {
// SecretKey is the Stripe secret key. It is required.
SecretKey string
// HTTPClient sends the requests. The default is the library's client.
HTTPClient *http.Client
// APIURL replaces the Stripe API's base URL. It exists for tests that stand
// in for Stripe; leave it empty otherwise.
APIURL string
// AllowedURLHosts are the hosts the success, cancel, refresh and return URLs
// may name, so a URL taken from a request cannot send a buyer from Stripe to a
// site of an attacker's choosing. Set it to your own hostnames. It is required
// for checkout, the portal and onboarding links. A client that only syncs the
// catalog may leave it empty. A URL must be https and may not carry a user
// name or password.
//
// With a test-mode key, localhost, 127.0.0.1 and ::1 are also accepted, over
// http too, even when the list does not name them, for local development.
// With a live key they are never accepted: New refuses a list that names
// one, and a URL on one is refused, since a live checkout that returns the
// buyer to their own machine is always a misconfiguration.
AllowedURLHosts []string
// Locker, when set, is held per subject around a plan checkout, under the
// same key the lifecycle uses, so two checkouts for one subject made at once
// by different processes run in turn. See CheckoutPlan.
Locker payforge.Locker
// MaxNetworkRetries is how often the library retries a request that failed
// for a transient reason. The library's retries reuse the idempotency key.
// Zero uses the library's default of 2.
MaxNetworkRetries int64
}
Config configures a Client. It holds a secret: do not log it.
type Mode ¶
type Mode string
Mode is whether a key, and the events for it, are Stripe's test mode or live mode. Objects and events of one mode are never visible to the other.
type SyncAction ¶
type SyncAction struct {
Kind SyncKind
// Key is the product key or the price's lookup key.
Key string
Detail string
}
SyncAction is one step of a sync.
type SyncKind ¶
type SyncKind string
SyncKind is what sync did, or would do, to one object.
const ( SyncCreateProduct SyncKind = "create_product" SyncUpdateProduct SyncKind = "update_product" SyncCreatePrice SyncKind = "create_price" // SyncReplacePrice creates a new price because a price is immutable and the // spec's amount, currency or interval changed, and moves the lookup key to // it. The old price stays for the subscribers already on it. SyncReplacePrice SyncKind = "replace_price" SyncUnchanged SyncKind = "unchanged" )
type SyncReport ¶
type SyncReport struct {
DryRun bool
Actions []SyncAction
}
SyncReport lists what a sync did, in a stable order.
func (SyncReport) Changed ¶
func (r SyncReport) Changed() bool
Changed reports whether the sync wrote, or in a dry run would write, anything.
type Webhook ¶
type Webhook struct {
// Mode is the mode of the API key the host uses, usually Client.Mode() or
// KeyMode(key). It is required. An event whose livemode does not match is
// returned as payforge.EventIgnored, to be acknowledged and not acted on, so
// a test-mode secret configured by mistake cannot change live accounts.
Mode Mode
// Secrets are the signing secrets of the endpoints that deliver here. A
// payload is accepted if any one verifies it, so the platform's endpoint and
// the connected-account endpoint can share a handler.
Secrets []string
// LegacyPrefixes name host-specific metadata prefixes written before
// payforge, such as "myapp" for myapp_organization_id and myapp_plan. They
// are read as aliases for the subject and plan, so objects that already exist
// at Stripe keep working.
LegacyPrefixes []string
// Tolerance is how old a signature may be. Zero is five minutes.
Tolerance time.Duration
// MaxBytes limits the body Read accepts. Zero is 1 MiB.
MaxBytes int64
}
Webhook verifies and reads Stripe webhook requests. It holds secrets: do not log it.
func (Webhook) Parse ¶
Parse verifies a payload against its Stripe-Signature header and reduces it to an Event.
An event of the other mode than Mode comes back as EventIgnored with only its ID, type, creation time, livemode and account, never acted on.
The signature is checked before anything else is read. Only a few stable fields are taken from the payload, so an endpoint pinned to a different API version than this library's still works. An event type the lifecycle does not use comes back as EventIgnored with only its ID and type.