@thg-altitude/schemaorg 1.0.39 → 1.0.42
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thg-altitude/schemaorg",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
"author": "Phillip Gourley",
|
|
12
12
|
"license": "ISC",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"astro": "^4.0.8 || ^5.0.0"
|
|
14
|
+
"astro": "^4.0.8 || ^5.0.0 || ^6.0.0"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { mapProductSchemaDataEnhanced } from '../utils/productSchema.js';
|
|
2
|
+
import { mapProductSchemaDataEnhanced, buildSubscriptionFromContracts } from '../utils/productSchema.js';
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
image,
|
|
@@ -30,6 +30,9 @@ const {
|
|
|
30
30
|
beauty_targetAge,
|
|
31
31
|
gender,
|
|
32
32
|
weight,
|
|
33
|
+
subscription, // Optional: explicit subscription pricing data { price, priceCurrency, billingIncrement, unitCode, billingDuration }
|
|
34
|
+
subscriptionConfig, // Optional: config for auto-detecting from subscriptionContracts on variants
|
|
35
|
+
// { discountPercentage: 21, frequencyUnit: "MONTH", frequencyValue: 1 }
|
|
33
36
|
colorVariantPages, // Array of {url, sku, color} for sibling color pages
|
|
34
37
|
currentColor, // The color of the current page (used to filter variants)
|
|
35
38
|
baseProductGroupID, // Optional: explicit base product group ID without color suffix
|
|
@@ -386,6 +389,26 @@ function generateProductSchema() {
|
|
|
386
389
|
},
|
|
387
390
|
]
|
|
388
391
|
: []),
|
|
392
|
+
...(() => {
|
|
393
|
+
// Subscription pricing: subscriptionContracts + config, or explicit subscription prop
|
|
394
|
+
const variantPrice = parseFloat(variant.price.price.amount);
|
|
395
|
+
let effectiveSub;
|
|
396
|
+
if (variant.subscriptionContracts?.length > 0 && subscriptionConfig) {
|
|
397
|
+
effectiveSub = buildSubscriptionFromContracts(variant, subscriptionConfig, variantPrice, currency);
|
|
398
|
+
} else if (subscription) {
|
|
399
|
+
effectiveSub = { ...subscription };
|
|
400
|
+
}
|
|
401
|
+
if (!effectiveSub) return [];
|
|
402
|
+
return [{
|
|
403
|
+
"@type": "UnitPriceSpecification",
|
|
404
|
+
priceComponentType: "https://schema.org/Subscription",
|
|
405
|
+
...(effectiveSub.price != null && { price: effectiveSub.price }),
|
|
406
|
+
...(effectiveSub.priceCurrency && { priceCurrency: effectiveSub.priceCurrency }),
|
|
407
|
+
...(effectiveSub.billingIncrement != null && { billingIncrement: effectiveSub.billingIncrement }),
|
|
408
|
+
...(effectiveSub.unitCode && { unitCode: effectiveSub.unitCode }),
|
|
409
|
+
...(effectiveSub.billingDuration != null && { billingDuration: effectiveSub.billingDuration }),
|
|
410
|
+
}];
|
|
411
|
+
})(),
|
|
389
412
|
],
|
|
390
413
|
}
|
|
391
414
|
};
|
|
@@ -489,6 +512,26 @@ function generateProductSchema() {
|
|
|
489
512
|
},
|
|
490
513
|
]
|
|
491
514
|
: []),
|
|
515
|
+
...(() => {
|
|
516
|
+
// Subscription pricing: subscriptionContracts + config, or explicit subscription prop
|
|
517
|
+
const variantPrice = parseFloat(variant.price.price.amount);
|
|
518
|
+
let effectiveSub;
|
|
519
|
+
if (variant.subscriptionContracts?.length > 0 && subscriptionConfig) {
|
|
520
|
+
effectiveSub = buildSubscriptionFromContracts(variant, subscriptionConfig, variantPrice, currency);
|
|
521
|
+
} else if (subscription) {
|
|
522
|
+
effectiveSub = { ...subscription };
|
|
523
|
+
}
|
|
524
|
+
if (!effectiveSub) return [];
|
|
525
|
+
return [{
|
|
526
|
+
"@type": "UnitPriceSpecification",
|
|
527
|
+
priceComponentType: "https://schema.org/Subscription",
|
|
528
|
+
...(effectiveSub.price != null && { price: effectiveSub.price }),
|
|
529
|
+
...(effectiveSub.priceCurrency && { priceCurrency: effectiveSub.priceCurrency }),
|
|
530
|
+
...(effectiveSub.billingIncrement != null && { billingIncrement: effectiveSub.billingIncrement }),
|
|
531
|
+
...(effectiveSub.unitCode && { unitCode: effectiveSub.unitCode }),
|
|
532
|
+
...(effectiveSub.billingDuration != null && { billingDuration: effectiveSub.billingDuration }),
|
|
533
|
+
}];
|
|
534
|
+
})(),
|
|
492
535
|
],
|
|
493
536
|
};
|
|
494
537
|
});
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { mapProductSchemaData, stripHtml } from '../utils/productSchema.js';
|
|
2
|
+
import { mapProductSchemaData, buildSubscriptionFromContracts, stripHtml } from '../utils/productSchema.js';
|
|
3
3
|
|
|
4
4
|
// Use the mapping function to extract data from the full product data
|
|
5
5
|
const mappedData = mapProductSchemaData(Astro.props);
|
|
6
6
|
|
|
7
|
+
// Subscription data resolution:
|
|
8
|
+
// 1. Explicit subscription prop: { price, priceCurrency, billingIncrement, unitCode, billingDuration }
|
|
9
|
+
// 2. Auto-detect from variant.subscriptionContracts + subscriptionConfig prop
|
|
10
|
+
// subscriptionConfig: { discountPercentage, frequencyUnit, frequencyValue, billingDuration }
|
|
11
|
+
const subscription = Astro.props.subscription || null;
|
|
12
|
+
const subscriptionConfig = Astro.props.subscriptionConfig;
|
|
13
|
+
|
|
7
14
|
// Extract suggestedGender and suggestedAge using the comprehensive mapping function
|
|
8
15
|
let suggestedGender = Astro.props.suggestedGender || mappedData.suggestedGender;
|
|
9
16
|
let suggestedAge = Astro.props.suggestedAge || mappedData.suggestedAge;
|
|
@@ -253,6 +260,39 @@ if (hasMultipleVariants) {
|
|
|
253
260
|
|
|
254
261
|
if (variant?.price?.price?.amount) offerObj.price = parseFloat(variant.price.price.amount);
|
|
255
262
|
if (Astro.props.currency) offerObj.priceCurrency = Astro.props.currency;
|
|
263
|
+
|
|
264
|
+
// Add subscription pricing:
|
|
265
|
+
// 1. subscriptionContracts + subscriptionConfig (subscribe & gain / auto-replenish)
|
|
266
|
+
// 2. Explicit subscription prop
|
|
267
|
+
// 3. Box subscription variant (isSubscription + subscriptionTerm/subscriptionFrequency)
|
|
268
|
+
const variantPrice = variant?.price?.price?.amount ? parseFloat(variant.price.price.amount) : null;
|
|
269
|
+
let effectiveSub;
|
|
270
|
+
if (variant.subscriptionContracts?.length > 0 && subscriptionConfig) {
|
|
271
|
+
effectiveSub = buildSubscriptionFromContracts(variant, subscriptionConfig, variantPrice, Astro.props.currency);
|
|
272
|
+
} else if (variant.isSubscription) {
|
|
273
|
+
// Box subscription variant - build from variant's own subscription fields
|
|
274
|
+
effectiveSub = {
|
|
275
|
+
price: variantPrice,
|
|
276
|
+
priceCurrency: Astro.props.currency || null,
|
|
277
|
+
billingIncrement: variant.subscriptionFrequency ?? 1,
|
|
278
|
+
unitCode: 'MON',
|
|
279
|
+
billingDuration: variant.subscriptionTerm ?? null,
|
|
280
|
+
};
|
|
281
|
+
} else if (subscription) {
|
|
282
|
+
effectiveSub = { ...subscription };
|
|
283
|
+
}
|
|
284
|
+
if (effectiveSub) {
|
|
285
|
+
const subscriptionSpec = {
|
|
286
|
+
"@type": "UnitPriceSpecification",
|
|
287
|
+
"priceComponentType": "https://schema.org/Subscription"
|
|
288
|
+
};
|
|
289
|
+
if (effectiveSub.price != null) subscriptionSpec.price = effectiveSub.price;
|
|
290
|
+
if (effectiveSub.priceCurrency) subscriptionSpec.priceCurrency = effectiveSub.priceCurrency;
|
|
291
|
+
if (effectiveSub.billingIncrement != null) subscriptionSpec.billingIncrement = effectiveSub.billingIncrement;
|
|
292
|
+
if (effectiveSub.unitCode) subscriptionSpec.unitCode = effectiveSub.unitCode;
|
|
293
|
+
if (effectiveSub.billingDuration != null) subscriptionSpec.billingDuration = effectiveSub.billingDuration;
|
|
294
|
+
offerObj.priceSpecification = subscriptionSpec;
|
|
295
|
+
}
|
|
256
296
|
|
|
257
297
|
variantObj.offers = offerObj;
|
|
258
298
|
variantsArray.push(variantObj);
|
|
@@ -359,6 +399,39 @@ if (hasMultipleVariants) {
|
|
|
359
399
|
|
|
360
400
|
if (variant?.price?.price?.amount) offerObj.price = parseFloat(variant.price.price.amount);
|
|
361
401
|
if (Astro.props.currency) offerObj.priceCurrency = Astro.props.currency;
|
|
402
|
+
|
|
403
|
+
// Add subscription pricing:
|
|
404
|
+
// 1. subscriptionContracts + subscriptionConfig (subscribe & gain / auto-replenish)
|
|
405
|
+
// 2. Box subscription variant (isSubscription + subscriptionTerm/subscriptionFrequency)
|
|
406
|
+
// 3. Explicit subscription prop
|
|
407
|
+
const variantPrice = variant?.price?.price?.amount ? parseFloat(variant.price.price.amount) : null;
|
|
408
|
+
let effectiveSub;
|
|
409
|
+
if (variant.subscriptionContracts?.length > 0 && subscriptionConfig) {
|
|
410
|
+
effectiveSub = buildSubscriptionFromContracts(variant, subscriptionConfig, variantPrice, Astro.props.currency);
|
|
411
|
+
} else if (variant.isSubscription) {
|
|
412
|
+
// Box subscription variant - build from variant's own subscription fields
|
|
413
|
+
effectiveSub = {
|
|
414
|
+
price: variantPrice,
|
|
415
|
+
priceCurrency: Astro.props.currency || null,
|
|
416
|
+
billingIncrement: variant.subscriptionFrequency ?? 1,
|
|
417
|
+
unitCode: 'MON',
|
|
418
|
+
billingDuration: variant.subscriptionTerm ?? null,
|
|
419
|
+
};
|
|
420
|
+
} else if (subscription) {
|
|
421
|
+
effectiveSub = { ...subscription };
|
|
422
|
+
}
|
|
423
|
+
if (effectiveSub) {
|
|
424
|
+
const subscriptionSpec = {
|
|
425
|
+
"@type": "UnitPriceSpecification",
|
|
426
|
+
"priceComponentType": "https://schema.org/Subscription"
|
|
427
|
+
};
|
|
428
|
+
if (effectiveSub.price != null) subscriptionSpec.price = effectiveSub.price;
|
|
429
|
+
if (effectiveSub.priceCurrency) subscriptionSpec.priceCurrency = effectiveSub.priceCurrency;
|
|
430
|
+
if (effectiveSub.billingIncrement != null) subscriptionSpec.billingIncrement = effectiveSub.billingIncrement;
|
|
431
|
+
if (effectiveSub.unitCode) subscriptionSpec.unitCode = effectiveSub.unitCode;
|
|
432
|
+
if (effectiveSub.billingDuration != null) subscriptionSpec.billingDuration = effectiveSub.billingDuration;
|
|
433
|
+
offerObj.priceSpecification = subscriptionSpec;
|
|
434
|
+
}
|
|
362
435
|
|
|
363
436
|
offersArray.push(offerObj);
|
|
364
437
|
}
|
|
@@ -463,4 +536,4 @@ if (reviewsArray.length > 0) schema.review = reviewsArray;
|
|
|
463
536
|
return undefined;
|
|
464
537
|
}
|
|
465
538
|
return value;
|
|
466
|
-
})}></script>
|
|
539
|
+
})}></script>
|
|
@@ -87,6 +87,66 @@ export function mapProductSchemaDataEnhanced(productData) {
|
|
|
87
87
|
return mapProductSchemaData(productData);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Checks if a variant has subscription contracts and builds subscription data
|
|
92
|
+
* from the variant's subscriptionContracts array combined with a subscriptionConfig.
|
|
93
|
+
*
|
|
94
|
+
* The subscriptionConfig provides the discount/frequency details that aren't
|
|
95
|
+
* available on the contract objects themselves.
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} variant - The variant object with subscriptionContracts and price data
|
|
98
|
+
* @param {Object} subscriptionConfig - Config with discount/frequency defaults:
|
|
99
|
+
* {
|
|
100
|
+
* discountPercentage: 21, // % discount for subscription (applied to RRP when available)
|
|
101
|
+
* frequencyUnit: "MONTH", // MONTH, WEEK, DAY, YEAR
|
|
102
|
+
* frequencyValue: 1, // billing increment (1 = every month)
|
|
103
|
+
* billingDuration: null, // null = ongoing
|
|
104
|
+
* }
|
|
105
|
+
* @param {number} variantPrice - The variant's current/sale price (fallback if no RRP)
|
|
106
|
+
* @param {string} currency - Currency code (e.g. "GBP")
|
|
107
|
+
* @returns {Object|null} Mapped subscription data or null
|
|
108
|
+
*/
|
|
109
|
+
export function buildSubscriptionFromContracts(variant, subscriptionConfig, variantPrice, currency) {
|
|
110
|
+
if (!variant?.subscriptionContracts || variant?.subscriptionContracts?.length === 0) return null;
|
|
111
|
+
if (!subscriptionConfig) return null;
|
|
112
|
+
|
|
113
|
+
const discountPct = subscriptionConfig.discountPercentage ?? 0;
|
|
114
|
+
|
|
115
|
+
// Use RRP as the base price for discount calculation when available
|
|
116
|
+
// The subscription discount is typically applied to the RRP (full price), not the sale price
|
|
117
|
+
const rrpPrice = variant?.price?.rrp?.amount ? parseFloat(variant.price.rrp.amount) : null;
|
|
118
|
+
const basePrice = rrpPrice ?? variantPrice;
|
|
119
|
+
|
|
120
|
+
// Compute discounted price from the base price (RRP or sale price)
|
|
121
|
+
let subscriptionPrice = null;
|
|
122
|
+
if (basePrice != null && discountPct > 0) {
|
|
123
|
+
subscriptionPrice = parseFloat((basePrice * (1 - discountPct / 100)).toFixed(2));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Map frequency unit
|
|
127
|
+
const frequencyUnit = (subscriptionConfig.frequencyUnit || 'MONTH').toUpperCase();
|
|
128
|
+
const unitCodeMap = {
|
|
129
|
+
'MONTH': 'MON',
|
|
130
|
+
'WEEK': 'WEE',
|
|
131
|
+
'DAY': 'DAY',
|
|
132
|
+
'YEAR': 'ANN',
|
|
133
|
+
};
|
|
134
|
+
const unitCode = unitCodeMap[frequencyUnit] || frequencyUnit;
|
|
135
|
+
|
|
136
|
+
const billingIncrement = subscriptionConfig.frequencyValue ?? 1;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
price: subscriptionPrice,
|
|
140
|
+
priceCurrency: currency || null,
|
|
141
|
+
billingIncrement,
|
|
142
|
+
unitCode,
|
|
143
|
+
billingDuration: subscriptionConfig.billingDuration ?? null,
|
|
144
|
+
initialDiscountPercentage: discountPct,
|
|
145
|
+
recurringDiscountPercentage: subscriptionConfig.recurringDiscountPercentage ?? discountPct,
|
|
146
|
+
contractIds: variant.subscriptionContracts.map(c => c.id),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
90
150
|
/**
|
|
91
151
|
* Simple function to strip HTML tags from text
|
|
92
152
|
*/
|