flex-template-web/src/forms/BookingDatesForm/EstimatedBreakdownMaybe.js
2018-12-05 11:07:47 +02:00

148 lines
4.8 KiB
JavaScript

/**
* Booking breakdown estimation
*
* Transactions have payment information that can be shown with the
* BookingBreakdown component. However, when selecting booking
* details, there is no transaction object present and we have to
* estimate the breakdown of the transaction without data from the
* API.
*
* If the payment process of a customized marketplace is something
* else than simply daily or nightly bookings, the estimation will
* most likely need some changes.
*
* To customize the estimation, first change the BookingDatesForm to
* collect all booking information from the user (in addition to the
* default date pickers), and provide that data to the
* EstimatedBreakdownMaybe components. You can then make customization
* within this file to create a fake transaction object that
* calculates the breakdown information correctly according to the
* process.
*
* In the future, the optimal scenario would be to use the same
* transactions.initiateSpeculative API endpoint as the CheckoutPage
* is using to get the breakdown information from the API, but
* currently the API doesn't support that for logged out users, and we
* are forced to estimate the information here.
*/
import React from 'react';
import moment from 'moment';
import Decimal from 'decimal.js';
import { types as sdkTypes } from '../../util/sdkLoader';
import { dateFromLocalToAPI, nightsBetween, daysBetween } from '../../util/dates';
import {
LINE_ITEM_DAY,
LINE_ITEM_NIGHT,
LINE_ITEM_UNITS,
TRANSITION_REQUEST,
TX_TRANSITION_ACTOR_CUSTOMER,
} from '../../util/types';
import { unitDivisor, convertMoneyToNumber, convertUnitToSubUnit } from '../../util/currency';
import { BookingBreakdown } from '../../components';
import css from './BookingDatesForm.css';
const { Money, UUID } = sdkTypes;
const estimatedTotalPrice = (unitPrice, unitCount) => {
const numericPrice = convertMoneyToNumber(unitPrice);
const numericTotalPrice = new Decimal(numericPrice).times(unitCount).toNumber();
return new Money(
convertUnitToSubUnit(numericTotalPrice, unitDivisor(unitPrice.currency)),
unitPrice.currency
);
};
// When we cannot speculatively initiate a transaction (i.e. logged
// out), we must estimate the booking breakdown. This function creates
// an estimated transaction object for that use case.
const estimatedTransaction = (unitType, bookingStart, bookingEnd, unitPrice, quantity) => {
const now = new Date();
const isNightly = unitType === LINE_ITEM_NIGHT;
const isDaily = unitType === LINE_ITEM_DAY;
const unitCount = isNightly
? nightsBetween(bookingStart, bookingEnd)
: isDaily
? daysBetween(bookingStart, bookingEnd)
: quantity;
const totalPrice = estimatedTotalPrice(unitPrice, unitCount);
// bookingStart: "Fri Mar 30 2018 12:00:00 GMT-1100 (SST)" aka "Fri Mar 30 2018 23:00:00 GMT+0000 (UTC)"
// Server normalizes night/day bookings to start from 00:00 UTC aka "Thu Mar 29 2018 13:00:00 GMT-1100 (SST)"
// The result is: local timestamp.subtract(12h).add(timezoneoffset) (in eg. -23 h)
// local noon -> startOf('day') => 00:00 local => remove timezoneoffset => 00:00 API (UTC)
const serverDayStart = dateFromLocalToAPI(
moment(bookingStart)
.startOf('day')
.toDate()
);
const serverDayEnd = dateFromLocalToAPI(
moment(bookingEnd)
.startOf('day')
.toDate()
);
return {
id: new UUID('estimated-transaction'),
type: 'transaction',
attributes: {
createdAt: now,
lastTransitionedAt: now,
lastTransition: TRANSITION_REQUEST,
payinTotal: totalPrice,
payoutTotal: totalPrice,
lineItems: [
{
code: unitType,
includeFor: ['customer', 'provider'],
unitPrice: unitPrice,
quantity: new Decimal(unitCount),
lineTotal: totalPrice,
reversal: false,
},
],
transitions: [
{
createdAt: now,
by: TX_TRANSITION_ACTOR_CUSTOMER,
transition: TRANSITION_REQUEST,
},
],
},
booking: {
id: new UUID('estimated-booking'),
type: 'booking',
attributes: {
start: serverDayStart,
end: serverDayEnd,
},
},
};
};
const EstimatedBreakdownMaybe = props => {
const { unitType, unitPrice, startDate, endDate, quantity } = props.bookingData;
const isUnits = unitType === LINE_ITEM_UNITS;
const quantityIfUsingUnits = !isUnits || Number.isInteger(quantity);
const canEstimatePrice = startDate && endDate && unitPrice && quantityIfUsingUnits;
if (!canEstimatePrice) {
return null;
}
const tx = estimatedTransaction(unitType, startDate, endDate, unitPrice, quantity);
return (
<BookingBreakdown
className={css.receipt}
userRole="customer"
unitType={unitType}
transaction={tx}
booking={tx.booking}
/>
);
};
export default EstimatedBreakdownMaybe;