flex-template-web/src/containers/CheckoutPage/CheckoutPage.duck.js

309 lines
9.7 KiB
JavaScript

import pick from 'lodash/pick';
import config from '../../config';
import { denormalisedResponseEntities } from '../../util/data';
import { storableError } from '../../util/errors';
import {
TRANSITION_REQUEST_PAYMENT,
TRANSITION_REQUEST_PAYMENT_AFTER_ENQUIRY,
TRANSITION_CONFIRM_PAYMENT,
} from '../../util/transaction';
import * as log from '../../util/log';
import { fetchCurrentUserHasOrdersSuccess, fetchCurrentUser } from '../../ducks/user.duck';
// ================ Action types ================ //
export const SET_INITAL_VALUES = 'app/CheckoutPage/SET_INITIAL_VALUES';
export const INITIATE_ORDER_REQUEST = 'app/CheckoutPage/INITIATE_ORDER_REQUEST';
export const INITIATE_ORDER_SUCCESS = 'app/CheckoutPage/INITIATE_ORDER_SUCCESS';
export const INITIATE_ORDER_ERROR = 'app/CheckoutPage/INITIATE_ORDER_ERROR';
export const CONFIRM_PAYMENT_REQUEST = 'app/CheckoutPage/CONFIRM_PAYMENT_REQUEST';
export const CONFIRM_PAYMENT_SUCCESS = 'app/CheckoutPage/CONFIRM_PAYMENT_SUCCESS';
export const CONFIRM_PAYMENT_ERROR = 'app/CheckoutPage/CONFIRM_PAYMENT_ERROR';
export const SPECULATE_TRANSACTION_REQUEST = 'app/ListingPage/SPECULATE_TRANSACTION_REQUEST';
export const SPECULATE_TRANSACTION_SUCCESS = 'app/ListingPage/SPECULATE_TRANSACTION_SUCCESS';
export const SPECULATE_TRANSACTION_ERROR = 'app/ListingPage/SPECULATE_TRANSACTION_ERROR';
export const STRIPE_CUSTOMER_REQUEST = 'app/CheckoutPage/STRIPE_CUSTOMER_REQUEST';
export const STRIPE_CUSTOMER_SUCCESS = 'app/CheckoutPage/STRIPE_CUSTOMER_SUCCESS';
export const STRIPE_CUSTOMER_ERROR = 'app/CheckoutPage/STRIPE_CUSTOMER_ERROR';
// ================ Reducer ================ //
const initialState = {
listing: null,
bookingData: null,
bookingDates: null,
speculateTransactionInProgress: false,
speculateTransactionError: null,
speculatedTransaction: null,
transaction: null,
initiateOrderError: null,
confirmPaymentError: null,
stripeCustomerFetched: false,
};
export default function checkoutPageReducer(state = initialState, action = {}) {
const { type, payload } = action;
switch (type) {
case SET_INITAL_VALUES:
return { ...initialState, ...payload };
case SPECULATE_TRANSACTION_REQUEST:
return {
...state,
speculateTransactionInProgress: true,
speculateTransactionError: null,
speculatedTransaction: null,
};
case SPECULATE_TRANSACTION_SUCCESS:
return {
...state,
speculateTransactionInProgress: false,
speculatedTransaction: payload.transaction,
};
case SPECULATE_TRANSACTION_ERROR:
console.error(payload); // eslint-disable-line no-console
return {
...state,
speculateTransactionInProgress: false,
speculateTransactionError: payload,
};
case INITIATE_ORDER_REQUEST:
return { ...state, initiateOrderError: null };
case INITIATE_ORDER_SUCCESS:
return { ...state, transaction: payload };
case INITIATE_ORDER_ERROR:
console.error(payload); // eslint-disable-line no-console
return { ...state, initiateOrderError: payload };
case CONFIRM_PAYMENT_REQUEST:
return { ...state, confirmPaymentError: null };
case CONFIRM_PAYMENT_SUCCESS:
return state;
case CONFIRM_PAYMENT_ERROR:
console.error(payload); // eslint-disable-line no-console
return { ...state, confirmPaymentError: payload };
case STRIPE_CUSTOMER_REQUEST:
return { ...state, stripeCustomerFetched: false };
case STRIPE_CUSTOMER_SUCCESS:
return { ...state, stripeCustomerFetched: true };
case STRIPE_CUSTOMER_ERROR:
console.error(payload); // eslint-disable-line no-console
return { ...state, stripeCustomerFetchError: payload };
default:
return state;
}
}
// ================ Selectors ================ //
// ================ Action creators ================ //
export const setInitialValues = initialValues => ({
type: SET_INITAL_VALUES,
payload: pick(initialValues, Object.keys(initialState)),
});
const initiateOrderRequest = () => ({ type: INITIATE_ORDER_REQUEST });
const initiateOrderSuccess = order => ({
type: INITIATE_ORDER_SUCCESS,
payload: order,
});
const initiateOrderError = e => ({
type: INITIATE_ORDER_ERROR,
error: true,
payload: e,
});
const confirmPaymentRequest = () => ({ type: CONFIRM_PAYMENT_REQUEST });
const confirmPaymentSuccess = orderId => ({
type: CONFIRM_PAYMENT_SUCCESS,
payload: orderId,
});
const confirmPaymentError = e => ({
type: CONFIRM_PAYMENT_ERROR,
error: true,
payload: e,
});
export const speculateTransactionRequest = () => ({ type: SPECULATE_TRANSACTION_REQUEST });
export const speculateTransactionSuccess = transaction => ({
type: SPECULATE_TRANSACTION_SUCCESS,
payload: { transaction },
});
export const speculateTransactionError = e => ({
type: SPECULATE_TRANSACTION_ERROR,
error: true,
payload: e,
});
export const stripeCustomerRequest = () => ({ type: STRIPE_CUSTOMER_REQUEST });
export const stripeCustomerSuccess = () => ({ type: STRIPE_CUSTOMER_SUCCESS });
export const stripeCustomerError = e => ({
type: STRIPE_CUSTOMER_ERROR,
error: true,
payload: e,
});
/* ================ Thunks ================ */
export const initiateOrder = (orderParams, transactionId) => (dispatch, getState, sdk) => {
dispatch(initiateOrderRequest());
const bodyParams = transactionId
? {
id: transactionId,
transition: TRANSITION_REQUEST_PAYMENT_AFTER_ENQUIRY,
params: orderParams,
}
: {
processAlias: config.bookingProcessAlias,
transition: TRANSITION_REQUEST_PAYMENT,
params: orderParams,
};
const queryParams = {
include: ['booking', 'provider'],
expand: true,
};
const createOrder = transactionId ? sdk.transactions.transition : sdk.transactions.initiate;
return createOrder(bodyParams, queryParams)
.then(response => {
const entities = denormalisedResponseEntities(response);
const order = entities[0];
dispatch(initiateOrderSuccess(order));
dispatch(fetchCurrentUserHasOrdersSuccess(true));
return order;
})
.catch(e => {
dispatch(initiateOrderError(storableError(e)));
const transactionIdMaybe = transactionId ? { transactionId: transactionId.uuid } : {};
log.error(e, 'initiate-order-failed', {
...transactionIdMaybe,
listingId: orderParams.listingId.uuid,
bookingStart: orderParams.bookingStart,
bookingEnd: orderParams.bookingEnd,
});
throw e;
});
};
export const confirmPayment = orderParams => (dispatch, getState, sdk) => {
dispatch(confirmPaymentRequest());
const bodyParams = {
id: orderParams.transactionId,
transition: TRANSITION_CONFIRM_PAYMENT,
params: {},
};
return sdk.transactions
.transition(bodyParams)
.then(response => {
const order = response.data.data;
dispatch(confirmPaymentSuccess(order.id));
return order;
})
.catch(e => {
dispatch(confirmPaymentError(storableError(e)));
const transactionIdMaybe = orderParams.transactionId
? { transactionId: orderParams.transactionId.uuid }
: {};
log.error(e, 'initiate-order-failed', {
...transactionIdMaybe,
});
throw e;
});
};
export const sendMessage = params => (dispatch, getState, sdk) => {
const message = params.message;
const orderId = params.id;
if (message) {
return sdk.messages
.send({ transactionId: orderId, content: message })
.then(() => {
return { orderId, messageSuccess: true };
})
.catch(e => {
log.error(e, 'initial-message-send-failed', { txId: orderId });
return { orderId, messageSuccess: false };
});
} else {
return Promise.resolve({ orderId, messageSuccess: true });
}
};
/**
* Initiate the speculative transaction with the given booking details
*
* The API allows us to do speculative transaction initiation and
* transitions. This way we can create a test transaction and get the
* actual pricing information as if the transaction had been started,
* without affecting the actual data.
*
* We store this speculative transaction in the page store and use the
* pricing info for the booking breakdown to get a proper estimate for
* the price with the chosen information.
*/
export const speculateTransaction = params => (dispatch, getState, sdk) => {
dispatch(speculateTransactionRequest());
const bodyParams = {
transition: TRANSITION_REQUEST_PAYMENT,
processAlias: config.bookingProcessAlias,
params: {
...params,
cardToken: 'CheckoutPage_speculative_card_token',
},
};
const queryParams = {
include: ['booking', 'provider'],
expand: true,
};
return sdk.transactions
.initiateSpeculative(bodyParams, queryParams)
.then(response => {
const entities = denormalisedResponseEntities(response);
if (entities.length !== 1) {
throw new Error('Expected a resource in the sdk.transactions.initiateSpeculative response');
}
const tx = entities[0];
dispatch(speculateTransactionSuccess(tx));
})
.catch(e => {
const { listingId, bookingStart, bookingEnd } = params;
log.error(e, 'speculate-transaction-failed', {
listingId: listingId.uuid,
bookingStart,
bookingEnd,
});
return dispatch(speculateTransactionError(storableError(e)));
});
};
// StripeCustomer is a relantionship to currentUser
// We need to fetch currentUser with correct params to include relationship
export const stripeCustomer = () => (dispatch, getState, sdk) => {
dispatch(stripeCustomerRequest());
return dispatch(fetchCurrentUser({ include: ['stripeCustomer.defaultPaymentMethod'] }))
.then(response => {
dispatch(stripeCustomerSuccess());
})
.catch(e => {
dispatch(stripeCustomerError(storableError(e)));
});
};