perspectapi-ts-sdk 2.1.0 → 2.2.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.d.mts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +111 -10
- package/dist/index.mjs +111 -10
- package/package.json +1 -1
- package/src/client/content-client.ts +28 -2
- package/src/client/products-client.ts +11 -0
- package/src/loaders.ts +65 -10
- package/src/types/index.ts +40 -0
- package/src/utils/validators.ts +53 -0
package/dist/index.d.mts
CHANGED
|
@@ -424,6 +424,34 @@ type CheckoutMetadataValue = string | number | boolean | null | CheckoutMetadata
|
|
|
424
424
|
[key: string]: CheckoutMetadataValue;
|
|
425
425
|
};
|
|
426
426
|
type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
427
|
+
type CheckoutTaxStrategy = 'disabled' | 'gateway_auto' | 'manual_rates' | 'external_service';
|
|
428
|
+
type CheckoutTaxExemptionStatus = 'none' | 'exempt' | 'reverse_charge';
|
|
429
|
+
interface CheckoutTaxCustomerExemptionRequest {
|
|
430
|
+
status?: CheckoutTaxExemptionStatus;
|
|
431
|
+
reason?: string;
|
|
432
|
+
tax_id?: string;
|
|
433
|
+
tax_id_type?: string;
|
|
434
|
+
certificate_url?: string;
|
|
435
|
+
metadata?: Record<string, any>;
|
|
436
|
+
expires_at?: string;
|
|
437
|
+
}
|
|
438
|
+
interface CheckoutTaxRequest {
|
|
439
|
+
strategy?: CheckoutTaxStrategy;
|
|
440
|
+
customer_identifier?: string;
|
|
441
|
+
customer_profile_id?: string;
|
|
442
|
+
customer_display_name?: string;
|
|
443
|
+
allow_exemption?: boolean;
|
|
444
|
+
require_tax_id?: boolean;
|
|
445
|
+
save_profile?: boolean;
|
|
446
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
447
|
+
manual_rate_percent?: number;
|
|
448
|
+
manual_rate_map?: Record<string, number>;
|
|
449
|
+
external_service?: {
|
|
450
|
+
provider: string;
|
|
451
|
+
config?: Record<string, any>;
|
|
452
|
+
};
|
|
453
|
+
metadata?: Record<string, any>;
|
|
454
|
+
}
|
|
427
455
|
interface CreateCheckoutSessionRequest {
|
|
428
456
|
priceId?: string;
|
|
429
457
|
quantity?: number;
|
|
@@ -455,6 +483,7 @@ interface CreateCheckoutSessionRequest {
|
|
|
455
483
|
allowed_countries: string[];
|
|
456
484
|
};
|
|
457
485
|
billing_address_collection?: 'auto' | 'required';
|
|
486
|
+
tax?: CheckoutTaxRequest;
|
|
458
487
|
}
|
|
459
488
|
interface CheckoutSession {
|
|
460
489
|
id: string;
|
|
@@ -2322,6 +2351,10 @@ interface CheckoutSessionOptions {
|
|
|
2322
2351
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
2323
2352
|
*/
|
|
2324
2353
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
2354
|
+
/**
|
|
2355
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
2356
|
+
*/
|
|
2357
|
+
tax?: CheckoutTaxRequest;
|
|
2325
2358
|
}
|
|
2326
2359
|
/**
|
|
2327
2360
|
* Convenience helper that creates a checkout session by looking up Stripe price IDs
|
|
@@ -2331,4 +2364,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2331
2364
|
error: string;
|
|
2332
2365
|
}>;
|
|
2333
2366
|
|
|
2334
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
|
2367
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
package/dist/index.d.ts
CHANGED
|
@@ -424,6 +424,34 @@ type CheckoutMetadataValue = string | number | boolean | null | CheckoutMetadata
|
|
|
424
424
|
[key: string]: CheckoutMetadataValue;
|
|
425
425
|
};
|
|
426
426
|
type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
427
|
+
type CheckoutTaxStrategy = 'disabled' | 'gateway_auto' | 'manual_rates' | 'external_service';
|
|
428
|
+
type CheckoutTaxExemptionStatus = 'none' | 'exempt' | 'reverse_charge';
|
|
429
|
+
interface CheckoutTaxCustomerExemptionRequest {
|
|
430
|
+
status?: CheckoutTaxExemptionStatus;
|
|
431
|
+
reason?: string;
|
|
432
|
+
tax_id?: string;
|
|
433
|
+
tax_id_type?: string;
|
|
434
|
+
certificate_url?: string;
|
|
435
|
+
metadata?: Record<string, any>;
|
|
436
|
+
expires_at?: string;
|
|
437
|
+
}
|
|
438
|
+
interface CheckoutTaxRequest {
|
|
439
|
+
strategy?: CheckoutTaxStrategy;
|
|
440
|
+
customer_identifier?: string;
|
|
441
|
+
customer_profile_id?: string;
|
|
442
|
+
customer_display_name?: string;
|
|
443
|
+
allow_exemption?: boolean;
|
|
444
|
+
require_tax_id?: boolean;
|
|
445
|
+
save_profile?: boolean;
|
|
446
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
447
|
+
manual_rate_percent?: number;
|
|
448
|
+
manual_rate_map?: Record<string, number>;
|
|
449
|
+
external_service?: {
|
|
450
|
+
provider: string;
|
|
451
|
+
config?: Record<string, any>;
|
|
452
|
+
};
|
|
453
|
+
metadata?: Record<string, any>;
|
|
454
|
+
}
|
|
427
455
|
interface CreateCheckoutSessionRequest {
|
|
428
456
|
priceId?: string;
|
|
429
457
|
quantity?: number;
|
|
@@ -455,6 +483,7 @@ interface CreateCheckoutSessionRequest {
|
|
|
455
483
|
allowed_countries: string[];
|
|
456
484
|
};
|
|
457
485
|
billing_address_collection?: 'auto' | 'required';
|
|
486
|
+
tax?: CheckoutTaxRequest;
|
|
458
487
|
}
|
|
459
488
|
interface CheckoutSession {
|
|
460
489
|
id: string;
|
|
@@ -2322,6 +2351,10 @@ interface CheckoutSessionOptions {
|
|
|
2322
2351
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
2323
2352
|
*/
|
|
2324
2353
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
2354
|
+
/**
|
|
2355
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
2356
|
+
*/
|
|
2357
|
+
tax?: CheckoutTaxRequest;
|
|
2325
2358
|
}
|
|
2326
2359
|
/**
|
|
2327
2360
|
* Convenience helper that creates a checkout session by looking up Stripe price IDs
|
|
@@ -2331,4 +2364,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2331
2364
|
error: string;
|
|
2332
2365
|
}>;
|
|
2333
2366
|
|
|
2334
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
|
2367
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
package/dist/index.js
CHANGED
|
@@ -729,6 +729,43 @@ var AuthClient = class extends BaseClient {
|
|
|
729
729
|
}
|
|
730
730
|
};
|
|
731
731
|
|
|
732
|
+
// src/utils/validators.ts
|
|
733
|
+
var MAX_API_QUERY_LIMIT = 100;
|
|
734
|
+
var ALLOWED_CONTENT_TYPES = ["post", "page"];
|
|
735
|
+
function validateLimit(limit, context) {
|
|
736
|
+
if (typeof limit !== "number" || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
737
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
738
|
+
}
|
|
739
|
+
if (limit < 1) {
|
|
740
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
741
|
+
}
|
|
742
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
743
|
+
throw new Error(
|
|
744
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
return limit;
|
|
748
|
+
}
|
|
749
|
+
function validateOptionalLimit(limit, context) {
|
|
750
|
+
if (limit === void 0 || limit === null) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
return validateLimit(limit, context);
|
|
754
|
+
}
|
|
755
|
+
function validateOptionalContentType(pageType, context) {
|
|
756
|
+
if (pageType === void 0 || pageType === null) {
|
|
757
|
+
return void 0;
|
|
758
|
+
}
|
|
759
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType)) {
|
|
760
|
+
return pageType;
|
|
761
|
+
}
|
|
762
|
+
throw new Error(
|
|
763
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
764
|
+
", "
|
|
765
|
+
)}.`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
732
769
|
// src/client/content-client.ts
|
|
733
770
|
var ContentClient = class extends BaseClient {
|
|
734
771
|
constructor(http, cache) {
|
|
@@ -740,12 +777,33 @@ var ContentClient = class extends BaseClient {
|
|
|
740
777
|
async getContent(siteName, params, cachePolicy) {
|
|
741
778
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
742
779
|
const path = this.buildPath(endpoint);
|
|
780
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
781
|
+
if (normalizedParams) {
|
|
782
|
+
const validatedLimit = validateOptionalLimit(
|
|
783
|
+
normalizedParams.limit,
|
|
784
|
+
"content query"
|
|
785
|
+
);
|
|
786
|
+
if (validatedLimit !== void 0) {
|
|
787
|
+
normalizedParams.limit = validatedLimit;
|
|
788
|
+
} else {
|
|
789
|
+
delete normalizedParams.limit;
|
|
790
|
+
}
|
|
791
|
+
const validatedPageType = validateOptionalContentType(
|
|
792
|
+
normalizedParams.page_type,
|
|
793
|
+
"content query"
|
|
794
|
+
);
|
|
795
|
+
if (validatedPageType !== void 0) {
|
|
796
|
+
normalizedParams.page_type = validatedPageType;
|
|
797
|
+
} else {
|
|
798
|
+
delete normalizedParams.page_type;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
743
801
|
return this.fetchWithCache(
|
|
744
802
|
endpoint,
|
|
745
|
-
|
|
803
|
+
normalizedParams,
|
|
746
804
|
this.buildContentTags(siteName),
|
|
747
805
|
cachePolicy,
|
|
748
|
-
() => this.http.get(path,
|
|
806
|
+
() => this.http.get(path, normalizedParams)
|
|
749
807
|
);
|
|
750
808
|
}
|
|
751
809
|
/**
|
|
@@ -1107,6 +1165,15 @@ var ProductsClient = class extends BaseClient {
|
|
|
1107
1165
|
};
|
|
1108
1166
|
const normalizedParams = params ? { ...params } : void 0;
|
|
1109
1167
|
if (normalizedParams) {
|
|
1168
|
+
const validatedLimit = validateOptionalLimit(
|
|
1169
|
+
normalizedParams.limit,
|
|
1170
|
+
"products query"
|
|
1171
|
+
);
|
|
1172
|
+
if (validatedLimit !== void 0) {
|
|
1173
|
+
normalizedParams.limit = validatedLimit;
|
|
1174
|
+
} else {
|
|
1175
|
+
delete normalizedParams.limit;
|
|
1176
|
+
}
|
|
1110
1177
|
const normalizedCategories = normalizeList(normalizedParams.category);
|
|
1111
1178
|
if (normalizedCategories !== void 0) {
|
|
1112
1179
|
normalizedParams.category = normalizedCategories;
|
|
@@ -2322,12 +2389,16 @@ async function loadProducts(options) {
|
|
|
2322
2389
|
siteName,
|
|
2323
2390
|
logger = noopLogger,
|
|
2324
2391
|
fallbackProducts,
|
|
2325
|
-
limit
|
|
2392
|
+
limit: requestedLimit,
|
|
2326
2393
|
offset,
|
|
2327
2394
|
search,
|
|
2328
2395
|
category,
|
|
2329
2396
|
categoryIds
|
|
2330
2397
|
} = options;
|
|
2398
|
+
const resolvedLimit = validateLimit(
|
|
2399
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2400
|
+
"products query"
|
|
2401
|
+
);
|
|
2331
2402
|
if (!client) {
|
|
2332
2403
|
log(logger, "warn", "[PerspectAPI] No client configured, using fallback products");
|
|
2333
2404
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -2336,7 +2407,7 @@ async function loadProducts(options) {
|
|
|
2336
2407
|
log(logger, "info", `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
2337
2408
|
const queryParams = {
|
|
2338
2409
|
isActive: true,
|
|
2339
|
-
limit,
|
|
2410
|
+
limit: resolvedLimit,
|
|
2340
2411
|
offset,
|
|
2341
2412
|
search
|
|
2342
2413
|
};
|
|
@@ -2396,13 +2467,20 @@ async function loadPages(options) {
|
|
|
2396
2467
|
}
|
|
2397
2468
|
try {
|
|
2398
2469
|
log(logger, "info", `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
2470
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2471
|
+
const resolvedLimit = validateLimit(
|
|
2472
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2473
|
+
"pages query"
|
|
2474
|
+
);
|
|
2475
|
+
const requestedPageType = params?.page_type;
|
|
2476
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "pages query") ?? "page";
|
|
2399
2477
|
const response = await client.content.getContent(
|
|
2400
2478
|
siteName,
|
|
2401
2479
|
{
|
|
2402
2480
|
...params,
|
|
2403
2481
|
page_status: params?.page_status ?? "publish",
|
|
2404
|
-
page_type:
|
|
2405
|
-
limit:
|
|
2482
|
+
page_type: validatedPageType,
|
|
2483
|
+
limit: resolvedLimit
|
|
2406
2484
|
}
|
|
2407
2485
|
);
|
|
2408
2486
|
if (!response.data) {
|
|
@@ -2422,13 +2500,20 @@ async function loadPosts(options) {
|
|
|
2422
2500
|
}
|
|
2423
2501
|
try {
|
|
2424
2502
|
log(logger, "info", `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
2503
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2504
|
+
const resolvedLimit = validateLimit(
|
|
2505
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2506
|
+
"posts query"
|
|
2507
|
+
);
|
|
2508
|
+
const requestedPageType = params?.page_type;
|
|
2509
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "posts query") ?? "post";
|
|
2425
2510
|
const response = await client.content.getContent(
|
|
2426
2511
|
siteName,
|
|
2427
2512
|
{
|
|
2428
2513
|
...params,
|
|
2429
2514
|
page_status: params?.page_status ?? "publish",
|
|
2430
|
-
page_type:
|
|
2431
|
-
limit:
|
|
2515
|
+
page_type: validatedPageType,
|
|
2516
|
+
limit: resolvedLimit
|
|
2432
2517
|
}
|
|
2433
2518
|
);
|
|
2434
2519
|
if (!response.data) {
|
|
@@ -2488,7 +2573,8 @@ async function createCheckoutSession(options) {
|
|
|
2488
2573
|
logger = noopLogger,
|
|
2489
2574
|
fallbackProducts,
|
|
2490
2575
|
metadata,
|
|
2491
|
-
priceIdResolver
|
|
2576
|
+
priceIdResolver,
|
|
2577
|
+
tax
|
|
2492
2578
|
} = options;
|
|
2493
2579
|
if (!client) {
|
|
2494
2580
|
log(logger, "error", "[PerspectAPI] Cannot create checkout session without SDK client");
|
|
@@ -2499,7 +2585,7 @@ async function createCheckoutSession(options) {
|
|
|
2499
2585
|
client,
|
|
2500
2586
|
siteName,
|
|
2501
2587
|
logger,
|
|
2502
|
-
limit:
|
|
2588
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
2503
2589
|
fallbackProducts
|
|
2504
2590
|
});
|
|
2505
2591
|
const productMap = new Map(
|
|
@@ -2519,6 +2605,18 @@ async function createCheckoutSession(options) {
|
|
|
2519
2605
|
quantity: item.quantity
|
|
2520
2606
|
};
|
|
2521
2607
|
});
|
|
2608
|
+
const resolvedTax = (() => {
|
|
2609
|
+
if (!tax && !customerEmail) {
|
|
2610
|
+
return void 0;
|
|
2611
|
+
}
|
|
2612
|
+
if (!tax) {
|
|
2613
|
+
return { customer_identifier: customerEmail };
|
|
2614
|
+
}
|
|
2615
|
+
return {
|
|
2616
|
+
...tax,
|
|
2617
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? void 0
|
|
2618
|
+
};
|
|
2619
|
+
})();
|
|
2522
2620
|
const checkoutData = {
|
|
2523
2621
|
line_items,
|
|
2524
2622
|
successUrl,
|
|
@@ -2530,6 +2628,9 @@ async function createCheckoutSession(options) {
|
|
|
2530
2628
|
mode: mode === "test" ? "payment" : "payment",
|
|
2531
2629
|
metadata
|
|
2532
2630
|
};
|
|
2631
|
+
if (resolvedTax) {
|
|
2632
|
+
checkoutData.tax = resolvedTax;
|
|
2633
|
+
}
|
|
2533
2634
|
log(logger, "info", "[PerspectAPI] Creating checkout session");
|
|
2534
2635
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
2535
2636
|
} catch (error) {
|
package/dist/index.mjs
CHANGED
|
@@ -668,6 +668,43 @@ var AuthClient = class extends BaseClient {
|
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
670
|
|
|
671
|
+
// src/utils/validators.ts
|
|
672
|
+
var MAX_API_QUERY_LIMIT = 100;
|
|
673
|
+
var ALLOWED_CONTENT_TYPES = ["post", "page"];
|
|
674
|
+
function validateLimit(limit, context) {
|
|
675
|
+
if (typeof limit !== "number" || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
676
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
677
|
+
}
|
|
678
|
+
if (limit < 1) {
|
|
679
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
680
|
+
}
|
|
681
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
return limit;
|
|
687
|
+
}
|
|
688
|
+
function validateOptionalLimit(limit, context) {
|
|
689
|
+
if (limit === void 0 || limit === null) {
|
|
690
|
+
return void 0;
|
|
691
|
+
}
|
|
692
|
+
return validateLimit(limit, context);
|
|
693
|
+
}
|
|
694
|
+
function validateOptionalContentType(pageType, context) {
|
|
695
|
+
if (pageType === void 0 || pageType === null) {
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType)) {
|
|
699
|
+
return pageType;
|
|
700
|
+
}
|
|
701
|
+
throw new Error(
|
|
702
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
703
|
+
", "
|
|
704
|
+
)}.`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
671
708
|
// src/client/content-client.ts
|
|
672
709
|
var ContentClient = class extends BaseClient {
|
|
673
710
|
constructor(http, cache) {
|
|
@@ -679,12 +716,33 @@ var ContentClient = class extends BaseClient {
|
|
|
679
716
|
async getContent(siteName, params, cachePolicy) {
|
|
680
717
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
681
718
|
const path = this.buildPath(endpoint);
|
|
719
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
720
|
+
if (normalizedParams) {
|
|
721
|
+
const validatedLimit = validateOptionalLimit(
|
|
722
|
+
normalizedParams.limit,
|
|
723
|
+
"content query"
|
|
724
|
+
);
|
|
725
|
+
if (validatedLimit !== void 0) {
|
|
726
|
+
normalizedParams.limit = validatedLimit;
|
|
727
|
+
} else {
|
|
728
|
+
delete normalizedParams.limit;
|
|
729
|
+
}
|
|
730
|
+
const validatedPageType = validateOptionalContentType(
|
|
731
|
+
normalizedParams.page_type,
|
|
732
|
+
"content query"
|
|
733
|
+
);
|
|
734
|
+
if (validatedPageType !== void 0) {
|
|
735
|
+
normalizedParams.page_type = validatedPageType;
|
|
736
|
+
} else {
|
|
737
|
+
delete normalizedParams.page_type;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
682
740
|
return this.fetchWithCache(
|
|
683
741
|
endpoint,
|
|
684
|
-
|
|
742
|
+
normalizedParams,
|
|
685
743
|
this.buildContentTags(siteName),
|
|
686
744
|
cachePolicy,
|
|
687
|
-
() => this.http.get(path,
|
|
745
|
+
() => this.http.get(path, normalizedParams)
|
|
688
746
|
);
|
|
689
747
|
}
|
|
690
748
|
/**
|
|
@@ -1046,6 +1104,15 @@ var ProductsClient = class extends BaseClient {
|
|
|
1046
1104
|
};
|
|
1047
1105
|
const normalizedParams = params ? { ...params } : void 0;
|
|
1048
1106
|
if (normalizedParams) {
|
|
1107
|
+
const validatedLimit = validateOptionalLimit(
|
|
1108
|
+
normalizedParams.limit,
|
|
1109
|
+
"products query"
|
|
1110
|
+
);
|
|
1111
|
+
if (validatedLimit !== void 0) {
|
|
1112
|
+
normalizedParams.limit = validatedLimit;
|
|
1113
|
+
} else {
|
|
1114
|
+
delete normalizedParams.limit;
|
|
1115
|
+
}
|
|
1049
1116
|
const normalizedCategories = normalizeList(normalizedParams.category);
|
|
1050
1117
|
if (normalizedCategories !== void 0) {
|
|
1051
1118
|
normalizedParams.category = normalizedCategories;
|
|
@@ -2261,12 +2328,16 @@ async function loadProducts(options) {
|
|
|
2261
2328
|
siteName,
|
|
2262
2329
|
logger = noopLogger,
|
|
2263
2330
|
fallbackProducts,
|
|
2264
|
-
limit
|
|
2331
|
+
limit: requestedLimit,
|
|
2265
2332
|
offset,
|
|
2266
2333
|
search,
|
|
2267
2334
|
category,
|
|
2268
2335
|
categoryIds
|
|
2269
2336
|
} = options;
|
|
2337
|
+
const resolvedLimit = validateLimit(
|
|
2338
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2339
|
+
"products query"
|
|
2340
|
+
);
|
|
2270
2341
|
if (!client) {
|
|
2271
2342
|
log(logger, "warn", "[PerspectAPI] No client configured, using fallback products");
|
|
2272
2343
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -2275,7 +2346,7 @@ async function loadProducts(options) {
|
|
|
2275
2346
|
log(logger, "info", `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
2276
2347
|
const queryParams = {
|
|
2277
2348
|
isActive: true,
|
|
2278
|
-
limit,
|
|
2349
|
+
limit: resolvedLimit,
|
|
2279
2350
|
offset,
|
|
2280
2351
|
search
|
|
2281
2352
|
};
|
|
@@ -2335,13 +2406,20 @@ async function loadPages(options) {
|
|
|
2335
2406
|
}
|
|
2336
2407
|
try {
|
|
2337
2408
|
log(logger, "info", `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
2409
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2410
|
+
const resolvedLimit = validateLimit(
|
|
2411
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2412
|
+
"pages query"
|
|
2413
|
+
);
|
|
2414
|
+
const requestedPageType = params?.page_type;
|
|
2415
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "pages query") ?? "page";
|
|
2338
2416
|
const response = await client.content.getContent(
|
|
2339
2417
|
siteName,
|
|
2340
2418
|
{
|
|
2341
2419
|
...params,
|
|
2342
2420
|
page_status: params?.page_status ?? "publish",
|
|
2343
|
-
page_type:
|
|
2344
|
-
limit:
|
|
2421
|
+
page_type: validatedPageType,
|
|
2422
|
+
limit: resolvedLimit
|
|
2345
2423
|
}
|
|
2346
2424
|
);
|
|
2347
2425
|
if (!response.data) {
|
|
@@ -2361,13 +2439,20 @@ async function loadPosts(options) {
|
|
|
2361
2439
|
}
|
|
2362
2440
|
try {
|
|
2363
2441
|
log(logger, "info", `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
2442
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2443
|
+
const resolvedLimit = validateLimit(
|
|
2444
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2445
|
+
"posts query"
|
|
2446
|
+
);
|
|
2447
|
+
const requestedPageType = params?.page_type;
|
|
2448
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "posts query") ?? "post";
|
|
2364
2449
|
const response = await client.content.getContent(
|
|
2365
2450
|
siteName,
|
|
2366
2451
|
{
|
|
2367
2452
|
...params,
|
|
2368
2453
|
page_status: params?.page_status ?? "publish",
|
|
2369
|
-
page_type:
|
|
2370
|
-
limit:
|
|
2454
|
+
page_type: validatedPageType,
|
|
2455
|
+
limit: resolvedLimit
|
|
2371
2456
|
}
|
|
2372
2457
|
);
|
|
2373
2458
|
if (!response.data) {
|
|
@@ -2427,7 +2512,8 @@ async function createCheckoutSession(options) {
|
|
|
2427
2512
|
logger = noopLogger,
|
|
2428
2513
|
fallbackProducts,
|
|
2429
2514
|
metadata,
|
|
2430
|
-
priceIdResolver
|
|
2515
|
+
priceIdResolver,
|
|
2516
|
+
tax
|
|
2431
2517
|
} = options;
|
|
2432
2518
|
if (!client) {
|
|
2433
2519
|
log(logger, "error", "[PerspectAPI] Cannot create checkout session without SDK client");
|
|
@@ -2438,7 +2524,7 @@ async function createCheckoutSession(options) {
|
|
|
2438
2524
|
client,
|
|
2439
2525
|
siteName,
|
|
2440
2526
|
logger,
|
|
2441
|
-
limit:
|
|
2527
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
2442
2528
|
fallbackProducts
|
|
2443
2529
|
});
|
|
2444
2530
|
const productMap = new Map(
|
|
@@ -2458,6 +2544,18 @@ async function createCheckoutSession(options) {
|
|
|
2458
2544
|
quantity: item.quantity
|
|
2459
2545
|
};
|
|
2460
2546
|
});
|
|
2547
|
+
const resolvedTax = (() => {
|
|
2548
|
+
if (!tax && !customerEmail) {
|
|
2549
|
+
return void 0;
|
|
2550
|
+
}
|
|
2551
|
+
if (!tax) {
|
|
2552
|
+
return { customer_identifier: customerEmail };
|
|
2553
|
+
}
|
|
2554
|
+
return {
|
|
2555
|
+
...tax,
|
|
2556
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? void 0
|
|
2557
|
+
};
|
|
2558
|
+
})();
|
|
2461
2559
|
const checkoutData = {
|
|
2462
2560
|
line_items,
|
|
2463
2561
|
successUrl,
|
|
@@ -2469,6 +2567,9 @@ async function createCheckoutSession(options) {
|
|
|
2469
2567
|
mode: mode === "test" ? "payment" : "payment",
|
|
2470
2568
|
metadata
|
|
2471
2569
|
};
|
|
2570
|
+
if (resolvedTax) {
|
|
2571
|
+
checkoutData.tax = resolvedTax;
|
|
2572
|
+
}
|
|
2472
2573
|
log(logger, "info", "[PerspectAPI] Creating checkout session");
|
|
2473
2574
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
2474
2575
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
PaginatedResponse,
|
|
14
14
|
ApiResponse,
|
|
15
15
|
} from '../types';
|
|
16
|
+
import { validateOptionalContentType, validateOptionalLimit } from '../utils/validators';
|
|
16
17
|
|
|
17
18
|
export class ContentClient extends BaseClient {
|
|
18
19
|
constructor(http: any, cache?: CacheManager) {
|
|
@@ -29,13 +30,38 @@ export class ContentClient extends BaseClient {
|
|
|
29
30
|
): Promise<PaginatedResponse<Content>> {
|
|
30
31
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
31
32
|
const path = this.buildPath(endpoint);
|
|
33
|
+
const normalizedParams: ContentQueryParams | undefined = params
|
|
34
|
+
? { ...params }
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
if (normalizedParams) {
|
|
38
|
+
const validatedLimit = validateOptionalLimit(
|
|
39
|
+
normalizedParams.limit,
|
|
40
|
+
'content query',
|
|
41
|
+
);
|
|
42
|
+
if (validatedLimit !== undefined) {
|
|
43
|
+
normalizedParams.limit = validatedLimit;
|
|
44
|
+
} else {
|
|
45
|
+
delete normalizedParams.limit;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const validatedPageType = validateOptionalContentType(
|
|
49
|
+
normalizedParams.page_type,
|
|
50
|
+
'content query',
|
|
51
|
+
);
|
|
52
|
+
if (validatedPageType !== undefined) {
|
|
53
|
+
normalizedParams.page_type = validatedPageType;
|
|
54
|
+
} else {
|
|
55
|
+
delete normalizedParams.page_type;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
32
58
|
|
|
33
59
|
return this.fetchWithCache<PaginatedResponse<Content>>(
|
|
34
60
|
endpoint,
|
|
35
|
-
|
|
61
|
+
normalizedParams,
|
|
36
62
|
this.buildContentTags(siteName),
|
|
37
63
|
cachePolicy,
|
|
38
|
-
() => this.http.get(path,
|
|
64
|
+
() => this.http.get(path, normalizedParams) as Promise<PaginatedResponse<Content>>
|
|
39
65
|
);
|
|
40
66
|
}
|
|
41
67
|
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ApiResponse,
|
|
13
13
|
ProductQueryParams,
|
|
14
14
|
} from '../types';
|
|
15
|
+
import { validateOptionalLimit } from '../utils/validators';
|
|
15
16
|
|
|
16
17
|
export class ProductsClient extends BaseClient {
|
|
17
18
|
constructor(http: any, cache?: CacheManager) {
|
|
@@ -41,6 +42,16 @@ export class ProductsClient extends BaseClient {
|
|
|
41
42
|
const normalizedParams: ProductQueryParams | undefined = params ? { ...params } : undefined;
|
|
42
43
|
|
|
43
44
|
if (normalizedParams) {
|
|
45
|
+
const validatedLimit = validateOptionalLimit(
|
|
46
|
+
normalizedParams.limit,
|
|
47
|
+
'products query',
|
|
48
|
+
);
|
|
49
|
+
if (validatedLimit !== undefined) {
|
|
50
|
+
normalizedParams.limit = validatedLimit;
|
|
51
|
+
} else {
|
|
52
|
+
delete normalizedParams.limit;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
const normalizedCategories = normalizeList(normalizedParams.category as any);
|
|
45
56
|
if (normalizedCategories !== undefined) {
|
|
46
57
|
normalizedParams.category = normalizedCategories;
|
package/src/loaders.ts
CHANGED
|
@@ -17,8 +17,14 @@ import type {
|
|
|
17
17
|
BlogPost,
|
|
18
18
|
CreateCheckoutSessionRequest,
|
|
19
19
|
CheckoutSession,
|
|
20
|
-
CheckoutMetadata
|
|
20
|
+
CheckoutMetadata,
|
|
21
|
+
CheckoutTaxRequest
|
|
21
22
|
} from './types';
|
|
23
|
+
import {
|
|
24
|
+
MAX_API_QUERY_LIMIT,
|
|
25
|
+
validateLimit,
|
|
26
|
+
validateOptionalContentType
|
|
27
|
+
} from './utils/validators';
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* Logger interface so consumers can supply custom logging behaviour (or noop).
|
|
@@ -311,13 +317,18 @@ export async function loadProducts(options: LoadProductsOptions): Promise<Produc
|
|
|
311
317
|
siteName,
|
|
312
318
|
logger = noopLogger,
|
|
313
319
|
fallbackProducts,
|
|
314
|
-
limit
|
|
320
|
+
limit: requestedLimit,
|
|
315
321
|
offset,
|
|
316
322
|
search,
|
|
317
323
|
category,
|
|
318
324
|
categoryIds
|
|
319
325
|
} = options;
|
|
320
326
|
|
|
327
|
+
const resolvedLimit = validateLimit(
|
|
328
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
329
|
+
'products query',
|
|
330
|
+
);
|
|
331
|
+
|
|
321
332
|
if (!client) {
|
|
322
333
|
log(logger, 'warn', '[PerspectAPI] No client configured, using fallback products');
|
|
323
334
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -327,7 +338,7 @@ export async function loadProducts(options: LoadProductsOptions): Promise<Produc
|
|
|
327
338
|
log(logger, 'info', `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
328
339
|
const queryParams: ProductQueryParams = {
|
|
329
340
|
isActive: true,
|
|
330
|
-
limit,
|
|
341
|
+
limit: resolvedLimit,
|
|
331
342
|
offset,
|
|
332
343
|
search
|
|
333
344
|
};
|
|
@@ -427,13 +438,23 @@ export async function loadPages(
|
|
|
427
438
|
|
|
428
439
|
try {
|
|
429
440
|
log(logger, 'info', `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
441
|
+
const requestedLimit =
|
|
442
|
+
options.limit !== undefined ? options.limit : params?.limit;
|
|
443
|
+
const resolvedLimit = validateLimit(
|
|
444
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
445
|
+
'pages query',
|
|
446
|
+
);
|
|
447
|
+
const requestedPageType = params?.page_type;
|
|
448
|
+
const validatedPageType =
|
|
449
|
+
validateOptionalContentType(requestedPageType, 'pages query') ?? 'page';
|
|
450
|
+
|
|
430
451
|
const response: PaginatedResponse<Content> = await client.content.getContent(
|
|
431
452
|
siteName,
|
|
432
453
|
{
|
|
433
454
|
...params,
|
|
434
455
|
page_status: (params?.page_status ?? 'publish') as ContentStatus,
|
|
435
|
-
page_type:
|
|
436
|
-
limit:
|
|
456
|
+
page_type: validatedPageType as ContentType,
|
|
457
|
+
limit: resolvedLimit
|
|
437
458
|
}
|
|
438
459
|
);
|
|
439
460
|
|
|
@@ -463,13 +484,23 @@ export async function loadPosts(
|
|
|
463
484
|
|
|
464
485
|
try {
|
|
465
486
|
log(logger, 'info', `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
487
|
+
const requestedLimit =
|
|
488
|
+
options.limit !== undefined ? options.limit : params?.limit;
|
|
489
|
+
const resolvedLimit = validateLimit(
|
|
490
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
491
|
+
'posts query',
|
|
492
|
+
);
|
|
493
|
+
const requestedPageType = params?.page_type;
|
|
494
|
+
const validatedPageType =
|
|
495
|
+
validateOptionalContentType(requestedPageType, 'posts query') ?? 'post';
|
|
496
|
+
|
|
466
497
|
const response: PaginatedResponse<Content> = await client.content.getContent(
|
|
467
498
|
siteName,
|
|
468
499
|
{
|
|
469
500
|
...params,
|
|
470
501
|
page_status: (params?.page_status ?? 'publish') as ContentStatus,
|
|
471
|
-
page_type:
|
|
472
|
-
limit:
|
|
502
|
+
page_type: validatedPageType as ContentType,
|
|
503
|
+
limit: resolvedLimit
|
|
473
504
|
}
|
|
474
505
|
);
|
|
475
506
|
|
|
@@ -558,6 +589,10 @@ export interface CheckoutSessionOptions {
|
|
|
558
589
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
559
590
|
*/
|
|
560
591
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
592
|
+
/**
|
|
593
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
594
|
+
*/
|
|
595
|
+
tax?: CheckoutTaxRequest;
|
|
561
596
|
}
|
|
562
597
|
|
|
563
598
|
/**
|
|
@@ -578,7 +613,8 @@ export async function createCheckoutSession(
|
|
|
578
613
|
logger = noopLogger,
|
|
579
614
|
fallbackProducts,
|
|
580
615
|
metadata,
|
|
581
|
-
priceIdResolver
|
|
616
|
+
priceIdResolver,
|
|
617
|
+
tax
|
|
582
618
|
} = options;
|
|
583
619
|
|
|
584
620
|
if (!client) {
|
|
@@ -592,7 +628,7 @@ export async function createCheckoutSession(
|
|
|
592
628
|
client,
|
|
593
629
|
siteName,
|
|
594
630
|
logger,
|
|
595
|
-
limit:
|
|
631
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
596
632
|
fallbackProducts
|
|
597
633
|
});
|
|
598
634
|
|
|
@@ -623,6 +659,21 @@ export async function createCheckoutSession(
|
|
|
623
659
|
};
|
|
624
660
|
});
|
|
625
661
|
|
|
662
|
+
const resolvedTax: CheckoutTaxRequest | undefined = (() => {
|
|
663
|
+
if (!tax && !customerEmail) {
|
|
664
|
+
return undefined;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (!tax) {
|
|
668
|
+
return { customer_identifier: customerEmail };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return {
|
|
672
|
+
...tax,
|
|
673
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? undefined,
|
|
674
|
+
};
|
|
675
|
+
})();
|
|
676
|
+
|
|
626
677
|
const checkoutData: CreateCheckoutSessionRequest = {
|
|
627
678
|
line_items,
|
|
628
679
|
successUrl,
|
|
@@ -632,9 +683,13 @@ export async function createCheckoutSession(
|
|
|
632
683
|
customerEmail,
|
|
633
684
|
customer_email: customerEmail,
|
|
634
685
|
mode: mode === 'test' ? 'payment' : 'payment',
|
|
635
|
-
metadata
|
|
686
|
+
metadata,
|
|
636
687
|
};
|
|
637
688
|
|
|
689
|
+
if (resolvedTax) {
|
|
690
|
+
checkoutData.tax = resolvedTax;
|
|
691
|
+
}
|
|
692
|
+
|
|
638
693
|
log(logger, 'info', '[PerspectAPI] Creating checkout session');
|
|
639
694
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
640
695
|
} catch (error) {
|
package/src/types/index.ts
CHANGED
|
@@ -399,6 +399,45 @@ export type CheckoutMetadataValue =
|
|
|
399
399
|
|
|
400
400
|
export type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
401
401
|
|
|
402
|
+
export type CheckoutTaxStrategy =
|
|
403
|
+
| 'disabled'
|
|
404
|
+
| 'gateway_auto'
|
|
405
|
+
| 'manual_rates'
|
|
406
|
+
| 'external_service';
|
|
407
|
+
|
|
408
|
+
export type CheckoutTaxExemptionStatus =
|
|
409
|
+
| 'none'
|
|
410
|
+
| 'exempt'
|
|
411
|
+
| 'reverse_charge';
|
|
412
|
+
|
|
413
|
+
export interface CheckoutTaxCustomerExemptionRequest {
|
|
414
|
+
status?: CheckoutTaxExemptionStatus;
|
|
415
|
+
reason?: string;
|
|
416
|
+
tax_id?: string;
|
|
417
|
+
tax_id_type?: string;
|
|
418
|
+
certificate_url?: string;
|
|
419
|
+
metadata?: Record<string, any>;
|
|
420
|
+
expires_at?: string;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export interface CheckoutTaxRequest {
|
|
424
|
+
strategy?: CheckoutTaxStrategy;
|
|
425
|
+
customer_identifier?: string;
|
|
426
|
+
customer_profile_id?: string;
|
|
427
|
+
customer_display_name?: string;
|
|
428
|
+
allow_exemption?: boolean;
|
|
429
|
+
require_tax_id?: boolean;
|
|
430
|
+
save_profile?: boolean;
|
|
431
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
432
|
+
manual_rate_percent?: number;
|
|
433
|
+
manual_rate_map?: Record<string, number>;
|
|
434
|
+
external_service?: {
|
|
435
|
+
provider: string;
|
|
436
|
+
config?: Record<string, any>;
|
|
437
|
+
};
|
|
438
|
+
metadata?: Record<string, any>;
|
|
439
|
+
}
|
|
440
|
+
|
|
402
441
|
export interface CreateCheckoutSessionRequest {
|
|
403
442
|
// Single item checkout (legacy/simple)
|
|
404
443
|
priceId?: string;
|
|
@@ -437,6 +476,7 @@ export interface CreateCheckoutSessionRequest {
|
|
|
437
476
|
allowed_countries: string[];
|
|
438
477
|
};
|
|
439
478
|
billing_address_collection?: 'auto' | 'required';
|
|
479
|
+
tax?: CheckoutTaxRequest;
|
|
440
480
|
}
|
|
441
481
|
|
|
442
482
|
export interface CheckoutSession {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ContentType } from '../types';
|
|
2
|
+
|
|
3
|
+
export const MAX_API_QUERY_LIMIT = 100;
|
|
4
|
+
const ALLOWED_CONTENT_TYPES: ReadonlyArray<ContentType> = ['post', 'page'];
|
|
5
|
+
|
|
6
|
+
export function validateLimit(limit: number, context: string): number {
|
|
7
|
+
if (typeof limit !== 'number' || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
8
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (limit < 1) {
|
|
12
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return limit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function validateOptionalLimit(
|
|
25
|
+
limit: number | undefined,
|
|
26
|
+
context: string,
|
|
27
|
+
): number | undefined {
|
|
28
|
+
if (limit === undefined || limit === null) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return validateLimit(limit, context);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateOptionalContentType(
|
|
36
|
+
pageType: string | undefined,
|
|
37
|
+
context: string,
|
|
38
|
+
): ContentType | undefined {
|
|
39
|
+
if (pageType === undefined || pageType === null) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType as ContentType)) {
|
|
44
|
+
return pageType as ContentType;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(
|
|
48
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
49
|
+
', ',
|
|
50
|
+
)}.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|