import { apiFetch } from '@wordpress/data-controls';
import { __ } from '@wordpress/i18n';
import {
ADD_ORIGIN_ADDRESS,
ADD_ORIGIN_ADDRESS_FAILED,
ADDRESS_NORMALIZATION,
ADDRESS_NORMALIZATION_FAILED,
DELETE_ORIGIN_ADDRESS,
FETCH_ORIGIN_ADDRESSES,
RESET_ADDRESS_NORMALIZATION,
SET_DESTINATION_RESIDENTIAL,
SET_STORE_ADDRESS_SYNC_STATUS,
STATE_RESET,
UPDATE_SHIPMENT_ADDRESS,
UPDATE_SHIPMENT_ADDRESS_FAILED,
VERIFY_ORDER_SHIPPING_ADDRESS,
VERIFY_ORDER_SHIPPING_ADDRESS_FAILED,
VERIFY_ORDER_SHIPPING_ADDRESS_START,
} from './action-types';
import {
getAddressNormalizationPath,
getDeleteOriginAddressPath,
getOriginAddressesPath,
getStoreAddressSyncPath,
getUpdateDestinationPath,
getUpdateOriginPath,
getVerifyOrderShippingAddressPath,
} from 'data/routes';
import {
camelCaseKeys,
composeAddress,
composeName,
normalizeStoreAddressDraft,
snakeCaseKeys,
} from 'utils';
import { ADDRESS_TYPES } from 'data/constants';
import {
AddressTypes,
Destination,
LocationResponse,
OriginAddress,
} from 'types';
import {
AddOriginAddressAction,
AddOriginAddressFailedAction,
DeleteOriginAddressAction,
NormalizationAddressAction,
NormalizationAddressFailedAction,
SetDestinationResidentialAction,
SetStoreAddressSyncStatusAction,
ShippingAddressVerifyAction,
ShippingAddressVerifyFailedAction,
ShippingAddressVerifyStartAction,
StateResetAction,
UpdateShipmentAddressAction,
UpdateShipmentAddressFailedAction,
} from './types.d';
export function* verifyOrderShippingAddress( {
orderId,
}: {
orderId: string;
} ): Generator<
ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
ShippingAddressVerifyAction | ShippingAddressVerifyFailedAction,
{
success: boolean;
normalizedAddress: LocationResponse;
isTrivialNormalization: boolean;
isVerified: boolean;
}
> {
yield {
type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
payload: {
addressType: 'destination',
},
};
try {
const result = yield apiFetch( {
path: getVerifyOrderShippingAddressPath( orderId ),
} );
return {
type: VERIFY_ORDER_SHIPPING_ADDRESS,
payload: {
...result,
addressType: 'destination',
},
};
} catch {
return {
type: VERIFY_ORDER_SHIPPING_ADDRESS_FAILED,
payload: {
addressType: 'destination',
},
};
}
}
export function* normalizeAddress(
{
address,
}: {
address: Destination | OriginAddress;
},
addressType: 'origin' | 'destination'
): Generator<
ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
NormalizationAddressAction | NormalizationAddressFailedAction,
{
success: boolean;
errors?: Record< string, string >;
isTrivialNormalization: boolean;
address: LocationResponse;
normalizedAddress: LocationResponse;
}
> {
yield {
type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
payload: {
addressType,
},
};
try {
const {
address: submittedAddress,
normalizedAddress,
success,
errors,
...rest
} = yield apiFetch( {
path: getAddressNormalizationPath(),
method: 'POST',
data: {
address: snakeCaseKeys( address ),
// Forward the type so the server-bound request body wraps
// the address as `origin` (vs. the historical `destination`
// fallback) when the caller is normalizing a store origin.
// See WOOSHIP-2230.
addressType,
},
} );
if ( success ) {
return {
type: ADDRESS_NORMALIZATION,
payload: {
...rest,
normalizedAddress: camelCaseKeys( normalizedAddress ),
address: camelCaseKeys( submittedAddress ),
addressType,
},
};
}
return {
type: ADDRESS_NORMALIZATION_FAILED,
payload: {
errors,
address,
addressType,
},
};
} catch ( error ) {
return {
type: ADDRESS_NORMALIZATION_FAILED,
payload: {
errors:
( error as Record< string, Record< string, string > > )
?.errors ?? {},
message: ( error as Record< string, string > )?.message ?? '',
address,
addressType,
},
};
}
}
export function resetAddressNormalizationResponse( addressType: AddressTypes ) {
return {
type: RESET_ADDRESS_NORMALIZATION,
payload: { addressType } as {
addressType: AddressTypes;
},
};
}
export function* updateShipmentAddress(
{
orderId,
address,
isVerified,
clearStoreAddressDrift = false,
}: {
orderId: string;
address: Destination | OriginAddress;
isVerified: boolean;
clearStoreAddressDrift?: boolean;
},
type: AddressTypes
): Generator<
ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
UpdateShipmentAddressAction | UpdateShipmentAddressFailedAction,
{
success: boolean;
address: LocationResponse;
isVerified: boolean;
message?: string;
}
> {
yield {
type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
payload: {
addressType: type,
},
};
try {
const snakeCaseAddress = snakeCaseKeys<
Destination | OriginAddress,
LocationResponse
>( address );
const requestData = {
address: {
...snakeCaseAddress,
address: composeAddress( snakeCaseAddress ),
name: composeName( snakeCaseAddress ),
},
isVerified,
clearStoreAddressDrift,
};
if ( type === ADDRESS_TYPES.ORIGIN ) {
// we only attempt to save the origin address if it's verified by the merchant
requestData.address.is_verified = true;
}
const {
success,
address: resultAddress,
...rest
} = yield apiFetch( {
path:
type === ADDRESS_TYPES.ORIGIN
? getUpdateOriginPath()
: getUpdateDestinationPath( orderId ),
method: 'POST',
data: requestData,
} );
if ( success ) {
return {
type: UPDATE_SHIPMENT_ADDRESS,
payload: {
...rest,
address: {
...camelCaseKeys( resultAddress ),
name: composeName( resultAddress ),
address: composeAddress( resultAddress ),
},
addressType: type,
},
};
}
return {
type: UPDATE_SHIPMENT_ADDRESS_FAILED,
payload: {
message: __(
'Failed to update shipment address',
'woocommerce-shipping'
),
addressType: type,
},
};
} catch ( error ) {
return {
type: UPDATE_SHIPMENT_ADDRESS_FAILED,
payload: {
message: ( error as Record< string, string > )?.message ?? '',
addressType: type,
},
};
}
}
export function* deleteOriginAddress( addressId: string ): Generator<
ReturnType< typeof apiFetch >,
DeleteOriginAddressAction,
{
success: boolean;
addresses: LocationResponse[];
deleted_id: string;
}
> {
const { deleted_id: deletedId } = yield apiFetch( {
path: getDeleteOriginAddressPath( addressId ),
method: 'DELETE',
} );
return {
type: DELETE_ORIGIN_ADDRESS,
payload: {
deletedId,
},
};
}
export function* addOriginAddress( address: OriginAddress ): Generator<
ReturnType< typeof apiFetch >,
AddOriginAddressAction | AddOriginAddressFailedAction,
{
success: boolean;
address: LocationResponse;
isVerified: boolean;
message?: string;
}
> {
// we only attempt to save the origin address if it's verified by the merchant, but this parameter is needed by the API
const isVerified = true;
try {
const snakeCaseAddress = snakeCaseKeys<
Destination | OriginAddress,
LocationResponse
>( address );
const requestData = {
address: {
...snakeCaseAddress,
address: composeAddress( snakeCaseAddress ),
name: composeName( snakeCaseAddress ),
},
isVerified,
};
requestData.address.is_verified = isVerified;
const {
success,
address: resultAddress,
...rest
} = yield apiFetch( {
path: getUpdateOriginPath(),
method: 'POST',
data: requestData,
} );
if ( success ) {
return {
type: ADD_ORIGIN_ADDRESS,
payload: {
...rest,
address: {
...camelCaseKeys( resultAddress ),
name: composeName( resultAddress ),
address: composeAddress( resultAddress ),
},
},
};
}
return {
type: ADD_ORIGIN_ADDRESS_FAILED,
payload: {
error: {
general: __(
'Failed to add origin address',
'woocommerce-shipping'
),
},
},
};
} catch ( error ) {
return {
type: ADD_ORIGIN_ADDRESS_FAILED,
payload: {
error: {
general:
( error as Record< string, string > )?.message ?? '',
},
},
};
}
}
export function* fetchOriginAddresses() {
const addresses: LocationResponse[] = yield apiFetch( {
path: getOriginAddressesPath(),
} );
let isMainOriginInSyncWithStore: boolean | undefined;
let formattedStoreAddress: string | undefined;
let storeAddressDraft: OriginAddress | null | undefined;
try {
const syncMeta: {
is_main_origin_in_sync_with_store: boolean;
formatted_store_address: string;
store_address_draft?: LocationResponse | null;
} = yield apiFetch( {
path: getStoreAddressSyncPath(),
} );
isMainOriginInSyncWithStore =
syncMeta.is_main_origin_in_sync_with_store;
formattedStoreAddress = syncMeta.formatted_store_address ?? '';
storeAddressDraft = syncMeta.store_address_draft
? normalizeStoreAddressDraft( syncMeta.store_address_draft )
: null;
} catch {
// Older servers without the v2 meta route: keep existing Redux sync fields.
}
return {
type: FETCH_ORIGIN_ADDRESSES,
payload: {
addresses: addresses.map( ( addr ) => ( {
...camelCaseKeys( addr ),
address: composeAddress( addr ),
name: composeName( addr ),
} ) ),
isMainOriginInSyncWithStore,
formattedStoreAddress,
storeAddressDraft,
},
};
}
/**
* Refreshes is_main_origin_in_sync_with_store and formatted_store_address from the REST API.
*/
export function* refreshStoreAddressSyncFromApi() {
const syncMeta: {
is_main_origin_in_sync_with_store: boolean;
formatted_store_address: string;
store_address_draft?: LocationResponse | null;
} = yield apiFetch( {
path: getStoreAddressSyncPath(),
} );
return {
type: SET_STORE_ADDRESS_SYNC_STATUS,
payload: {
isMainOriginInSyncWithStore:
syncMeta.is_main_origin_in_sync_with_store,
formattedStoreAddress: syncMeta.formatted_store_address ?? '',
storeAddressDraft: syncMeta.store_address_draft
? normalizeStoreAddressDraft( syncMeta.store_address_draft )
: null,
},
};
}
export function stateReset(): StateResetAction {
return {
type: STATE_RESET,
};
}
export function setStoreAddressSyncStatus(
isMainOriginInSyncWithStore: boolean,
formattedStoreAddress: string,
storeAddressDraft?: OriginAddress | null
): SetStoreAddressSyncStatusAction {
return {
type: SET_STORE_ADDRESS_SYNC_STATUS,
payload: {
isMainOriginInSyncWithStore,
formattedStoreAddress,
storeAddressDraft,
},
};
}
export function setDestinationResidential(
residential: boolean | undefined
): SetDestinationResidentialAction {
return {
type: SET_DESTINATION_RESIDENTIAL,
payload: { residential },
};
}