brainerce 1.46.2 → 1.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,6 +34,12 @@ __export(index_exports, {
34
34
  BrainerceError: () => BrainerceError,
35
35
  RTL_LOCALES: () => RTL_LOCALES,
36
36
  SDK_VERSION: () => SDK_VERSION,
37
+ buildArticleJsonLd: () => buildArticleJsonLd,
38
+ buildBreadcrumbJsonLd: () => buildBreadcrumbJsonLd,
39
+ buildCollectionPageJsonLd: () => buildCollectionPageJsonLd,
40
+ buildOrganizationJsonLd: () => buildOrganizationJsonLd,
41
+ buildProductJsonLd: () => buildProductJsonLd,
42
+ buildWebsiteJsonLd: () => buildWebsiteJsonLd,
37
43
  createWebhookHandler: () => createWebhookHandler,
38
44
  deriveSeoDescription: () => deriveSeoDescription,
39
45
  enableDevGuards: () => enableDevGuards,
@@ -41,9 +47,11 @@ __export(index_exports, {
41
47
  formatPrice: () => formatPrice,
42
48
  formatProductPrice: () => formatProductPrice,
43
49
  formatVariantPrice: () => formatVariantPrice,
50
+ getBlogSitemapEntries: () => getBlogSitemapEntries,
44
51
  getCartItemImage: () => getCartItemImage,
45
52
  getCartItemName: () => getCartItemName,
46
53
  getCartTotals: () => getCartTotals,
54
+ getCategorySitemapEntries: () => getCategorySitemapEntries,
47
55
  getDescriptionContent: () => getDescriptionContent,
48
56
  getDirectionForLocale: () => getDirectionForLocale,
49
57
  getPriceDisplay: () => formatPrice,
@@ -61,9 +69,10 @@ __export(index_exports, {
61
69
  isCouponApplicableToProduct: () => isCouponApplicableToProduct,
62
70
  isHtmlDescription: () => isHtmlDescription,
63
71
  isWebhookEventType: () => isWebhookEventType,
72
+ jsonLdScriptProps: () => jsonLdScriptProps,
64
73
  parseWebhookEvent: () => parseWebhookEvent,
65
74
  safePaymentRedirect: () => safePaymentRedirect,
66
- stripHtml: () => stripHtml,
75
+ stripHtml: () => stripHtml2,
67
76
  verifyWebhook: () => verifyWebhook
68
77
  });
69
78
  module.exports = __toCommonJS(index_exports);
@@ -1314,6 +1323,34 @@ var BrainerceClient = class {
1314
1323
  }
1315
1324
  throw new BrainerceError("getCategories is only available in vibe-coded mode", 400);
1316
1325
  }
1326
+ /**
1327
+ * Get one category by slug — the payload for a storefront category
1328
+ * (collection) landing page: name, description HTML, meta, breadcrumb and
1329
+ * product count. Fetch the products themselves with
1330
+ * `getProducts({ categories: [category.id] })`. Vibe-coded mode only, like
1331
+ * {@link getCategories}.
1332
+ *
1333
+ * @example
1334
+ * ```typescript
1335
+ * const category = await client.getCategoryBySlug('running-shoes').catch(() => null);
1336
+ * if (!category) notFound();
1337
+ * const { data: products } = await client.getProducts({ categories: [category.id] });
1338
+ * ```
1339
+ */
1340
+ async getCategoryBySlug(slug, options) {
1341
+ const headerOverrides = options?.locale ? { "Accept-Language": options.locale } : void 0;
1342
+ const encodedSlug = encodePathSegment(slug);
1343
+ if (this.isVibeCodedMode()) {
1344
+ return this.vibeCodedRequest(
1345
+ "GET",
1346
+ `/categories/slug/${encodedSlug}`,
1347
+ void 0,
1348
+ void 0,
1349
+ headerOverrides
1350
+ );
1351
+ }
1352
+ throw new BrainerceError("getCategoryBySlug is only available in vibe-coded mode", 400);
1353
+ }
1317
1354
  /**
1318
1355
  * Get available brands for filtering products
1319
1356
  * Works in vibe-coded mode
@@ -6466,15 +6503,18 @@ var BrainerceClient = class {
6466
6503
  // these do NOT branch on isVibeCodedMode() (that would 404 silently).
6467
6504
  /**
6468
6505
  * Get the logged-in customer's loyalty status: enrollment, points balance,
6469
- * lifetime earned, and the program's display config (requires customerToken).
6470
- * Only available in storefront mode. `program` is null when the store has no
6471
- * loyalty program.
6506
+ * lifetime earned, the program's display config, earned milestone `badges`,
6507
+ * and the `paidMembership` subscription state (null for free members).
6508
+ * Requires customerToken. Only available in storefront mode. `program` is
6509
+ * null when the store has no loyalty program.
6472
6510
  *
6473
6511
  * @example
6474
6512
  * ```typescript
6475
6513
  * client.setCustomerToken(auth.token);
6476
6514
  * const status = await client.getLoyaltyStatus();
6477
6515
  * if (status.enrolled) console.log(`${status.pointsBalance} ${status.program?.pointsName}`);
6516
+ * status.badges?.forEach((b) => console.log(`🏅 ${b.name}`));
6517
+ * if (status.paidMembership?.status === 'ACTIVE') showPremiumPerks(status.paidMembership.plan);
6478
6518
  * ```
6479
6519
  */
6480
6520
  async getLoyaltyStatus() {
@@ -6607,6 +6647,144 @@ var BrainerceClient = class {
6607
6647
  }
6608
6648
  throw new BrainerceError("getReferralInfo is only available in storefront mode", 400);
6609
6649
  }
6650
+ /**
6651
+ * AI-recommended reward for the logged-in customer — "recommended for you"
6652
+ * at the top of the rewards list (requires customerToken). The result is
6653
+ * ALWAYS a real reward from the store's catalog (the AI only ranks; a
6654
+ * hallucinated pick falls back to a deterministic choice). Returns
6655
+ * `{ reward: null }` when the catalog is empty. Rate-limited (5/min per
6656
+ * customer — it spends the merchant's AI credits). Only available in
6657
+ * storefront mode.
6658
+ *
6659
+ * @example
6660
+ * ```typescript
6661
+ * const { reward, reason } = await client.getRecommendedReward();
6662
+ * if (reward) showRecommendation(reward, reason);
6663
+ * ```
6664
+ */
6665
+ async getRecommendedReward() {
6666
+ if (!this.customerToken && !this.proxyMode) {
6667
+ throw new BrainerceError(
6668
+ "Customer token required. Call setCustomerToken() after login.",
6669
+ 401
6670
+ );
6671
+ }
6672
+ if (this.storeId && !this.apiKey) {
6673
+ return this.storefrontRequest(
6674
+ "GET",
6675
+ "/loyalty/rewards/recommended"
6676
+ );
6677
+ }
6678
+ throw new BrainerceError("getRecommendedReward is only available in storefront mode", 400);
6679
+ }
6680
+ /**
6681
+ * List the paid membership plans the customer can subscribe to (requires
6682
+ * customerToken). Empty when the store offers none or the program is not
6683
+ * active. Only available in storefront mode.
6684
+ *
6685
+ * @example
6686
+ * ```typescript
6687
+ * const plans = await client.getMembershipPlans();
6688
+ * ```
6689
+ */
6690
+ async getMembershipPlans() {
6691
+ if (!this.customerToken && !this.proxyMode) {
6692
+ throw new BrainerceError(
6693
+ "Customer token required. Call setCustomerToken() after login.",
6694
+ 401
6695
+ );
6696
+ }
6697
+ if (this.storeId && !this.apiKey) {
6698
+ return this.storefrontRequest("GET", "/loyalty/membership/plans");
6699
+ }
6700
+ throw new BrainerceError("getMembershipPlans is only available in storefront mode", 400);
6701
+ }
6702
+ /**
6703
+ * List the customer's saved payment methods (display fields only — brand /
6704
+ * last4 / expiry, never card data) for the membership subscribe flow.
6705
+ * Requires customerToken. Cards are vaulted by checking out with
6706
+ * `saveCard: true`. Only available in storefront mode.
6707
+ *
6708
+ * @example
6709
+ * ```typescript
6710
+ * const methods = await client.getMySavedPaymentMethods();
6711
+ * ```
6712
+ */
6713
+ async getMySavedPaymentMethods() {
6714
+ if (!this.customerToken && !this.proxyMode) {
6715
+ throw new BrainerceError(
6716
+ "Customer token required. Call setCustomerToken() after login.",
6717
+ 401
6718
+ );
6719
+ }
6720
+ if (this.storeId && !this.apiKey) {
6721
+ return this.storefrontRequest(
6722
+ "GET",
6723
+ "/loyalty/membership/payment-methods"
6724
+ );
6725
+ }
6726
+ throw new BrainerceError(
6727
+ "getMySavedPaymentMethods is only available in storefront mode",
6728
+ 400
6729
+ );
6730
+ }
6731
+ /**
6732
+ * Subscribe the customer to a paid membership plan — charges the saved card
6733
+ * IMMEDIATELY and starts the recurring cycle (requires customerToken).
6734
+ * Throws a 409 with a `code` ('card_declined' | 'requires_action' | ...)
6735
+ * when the charge fails; 3D-Secure challenges are not supported off-session
6736
+ * and surface as `requires_action`. Only available in storefront mode.
6737
+ *
6738
+ * @example
6739
+ * ```typescript
6740
+ * const membership = await client.subscribeToMembership({
6741
+ * planId: plan.id,
6742
+ * savedPaymentTokenId: method.id,
6743
+ * });
6744
+ * // membership.status === 'ACTIVE'
6745
+ * ```
6746
+ */
6747
+ async subscribeToMembership(params) {
6748
+ if (!this.customerToken && !this.proxyMode) {
6749
+ throw new BrainerceError(
6750
+ "Customer token required. Call setCustomerToken() after login.",
6751
+ 401
6752
+ );
6753
+ }
6754
+ if (this.storeId && !this.apiKey) {
6755
+ return this.storefrontRequest(
6756
+ "POST",
6757
+ "/loyalty/membership/subscribe",
6758
+ params
6759
+ );
6760
+ }
6761
+ throw new BrainerceError("subscribeToMembership is only available in storefront mode", 400);
6762
+ }
6763
+ /**
6764
+ * Cancel the customer's paid membership (requires customerToken).
6765
+ * End-of-period semantics: perks continue until `nextBillingAt`, then the
6766
+ * subscription ends without another charge (a PAST_DUE membership cancels
6767
+ * immediately). Re-subscribing to the same plan before period end simply
6768
+ * un-cancels. Only available in storefront mode.
6769
+ *
6770
+ * @example
6771
+ * ```typescript
6772
+ * const membership = await client.cancelMembership();
6773
+ * // membership.cancelAtPeriodEnd === true
6774
+ * ```
6775
+ */
6776
+ async cancelMembership() {
6777
+ if (!this.customerToken && !this.proxyMode) {
6778
+ throw new BrainerceError(
6779
+ "Customer token required. Call setCustomerToken() after login.",
6780
+ 401
6781
+ );
6782
+ }
6783
+ if (this.storeId && !this.apiKey) {
6784
+ return this.storefrontRequest("POST", "/loyalty/membership/cancel");
6785
+ }
6786
+ throw new BrainerceError("cancelMembership is only available in storefront mode", 400);
6787
+ }
6610
6788
  /**
6611
6789
  * Get the current customer's orders (requires customerToken)
6612
6790
  * Works in vibe-coded and storefront modes
@@ -8892,6 +9070,212 @@ function formatAmount(amount, currency, locale) {
8892
9070
  }
8893
9071
  }
8894
9072
 
9073
+ // src/jsonld.ts
9074
+ function absoluteUrl(siteUrl, path) {
9075
+ if (!path) return void 0;
9076
+ const base = siteUrl.replace(/\/+$/, "");
9077
+ return path.startsWith("http") ? path : `${base}${path.startsWith("/") ? path : `/${path}`}`;
9078
+ }
9079
+ function stripHtml(html) {
9080
+ return (html ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
9081
+ }
9082
+ function buildArticleJsonLd(post, opts) {
9083
+ const url = absoluteUrl(opts.siteUrl, opts.path ?? `/blog/${post.slug}`);
9084
+ const result = {
9085
+ "@context": "https://schema.org",
9086
+ "@type": "Article",
9087
+ headline: post.seoTitle ?? post.title,
9088
+ name: post.title,
9089
+ ...post.excerpt || post.seoDescription ? { description: post.seoDescription ?? post.excerpt } : {},
9090
+ ...url ? { url, mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
9091
+ ...post.coverImageUrl ? { image: [post.coverImageUrl] } : {},
9092
+ ...post.publishedAt ? { datePublished: post.publishedAt } : {},
9093
+ dateModified: post.updatedAt,
9094
+ ...post.author ? { author: { "@type": "Person", name: post.author } } : opts.organizationName ? { author: { "@type": "Organization", name: opts.organizationName } } : {},
9095
+ ...opts.organizationName ? { publisher: { "@type": "Organization", name: opts.organizationName } } : {},
9096
+ ...post.tags.length > 0 ? { keywords: post.tags.join(", ") } : {}
9097
+ };
9098
+ return result;
9099
+ }
9100
+ function buildProductJsonLd(product, opts) {
9101
+ const url = absoluteUrl(opts.siteUrl, opts.path ?? (product.slug ? `/products/${product.slug}` : void 0));
9102
+ const images = (product.images ?? []).map((img) => img.url).filter(Boolean);
9103
+ const brand = opts.brandName ?? product.brands?.[0]?.name;
9104
+ const description = stripHtml(product.description).slice(0, 5e3);
9105
+ const effectivePrice = product.salePrice ?? product.basePrice;
9106
+ const inStock = product.inventory ? (product.inventory.available ?? 0) > 0 : true;
9107
+ const isVariable = product.type === "VARIABLE" && product.priceMin && product.priceMax;
9108
+ const offer = isVariable ? {
9109
+ "@type": "AggregateOffer",
9110
+ lowPrice: product.priceMin,
9111
+ highPrice: product.priceMax,
9112
+ priceCurrency: opts.currency,
9113
+ availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
9114
+ ...url ? { url } : {}
9115
+ } : {
9116
+ "@type": "Offer",
9117
+ price: effectivePrice,
9118
+ priceCurrency: opts.currency,
9119
+ availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
9120
+ ...url ? { url } : {}
9121
+ };
9122
+ return {
9123
+ "@context": "https://schema.org",
9124
+ "@type": "Product",
9125
+ name: product.name,
9126
+ ...description ? { description } : {},
9127
+ ...images.length > 0 ? { image: images } : {},
9128
+ ...url ? { url } : {},
9129
+ sku: product.sku,
9130
+ // Identifiers Google uses to match a product to its catalog — key for
9131
+ // free merchant-listing eligibility without a Merchant Center feed.
9132
+ ...product.gtin ? { gtin: product.gtin } : {},
9133
+ ...product.mpn ? { mpn: product.mpn } : {},
9134
+ ...brand ? { brand: { "@type": "Brand", name: brand } } : {},
9135
+ offers: offer,
9136
+ // Google policy: never emit an empty/zero rating block.
9137
+ ...product.reviewCount && product.reviewCount > 0 && product.avgRating ? {
9138
+ aggregateRating: {
9139
+ "@type": "AggregateRating",
9140
+ ratingValue: product.avgRating,
9141
+ reviewCount: product.reviewCount
9142
+ }
9143
+ } : {}
9144
+ };
9145
+ }
9146
+ function buildOrganizationJsonLd(store, opts) {
9147
+ const sameAs = Object.values(store.socialLinks ?? {}).filter(Boolean);
9148
+ return {
9149
+ "@context": "https://schema.org",
9150
+ "@type": "Organization",
9151
+ name: store.name,
9152
+ url: opts.siteUrl,
9153
+ ...store.logo ? { logo: store.logo } : {},
9154
+ ...store.metaDescription ? { description: store.metaDescription } : {},
9155
+ ...store.contactEmail ? { email: store.contactEmail } : {},
9156
+ ...store.contactPhone ? { telephone: store.contactPhone } : {},
9157
+ ...sameAs.length > 0 ? { sameAs } : {}
9158
+ };
9159
+ }
9160
+ function buildWebsiteJsonLd(store, opts) {
9161
+ const base = opts.siteUrl.replace(/\/+$/, "");
9162
+ const result = {
9163
+ "@context": "https://schema.org",
9164
+ "@type": "WebSite",
9165
+ name: store.name,
9166
+ url: opts.siteUrl
9167
+ };
9168
+ if (opts.searchUrlTemplate?.includes("{search_term_string}")) {
9169
+ const tmpl = opts.searchUrlTemplate.startsWith("http") ? opts.searchUrlTemplate : `${base}${opts.searchUrlTemplate.startsWith("/") ? "" : "/"}${opts.searchUrlTemplate}`;
9170
+ result.potentialAction = {
9171
+ "@type": "SearchAction",
9172
+ target: { "@type": "EntryPoint", urlTemplate: tmpl },
9173
+ "query-input": "required name=search_term_string"
9174
+ };
9175
+ }
9176
+ return result;
9177
+ }
9178
+ function buildCollectionPageJsonLd(category, opts) {
9179
+ const url = absoluteUrl(opts.siteUrl, opts.path);
9180
+ const description = category.metaDescription ?? (stripHtml(category.description) || void 0);
9181
+ return {
9182
+ "@context": "https://schema.org",
9183
+ "@type": "CollectionPage",
9184
+ name: category.name,
9185
+ ...description ? { description } : {},
9186
+ ...url ? { url, mainEntityOfPage: { "@type": "WebPage", "@id": url } } : {},
9187
+ ...category.image ? { image: [category.image] } : {}
9188
+ };
9189
+ }
9190
+ function buildBreadcrumbJsonLd(items) {
9191
+ return {
9192
+ "@context": "https://schema.org",
9193
+ "@type": "BreadcrumbList",
9194
+ itemListElement: items.map((item, index) => ({
9195
+ "@type": "ListItem",
9196
+ position: index + 1,
9197
+ name: item.name,
9198
+ item: item.url
9199
+ }))
9200
+ };
9201
+ }
9202
+ function jsonLdScriptProps(data) {
9203
+ return {
9204
+ type: "application/ld+json",
9205
+ dangerouslySetInnerHTML: { __html: JSON.stringify(data).replace(/</g, "\\u003c") }
9206
+ };
9207
+ }
9208
+
9209
+ // src/sitemap.ts
9210
+ async function getBlogSitemapEntries(client, opts) {
9211
+ const base = opts.siteUrl.replace(/\/+$/, "");
9212
+ const basePath = opts.basePath ?? "/blog";
9213
+ const pageSize = Math.min(opts.pageSize ?? 100, 100);
9214
+ const maxEntries = opts.maxEntries ?? 5e3;
9215
+ const posts = [];
9216
+ let page = 1;
9217
+ for (; ; ) {
9218
+ const res = await client.blog.getPosts({ page, limit: pageSize });
9219
+ posts.push(...res.data.map((p) => ({ slug: p.slug, updatedAt: p.updatedAt })));
9220
+ if (page >= res.meta.totalPages || posts.length >= maxEntries) break;
9221
+ page += 1;
9222
+ }
9223
+ const nonDefaultLocales = opts.locales?.filter((locale) => locale !== opts.defaultLocale) ?? [];
9224
+ const entries = [];
9225
+ entries.push({ url: `${base}${basePath}`, changeFrequency: "daily", priority: 0.7 });
9226
+ for (const locale of nonDefaultLocales) {
9227
+ entries.push({
9228
+ url: `${base}/${locale}${basePath}`,
9229
+ changeFrequency: "daily",
9230
+ priority: 0.7
9231
+ });
9232
+ }
9233
+ for (const post of posts.slice(0, maxEntries)) {
9234
+ const lastModified = new Date(post.updatedAt);
9235
+ entries.push({
9236
+ url: `${base}${basePath}/${post.slug}`,
9237
+ lastModified,
9238
+ changeFrequency: "weekly",
9239
+ priority: 0.6
9240
+ });
9241
+ for (const locale of nonDefaultLocales) {
9242
+ entries.push({
9243
+ url: `${base}/${locale}${basePath}/${post.slug}`,
9244
+ lastModified,
9245
+ changeFrequency: "weekly",
9246
+ priority: 0.5
9247
+ });
9248
+ }
9249
+ }
9250
+ return entries;
9251
+ }
9252
+ async function getCategorySitemapEntries(client, opts) {
9253
+ const base = opts.siteUrl.replace(/\/+$/, "");
9254
+ const basePath = opts.basePath ?? "/category";
9255
+ const nonDefaultLocales = opts.locales?.filter((l) => l !== opts.defaultLocale) ?? [];
9256
+ const { categories } = await client.getCategories();
9257
+ const slugs = [];
9258
+ const walk = (nodes) => {
9259
+ for (const node of nodes) {
9260
+ if (node.slug) slugs.push(node.slug);
9261
+ if (node.children?.length) walk(node.children);
9262
+ }
9263
+ };
9264
+ walk(categories);
9265
+ const entries = [];
9266
+ for (const slug of slugs) {
9267
+ entries.push({ url: `${base}${basePath}/${slug}`, changeFrequency: "daily", priority: 0.8 });
9268
+ for (const locale of nonDefaultLocales) {
9269
+ entries.push({
9270
+ url: `${base}/${locale}${basePath}/${slug}`,
9271
+ changeFrequency: "daily",
9272
+ priority: 0.7
9273
+ });
9274
+ }
9275
+ }
9276
+ return entries;
9277
+ }
9278
+
8895
9279
  // src/types.ts
8896
9280
  function isHtmlDescription(product) {
8897
9281
  if (product?.descriptionFormat === "html") return true;
@@ -8907,7 +9291,7 @@ function getDescriptionContent(product) {
8907
9291
  }
8908
9292
  return { text: product.description };
8909
9293
  }
8910
- function stripHtml(html) {
9294
+ function stripHtml2(html) {
8911
9295
  if (!html) return "";
8912
9296
  return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&#x27;/gi, "'").replace(/&#x2F;/gi, "/").replace(/\s+/g, " ").trim();
8913
9297
  }
@@ -8916,7 +9300,7 @@ function deriveSeoDescription(product, options) {
8916
9300
  const maxLength = options?.maxLength ?? 160;
8917
9301
  const authored = (product.seoDescription ?? product.metaDescription ?? "").trim();
8918
9302
  if (authored) return authored;
8919
- const plain = stripHtml(product.description);
9303
+ const plain = stripHtml2(product.description);
8920
9304
  if (plain) {
8921
9305
  if (plain.length <= maxLength) return plain;
8922
9306
  const sliceLength = maxLength - 1;
@@ -9138,6 +9522,12 @@ function isCouponApplicableToProduct(coupon, productId) {
9138
9522
  BrainerceError,
9139
9523
  RTL_LOCALES,
9140
9524
  SDK_VERSION,
9525
+ buildArticleJsonLd,
9526
+ buildBreadcrumbJsonLd,
9527
+ buildCollectionPageJsonLd,
9528
+ buildOrganizationJsonLd,
9529
+ buildProductJsonLd,
9530
+ buildWebsiteJsonLd,
9141
9531
  createWebhookHandler,
9142
9532
  deriveSeoDescription,
9143
9533
  enableDevGuards,
@@ -9145,9 +9535,11 @@ function isCouponApplicableToProduct(coupon, productId) {
9145
9535
  formatPrice,
9146
9536
  formatProductPrice,
9147
9537
  formatVariantPrice,
9538
+ getBlogSitemapEntries,
9148
9539
  getCartItemImage,
9149
9540
  getCartItemName,
9150
9541
  getCartTotals,
9542
+ getCategorySitemapEntries,
9151
9543
  getDescriptionContent,
9152
9544
  getDirectionForLocale,
9153
9545
  getPriceDisplay,
@@ -9165,6 +9557,7 @@ function isCouponApplicableToProduct(coupon, productId) {
9165
9557
  isCouponApplicableToProduct,
9166
9558
  isHtmlDescription,
9167
9559
  isWebhookEventType,
9560
+ jsonLdScriptProps,
9168
9561
  parseWebhookEvent,
9169
9562
  safePaymentRedirect,
9170
9563
  stripHtml,