Skip to content

Generator Material Purchase Integration Guide

Framework-agnostic xTool material-purchase modal SDK. Drop it into any Vue / React / vanilla JS project via <script src> or import.


Table of contents

  1. Overview
  2. Install and import
  3. Quick start
  4. Public API
  5. Configuration reference
  6. Authentication
  7. Framework integration examples
  8. Internationalization (i18n)
  9. Styling and visual spec
  10. FAQ
  11. Build artifacts
  12. Versions and changelog

1. Overview

After calling GeneratorMaterialPurchase.init(...) followed by GeneratorMaterialPurchase.open(), the SDK inserts a modal under the current page's document.body:

  • position: fixed, slides in from the right edge
  • 320px wide, 100vh tall (full height)
  • Semi-transparent backdrop (rgba(0, 0, 0, 0.4)), click to dismiss
  • Contents: title bar / store switcher / product list (variant dropdown, quantity adjust, checkboxes) / total + Buy Now

Business capabilities:

  • Picks a Shopify storefront by IP (US / CA / EU / UK / FR / DE / JP / AU), with manual override
  • Uses @xtool/shopify-sdk to create a cart → fetches checkoutUrl → defaults to window.open(_, '_blank')
  • Calls the atomm backend for the accessory pack list / IP geolocation / distribution trackId
  • Bundles en / zh strings

2. Install and import

html
<!-- UMD: legacy-friendly, exposes window.GeneratorMaterialPurchase -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>

Once loaded, window.GeneratorMaterialPurchase is your public API.

bash
# Internal registry requires .npmrc → http://repository.makeblock.com/repository/npm-group/
pnpm add @atomm-developer/generator-material-purchase
# or
npm install @atomm-developer/generator-material-purchase
ts
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'
// or named import
import { GeneratorMaterialPurchase } from '@atomm-developer/generator-material-purchase'

The package ships both ESM (index.es.js) and UMD (index.umd.js). Modern bundlers pick the right entry automatically.


3. Quick start

Minimal working example — two steps: init + open.

html
<!doctype html>
<html>
  <head>
    <script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
  </head>
  <body>
    <button id="buyBtn">Buy accessories</button>

    <script>
      // Step 1: initialize with required business params
      GeneratorMaterialPurchase.init({
        generatorId: 'light-sign',
        accessoryType: 'default',
      })

      // Step 2: open the modal on click
      document.getElementById('buyBtn').addEventListener('click', () => {
        GeneratorMaterialPurchase.open()
      })
    </script>
  </body>
</html>

4. Public API

Four methods are exposed:

ts
interface GeneratorMaterialPurchaseApi {
  init(options: PurchaseModalInitOptions): void
  open(): Promise<void>
  close(): void
  destroy(): void
}
MethodDescription
init(options)Must be called first. Sets business params, locale, and callbacks. Can be called multiple times to update config.
open()Mounts the modal DOM into document.body and kicks off the first product load (accessory pack + IP geo). Returns a Promise; init failures fire onError.
close()Plays exit animation, then unmounts the modal DOM. Also triggered by the user clicking the backdrop / close icon.
destroy()Force-unmounts and clears internal caches (e.g. product cache). To use the SDK again, call init first.

open() can be called repeatedly. Each open resets UI state (checkboxes, variant selection, scroll position) but preserves init config and the previous product cache (no duplicate request for the same store).


5. Configuration reference

ts
interface PurchaseModalInitOptions {
  /** Required: generator code, e.g. 'light-sign', 'flower-generator' */
  generatorId: string

  /** Required: accessory type. Currently 'default' is used across prod. */
  accessoryType: string

  /** Optional: default locale, 'en' | 'zh'. Defaults to 'en'. */
  locale?: 'en' | 'zh' | string

  /** Optional: modal z-index. Defaults to 9999. */
  zIndex?: number

  /** Optional: override the default prod apiBaseUrl. Defaults to https://xcs-api.xtool.com */
  apiBaseUrl?: string

  /** Optional: extend or override locale strings */
  messages?: Partial<Record<string, Record<string, string>>>

  /** Optional: checkout success callback. Defaults to window.open(checkoutUrl, '_blank') */
  onCheckoutSuccess?: (checkoutUrl: string) => void

  /** Optional: modal close callback */
  onClose?: () => void

  /** Optional: error callback (API failure / checkout failure / etc.) */
  onError?: (err: unknown) => void
}

Field semantics:

  • generatorId — Sent to the backend /generator-accessory-pack endpoint to determine which generator's accessory list to load. Also used as relatedObjectType on the distribution endpoint.
  • accessoryType — Same endpoint's input. Currently 'default' across prod.
  • locale — Current i18n language. To switch dynamically, call init({ locale: ... }) then open() again.
  • messages — Overrides individual strings or adds non-bundled languages, e.g. { en: { buy_now: 'Checkout' }, ja: { ... } }.
  • zIndex — Raise when the host page has higher-stacked elements (e.g. global toasts) that would otherwise overlay the modal.
  • apiBaseUrl — Defaults to the prod atomm service. Point to other environments for local dev or staging.
  • onCheckoutSuccess — Default behavior opens a new tab on the Shopify checkout page. To redirect the current page, implement location.href = url.
  • onClose — Fires when the user dismisses the modal. Useful for analytics / business glue.
  • onError — Fires on product-load failure, checkout failure, IP geolocation failure, etc. The SDK already shows a toast internally; you don't need extra UI.

6. Authentication

The SDK automatically injects two headers into every request to the atomm backend:

HeaderSource
uTokenReads document.cookie.utoken first, then falls back to localStorage.utoken
langReads localStorage.LANG_KEY, defaults to 'en'

For cross-origin deployments:

  1. Make sure the host page has written utoken to cookie or localStorage (usually right after login).
  2. Make sure xcs-api.xtool.com whitelists the caller domain in CORS and allows the custom uToken / lang headers.

If you see 401 / 403, check whether uToken is present, expired, or stripped by cross-origin policy.


7. Framework integration examples

7.1 Vanilla JS (<script src>)

html
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
<script>
  GeneratorMaterialPurchase.init({
    generatorId: 'light-sign',
    accessoryType: 'default',
    locale: 'en',
    onCheckoutSuccess: (url) => (location.href = url), // redirect current tab instead of new tab
    onClose: () => console.log('modal closed'),
  })
  document.querySelector('#buyBtn').onclick = () => GeneratorMaterialPurchase.open()
</script>

7.2 Vue 3 (Composition API)

vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

onMounted(() => {
  GeneratorMaterialPurchase.init({
    generatorId: 'light-sign',
    accessoryType: 'default',
  })
})

onBeforeUnmount(() => {
  GeneratorMaterialPurchase.destroy()
})

function handleClick() {
  GeneratorMaterialPurchase.open()
}
</script>

<template>
  <button @click="handleClick">Buy accessories</button>
</template>

7.3 React (Hooks)

tsx
import { useEffect } from 'react'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

export function BuyButton() {
  useEffect(() => {
    GeneratorMaterialPurchase.init({
      generatorId: 'flower-generator',
      accessoryType: 'default',
      locale: 'en',
    })
    return () => GeneratorMaterialPurchase.destroy()
  }, [])

  return <button onClick={() => GeneratorMaterialPurchase.open()}>Buy accessories</button>
}

7.4 Vue 2 (Options API)

vue
<script>
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

export default {
  mounted() {
    GeneratorMaterialPurchase.init({
      generatorId: 'light-sign',
      accessoryType: 'default',
    })
  },
  beforeDestroy() {
    GeneratorMaterialPurchase.destroy()
  },
  methods: {
    openModal() {
      GeneratorMaterialPurchase.open()
    },
  },
}
</script>

<template>
  <button @click="openModal">Buy accessories</button>
</template>

8. Internationalization (i18n)

8.1 Bundled languages

The SDK ships with full en and zh string sets, covering:

  • Modal title / close button / country notice / loading / empty state / sold-out badge
  • Total / discount notice / Buy Now
  • Various error toasts (load failure, checkout failure, out of stock, etc.)
  • Country names for all 8 storefronts (USA / Canada / Australia / EU / Germany / France / UK / Japan)

Switch via init({ locale: 'zh' }).

8.2 Extending or overriding strings

ts
GeneratorMaterialPurchase.init({
  generatorId: 'light-sign',
  accessoryType: 'default',
  locale: 'en',
  messages: {
    en: {
      buy_now: 'Checkout securely', // override one entry
    },
    ja: {
      // add Japanese
      shopify_supplies_kit: '消耗品キット',
      buy_now: '今すぐ購入',
      // ...missing entries fall back to en
    },
  },
})

8.3 String key reference

keyMeaning
shopify_supplies_kitModal title
closeClose button aria-label
current_country_noticeCountry notice, template includes {country}
loadingLoading text
no_items_in_cartEmpty product state
sold_outSold-out badge
totalTotal label
discount_noticeDiscount code notice
buy_nowBuy Now button
failed_load_product_listLoad failure toast
checkout_failedCheckout failure toast
selected_items_out_of_stockSelected items out of stock toast
no_valid_products_selectedNo purchasable products toast
store_us / store_ca / store_au / store_eu / store_de / store_fr / store_uk / store_jpStorefront dropdown short names
country_usa / country_canada / country_australia / country_eu / country_germany / country_france / country_uk / country_japanLong country names

9. Styling and visual spec

AspectSpec
Mountdocument.body.appendChild(host), host uses Shadow DOM for style isolation
Backdropposition: fixed; inset: 0; background: rgba(0,0,0,0.4);, click to dismiss
Modalposition: fixed; top: 0; right: 0; width: 320px; height: 100vh;
AnimationBackdrop fades over 0.2s, modal slides over 0.25s translateX(100% → 0)
FontInter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif
Primary colorsBuy button #ff0035 / accent #070b10 / notice orange #ff7c23
CSS namingBEM style with unified xpm- prefix (Xtool Purchase Modal)

Because the modal uses Shadow DOM, host-page CSS resets, Tailwind, or * { box-sizing: ... } won't bleed into the modal. Likewise, internal modal styles don't leak out to the host page.


10. FAQ

Q1: Calling open() throws "call init() before open()"

You called open() before init(). Call init once during mount (onMounted in Vue / useEffect in React).

Q2: Modal opens but product list spins forever / shows empty

Step through:

  1. In DevTools Network, verify /generator-accessory-pack returns 200 with code: 0.
  2. On 401 / 403, confirm uToken is in cookie or localStorage.
  3. On CORS errors, ask the backend to add your domain to the whitelist.

Q3: Checkout button is greyed out, what now?

When every checked item is detected as out-of-stock (outOfStock === true or availableForSale === false), the button greys out. Switch to a different variant or storefront.

Q4: How do I redirect the current tab instead of opening a new tab?

Pass onCheckoutSuccess:

ts
GeneratorMaterialPurchase.init({
  generatorId: '...',
  accessoryType: 'default',
  onCheckoutSuccess: (url) => (window.location.href = url),
})

Q5: Locale switched but the modal didn't refresh

Re-call init({ locale: 'xx' }) after switching. If the modal is currently open, close() then open().

Q6: Can I open multiple modals at once?

No. The SDK is a singleton; a second open() waits for the first one's exit animation before mounting. Need concurrent modals? Let us know.

Q7: Does it pollute globals?

Only attaches window.GeneratorMaterialPurchase (UMD mode). Doesn't modify body styles; only appends a host element while the modal exists, removed after close/destroy.

Q8: DevTools is awkward inside Shadow DOM. Any workaround?

In Chrome DevTools → Settings → Preferences → Elements, enable "Show user agent shadow DOM" to expand it. Or call methods directly via window.GeneratorMaterialPurchase in the Console.


11. Build artifacts

Build command:

bash
pnpm build

Output in dist/:

FileUseSize (gzip)
index.es.jsESM entry, for npm / modern bundlers~15 KB
index.umd.jsUMD, browser <script src> or Node require~12 KB
index.d.tsTypeScript declarations (rollup-merged single file)
*.mapsourcemaps for runtime error tracing

Published to npm with files:

  • dist/ (artifacts)
  • README.md

12. Versions and changelog

0.1.0 (initial release)

  • First published version
  • Exposes init / open / close / destroy, attaches global GeneratorMaterialPurchase
  • Ships 8 prod Shopify storefronts
  • Modal layout: right-side 320px / full height / backdrop
  • Bundled en + zh strings
  • Auto-injects uToken + lang on every request
  • BEM CSS + Shadow DOM style isolation

Feedback

For bugs or feature requests, file an issue on the internal GitLab repo, or ping the maintainers in the team Feishu group.

MIT Licensed