UNPKG

@automattic/shopping-cart

Version:
327 lines (300 loc) 8.4 kB
import debugFactory from 'debug'; import { getEmptyResponseCart } from './empty-carts'; import type { TempResponseCart, CartLocation, RequestCart, RequestCartProduct, ResponseCart, ResponseCartProduct, GetCart, CartKey, } from './types'; const debug = debugFactory( 'shopping-cart:cart-functions' ); let lastUUID = 100; const emptyResponseCart = getEmptyResponseCart(); function convertResponseCartProductToRequestCartProduct( product: ResponseCartProduct | RequestCartProduct ): RequestCartProduct { const { product_slug, meta, product_id, extra, volume, quantity } = product; return { product_slug, meta, volume, quantity, product_id, extra, }; } export function convertResponseCartToRequestCart( { products, coupon, tax, blog_id, }: TempResponseCart ): RequestCart { let requestCartTax = null; if ( tax.location.country_code || tax.location.postal_code || tax.location.subdivision_code || tax.location.vat_id || tax.location.organization || tax.location.address || tax.location.city ) { requestCartTax = { location: { country_code: tax.location.country_code, postal_code: tax.location.postal_code, subdivision_code: tax.location.subdivision_code, vat_id: tax.location.vat_id, organization: tax.location.organization, address: tax.location.address, city: tax.location.city, }, }; } return { blog_id, products: products.map( convertResponseCartProductToRequestCartProduct ), coupon, temporary: false, tax: requestCartTax, }; } export function convertTempResponseCartToResponseCart( cart: TempResponseCart ): ResponseCart { return { ...cart, products: cart.products.filter( isValidResponseCartProduct ), }; } function isValidResponseCartProduct( product: RequestCartProduct | ResponseCartProduct ): product is ResponseCartProduct { return 'uuid' in product; } export function removeItemFromResponseCart( cart: TempResponseCart, uuidToRemove: string ): TempResponseCart { return { ...cart, products: cart.products.filter( ( product ) => { return isValidResponseCartProduct( product ) ? product.uuid !== uuidToRemove : true; } ), }; } export function addCouponToResponseCart( cart: TempResponseCart, couponToAdd: string ): TempResponseCart { return { ...cart, coupon: couponToAdd, is_coupon_applied: false, }; } export function removeCouponFromResponseCart( cart: TempResponseCart ): TempResponseCart { return { ...cart, coupon: '', is_coupon_applied: false, }; } export function addLocationToResponseCart( cart: TempResponseCart, location: CartLocation ): TempResponseCart { return { ...cart, tax: { ...cart.tax, location: { country_code: location.countryCode || undefined, postal_code: location.postalCode || undefined, subdivision_code: location.subdivisionCode || undefined, vat_id: location.vatId || undefined, organization: location.organization || undefined, address: location.address || undefined, city: location.city || undefined, }, }, }; } export function doesCartLocationDifferFromResponseCartLocation( cart: TempResponseCart, location: CartLocation ): boolean { const { countryCode: newCountryCode = '', postalCode: newPostalCode = '', subdivisionCode: newSubdivisionCode = '', vatId: newVatId = '', organization: newOrganization = '', address: newAddress = '', city: newCity = '', } = location; const { country_code: oldCountryCode = '', postal_code: oldPostalCode = '', subdivision_code: oldSubdivisionCode = '', vat_id: oldVatId = '', organization: oldOrganization = '', address: oldAddress = '', city: oldCity = '', } = cart.tax?.location ?? {}; if ( location.countryCode !== undefined && newCountryCode !== oldCountryCode ) { return true; } if ( location.postalCode !== undefined && newPostalCode !== oldPostalCode ) { return true; } if ( location.subdivisionCode !== undefined && newSubdivisionCode !== oldSubdivisionCode ) { return true; } if ( location.vatId !== undefined && newVatId !== oldVatId ) { return true; } if ( location.organization !== undefined && newOrganization !== oldOrganization ) { return true; } if ( location.address !== undefined && newAddress !== oldAddress ) { return true; } if ( location.city !== undefined && newCity !== oldCity ) { return true; } return false; } export function convertRawResponseCartToResponseCart( rawResponseCart: Partial< ResponseCart > ): ResponseCart { if ( typeof rawResponseCart !== 'object' || rawResponseCart === null ) { return emptyResponseCart; } // If tax.location is an empty PHP associative array, it will be JSON serialized to [] but we need {} let taxLocation = {}; if ( rawResponseCart.tax?.location ) { if ( Array.isArray( rawResponseCart.tax.location ) ) { taxLocation = {}; } else { taxLocation = rawResponseCart.tax.location; } } const rawProducts = rawResponseCart.products?.length && Array.isArray( rawResponseCart.products ) ? rawResponseCart.products : []; return { ...emptyResponseCart, ...rawResponseCart, tax: { location: taxLocation, display_taxes: rawResponseCart.tax?.display_taxes ?? false, }, // Add uuid to products returned by the server products: rawProducts.filter( isRealProduct ).map( ( product ) => { return { ...product, uuid: product.product_slug + lastUUID++, }; } ), }; } function isRealProduct( serverCartItem: ResponseCartProduct ): boolean { // Credits are reported separately, so we do not need to include the pseudo-product in the line items. if ( serverCartItem.product_slug === 'wordpress-com-credits' ) { return false; } return true; } function shouldProductReplaceCart( product: RequestCartProduct, responseCart: TempResponseCart ): boolean { const doesCartHaveRenewals = responseCart.products.some( ( cartProduct ) => cartProduct.extra?.purchaseType === 'renewal' ); if ( ! doesCartHaveRenewals && product.extra?.purchaseType === 'renewal' && product.product_slug !== 'domain_redemption' ) { // adding a renewal replaces the cart unless it is a privacy protection (comment copied from original code from old checkout; is domain_redemption really privacy protection?) return true; } if ( doesCartHaveRenewals && product.extra?.purchaseType !== 'renewal' ) { // all items should replace the cart if the cart contains a renewal return true; } return false; } function shouldProductsReplaceCart( products: RequestCartProduct[], responseCart: TempResponseCart ): boolean { return products.some( ( product ) => shouldProductReplaceCart( product, responseCart ) ); } export function addItemsToResponseCart( responseCart: TempResponseCart, products: RequestCartProduct[] ): TempResponseCart { if ( shouldProductsReplaceCart( products, responseCart ) ) { debug( 'items should replace response cart', products ); return replaceAllItemsInResponseCart( responseCart, products ); } debug( 'items should not replace response cart', products ); return { ...responseCart, products: [ ...responseCart.products, ...products ], }; } export function replaceAllItemsInResponseCart( responseCart: TempResponseCart, products: RequestCartProduct[] ): TempResponseCart { return { ...responseCart, products: [ ...products ], }; } export function replaceItemInResponseCart( cart: TempResponseCart, uuidToReplace: string, productPropertiesToChange: Partial< RequestCartProduct > ): TempResponseCart { return { ...cart, products: cart.products.map( ( item ) => { if ( isValidResponseCartProduct( item ) && item.uuid === uuidToReplace ) { return { ...item, ...productPropertiesToChange }; } return item; } ), }; } export function doesResponseCartContainProductMatching( responseCart: TempResponseCart, productProperties: Partial< ResponseCartProduct > ): boolean { return responseCart.products.some( ( product ) => { return ( isValidResponseCartProduct( product ) && Object.keys( productProperties ).every( ( key ) => { const typedKey = key as keyof ResponseCartProduct; return product[ typedKey ] === productProperties[ typedKey ]; } ) ); } ); } export async function findCartKeyFromSiteSlug( slug: string, getCart: GetCart ): Promise< CartKey > { try { const cart = await getCart( slug as CartKey ); return cart.cart_key; } catch { return 'no-site'; } }