@liquidcommerce/cloud-sdk
v1.18.0
Published
LiquidCommerce Cloud SDK
Readme
LiquidCommerce Cloud SDK
The LiquidCommerce Cloud SDK provides an easy way to interact with our APIs through a server-side SDK for Node.js and Web JS script.
Table of Contents
Installation
Install the package with:
npm install @liquidcommerce/cloud-sdk
# or
yarn add @liquidcommerce/cloud-sdk
# or
pnpm add @liquidcommerce/cloud-sdkConfiguration
API Key Authentication
The LiquidCommerce API Key Authentication provides a secure method to obtain an access token for all other API calls to LiquidCommerce Services.
Example using LiquidCommerce client:
// The SDK automatically handles authentication
const client = await LiquidCommerce('YOUR_LIQUIDCOMMERCE_API_KEY', {
googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY', // Required for address services
env: LIQUID_COMMERCE_ENV.STAGE, // or PROD
});Click Here For Manual Authentication
Order API Authentication
LiquidCommerce provides a separate authentication mechanism for Order API endpoints. The Order client uses Basic Authentication with a username and password.
Example using LiquidCommerceOrders client:
const orderClient = await LiquidCommerceOrders({
userID: 'YOUR_ORDER_API_USER_ID',
password: 'YOUR_ORDER_API_PASSWORD',
env: LIQUID_COMMERCE_ENV.STAGE, // or PROD
});Click Here For Manual Authentication
Note: Order authentication credentials are required to access Order API. The SDK will return appropriate authentication errors if these credentials are missing or invalid.
Response Types
All API responses follow a consistent structure:
interface ApiResponse<T> {
statusCode: number;
message: string;
metadata: {
languages: string[];
timestamp: number;
timezone: string;
requestId: string;
path: string;
version: string;
};
data?: T; // Present in responses with data
}Services and Usage
Auth
While the SDK handles authentication automatically, you can also manually retrieve the authentication details:
// Manually retrieve authentication details
try {
const authDetails = await client.auth();
console.log('Access Token:', authDetails.token);
console.log('Expires In:', authDetails.exp);
} catch (error) {
console.error('Failed to get auth details:', error);
}Address
Services for address validation and lookup:
// Address autocompletion
const autocompleteResponse = await client.address.autocomplete({
input: '100 Madison Ave, New York',
});
// Response type: IApiResponseWithData<IAddressAutocompleteResult[]>
// {
// id: string;
// description: string;
// }
// Get detailed address information
const detailsResponse = await client.address.details({
id: 'ChIJd8BlQ2BZwokRjMKtTjMezRw',
});
// Response type: IApiResponseWithData<IAddressDetailsResult>
// {
// formattedAddress: string;
// coords: {
// lat: number;
// long: number;
// }
// address: {
// one: string,
// two: string,
// city": string,
// state: string,
// zip: string,
// country: "US"
// }
// }Catalog
Product catalog search and availability services:
// Check product availability
const availabilityResponse = await client.catalog.availability({
upcs: ['123456789012', '210987654321'], // UPC codes
grouping: ['group1', 'group2'], // Optional group identifiers
ids: ['id1', 'id2'], // Optional product IDs
loc: {
address: {
one: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001',
},
},
shouldShowOffHours: true,
});
// Search catalog with filters
const searchResponse = await client.catalog.search({
search: 'whiskey',
// Requires a complete loc.address. When false or omitted, legacy ordering is used.
deliveryFirst: true,
pageToken: '',
entity: '',
page: 1,
perPage: 20,
orderBy: ENUM_ORDER_BY.PRICE,
orderDirection: ENUM_NAVIGATION_ORDER_DIRECTION_TYPE.ASC,
filters: [
{
key: ENUM_FILTER_KEYS.CATEGORIES,
values: [ENUM_SPIRITS.WHISKEY],
},
{
key: ENUM_FILTER_KEYS.PRICE,
values: { min: 2000, max: 10000 }, // Prices in cents
},
{
key: ENUM_FILTER_KEYS.AVAILABILITY,
values: ENUM_AVAILABILITY_VALUE.IN_STOCK,
},
],
loc: {
address: {
one: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US',
},
},
});
// Search-as-you-type suggestions (cheap per-keystroke endpoint; no facets,
// availability or price — only the final token is prefix-matched)
const suggestions = await client.catalog.autocomplete({
term: 'hendricks oasi',
limit: 8, // 1–25, defaults to 10
});
// suggestions.data: [{ itemType: 'catalog', grouping: '...', name: "Hendrick's Oasium Gin", image?: '...' }]
// Enumerate every product in your own catalog — for building a sitemap.
// The partner comes from the API key, so there is no partner argument.
for await (const product of client.catalog.iterateProducts()) {
console.log(product.grouping, product.upc); // upc is zero-padded: use it verbatim
}
// Or page manually. Terminate on `nextCursor`, NOT on an empty `items`:
// products are dropped after a page is read, so a page can be empty and
// still have successors — stopping there truncates the enumeration silently.
// Page manually when you need to resume: `iterateProducts` keeps the cursor
// to itself, so a page that fails mid-walk restarts the whole enumeration.
let cursor: string | undefined;
do {
const page = await client.catalog.listProducts({ pageSize: 1000, cursor });
for (const { grouping, upc } of page.data.items) {
console.log(grouping, upc);
}
// counts: { inScope, emitted, droppedNoUpc, droppedUnresolvable }
// `inScope - emitted` is how many of your products are currently unlinkable.
console.log(page.data.counts);
cursor = page.data.nextCursor;
} while (cursor);The enumeration is availability-blind by design: it lists products whose page exists, not products purchasable right now. Products whose barcode does not resolve are dropped rather than published as dead URLs, so the total can fall short of your assignment count —
countsreports how many and why.
Optional catalog composition capabilities
The SDK client provides catalog.compose() and catalog.createLocationContext().
compose() assembles product sections for homepages, category pages, or other
discovery experiences through POST /catalog/compose. Cloud shares location
resolution, availability, and offer selection across all sections in the request.
These members are optional in ICatalogMethod so existing custom catalog/client
implementations and typed mocks remain assignable without adding methods. Callers
using these capabilities must check for them first:
if (typeof client.catalog.compose !== 'function') {
throw new Error('This client does not support catalog composition');
}
const composition = await client.catalog.compose({
sections: [{ sectionId: 'whiskey', search: 'whiskey', perPage: 12 }],
});Check createLocationContext the same way before calling it. Both methods require
the corresponding Cloud endpoints to be deployed. Existing catalog calls and the
LiquidCommerce() factory signature are unchanged; upgrading alone does not enable
catalog composition.
Cart
Shopping cart management:
// Create new cart
const newCart = await client.cart.get();
// Retrieve existing cart
const existingCart = await client.cart.get('cart_id', true); // Second parameter for refresh
// Update cart
const updatedCart = await client.cart.update({
id: 'cart_id',
items: [
{
partNumber: '123456789012_retailer_id', // Required: {UPC}_{retailerId}
quantity: 2,
fulfillmentId: 'fulfillment_id',
engravingLines: ['Line 1', 'Line 2'], // Optional
scheduledFor: '2024-12-25', // Optional
},
],
loc: {
address: {
one: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001',
},
},
promoCode: 'DISCOUNT10', // Optional
giftCards: ['GC123456'], // Optional
});User
User profile and preferences management:
// Create/update user session
const userSession = await client.user.session({
email: '[email protected]',
firstName: 'John',
lastName: 'Smith',
phone: '2125551234',
company: 'Company Inc',
profileImage: 'https://...',
birthDate: '1990-01-01',
id: 'user_id', // Existing user identifier (for updates only), email becomes optional
});
// Fetch user by ID or email
const userData = await client.user.fetch('user_id_or_email');
// Address management
const newAddress = await client.user.addAddress({
userId: 'user_id',
placesId: 'google_places_id', // Optional if providing address details
one: '100 Madison St',
two: 'Apt 4B',
city: 'New York',
state: 'NY',
zip: '10004',
country: 'US',
lat: 40.7128, // Optional
long: -74.006, // Optional
type: ENUM_ADDRESS_TYPE.SHIPPING,
isDefault: true,
});
const updatedAddress = await client.user.updateAddress({
// Same parameters as addAddress
});
// Payment methods
const newPayment = await client.user.addPayment({
customerId: 'customer_id',
paymentMethodId: 'payment_method_id',
isDefault: true,
});
const updatedPayment = await client.user.updatePayment({
customerId: 'customer_id',
paymentMethodId: 'payment_method_id',
isDefault: true, // Required for updates
});
// Data removal
await client.user.purge('user_id_or_email');
await client.user.purgeAddress('address_id');
await client.user.purgePayment('customer_id', 'payment_id');Payment Element
The Payment Element is a secure and modern UI component for collecting payment details. It simplifies PCI compliance by handling all sensitive information within an iframe.
Prerequisites
Before mounting the Payment Element, you must create a payment session. This can be tied to a user, cart, or checkout.
// Create a payment session to get a client secret
const paymentSession = await client.user.paymentSession({
// Optionally link to a cart, checkout, or customer
// cartId: 'your_cart_id',
// checkoutToken: 'your_checkout_token',
// customerId: 'your_customer_id',
// customerEmail: 'your_customer_id',
});
const { key, secret } = paymentSession.data.session;Integration
Initialize and Mount
The
LiquidCommercePaymentElementfunction creates and manages the UI component.// Initialize the payment element const paymentElement = LiquidCommercePaymentElement({ session: { key, // Public key from payment session secret, // Client secret from payment session }, }); // Mount the element to a container in your DOM await paymentElement.mount({ elementId: 'payment-element-container', appearance: { theme: 'night', // 'stripe' | 'night' | 'flat' }, elementOptions: { layout: 'tabs', // 'tabs' | 'accordion' | 'auto' }, });Generate a Confirmation Token
Once the user has filled out the payment form, create a confirmation token. This token securely represents the payment details.
const result = await paymentElement.createConfirmationToken(); if (result.token) { // Token successfully created const confirmationToken = result.token; // Use this token to complete the checkout or save the payment method } else { // Handle error console.error(result.message); }Confirm the Payment Session
Use the confirmation token to finalize the payment and retrieve the payment method details.
const confirmation = await client.user.confirmPaymentSession(confirmationToken); if (confirmation.data) { const paymentMethod = confirmation.data; // Now you have the payment method ID, card details, etc. // e.g., paymentMethod.id, paymentMethod.card.brand }Lifecycle Management
Properly manage the element's lifecycle to ensure a smooth user experience and resource cleanup.
// Listen to events paymentElement.subscribe('ready', () => { console.log('Payment element is ready.'); }); paymentElement.subscribe('change', (event) => { // Handle form state changes (e.g., enable/disable submit button) }); // Clean up when the element is no longer needed paymentElement.unmount(); paymentElement.destroy();
Legacy Payment
The legacy payment system uses a previous version of our payment integration. For new integrations, we strongly recommend using the Payment Element for a more secure and flexible solution.
Prerequisites
- User Session Creation:
// First create or get a user session
const userSession = await client.user.session({
email: '[email protected]',
// ... other user details
});
// The session response includes necessary payment credentials
const { setupIntent, publicKey } = userSession.data.session;Payment Element Integration
// Initialize payment form using session credentials
await client.payment.mount({
clientSecret: userSession.data.session.setupIntent, // Required: from session
key: userSession.data.session.publicKey, // Required: from session
elementId: 'payment-element-container', // Your DOM element ID
appearance: {
theme: 'night', // 'stripe' | 'night' | 'flat'
},
elementOptions: {
layout: 'tabs', // 'tabs' | 'accordion' | 'auto'
},
});
// Monitor payment element state
client.payment.subscribe('ready', () => {
// Element is ready to accept input
});
client.payment.subscribe('change', (event) => {
const { complete, empty, value } = event;
// Handle validation state changes
});
// Process payment when ready
const tokenResult = await client.payment.generateToken();
// Handle the result
if ('error' in tokenResult) {
const { type, message, code } = tokenResult.error;
// type can be: 'validation_error' | 'api_error' | 'client_error' | 'confirm_error'
} else {
// Use tokenResult.id for checkout completion or saving payment method
const { id, card } = tokenResult;
}
// Always clean up when done
client.payment.unmount();
client.payment.destroy();Security Considerations
PCI Compliance: The payment element handles card data securely within an iframe, ensuring your application never directly touches sensitive payment information.
Token-Based: All payment data is tokenized - you only receive secure tokens that can't be used to retrieve the original card details.
Single Use: Payment tokens are single-use and expire after a short time period.
Domain Validation: Payment elements will only work on domains that have been pre-registered with your account.
Best Practices
- Error Handling: Always implement proper error handling:
try {
const token = await client.payment.generateToken();
if ('error' in token) {
switch (token.error.type) {
case 'validation_error':
// Handle invalid card data
break;
case 'api_error':
// Handle API/network issues
break;
case 'client_error':
// Handle setup/configuration issues
break;
case 'confirm_error':
// Handle payment confirmation failures
break;
}
}
} catch (error) {
// Handle unexpected errors
}- Cleanup: Always clean up payment elements when done:
- When navigation away from payment page
- After successful payment
- After failed payment attempt
- Before unmounting payment component
- Event Handling: Monitor element state for better user experience:
client.payment.subscribe('change', (event) => {
// Update UI based on validation state
const { complete, empty } = event;
submitButton.disabled = !complete || empty;
});
client.payment.subscribe('loaderror', (event) => {
// Handle element loading failures
console.error('Payment element failed:', event.error);
});Responsive Design
The payment element automatically adapts to:
- Mobile and desktop viewports
- Right-to-left languages
- Dark/light themes
- Different container sizes
Testing Cards
When testing payments in staging environment, use these test cards:
// Test Visa Card
Card Number: 4242 4242 4242 4242
Expiry: Any future date
CVC: Any 3 digits
ZIP: Any 5 digits
// Test Mastercard
Card Number: 5555 5555 5555 4444
Expiry: Any future date
CVC: Any 3 digits
ZIP: Any 5 digits
// Example test card usage:
/*
Card: 4242 4242 4242 4242
Expiry: 12/29
CVC: 123
ZIP: 10001
*/These cards will be accepted in test mode and will simulate successful payments. They should only be used in the staging environment, never in production.
Important Notes:
- These cards work only in test/staging environment
- Real cards will be declined in test mode
- Test cards will be declined in production
- All test transactions use simulated funds
- Use test credentials in staging environment
- Never use production credentials in development
- Test all error scenarios
- Verify proper cleanup implementation
- Test on multiple devices and browsers
Checkout
Checkout process management:
// Prepare checkout
const preparedCheckout = await client.checkout.prepare({
cartId: 'cart_id',
customer: {
id: 'customer_id', // Optional
email: '[email protected]',
firstName: 'John',
lastName: 'Smith',
phone: '2125551234',
birthDate: '1990-01-01',
},
hasAgeVerify: true,
billingAddress: {
firstName: 'John',
lastName: 'Smith',
email: '[email protected]',
phone: '2125551234',
one: '123 Main St',
two: 'Apt 4B',
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US',
},
hasSubstitutionPolicy: true,
isGift: true,
billingSameAsShipping: false,
giftOptions: {
message: 'Happy Birthday!',
recipient: {
name: 'Jane Smith',
email: '[email protected]',
phone: '2125555678',
},
},
marketingPreferences: {
canEmail: true,
canSms: true,
},
deliveryTips: [
{
fulfillmentId: 'fulfillment_id',
tip: 500, // Amount in cents
},
],
deliveryInstructions: [
{
fulfillmentId: 'fulfillment_id',
instructions: "", // 250 Max characters
},
],
acceptedAccountCreation: true,
scheduledDelivery: '2024-12-25T14:00:00Z',
promoCode: 'DISCOUNT10', // Optional
giftCards: ['GC123456'], // Optional
});
// Complete checkout
const completedCheckout = await client.checkout.complete({
token: preparedCheckout.token,
payment: 'payment_id',
});Checkout Payment
For direct checkout payments, the flow is similar but uses the checkout session:
// 1. First prepare the checkout
const preparedCheckout = await client.checkout.prepare({
cartId: 'cart_id',
// ... other checkout details
});
// 2. Initialize payment element with checkout session
const paymentElement = LiquidCommercePaymentElement({
session: {
key: preparedCheckout.data.payment.publicKey, // From checkout prepare response
secret: preparedCheckout.data.payment.clientSecret, // From checkout prepare response
}
});
// 3. Mount the element
await paymentElement.mount({
elementId: 'payment-element-container',
appearance: { theme: 'night' },
elementOptions: { layout: 'tabs' },
});
// 4. Handle payment element events and create confirmation token
const result = await paymentElement.createConfirmationToken();
if (result.token) {
// 5. Confirm the payment collected with the confirmation token
const confirmation = await client.user.confirmPaymentSession(confirmationToken);
if (confirmation?.data?.id) {
// 6. Complete checkout with the confirmation token
const completedCheckout = await client.checkout.complete({
token: preparedCheckout.data.token,
payment: confirmation?.data?.id,
});
}
}
// 7. Clean up
paymentElement.unmount();
paymentElement.destroy();Orders
Provides secure access to order data throughout the finalized states of the order lifecycle within the LiquidCommerce ecosystem.
const orderClient = await LiquidCommerceOrders({
userID: 'YOUR_ORDER_API_USER_ID',
password: 'YOUR_ORDER_API_PASSWORD',
env: LIQUID_COMMERCE_ENV.STAGE, // or PROD
});
// Fetch order details by ID or number
const orderResponse = await orderClient.order.fetch(/* reference id or order number */);Click here to access the docs for the order response structure
Webhook
Webhook test services:
// Test webhook endpoint
const webhookTestResult = await client.webhook.test(/* endpoint */);
// Response is a simple boolean indicating success or failure
// true = webhook test was successful
// false = webhook test failedError Handling
The SDK throws errors for various scenarios. Always wrap SDK calls in try-catch blocks:
try {
const result = await client.someMethod();
} catch (error) {
console.error('Operation failed:', error.message);
// Handle specific error cases
}Common error scenarios:
- Authentication failures
- Invalid parameters
- Network errors
- Resource not found
- Rate limiting
- Validation errors
Price Handling
All monetary values in the SDK are handled in cents (the smallest currency unit). For example:
- $10.00 is represented as 1000
- $5.99 is represented as 599
- $0.50 is represented as 50
Documentation
For more detailed information about each method and its parameters, please refer to our official documentation.
