0.10.0 • Published 2 years ago

@priceblocs/react-priceblocs-js v0.10.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

React PriceBlocs JS beta

Discord

PriceBlocs makes it easy for developers to get started with in-app payments and billing through a set of functional components.

In just a few lines of code you'll be able to:

  • Show products and prices
  • Initiate checkouts
  • Enable billing managment

Everything you need to get started with in-app payments, with less server side code.

Getting started

  • API Keys
    • Sign up for PriceBlocs and get your publishable API keys.
  • Development
    • Enable test mode within your PriceBlocs settings tab so that you can use Stripe test mode resources in local development.
  • Install
    • Add @priceblocs/react-priceblocs-js to your project

Our first set of components and hooks are compatible with React, examples of which you can see below.

Install

  • The @priceblocs/react-priceblocs-js functional components are available via npm:
npm i --save @priceblocs/react-priceblocs-js

Quick start

Single price

The quickest way to get started is with a single price:

  1. Wrap any part of your app with an authenticated PriceBlocs component. To authenticate, pass your PriceBlocs api_key prop:
    1. In Production: use your live publishable PriceBlocs api key and live Stripe price id (i.e. where livemode is true)
    2. In Development: use your test publishable PriceBlocs api key and test Stripe price id (i.e.where livemode is false)
  2. Attach the checkout function to any click handler
  3. Pass any price id to the checkout call
import { PriceBlocs } from '@priceblocs/react-priceblocs-js'

export default () => {
  const isProd = process.env.NODE_ENV === 'production'
  const apiKey = isProd ? 'PB_pk_live_*' : 'PB_pk_test_*'
  const price = isProd ? 'price_123' : 'price_456'

  return (
    <PriceBlocs api_key={apiKey}>
      {({ loading, values, checkout }) => {
        if (loading || !values) {
          return null
        }
        return <button onClick={() => checkout(price)}>Buy Now</button>
      }}
    </PriceBlocs>
  )
}

Display product metadata

If you need to display product metadata to the user (e.g name and description) then you can pass one or more prices to the functional component and on initialization. The products associated with the prices will be fetched and the made available via the values object for use within your UI.

import { PriceBlocs } from '@priceblocs/react-priceblocs-js'

export default () => {
  const props =
    process.env.NODE_ENV === 'production'
      ? {
          api_key: 'PB_pk_live_*',
          prices: ['price_123'],
        }
      : {
          api_key: 'PB_pk_test_*',
          prices: ['price_456'],
        }

  return (
    <PriceBlocs {...props}>
      {({ loading, values, checkout }) => {
        if (loading || !values) {
          return null
        }
        /**
         * 1. Destructure products from values
         * 2. Use product attributes
         */
        const { products } = values
        const { name, prices } = products[0]
        const { id } = prices[0]
        return <button onClick={() => checkout(id)}>{`Buy ${name}`}</button>
      }}
    </PriceBlocs>
  )
}

Workflow

There are 3 steps to adding prices and checkout to your app:

  • Setup
    • Wrap any part of your app with an authenticated PriceBlocs component
    • Pass an api_key and a set of prices
  • Presentation
    • Access your fetched data via context hooks and use it to present product options to your customers
  • Checkout
    • Attach the checkout function to any of your price CTA actions to initiate a new checkout session

Setup

  • Import PriceBlocs and initialize it with both:
    • api_key: your PriceBlocs publishable API key
      • Use your PB_pk_live_* API key for live Stripe resources and checkout
      • Use your PB_pk_test_* API key for test Stripe resources and checkout
    • prices: set of prices you want to show to customers
  • You can also pass additional checkout configuration options like a customer id / email
API keys
  • Your PriceBlocs account can have both live and test API key sets
  • Each set of API keys has both a public and secret key
  • Only public (publishable) keys should be used for client side requests
  • Only livemode: true keys can initiate live Stripe checkout charges
Key nameLivemodeAudiencePublishable
PB_sk_live_*trueSecretNo
PB_pk_live_*truePublicYes
PB_sk_test_*falseSecretNo
PB_pk_test_*falsePublicYes
  • Your connected Stripe account must have charges_enabled in order to initiate a checkout session
    • To achieve this, you will need to complete Stripe's business verification onboarding process
import { PriceBlocs } from '@priceblocs/react-priceblocs-js'

export default () => {
  const props =
    process.env.NODE_ENV === 'production'
      ? {
          api_key: 'PB_pk_live_*',
          prices: ['price_123'],
        }
      : {
          api_key: 'PB_pk_test_*',
          prices: ['price_456'],
        }

  return (
    <PriceBlocs {...props}>
      {({ loading, values }) => {
        if (loading) {
          return <YourLoader />
        } else if (values && values.products && values.products.length > 0) {
          return <YourPricingTable />
        }
        return null
      }}
    </PriceBlocs>
  )
}

Props

KeyRequiredTypeDescriptionExample
api_keyYesStringOne of your PriceBlocs publishable API keyPB_pk_test_sRADszm...
pricesNoArrayArray of Stripe price ids to fetch'price_123', 'price_456'
sessionsNoArrayArray of checkout inputs to generate session ids from[ { line_items: {price_id: 'price_123'}, ... } ]
success_urlNoStringRedirect location after a successful checkouthttps://your-site.com/success
cancel_urlNoStringRedirect location if a user cancels their current checkout sessionhttps://your-site.com/cancel
customerNoStringStripe customer idcu_123
emailNoStringEmail for your customer customer. If there is a matching customer within you Stripe account, we will use this to initiate the checkout session in the context of that customersome.one@email.com
presentationNoObjectControl the presentation of the response{ order: 'desc' }
queryNoObjectFetch associations or expand responses{ customer: {expand: 'default_source', associations: 'invoices' }
valuesNoObjectValues to initialize context with and prevent fetch on mount{products: ..., ...}
configNoObjectInitial configuration object for the lib internals. Control behavior like fetch on mount or customer change{ fetch: { on_mount: true, on_customer_change: true }
Query props
  • An optional query object can be passed to fetch associated collections and expand responses
  • For example if you wanted to fetch all of a customers invoices, you could pass that parameter within the customer associations array like below.
  • To expand part of the customer response itself, you can pass parameters which align to Stripe's customer expand parameters. Read more here about expanding Stripe responses.
{
  api_key: 'PB_pk_test_oYQN',
  prices: ['price_123', 'price_456'],
  customer: 'cus_123',
  query: {
    customer: {
      expand: [
        'default_source',
        'invoice_settings.default_payment_method'
      ],
      associations: [
        'invoices',
        'subscriptions',
        'cards'
      ]
    }
  }
}
  • You can then access the associations and expanded properties on the related resource within the context values
  • For example, customer associations and expansions are then available on the customer object
const {
  values: {
    customer: { default_source, subscriptions, invoices, cards },
  },
} = usePriceBlocsContext()
Session props
  • The sessions field allows you to compose checkout sessions using multiple Stripe resources
  • You can pass in one of more session inputs to generate a unique checkout id based on all of the values passed
  • The id returned in the response can then be passed in any checkout action call to initiate a new Stripe Checkout session
  • All of the values passed in the initial request will be used to initialize a new Checkout session
  • For example, a checkout session with multiple line_items and a discount will create a new Checkout session for those items with the disount applied
  • This way you can describe one or more checkout sessions from the client and then when you're ready, initiate a checkout session by just passing the checkout id
{
  sessions: [
    {
      line_items: [
        {
          price: 'price_123',
        },
      ],
      discounts: [
        {
          coupon: 'cou_123',
        },
      ],
    },
  ]
}
  • The Session input aligns closely to that of the Stripe checkout api which you can see here
KeyTypeDescription
line_itemsArrayOne or more prices to apply to this checkout session
success_urlstringurl to be redirected to after a successful checkout session
cancel_urlstringurl to be redirected to when a checkout session is canceled
payment_method_typesarrayarray of payment method types to use at checkout time
allow_promotion_codesbooleanwhether to allow promotion codes during checkout
discountsarrayDiscounts to apply to the checkout. Max 1
billing_address_collectionObjectWhether to collect billing address info
shipping_address_collectionObjectShipping adddress configuration. Set one or more allowed countries
shipping_worldwidebooleanWhether shipping options are worldwide
shipping_optionsarrayUp to 5 shipping rate options to present at checkout time
submit_typestringType of copy to use within checkout session
consent_collectionobjectConfiguration for consent collection related to marketing
after_expirationobjectConfiguration for post checkout session expiration
expires_atnumberTimestamp of when to expire the session
adjustable_quantityobjectConfiguration object for adjustable quantity
tax_ratesarrayArray of tax ids to apply to checkout session
dynamic_tax_ratesarrayArray of dynamic tax ids to apply to checkout session
automatic_taxobjectAutomatic tax configuration object
client_reference_idstringClient reference id to attach to payment
tax_id_collectionobjectTax id collection configuration object
payment_intent_dataobjectPayment intent confiration object which can control capture method.
trial_period_daysnumberNumber of days to apply to any line_item's with a recurring price type
trial_endnumberTimestamp of when the trial ends
metadataobjectKey value pair object to attach to the checkout and any resulting subscription / payment intent records
modestringWhat mode the session should be initialized in
Shared configuration (inheritence)
  • You can share session configuration options across multiple inputs by setting the field as a sibling to the sessions key.
  • For example, let's say you want to apply the same discount to all of the session inputs.
  • To do that you would set the discounts as a sibling to sessions like so
{
  sessions: [
    {
      line_items: [
        {
          price: 'price_123',
        },
      ],
    },
    {
      line_items: [
        {
          price: 'price_999',
        },
      ],
    },
  ],
  discounts: [
    {
      coupon: 'cou_123',
    },
  ],
}

Overrides

  • However if you wanted override common configuration you would define a more specific configuration within the session input itself like below.
  • Here we are sharing discount cou_123 across the first two sessions but then overriding it withing the third session by setting coupon cou_abc
  • This ability to override works for every key within the configuration
  • The most specific configuration wins
{
  sessions: [
    {
      line_items: [
        {
          price: 'price_123',
        },
      ],
    },
    {
      line_items: [
        {
          price: 'price_999',
        },
      ],
    },
    {
      line_items: [
        {
          price: 'price_456',
        },
      ],
      discounts: [
        {
          coupon: 'cou_abc',
        },
      ],
    },
  ],
  discounts: [
    {
      coupon: 'cou_123',
    },
  ],
}
Config props
  • You can configure how the default behavior of the lib internals with the configuration object
  • By default the component will fetch data from the PriceBlocs API on mount and when the customer reference changes
  • You can override that configuration by passing in a config object
{
  fetch: {
    on_mount: true,
    on_customer_change: true
  }
}
  • You can then access the associations and expanded properties on the related resource within the context values
  • For example, customer associations and expansions are then available on the customer object
const {
  values: {
    customer: { default_source, subscriptions, invoices, cards },
  },
} = usePriceBlocsContext()

Presentation

  • Once initialized, you will be able to access your fetched data via the usePriceBlocsContext context hook
  • There are a variety of fields to help you:
    • present price options
    • update context values
    • initiate checkout and billing sessions
    • preview and confirm subscription updates
    • manage checkout cart and more
KeyTypeDescription
valuesObjectCore pricing resources like products and featureGroups etc.
refetchFunctionFunction to refetch values from API using initial props
checkoutFunctionStart a checkout session
billingFunctionStart a billing portal session for the provided customer
checkoutAddFunctionAdd a price to the form checkout items
checkoutRemoveFunctionRemove a price from the form checkout items
previewInvoiceFunctionRequest a preview of the next invoice for the subscription
fetchUsageFunctionFetch usage summary for a subscription line item
reportUsageFunctionReport usage summary for a subscription line item
updateSubscriptionFunctionUpdate a subscription with the a collection of updated subscription items
setFieldValueFunctionUpdate any of the context values
setValuesFunctionUpdate all of the context values
readyBooleanTrue when Stripe has been initialized and consumer can initialize checkout sessions
loadingBooleanTrue when fetching
isSubmittingBooleanTrue when submitting
stripeObjectStripe instance initialized and available for use in context
errorErrorAny errors in local state
setErrorErrorSet error in local state
import {
  usePriceBlocsContext,
  getActiveProductPrice,
} from '@priceblocs/react-priceblocs-js'

const PricingTable = () => {
  const {
    values: { products },
    form: { currency, interval },
    checkout,
  } = usePriceBlocsContext()

  return (
    <div>
      <ul>
        {products.map((product, productIx) => {
          const price = getActiveProductPrice(product, { currency, interval })
          return (
            <li key={product.id}>
              <div>
                <div>{product.name}</div>
                <div>{product.description}</div>
              </div>
              <button onClick={() => checkout({ prices: [price.id] })}>
                Buy Now
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

refetch

  • Use the refetch function to refetch values from the API
const { refetch } = usePriceBlocsContext()

// Single price
<button onClick={() => refetch()}>Refetch</button>

checkout

  • Use the checkout function from context to start a checkout session
  • The simplest way is to call it with a single price id like so:
const { checkout } = usePriceBlocsContext()
// Single price
<button onClick={() => checkout(price.id)}>Buy Now</button>
  • checkout accepts a variety of arguments such as:
    • single price id
    • collection of price ids
    • encrypted session id
    • full Stripe Checkout session configuration
const { checkout } = usePriceBlocsContext()

/**
 * Single price
 */
const price = 'price_123'
<button onClick={() => checkout(price.id)}>Buy Now</button>
/**
 * Session id
 * - session id fetched via PriceBlocs fetchData function
 */
const sessionId = 'cs_live_123'
<button onClick={() => checkout(sessionId)}>Buy Now</button>
/**
 * Pass one or more prices to include multiple products within checkout
 */
const prices = ['price_123', 'price_456']
<button onClick={() => checkout({ prices })}>Buy Now</button>
/**
 * Pass full session configuration object
 */
const session = {
  line_items: [
    {
      price: 'price_123',
    }
  ]
}
<button onClick={() => checkout(session)}>Buy Now</button>

billing

  • Use the billing function from context to start a new Stripe billing portal session for one of your customers
  • A valid customer id is required to start a new session
  • By default, we will use the customer in context if you have initiated PriceBlocs with a valid customer
    • Otherwise you can pass a specific customer id parameter into the billing call
  • Your Stripe billing portal can be confiugred within Stripe here
const { billing } = usePriceBlocsContext()

// Use default customer if present
<button onClick={billing}>Manage billing</button>
// Provide a customer to the billing call
<button onClick={() => billing({ customer: 'cus_123' })}>Manage billing</button>

previewInvoice

  • Use the previewInvoice function from context to preview what an upcoming invoice for a subscription will look like based on the items passed in the request
  • To preview changes against a specific subscription, pass its id as well as a collection of items to preview
const previewData = await previewInvoice({
  subscription: 'sub-123',
  items: [
    {
      price: 'p_123',
    },
  ],
})
  • The preview response will include an itemized preview for the upcoming invoice and the raw invoice itself.
KeyDescription
previewItemized invoice preview including confirmation payload
invoiceThe raw Stripe invoice which is being previewed

Invoice preview

  • The preview response includes lineItems, amountItems which describe the invoices details as well as a confirm object which can be passed in a subsequent updateSubscription request to update the associated subscription.
KeyDescription
lineItemsA collection which describing the elements of the invoice
amountItemsA collection which describes the total amounts due / credited / paid etc.
confirmA payload which can be passed in a subscription update request to apply the previewed changes

updateSubscription

  • Use the updateSubscription function from context to update a customers subscription with the options passed.
const previewData = await updateSubscription({
  id: 'sub-123',
  items: [
    {
      id: "si_123",
      deleted: true,
      clear_usage: true,
    },
    {
      id: "si_456",
      price: "p_B_1"
    },
    {
      price: "p_A_3"
    }
  ]
  proration_data: 12345678
})
  • For convenience you can pick the confirm object from the invoicePreview response and use it when calling updateSubscription.
  • A proration_date timestamp is included in the preview response so that it too is available to be passed along in the request.
const response = await previewInvoice({
  subscription: 'sub-123',
  items: [
    {
      price: 'p_123',
    },
  ],
})
await updateSubscription(response.preview.confirm)

fetchUsage

  • Use the fetchUsage function from context to fetch all of the usage summaries for a subscription item
  • Each summary represents multiple usage records over a subscription billing period (e.g., 15 usage records in the month of September).
const previewData = await fetchUsage({
  subscription_item: 'sub-item-123',
})
  • The response will include a collection of summary records as well as some aggregate totals.
KeyDescription
total_usageSum total of all the summary records
total_costTotal usage multiplied by the cost per unit
unit_amountUnit amount for the subscription item
unit_costFormatted cost amount for a unit amount value
dataCollection of usage summaries

Usage data

  • Each usage summary record describes the amount of usage between the priod start and end date
KeyDescription
idSummary usage record id
invoiceId of the invoice the summary belongs to
livemodeWhether its a live or test Stripe record
periodTimestamp info for the summary
subscription_itemSubscription item id the usage summary belongs to
total_usageSum count of the total usage for that period
Period
  • Describes the timeframe for the usage summary
KeyDescription
labelFormatted timestamp for the period
startUnix timestamp for period start
endUnix timestamp for period end

reportUsage

  • Use the reportUsage function from context to report usage for a particular subscription_item
  • e.g. this could be used for counting restricted API calls
  • You can specify additional fields like quantity, timestamp, and action
const previewData = await fetchUsage({
  subscription_item: 'sub-item-123',
})
  • Each additional field has default values if not specified
KeyDescriptionDefault
quantityFormatted timestamp for the period1
timestampUnix timestamp usage recordingNow as unix timestamp
actionUsage action type, can be 'increment' or 'set'increment

setValues

  • You can use the setValues function to replace the entire values object in context
  • This function is used internally on refetch
  • Avoid using this function unless necessary. Calling refetch is better for most use cases.
const {
  setValues,
} = usePriceBlocsContext()

<button onClick={() => setValues({...})}>Set all values</button>

setFieldValue

  • You can use the setFieldValue function to update any of the state in context
  • This can be useful for updating values such as the interval or currency within the form state to assist with presentation logic
  • You can pass a dot path to target nested fields for updates
const {
  setFieldValue,
} = usePriceBlocsContext()

<button onClick={() => setFieldValue('form.interval', 'month')}>Show monthly prices</button>

setError

  • You can use the setError function to set the local error object
const {
  setError,
} = usePriceBlocsContext()

<button onClick={() => setError(new Error('Custom error'))}>Set error</button>

API

Context & Hooks

KeyTypeDescription
PriceBlocsHOCContext container
usePriceBlocsContextFunctionContext hook

Utils

KeyTypeDescription
getActiveProductPriceFunctionGet the product price based on the current form values for interval and currency
getProductFeaturesFunctionGet all features for the provided product
getProductsFeaturesTableFunctionGenerate a feature table representation for products in context
getGoodStandingSubscriptionsFunctionFilter subscriptions in context to ones with either an activetrialing status

Constants

KeyTypeDescriptionExample
RECURRING_INTERVALSObjectA mapping of the 4 recurring intervals'month'
INTERVAL_LABELS_MAPObjectA mapping of each interval to it's label'monthly'
INTERVAL_SHORTHAND_MAPObjectA mapping of each interval to it's shorthand'mo'

Schema

Values API

KeyTypeDescription
productsArrayAn array of products
customerObjectA Stripe customer
formObjectForm state values like currencies and intervals to help with presentation
featureGroupsArrayAn array of feature groups

Product API

This shape is closely aligned to the Stripe products API

KeyDescription
nameName of the product
descriptionDescription of the product
pricesArray of Stripe prices
featuresArray of features associated with the product
Price API

This shape is closely aligned to the Stripe prices API

KeyDescription
idStripe price id
unit_amountUnit amount for the price
currencyStripe price id
productStripe product id which the price belongs to
formattedFormatted price values
symbolCurrency symbol for this price
Price formatting API
  • We format the unit_amount of each price so you don't have to.
  • This also includes formatting them for a variery of different intervals day | week | month | year
  • Each formatted interval is accessible under the intervals key
KeyDescriptionExample
unit_amountFormatted version of the unit amount12000 -> $120.00
intervalsPrice formatted for different intervals day, week, month, year. e.g a yearly price presented as a per month cost{ day: "$0.33", week: "$2.31", month: "$10.00", year: "$120.00"}

Customer API

This shape is closely aligned to the Stripe customers API This object will be extended with any additional expanded attributes or associations requested via the initial query props

KeyPresentRequiredation
idalwaysID of the Customer
emailalwaysEmail of the Customer
invoicesconditionallyArray of this Customer's Stripe invoices
subscriptionsconditionallyArray of this Customer's Stripe subscriptions
cardsconditionallyArray of this Customer's Stripe cards

Form API

KeyDescriptionExample
intervalThe default interval based on prices in config response'month'
intervalsSet of intervals for prices in response'month', 'year'
currencyThe default currency based on prices in config response'usd'
currenciesSet of intervals for prices in response'usd','eur'
usage_typesSet of usage_types for prices in response'licensed'
checkoutThe checkout object which contains purchasable prices{items: {price: {id: 'price_123'}}}
presentationPresentation values for ationform{interval: "month"}
Form checkout API
  • Object which can be used to store checkout related options like items for purchase.
  • These items can be passed along in any checkout call
  • These items will also be used as the default collection of prices for an preview invoice request
KeyDescription
itemsArray of checkout items
Form checkout item API
KeyRequiredDescription
priceyesObject with price id
quantitynoQuantity of price

Form presentation API

KeyDescriptionExample
intervalThe interval presentation interval. For example 'month' will present all amounts in terms of per month pricing, so a $120 per year price will be presented as $10 per month'month'

Feature Groups API

KeyPresentDescriptionExample
uuidalwaysUUID for the feature group847169d9-05bf-485f-8d01-637189e9c9a1
titlealwaysTitle for the feature groupAnalytics & Reports
featuresconditionallyArray of Features included in the feature group
Feature API
KeyDescriptionExample
uuidUUID of the feature groupf0ecdee3-579f-4a9f-aeba-92ff9dbaa767
titleReport generatorAnalytics & Reports
descriptionGenerate monthly financial reportsAnalytics & Reports
Product Config API
KeyTypeDescriptionExample
enabledbooleanUUID of the feature grouptrue
valuestringValue for the configLimit 100 runs a month
tooltipstringOptional tooltip for featureCustom scheduling allowed
0.10.0

2 years ago

0.9.0

2 years ago

0.8.4

2 years ago

0.8.1

2 years ago

0.8.0

2 years ago

0.7.1

2 years ago

0.8.3

2 years ago

0.8.2

2 years ago

0.7.0

2 years ago

0.6.7

3 years ago

0.6.6-beta.0

3 years ago

0.6.7-beta.1

3 years ago

0.6.3

3 years ago

0.6.2

3 years ago

0.6.5

3 years ago

0.6.4

3 years ago

0.6.1

3 years ago

0.6.0

3 years ago

0.5.0

3 years ago

0.4.0

3 years ago

0.3.2

3 years ago

0.1.0

3 years ago

0.3.0

3 years ago

0.2.1

3 years ago

0.1.2

3 years ago

0.2.0

3 years ago

0.1.1

3 years ago

0.3.1

3 years ago

0.2.2

3 years ago

0.0.10

3 years ago

0.0.11

3 years ago

0.0.12

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.7

3 years ago

0.0.6

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago