import { Dispatch } from 'react' ; import { SetLocalOrder , DeleteLocalOrder , setCustomerOrderParam , CustomerOrderParams , } from '#utils/localStorage' ; import { CommerceLayerConfig } from '#context/CommerceLayerContext' ; import baseReducer from '#utils/baseReducer' ; import { ItemOption , CustomLineItem } from './ItemReducer' ; import { isEmpty , size , map } from 'lodash' ; import { BaseMetadataObject } from '#typings/index' ; import { BaseError } from '#typings/errors' ; import getSdk from '#utils/getSdk' ; import getErrors , { setErrors } from '../utils/getErrors' ; import { AddressResource } from './AddressReducer' ; import { Order , LineItemCreate , LineItemOptionCreate , OrderUpdate , QueryParamsRetrieve , } from '@commercelayer/sdk' ; export type GetOrderParams = Partial < { clearWhenPlaced : boolean ; config : CommerceLayerConfig ; deleteLocalOrder : DeleteLocalOrder ; dispatch : Dispatch < OrderActions > ; id : string ; persistKey : string ; state : OrderState ; } > ; export interface GetOrder { ( params ) : Promise < void | Order > ; } type CreateOrderParams = Pick < AddToCartParams , 'config' | 'dispatch' | 'persistKey' | 'state' | 'orderMetadata' | 'orderAttributes' | 'setLocalOrder' > ; export interface CreateOrder { ( params ? ) : Promise < string > ; } export type AddToCartParams = Partial < { bundleCode : string ; skuCode : string ; persistKey : string ; config : CommerceLayerConfig ; dispatch : Dispatch < OrderActions > ; state : Partial < OrderState > ; skuId : string ; quantity : number ; option : ItemOption ; lineItem : CustomLineItem ; orderMetadata : BaseMetadataObject ; orderAttributes : Record < string , any > ; errors : BaseError [ ] ; setLocalOrder : SetLocalOrder ; } > ; export interface AddToCartImportParams extends Omit < AddToCartParams , 'skuCode' | 'skuId' | 'quantity' | 'option' | 'lineItem' > { lineItems : CustomLineItem [ ] ; } export type AddToCartReturn = Promise < { success : boolean ; } > ; export interface AddToCart { ( params ) : AddToCartReturn ; } export interface AddToCartImport { ( params ) : AddToCartReturn ; } export interface UnsetOrderState { ( dispatch ) : void ; } export type ResourceIncluded = 'billing_address' | 'shipping_address' | 'line_items.line_item_options.sku_option' | 'available_customer_payment_sources.payment_source' | 'shipments.available_shipping_methods' | 'shipments.stock_transfers' | 'shipments.shipment_line_items.line_item' | 'shipments.shipping_method' | 'shipments.stock_location' | 'payment_source' | 'available_payment_methods' | 'payment_method' ; type ResourceIncludedLoaded = Partial < Record < ResourceIncluded , boolean > > ; export interface OrderPayload { loading ? : boolean ; orderId ? : string ; order ? : Order ; errors ? : BaseError [ ] ; include ? : ResourceIncluded [ ] ; includeLoaded ? : ResourceIncludedLoaded ; } export type AddToCartValues = Pick < AddToCartParams , 'bundleCode' | 'lineItem' | 'quantity' | 'skuCode' | 'skuId' | 'option' > ; export type AddToCartImportValues = Pick < AddToCartImportParams , 'lineItems' > ; export type getOrderContext = ( id : string ) => Promise < void | Order > ; export type OrderState = Partial < OrderPayload > ; export interface OrderActions { type : OrderActionType ; payload : OrderPayload ; } export type OrderActionType = 'setLoading' | 'setOrderId' | 'setOrder' | 'setSingleQuantity' | 'setCurrentSkuCodes' | 'setCurrentSkuPrices' | 'setCurrentItem' | 'setErrors' | 'setSaveAddressToCustomerAddressBook' | 'setGiftCardOrCouponCode' | 'setIncludesResource' ; const actionType = [ 'setLoading' , 'setOrderId' , 'setOrder' , 'setSingleQuantity' , 'setCurrentSkuCodes' , 'setCurrentSkuPrices' , 'setErrors' , 'setCurrentItem' , 'setSaveAddressToCustomerAddressBook' , 'setIncludesResource' , ] ; export const createOrder = async ( params ) => { if ( params ) { const { persistKey , state , dispatch , config , orderMetadata : metadata , orderAttributes = { } , setLocalOrder , } = params ; if ( state ?. orderId ) return state . orderId ; const sdk = getSdk ( config as CommerceLayerConfig ) ; try { const o = await sdk ?. orders . create ( { metadata , ... orderAttributes } ) ; dispatch && dispatch ( { type : 'setOrderId' , payload : { orderId : o ?. id , } , } ) ; persistKey && setLocalOrder && setLocalOrder ( persistKey , o . id ) ; return o . id ; } catch ( error ) { const errors = getErrors ( error , 'orders' ) ; console . error ( 'Create order' , errors ) ; if ( dispatch ) setErrors ( { currentErrors : state ?. errors , newErrors : errors , dispatch , } ) ; } } return '' ; } ; export const getApiOrder = async ( params ) => { const { id , dispatch , config , clearWhenPlaced , persistKey , deleteLocalOrder , state , } = params ; const sdk = getSdk ( config as CommerceLayerConfig ) ; try { const options = { } ; if ( state ?. include && state . include . length > 0 ) { options . include = state . include ; } const order = await sdk . orders . retrieve ( id as string , options ) ; if ( ( clearWhenPlaced && order . status === 'placed' ) || order . status === 'approved' || order . status === 'cancelled' ) { persistKey && deleteLocalOrder && deleteLocalOrder ( persistKey ) ; dispatch && dispatch ( { type : 'setOrder' , payload : { order : undefined , orderId : '' , } , } ) ; } else { dispatch && dispatch ( { type : 'setOrder' , payload : { order : order , orderId : order . id , } , } ) ; } return order ; } catch ( error ) { const errors = getErrors ( error , 'orders' ) ; console . error ( 'Retrieve order' , errors ) ; if ( dispatch ) setErrors ( { currentErrors : state ?. errors , newErrors : errors , dispatch , } ) ; return ; } } ; export type UpdateOrderArgs = { id : string ; attributes : Omit < OrderUpdate , 'id' > ; dispatch ? : Dispatch < OrderActions > ; include ? : string [ ] ; config ? : CommerceLayerConfig ; } ; export async function updateOrder ( { id , attributes , dispatch , config , include , } ) { const sdk = getSdk ( config as CommerceLayerConfig ) ; try { const resource = { ... attributes , id } ; const order = await sdk . orders . update ( resource , { include } ) ; dispatch && dispatch ( { type : 'setOrder' , payload : { order } } ) ; } catch ( error ) { const errors = getErrors ( error , 'orders' ) ; if ( dispatch ) { setOrderErrors ( { errors , dispatch } ) ; dispatch ( { type : 'setErrors' , payload : { errors , } , } ) ; } } } export const setOrder = ( order , dispatch ? ) : void => { dispatch && dispatch ( { type : 'setOrder' , payload : { order , } , } ) ; } ; export type AddResourceToInclude = { resourcesIncluded ? : ResourceIncluded [ ] ; dispatch ? : Dispatch < OrderActions > ; newResource ? : ResourceIncluded | ResourceIncluded [ ] ; resourceIncludedLoaded ? : ResourceIncludedLoaded ; newResourceLoaded ? : ResourceIncludedLoaded ; } ; export function addResourceToInclude ( { resourcesIncluded = [ ] , dispatch , newResource , newResourceLoaded , resourceIncludedLoaded , } ) { const payload = { include : undefined , includeLoaded : undefined , } ; if ( newResource ) { payload . include = [ ... resourcesIncluded , ... ( typeof newResource === 'string' ? [ newResource ] : newResource ) , ] ; } else { delete payload . include ; } payload . includeLoaded = { ... resourceIncludedLoaded , ... newResourceLoaded } ; dispatch && dispatch ( { type : 'setIncludesResource' , payload , } ) ; } export const addToCart = async ( params ) => { const { skuCode , bundleCode , skuId , quantity , option , config , dispatch , lineItem , state , errors = [ ] , } = params ; try { const sdk = getSdk ( config as CommerceLayerConfig ) ; const id = await createOrder ( params ) ; if ( id ) { const order = sdk . orders . relationship ( id ) ; const name = lineItem ?. name ; const imageUrl = lineItem ?. imageUrl as string ; const attrs = { order , sku_code : skuCode , name , image_url : imageUrl , quantity : quantity || 1 , _update_quantity : true , bundle_code : bundleCode , } ; if ( skuId ) { attrs [ 'item' ] = sdk . skus . relationship ( skuId ) ; } const newLineItem = await sdk . line_items . create ( attrs ) ; if ( ! isEmpty ( option ) ) { let c = 0 ; map ( option , async ( opt ) => { const { options , skuOptionId } = opt ; const skuOption = sdk . sku_options . relationship ( skuOptionId ) ; const lineItemRel = sdk . line_items . relationship ( newLineItem . id ) ; const lineItemOptionsAttributes = { quantity : 1 , options , sku_option : skuOption , line_item : lineItemRel , } ; await sdk . line_item_options . create ( lineItemOptionsAttributes ) ; c += 1 ; if ( c === size ( option ) ) { await getApiOrder ( { id , ... params } ) ; } } ) ; } else { await getApiOrder ( { id , ... params , state } ) ; } if ( ! isEmpty ( errors ) && dispatch ) { dispatch ( { type : 'setErrors' , payload : { errors : [ ] , } , } ) ; } return { success : true } ; } return { success : false } ; } catch ( error ) { const errors = getErrors ( error , 'orders' ) ; console . error ( 'Add to cart' , errors ) ; if ( dispatch ) setErrors ( { currentErrors : state ?. errors , newErrors : errors , dispatch , } ) ; return { success : false } ; } } ; export const unsetOrderState = ( dispatch ) => { dispatch ( { type : 'setOrderId' , payload : { orderId : '' , } , } ) ; dispatch ( { type : 'setOrder' , payload : { order : undefined , } , } ) ; } ; type OrderErrors = { dispatch ? : Dispatch < OrderActions > ; errors : any ; } ; export function setOrderErrors ( { dispatch , errors } ) { dispatch && dispatch ( { type : 'setErrors' , payload : { errors , } , } ) ; return { success : false } ; } export type SaveAddressToCustomerAddressBook = ( params : { dispatch ? : Dispatch < OrderActions > ; type : AddressResource ; value : boolean ; } ) => void ; export const saveAddressToCustomerAddressBook = ( { type , value , dispatch } ) => { const k = `_save_${ type }_to_customer_address_book` ; const v = `${ value }` ; setCustomerOrderParam ( k , v ) ; dispatch && dispatch ( { type : 'setSaveAddressToCustomerAddressBook' , payload : { [ k ] : v , } , } ) ; } ; export type SetGiftCardOrCouponCode = ( args : { code : string ; dispatch ? : Dispatch < OrderActions > ; config ? : CommerceLayerConfig ; order ? : Order ; } ) => Promise < { success : boolean ; } > ; export const setGiftCardOrCouponCode = async ( { code , dispatch , config , order , } ) => { try { if ( config && order && code && dispatch ) { const sdk = getSdk ( config ) ; const attributes = { id : order . id , gift_card_or_coupon_code : code , } ; const orderUpdated = await sdk . orders . update ( attributes ) ; dispatch ( { type : 'setErrors' , payload : { errors : [ ] , order : orderUpdated , } , } ) ; return { success : true } ; } return { success : false } ; } catch ( error ) { const errors = getErrors ( error , 'gift_card_or_coupon_code' ) ; dispatch && setOrderErrors ( { errors , dispatch } ) ; return { success : false } ; } } ; export type CodeType = 'coupon' | 'gift_card' ; export type OrderCodeType = `${ CodeType }_code` ; export type RemoveGiftCardOrCouponCode = ( args : { codeType : OrderCodeType ; dispatch ? : Dispatch < OrderActions > ; config ? : CommerceLayerConfig ; order ? : Order ; } ) => Promise < { success : boolean ; } > ; export const removeGiftCardOrCouponCode = async ( { codeType , dispatch , config , order , } ) => { try { if ( config && order && dispatch ) { const sdk = getSdk ( config ) ; const attributes = { id : order . id , [ codeType ] : '' , } ; const orderUpdated = await sdk . orders . update ( attributes ) ; dispatch ( { type : 'setErrors' , payload : { errors : [ ] , order : orderUpdated , } , } ) ; return { success : true } ; } return { success : false } ; } catch ( error ) { const errors = getErrors ( error , 'gift_card_or_coupon_code' ) ; console . error ( 'Remove gift card o coupon code' , errors ) ; dispatch && setOrderErrors ( { errors , dispatch } ) ; return { success : false } ; } } ; export const orderInitialState = { loading : false , orderId : '' , order : undefined , errors : [ ] , } ; const orderReducer = ( state , reducer ) : OrderState => baseReducer < OrderState , OrderActions , OrderActionType [ ] > ( state , reducer , actionType ) ; export default orderReducer ;