docs
  1. Checkout Guide
  2. Customization
  3. Dynamic Slots
  4. Integrating Slots

Integrating Slots

  1. Available slot names
    1. Authentication Step
    2. Shipping Step
    3. Payment Step
    4. Basket
  2. Styling
  3. Events
  4. Methods
  5. Types
  6. Examples
    1. Show all available slot names on the page
    2. Injecting static HTML into the top of the basket
    3. Injected a web component within a slot element
    4. Mutating the state
    5. Sorting and filtering
    6. Listen for change in the order state

Available slot names

A comprehensive list of all slot names where content can be added.

  • slot_dynamic_area_guest
  • slot_dynamic_area_register
  • slot_option_sign_in
  • slot_option_register
  • slot_option_guest
  • slot_option_selected_sign_in
  • slot_option_selected_register
  • slot_option_selected_guest

Styling

The content injected into the slot should come with its own styles as these are not inherited from the parent.

Events

Since slots can be clickable, if content is injected into one of the non-selected option slots, the slotContent will require an onclick attribute to dispatch an event that propagates the option click/switch on the checkout side. Make sure the event name matches with the slot name.

element.onclick = () => element.dispatchEvent(new CustomEvent({{slot_name}}, {
   bubbles: true,
   composed: true
 }));

Methods

Within the window.scayleCheckoutFunctions.mutations object, we expose native checkout methods to be used within the storefront within slots implementation that requires logic such as immediately returning the current state.

signIn: Promise<CheckoutState>
register: Promise<CheckoutState>
setPasswordForCustomerNumber: Promise<CheckoutState>
setBasketItemQuantity: Promise<number | undefined>
removeBasketItem: Promise<CheckoutState>
setCarrierAndShippingOption: Promise<CheckoutState>
setCollectionPoint: Promise<CheckoutState>
setShippingAddress: Promise<AddressSuggestion[] | undefined>
setShippingAddressAsBillingAddress: Promise<CheckoutState>
setBillingAddress: Promise<AddressSuggestion[] | undefined>
setDynamicFields: Promise<CheckoutState>
forceOrderUpdate: Promise<CheckoutState>
getState: Promise<CheckoutState>
setPaymentOption: Promise<CheckoutState>
updatePaymentOption: Promise<any>
addGiftCard: Promise<GiftCard[] | undefined>
removePromotions: Promise<CheckoutState>
removeGiftCard: Promise<CheckoutState>
addVoucher: Promise<CheckoutState>
removeVoucher: Promise<CheckoutState>
addPromotion: Promise<CheckoutState>
removePromotion: Promise<CheckoutState>
removeRecurringPaymentMethod: Promise<CheckoutState>
registerForLoyaltyProgram: Promise<CheckoutState>
setLoyaltyProgram: Promise<CheckoutState>
removeLoyaltyProgram: Promise<CheckoutState>
redeemLoyaltyProgramPoints: Promise<CheckoutState>
unredeemLoyaltyProgramPoints: Promise<CheckoutState>
confirmOrder: Promise<ConfirmOrderResponse>

The full types are listed below within the types section

Types

CheckoutState
{
  addressSuggestions?: {
    suggestions: Array<{
      street: string;
      houseNumber?: string;
      zipCode?: string;
      city: string;
      state?: string;
      country: { iso2Code: string };
      title?: string;
      salutationCode?: 'f' | 'm' | 'd' | 'n';
      firstName: string;
      lastName: string;
      additional?: string;
      phone?: string;
      forwardToCollectionPoint?: boolean;
      isOriginal: boolean;
      signature: string;
    }>;
  };
  customer?: {
    authentication: { data: any; type: string };
    data: {
      birthDate?: string;
      customData?: any;
      email: string;
      firstName: string;
      gender: string;
      id: number;
      lastName: string;
      trackingHash?: string;
      publicKey: string;
      referenceKey?: string;
      title?: string;
      type: string;
      phone?: { plainText: string };
    };
    flags?: {
      canUpgradeToFullAccount?: boolean;
      hasFreeShipping?: boolean;
      isGuest: boolean;
      hasReturnCosts?: boolean;
      isCarrierSelectionBlocked?: boolean;
      hasCodPaymentFees?: boolean;
    };
    groups?: string[];
    securityHash?: string;
  };
  integrations: {
    campaigns?: Array<{ isApplicable: boolean; name: string }>;
    newsletter: { customerIsConfirmedSubscriber: boolean; enabled: boolean };
    promotion?: { codes: { enabled: boolean } };
  };
  merchants?: Array<{
    id: number;
    isMarketplaceMerchant: boolean;
    legalNotice?: {
      city?: string;
      country?: { id: number; iso3166Alpha2Code: string; iso3166Alpha3Code: string };
      email?: string;
      legalName?: string;
      managingDirectors?: string;
      parentCompanyLegalName?: string;
      parentCompanyRegisterNumber?: string;
      parentCompanyRegistryCourtCity?: string;
      parentCompanyRegistryCourtType?: Record<string, string>;
      registerNumber?: string;
      registryCourtCity?: string;
      registryCourtType?: Record<string, string>;
      representative?: string;
      shippingMerchantName?: string;
      streetWithNumber?: string;
      telephone?: string;
      vatIds?: Array<{ country?: { id: number; iso3166Alpha2Code: string }; vatId: string }>;
      vatId?: string;
      zip?: string;
    };
  }>;
  orderOptions?: {
    payment?: {
      generic: Array<{
        paymentOptionKey: string;
        paymentOptionProvider: string;
        isDisabled: boolean;
        configuration?: {
          initializationData?: any;
          isRecurring?: boolean;
          isAutoCapture?: boolean;
          deferral?: { autoApplyDeferral: boolean; isSelected: boolean; isSupported: boolean; weeks: number };
          fee?: number;
          codPaymentFee?: number;
          instalments?: Array<{ number: number; isSelected: boolean }>;
          public?: { requiresTranslation?: boolean; requiresBrands?: string[] };
        };
        instalment?: { firstInstalmentValue: number; numberOfInstalments: number; subsequentInstalmentValue: number };
        notifications?: Array<{ id?: string; type: 'success' | 'info' | 'warning' | 'error'; text?: string }>;
        serviceCosts?: { total: number };
      }>;
      giftCards?: Array<{
        recurringPaymentOptionId: string;
        recurringPaymentInformation: { giftCardCode: string; remainingAmount: number };
      }>;
      recurring?: Array<{
        recurringPaymentOptionId: string;
        recurringPaymentInformation?: { creditcardBrand?: string; maskedCreditcardNumber?: string; customerEmail?: string };
      }>;
    };
    shipping?: {
      carrierChangeAllowed?: boolean;
      collectionPointDelivery?: Array<{
        carrierGroupKey: string;
        carrierKey: string;
        deliveryOptionKey: string;
        shippingOptionKey: string;
        hideDivergingBillingAddress?: boolean;
        imageFileName?: string;
        collectionPointType?: string;
        isDisabled?: boolean;
        returnCosts?: { minBruttoOrderValueAfterReturns: number; returnServiceCost: number };
        deliveryDate?: string;
        deliveryDates?: { maximum: string; minimum: string };
        serviceCosts?: { taxAmount: number; taxRate: number; withoutTax: number; withTax: number };
        deliveryEstimations?: { deliveryDateTime: { iso8601: string }; deliveryDays: { maximum: number; minimum: number } };
        requiresDateSchedule?: boolean;
        requiresTimeSchedule?: boolean;
      }>;
      homeDelivery?: Array<{
        carrierGroupKey: string;
        carrierKey: string;
        deliveryOptionKey: string;
        shippingOptionKey: string;
        hideDivergingBillingAddress?: boolean;
        imageFileName?: string;
        collectionPointType?: string;
        isDisabled?: boolean;
        returnCosts?: { minBruttoOrderValueAfterReturns: number; returnServiceCost: number };
        deliveryDate?: string;
        deliveryDates?: { maximum: string; minimum: string };
        serviceCosts?: { taxAmount: number; taxRate: number; withoutTax: number; withTax: number };
        deliveryEstimations?: { deliveryDateTime: { iso8601: string }; deliveryDays: { maximum: number; minimum: number } };
        requiresDateSchedule?: boolean;
        requiresTimeSchedule?: boolean;
      }>;
      onlineDelivery?: Array<{
        carrierGroupKey: string;
        carrierKey: string;
        deliveryOptionKey: string;
        shippingOptionKey: string;
        hideDivergingBillingAddress?: boolean;
        imageFileName?: string;
        collectionPointType?: string;
        isDisabled?: boolean;
        returnCosts?: { minBruttoOrderValueAfterReturns: number; returnServiceCost: number };
        deliveryDate?: string;
        deliveryDates?: { maximum: string; minimum: string };
        serviceCosts?: { taxAmount: number; taxRate: number; withoutTax: number; withTax: number };
        deliveryEstimations?: { deliveryDateTime: { iso8601: string }; deliveryDays: { maximum: number; minimum: number } };
        requiresDateSchedule?: boolean;
        requiresTimeSchedule?: boolean;
      }>;
    };
  };
  orderState: {
    id?: number;
    addresses?: {
      billing?: {
        street: string;
        houseNumber?: string;
        zipCode?: string;
        city: string;
        state?: string;
        country: { iso2Code: string };
        title?: string;
        salutationCode?: 'f' | 'm' | 'd' | 'n';
        firstName: string;
        lastName: string;
        additional?: string;
        phone?: string;
        forwardToCollectionPoint?: boolean;
        isOriginal: boolean;
        signature: string;
      };
      shipping?: {
        street: string;
        houseNumber?: string;
        zipCode?: string;
        city: string;
        state?: string;
        country: { iso2Code: string };
        title?: string;
        salutationCode?: 'f' | 'm' | 'd' | 'n';
        firstName: string;
        lastName: string;
        additional?: string;
        phone?: string;
        forwardToCollectionPoint?: boolean;
        isOriginal: boolean;
        signature: string;
      };
    };
    basket: {
      packages: Array<{
        id: string;
        carrierGroupKey: string;
        carriers: string[];
        deliveryDate: { min: string; max: string };
        isAvailable: boolean;
        items: Array<{
          id: string;
          brand: { id: number; name: string };
          colors?: Array<{ id: number; name: string }>;
          displayData: {
            identifier: { label: { translationKey: string }; value: string };
            meta: { label: { translationKey: string }; value: string };
            name: { label: { translationKey: string }; value: string };
          };
          discount: { sale: number; total: number };
          imageUrl: string;
          name: string;
          merchant: { id: number; variantId: string };
          pdpUrl: string;
          productId: number;
          quantity: number;
          size: { shop: string; vendor: string };
          styleKey: string;
          unit: { costWithTaxWithoutVoucher: number; costWithoutTaxWithoutVoucher: number; taxWithoutVoucher: number };
          variantId: number;
          voucherId: string;
          total: { costWithTaxWithoutDiscount: number; costWithTaxWithDiscount: number; costWithoutTaxWithDiscount: number };
        }>;
      }>;
      tax: { amount: number };
      id: string;
      total: {
        costWithTaxWithoutVouchersWithoutServiceCosts: number;
        costWithTaxWithVouchersWithServiceCosts: number;
        costWithTaxWithoutPromotion: number;
      };
    };
    memberships?: { activated?: Array<{
      membershipAccountNumber: string;
      totalPointBalance: number;
      redeemablePointBalance: number;
      redeemablePointBalanceInCurrency: number;
      redeemedPoints?: number;
      redeemedPointsInCurrency?: number;
      remainingPointBalance?: number;
    }>; suggestions: Array<{ membershipTypeKey: string; earnings: { points: number } }> };
    payment?: { giftCards?: Array<{
      recurringPaymentOptionId: string;
      recurringPaymentInformation: { giftCardCode: string; remainingAmount: number };
    }>; selected?: Array<{
      paymentOptionKey: string;
      paymentOptionProvider: string;
      isDisabled: boolean;
      configuration?: {
        initializationData?: any;
        isRecurring?: boolean;
        isAutoCapture?: boolean;
        deferral?: { autoApplyDeferral: boolean; isSelected: boolean; isSupported: boolean; weeks: number };
        fee?: number;
        codPaymentFee?: number;
        instalments?: Array<{ number: number; isSelected: boolean }>;
        public?: { requiresTranslation?: boolean; requiresBrands?: string[] };
      };
    }> };
    shipping?: { logisticsContact?: { plainText: string }; selected?: Array<{
      carrierGroupKey: string;
      carrierKey: string;
      deliveryOptionKey: string;
      shippingOptionKey: string;
      hideDivergingBillingAddress?: boolean;
      imageFileName?: string;
      collectionPointType?: string;
      isDisabled?: boolean;
      returnCosts?: { minBruttoOrderValueAfterReturns: number; returnServiceCost: number };
      deliveryDate?: string;
      deliveryDates?: { maximum: string; minimum: string };
      serviceCosts?: { taxAmount: number; taxRate: number; withoutTax: number; withTax: number };
      deliveryEstimations?: { deliveryDateTime: { iso8601: string }; deliveryDays: { maximum: number; minimum: number } };
      requiresDateSchedule?: boolean;
      requiresTimeSchedule?: boolean;
    }> };
    vouchers?: Array<{ discount: number; id: string; isAppliedAutomatically: boolean; name: string; type: string; value: number; code: string }>;
    promotions?: Array<{ id: string; code?: string; name?: string; discount: number; version: string }>;
    promotionDiscounts?: Array<{ id: string; name?: string; discount: number }>;
    promotionDiscountTotalAbsolute?: number;
    taxId?: string;
  };
  notifications?: Array<{ id?: string; type: 'success' | 'info' | 'warning' | 'error'; text?: string; textTranslationKey?: string; replacements?: any }>;
};
AddressSuggestion
{
  country: { iso2Code: string };
  title?: string;
  salutationCode?: 'f' | 'm' | 'd' | 'n';
  firstName: string;
  lastName: string;
  additional?: string;
  phone?: string;
  forwardToCollectionPoint?: boolean;
  isOriginal: boolean;
  signature: string;
}
GiftCard
{
  paymentOptionKey: string;
  paymentOptionProvider: string;
  isDisabled: boolean;
  configuration?: {
    initializationData?: any;
    isRecurring?: boolean;
    isAutoCapture?: boolean;
    deferral?: { autoApplyDeferral: boolean; isSelected: boolean; isSupported: boolean; weeks: number };
    fee?: number;
    codPaymentFee?: number;
    instalments?: { number: number; isSelected: boolean }[];
    public?: { requiresTranslation?: boolean; requiresBrands?: string[] };
  };
  instalment?: { firstInstalmentValue: number; numberOfInstalments: number; subsequentInstalmentValue: number };
  notifications?: Notification[];
  serviceCosts?: { total: number };
  recurringPaymentInformation: {
    giftCardCode: string;
    remainingAmount: number;
    creditcardBrand?: string;
    maskedCreditcardNumber?: string;
    customerEmail?: string;
  };
ConfirmOrderResponse
{
  hasRedirect: boolean;
  hasNotification: boolean
}

Examples

Show all available slot names on the page

(function() {
  const scayleCheckout = document.querySelector("scayle-checkout");

  if (!scayleCheckout) {
    console.warn("Component 'scayle-checkout' not found.");
    return;
  }

  const slots = scayleCheckout.shadowRoot.querySelectorAll("slot");

  slots.forEach((slot) => {
    const slotName = slot.name || "default";
    const div = document.createElement("div");

    div.innerHTML = `<strong>Slot Name:</strong><br/>${slotName}`;
    div.style.border = "2px solid black";
    div.style.padding = "12px";
    div.style.width = "100%";

    slot.appendChild(div);
  });
})();

Injecting static HTML into the top of the basket

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const replacementComponent = document.createElement('div');
    replacementComponent.innerHTML = '<p>Injected HTML content</p>'
    replacementComponent.slot = 'slot_basket_top';
  
    const checkoutWebComponent = document.getElementsByTagName('scayle-checkout')[0];
    checkoutWebComponent.appendChild(replacementComponent);
  });
</script>

Injected a web component within a slot element

Please contact your SCAYLE Key Account Manager for access to internally developed add-ons such as Ingrid.

<script type="module" crossorigin src="./node_modules/scayle-ingrid/dist/scayle-ingrid.min.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const checkoutWebComponent = document.querySelector('scayle-checkout');
    const ingridWebComponent = document.createElement('scayle-ingrid');
    ingridWebComponent.slot = 'slot_replace_delivery_options';
    checkoutWebComponent.appendChild(ingridWebComponent);
  });
</script>

Mutating the state

Native checkout functions are exposed via the window object and can be interacted with from the storefront. Above you find the entire list of exposed checkout mutations.

window.scayleCheckoutFunctions.mutations.addVoucher({code: 'TEST123'});

The above snippet of code will execute the addVoucher method with a code of "TEST123"

Listening for a change in the order state

window.scayleCheckoutEvents.addEventListener(
  "state:updated", 
  ({ detail: { state } }) => {
    console.log('Checkout state updated:', state);
  }
);

Sorting Payment Options

window.scayleCheckoutFunctions.sortPaymentOptions = (paymentOptions => {
        return {
          ...paymentOptions,
          generic: paymentOptions.generic.sort((a, b) => b.paymentOptionProvider.localeCompare(a.paymentOptionProvider)),
        };
      });

Filtering Payment Options

window.scayleCheckoutFunctions.filterPaymentOptions = ((paymentOptions) => {
    return {
        ...paymentOptions,
        generic: paymentOptions.generic.filter(({ paymentOptionKey }) => paymentOptionKey === "paypal_instant" )
    }
})

The above filtering and sorting works after the next /state update call. When the checkout mounts, we automatically make this call which would filter the payment options and apply the filters on every subsequent /state update.

If it is needed to manually get the latest state immediately, this can be done by calling window.scayleCheckoutFunctions.mutations.getState()

Provide Feedback