Recharge

Recharge integration for Shogun Frontend.

Recharge is a subscription payments platform designed for merchants to set up and manage dynamic recurring billing across web and mobile.

⚠️ This package runs on Shogun Frontend and is in customer Beta. It might not currently support all ecommerce platforms or cover all use cases. It also may require additional setup by a Shogun developer to work properly!

Official website →

Overview

Users of Shogun Frontend with Shopify stores can leverage this package to add subscription and/or selling plans to their products.

📘

@frontend-sdk/recharge supports Shopify Subscription API.

Installation

yarn add @frontend-sdk/recharge

npm install @frontend-sdk/recharge

Requirements

  • The Shogun Frontend store must use Shopify's GraphQL API (not Shopify's REST API).
  • Version 3.0.3 or higher of frontend-checkout must installed on the Shogun Frontend store.

Usage

  1. Create a new Section, for this example we will name the new section ReChargeProduct
  2. In the IDE, add a new variable to your section. This variable needs to be a reference type
  3. Name the variable product and select the products CMS Group from the reference field dropdown
  4. Initialize recharge by importing the hook and and adding boilerplate code as shown below
import { useReCharge } from '@frontend-sdk/recharge'

export const ReChargeProduct = (props) => {
  /**
   * The product data is added to your section in Shogun Frontend's IDE
   * by creating a reference variable to the Products CMS Group
   * */
  const { product } = props
  const { variants } = product

  const firstOrDefaultVariant = React.useMemo(() => {
    if (!variants.length) {
      return null
    }

    return variants.find((variant) => variant.position === 1) || product.variants[0]
  }, [variants])

  const recharge = useReCharge({
    product,
    storeUrl: '…', // the store’s domain in Shopify
    currentVariantId: firstOrDefaultVariant?.externalId,
  })

  return <div>...</div>
}

useReCharge

returns the following properties

  • isRechargeable
  • getCartItem
  • shippingIntervalFrequency
  • shippingIntervalUnitType

Now let's take a look at each of these properties and how they might be applied in code

isRechargeable

A boolean value that will return true when a product has subscription options.

import React from 'react'
import { useReCharge } from '@frontend-sdk/recharge'

export const ReChargeProduct = (props) => {
  const { product } = props
  const { variants } = product

  const firstOrDefaultVariant = React.useMemo(() => {...}, [variants])

  const recharge = useReCharge({...})

  // Here we check if the current product has subscriptions
  if (recharge.isRechargeable) {
    return <div>Subscription product, show the subscription options</div>
  }

  return <div>Regular product, no subscription</div>
}

shippingIntervalFrequency

An array of numbers for the interval at which delivery can be arranged for.

import React from 'react'
import { useReCharge } from '@frontend-sdk/recharge'

export const ReChargeProduct = (props) => {
  const { product } = props
  const { variants } = product

  const [currentShippingInterval, setCurrentShippingInterval] = React.useState()

  const firstOrDefaultVariant = React.useMemo(() => {...}, [variants])

  const recharge = useReCharge({...})

  const handleShippingIntervalChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
      setShippingInterval(Number(event.target.value))
  }

  return (
    <select
      value={String(currentShippingInterval)}
      onChange={handleShippingIntervalChange}
      onBlur={handleShippingIntervalChange}>
      {shippingIntervalFrequency?.map((frequency, idx) => (
          <option value={`${frequency}`} key={`${frequency}-${idx}`}>
              Deliver every {frequency} {shippingIntervalUnitType}
          </option>
      ))}
    </select>
  )
}

shippingIntervalUnitType

A string that contains the unit type that describes the shippingIntervalFrequency. Eg. Days, Weeks, Months.

import React from 'react'
import { useReCharge } from '@frontend-sdk/recharge'

export const ReChargeProduct = (props) => {
  const { product } = props
  const { variants } = product

  const [currentShippingInterval, setCurrentShippingInterval] = React.useState()

  const firstOrDefaultVariant = React.useMemo(() => {...}, [variants])

  const recharge = useReCharge({...})

  const handleShippingIntervalChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
      setShippingInterval(Number(event.target.value))
  }

  return (
    <select
      value={String(currentShippingInterval)}
      onChange={handleShippingIntervalChange}
      onBlur={handleShippingIntervalChange}>
      {shippingIntervalFrequency?.map((frequency, idx) => (
          <option value={`${frequency}`} key={`${frequency}-${idx}`}>
              Deliver every {frequency} {shippingIntervalUnitType}
          </option>
      ))}
    </select>
  )
}

getCartItem

A function that will return an object that can be passed to frontend-checkout's addItems action

import React from 'react'
import { useReCharge } from '@frontend-sdk/recharge'
import { useCartActions } from 'frontend-checkout'

export const ReChargeProduct = (props) => {
  const { product } = props
  const { variants } = product

  // Set current variant and shipping interval
  const [currentVariant, setCurrentVariant] = React.useState({ id: 0, quantity: 1 })
  const [currentShippingInterval, setCurrentShippingInterval] = React.useState(2)

  const firstOrDefaultVariant = React.useMemo(() => {...}, [variants])

  const recharge = useReCharge({...})

  const { addItems } = useCartActions()

  const handleAddToCart = React.useCallback(async () => {
    const defaultVariantCartItem = {
      id: currentVariant.externalId,
      quantity: 1
    }

    const cartItem = recharge.getCartItem(defaultVariantCartItem, currentShippingInterval)

    await addItems(cartItem)
  }, [currentVariant])

  return <button type="button" onClick={handleAddToCart}>Add To Cart</button>
}

Example

import React, { useState, useCallback, useMemo, useEffect } from 'react'
import { useReCharge } from '@frontend-sdk/recharge'
import { useCartActions, useCartState } from 'frontend-checkout'

function getFirstOrDefaultVariant(product) {
  if (!product.variants.length) return null
  return product.variants.find((variant) => variant.position === 1) || product.variants[0]
}

const Button = (props) => {
  const { onClick, children, status } = props
  const isLoading = status === 'loading'

  return (
    <button disabled={isLoading} type="button" onClick={onClick}>
      {isLoading ? 'loading...' : children}
    </button>
  )
}

const ReCharge = ({ product }: ReChargeProps) => {
  const { addItems } = useCartActions()
  const cart = useCartState()

  const [requestStatus, setRequestStatus] = useState<Status>('ready')
  const [defaultCartItem, setDefaultCartItem] = useState<CartItem>({ id: 0, quantity: 1 })
  const [productType, setProductType] = useState<RadioValues>('subscription')
  const [shippingInterval, setShippingInterval] = useState<number>()
  const [currentVariantId, setCurrentVariantId] = useState<number>(0)

  const recharge = useReCharge({
    product,
    storeUrl: '…', // the store’s domain in Shopify
    currentVariantId,
  })

  const { getCartItem, shippingIntervalFrequency, shippingIntervalUnitType, isRechargeable } = recharge

  /**
   * Default shipping interval
   *
   * We set a default for shippingIntervalFrequency selector
   */
  useEffect(() => {
    if (shippingIntervalFrequency && shippingIntervalFrequency.length > 0) {
      setShippingInterval(shippingIntervalFrequency[0])
    } else {
      setShippingInterval(undefined)
    }
  }, [shippingIntervalFrequency])

  /**
   * Currently selected variant.
   *
   * We keep track of the currently selected variant. Products could have many variants.
   * The selected variant determines which data is displayed
   */
  const selectedVariant = useMemo(() => {
    if (!product) return

    const currentVariant = getFirstOrDefaultVariant(product)

    if (currentVariant) {
      setDefaultCartItem({ id: currentVariant.externalId, quantity: 1 })
      setCurrentVariantId(currentVariant.externalId)
    }

    return currentVariant
  }, [product])

  const cartItem = useMemo(() => {
    if (!selectedVariant) return defaultCartItem
    if (productType === 'onetime') return defaultCartItem

    return getCartItem(defaultCartItem, Number(shippingInterval))
  }, [getCartItem, shippingInterval, selectedVariant, defaultCartItem, productType])

  /**
   * Based on selected variant and subscription option,
   * we build the cartItem that is passed to addItems when adding an item to cart.
   */
  const handleAddToCart = useCallback(async () => {
    setRequestStatus('loading')
    try {
      if (productType === 'onetime') {
        await addItems(defaultCartItem)
      }

      if (productType === 'subscription') {
        await addItems(cartItem)
      }
    } catch (error) {
      throw new Error(error)
    } finally {
      setRequestStatus('ready')
    }
  }, [cartItem, addItems, productType, defaultCartItem])

  const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value === 'onetime' ? 'onetime' : 'subscription'
    setProductType(value)
  }

  const handleShippingIntervalChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setShippingInterval(Number(event.target.value))
  }

  return (
    <div className={css.recharge}>
      <div className={css.rechargeDemoContainer}>
        <div className={css.rechargeForm}>
          <h2>Subscription Widget</h2>
          <h3>{product?.name}</h3>
          {isRechargeable && (
            <>
              <ReChargeRadioButton label="Onetime" value="onetime" checked={productType} onChange={handleRadioChange} />
              <ReChargeRadioButton
                label="Subscription"
                value="subscription"
                checked={productType}
                onChange={handleRadioChange}
                disabled={!isRechargeable}
              />
              {productType === 'subscription' && (
                <select
                  value={String(shippingInterval)}
                  onChange={handleShippingIntervalChange}
                  onBlur={handleShippingIntervalChange}>
                  {shippingIntervalFrequency?.map((frequency, idx) => (
                    <option value={`${frequency}`} key={`${frequency}-${idx}`}>
                      {frequency} {shippingIntervalUnitType}
                    </option>
                  ))}
                </select>
              )}
            </>
          )}
          <Button onClick={handleAddToCart} status={requestStatus}>
            Add to Cart
          </Button>
        </div>
      </div>
    </div>
  )
}

ReCharge.displayName = 'ReCharge Form and Data'
export default ReCharge