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

Installation

yarn add @frontend-sdk/recharge

npm install @frontend-sdk/recharge

Usage

  1. Create a new Section, for this example we will name the new section ReChargeProduct
    Create New SectionCreate New Section
  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
    Create Reference VariableCreate Reference Variable
  4. Initialize recharge by importing the hook and and adding boilerplate code as shown below
    Use ReChargeUse ReCharge
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: String(process.env.PLATFORM_PUBLIC_DOMAIN),
    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: String(process.env.PLATFORM_PUBLIC_DOMAIN),
    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

Development

Setting up .env

Create a new file /.env in the repo root with the following content:

PLATFORM_PUBLIC_DOMAIN= // Insert store domain here. E.g. myfancywidgets.myshopify.com

Did this page help you?