mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Use line items to show price breakdown
This commit is contained in:
parent
4364a4a7b2
commit
f265c72a55
5 changed files with 71 additions and 52 deletions
|
|
@ -24,6 +24,7 @@ const enforceSsl = require('express-enforces-ssl');
|
|||
const path = require('path');
|
||||
const qs = require('qs');
|
||||
const sharetribeSdk = require('sharetribe-sdk');
|
||||
const Decimal = require('decimal.js');
|
||||
const auth = require('./auth');
|
||||
const renderer = require('./renderer');
|
||||
const dataLoader = require('./dataLoader');
|
||||
|
|
@ -89,6 +90,14 @@ app.get('*', (req, res) => {
|
|||
req,
|
||||
res,
|
||||
}),
|
||||
typeHandlers: [
|
||||
{
|
||||
type: sharetribeSdk.types.BigDecimal,
|
||||
customType: Decimal,
|
||||
writer: v => new sharetribeSdk.types.BigDecimal(v.toString()),
|
||||
reader: v => new Decimal(v.value),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dataLoader
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* This component will show the booking info and calculated total price.
|
||||
* I.e. dates and other details related to payment decision in receipt format.
|
||||
*/
|
||||
import _ from 'lodash';
|
||||
import React, { PropTypes } from 'react';
|
||||
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
||||
import Decimal from 'decimal.js';
|
||||
|
|
@ -9,7 +10,6 @@ import classNames from 'classnames';
|
|||
import config from '../../config';
|
||||
import { convertMoneyToNumber } from '../../util/currency';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { nightsBetween } from '../../util/dates';
|
||||
|
||||
import css from './BookingBreakdown.css';
|
||||
|
||||
|
|
@ -19,9 +19,10 @@ export const BookingBreakdownComponent = props => {
|
|||
className,
|
||||
bookingStart,
|
||||
bookingEnd,
|
||||
unitPrice,
|
||||
commission,
|
||||
totalPrice,
|
||||
payinTotal,
|
||||
payoutTotal,
|
||||
lineItems,
|
||||
userRole,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
|
|
@ -50,7 +51,13 @@ export const BookingBreakdownComponent = props => {
|
|||
/>
|
||||
);
|
||||
|
||||
const nightCount = nightsBetween(bookingStart, bookingEnd);
|
||||
const nightPurchase = _.find(lineItems, item => item.code === 'line-item.purchase/night');
|
||||
const providerCommission = _.find(
|
||||
lineItems,
|
||||
item => item.code === 'line-item.commission/provider'
|
||||
);
|
||||
|
||||
const nightCount = nightPurchase.quantity.toFixed();
|
||||
const nightCountMessage = (
|
||||
<FormattedMessage id="BookingBreakdown.nightCount" values={{ count: nightCount }} />
|
||||
);
|
||||
|
|
@ -58,10 +65,11 @@ export const BookingBreakdownComponent = props => {
|
|||
const currencyConfig = config.currencyConfig;
|
||||
const subUnitDivisor = currencyConfig.subUnitDivisor;
|
||||
|
||||
const unitPriceAsNumber = convertMoneyToNumber(unitPrice, subUnitDivisor);
|
||||
const unitPriceAsNumber = convertMoneyToNumber(nightPurchase.unitPrice, subUnitDivisor);
|
||||
const formattedUnitPrice = intl.formatNumber(unitPriceAsNumber, currencyConfig);
|
||||
|
||||
// If commission is passed it will be shown as a fee already reduces from the total price
|
||||
const commission = providerCommission.lineTotal;
|
||||
const commissionAsNumber = commission ? convertMoneyToNumber(commission, subUnitDivisor) : 0;
|
||||
const formattedCommission = commission
|
||||
? intl.formatNumber(new Decimal(commissionAsNumber).negated().toNumber(), currencyConfig)
|
||||
|
|
@ -76,7 +84,10 @@ export const BookingBreakdownComponent = props => {
|
|||
</div>
|
||||
);
|
||||
|
||||
const totalPriceAsNumber = convertMoneyToNumber(totalPrice, subUnitDivisor);
|
||||
const totalPriceAsNumber = convertMoneyToNumber(
|
||||
userRole === 'customer' ? payinTotal : payoutTotal,
|
||||
subUnitDivisor
|
||||
);
|
||||
const formattedTotalPrice = totalPriceAsNumber
|
||||
? intl.formatNumber(totalPriceAsNumber, currencyConfig)
|
||||
: null;
|
||||
|
|
@ -95,7 +106,7 @@ export const BookingBreakdownComponent = props => {
|
|||
</span>
|
||||
<span className={css.itemValue}>{nightCountMessage}</span>
|
||||
</div>
|
||||
{commission ? commissionInfo : null}
|
||||
{userRole === 'provider' ? commissionInfo : null}
|
||||
<hr className={css.totalDivider} />
|
||||
<div className={css.lineItem}>
|
||||
<div className={css.totalLabel}>
|
||||
|
|
@ -115,7 +126,14 @@ BookingBreakdownComponent.defaultProps = {
|
|||
commission: null,
|
||||
};
|
||||
|
||||
const { string, instanceOf } = PropTypes;
|
||||
const { string, instanceOf, arrayOf, oneOf, shape } = PropTypes;
|
||||
|
||||
const lineItem = shape({
|
||||
code: string.isRequired,
|
||||
quantity: instanceOf(Decimal),
|
||||
unitPrice: propTypes.money,
|
||||
lineTotal: propTypes.money,
|
||||
});
|
||||
|
||||
BookingBreakdownComponent.propTypes = {
|
||||
rootClassName: string,
|
||||
|
|
@ -123,10 +141,10 @@ BookingBreakdownComponent.propTypes = {
|
|||
|
||||
bookingStart: instanceOf(Date).isRequired,
|
||||
bookingEnd: instanceOf(Date).isRequired,
|
||||
|
||||
unitPrice: propTypes.money.isRequired,
|
||||
totalPrice: propTypes.money.isRequired,
|
||||
commission: propTypes.money,
|
||||
lineItems: arrayOf(lineItem).isRequired,
|
||||
userRole: oneOf(['customer', 'provider']).isRequired,
|
||||
payinTotal: propTypes.money.isRequired,
|
||||
payoutTotal: propTypes.money.isRequired,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import css from './OrderDetailsPanel.css';
|
|||
|
||||
const breakdown = transaction => {
|
||||
const tx = ensureTransaction(transaction);
|
||||
const listing = ensureListing(tx.listing);
|
||||
const booking = ensureBooking(tx.booking);
|
||||
const bookingStart = booking.attributes.start;
|
||||
const bookingEnd = booking.attributes.end;
|
||||
const unitPrice = listing.attributes.price;
|
||||
const totalPrice = tx.attributes.payinTotal;
|
||||
const payinTotal = tx.attributes.payinTotal;
|
||||
const payoutTotal = tx.attributes.payoutTotal;
|
||||
const lineItems = tx.attributes.lineItems;
|
||||
|
||||
if (!bookingStart || !bookingEnd || !unitPrice || !totalPrice) {
|
||||
if (!bookingStart || !bookingEnd || !payinTotal || !payoutTotal || !lineItems) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
|
@ -25,8 +25,10 @@ const breakdown = transaction => {
|
|||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
payinTotal={payinTotal}
|
||||
payoutTotal={payoutTotal}
|
||||
lineItems={lineItems}
|
||||
userRole="customer"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,59 +1,35 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
import Decimal from 'decimal.js';
|
||||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import { ensureListing, ensureTransaction, ensureBooking, ensureUser } from '../../util/data';
|
||||
import { convertMoneyToNumber, convertUnitToSubUnit } from '../../util/currency';
|
||||
import config from '../../config';
|
||||
import { Avatar, BookingBreakdown, NamedLink } from '../../components';
|
||||
|
||||
import css from './SaleDetailsPanel.css';
|
||||
|
||||
// TODO: This is a temporary function to calculate the booking
|
||||
// price. This should be removed when the API supports dry-runs and we
|
||||
// can take the total price from the transaction itself.
|
||||
const estimatedCommission = (customerTotalPrice, providerTotalPrice) => {
|
||||
const { subUnitDivisor, currency } = config.currencyConfig;
|
||||
if (customerTotalPrice.currency !== currency || providerTotalPrice.currency !== currency) {
|
||||
throw new Error('Transaction total or commission currency does not match marketplace currency');
|
||||
}
|
||||
|
||||
const numericCustomerTotalPrice = convertMoneyToNumber(customerTotalPrice, subUnitDivisor);
|
||||
const numericProviderTotalPrice = convertMoneyToNumber(providerTotalPrice, subUnitDivisor);
|
||||
const numericCommission = new Decimal(numericProviderTotalPrice)
|
||||
.minus(numericCustomerTotalPrice)
|
||||
.toNumber();
|
||||
|
||||
return new types.Money(convertUnitToSubUnit(numericCommission, subUnitDivisor), currency);
|
||||
};
|
||||
|
||||
const breakdown = transaction => {
|
||||
const tx = ensureTransaction(transaction);
|
||||
const listing = ensureListing(tx.listing);
|
||||
const booking = ensureBooking(tx.booking);
|
||||
const bookingStart = booking.attributes.start;
|
||||
const bookingEnd = booking.attributes.end;
|
||||
const unitPrice = listing.attributes.price;
|
||||
const customerTotalPrice = tx.attributes.payinTotal;
|
||||
const providerTotalPrice = tx.attributes.payoutTotal;
|
||||
const payinTotal = tx.attributes.payinTotal;
|
||||
const payoutTotal = tx.attributes.payoutTotal;
|
||||
const lineItems = tx.attributes.lineItems;
|
||||
|
||||
if (!bookingStart || !bookingEnd || !unitPrice || !customerTotalPrice || !providerTotalPrice) {
|
||||
if (!bookingStart || !bookingEnd || !payinTotal || !payoutTotal || !lineItems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const commission = estimatedCommission(customerTotalPrice, providerTotalPrice);
|
||||
|
||||
return (
|
||||
<BookingBreakdown
|
||||
className={css.breakdown}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={providerTotalPrice}
|
||||
commission={commission}
|
||||
payinTotal={payinTotal}
|
||||
payoutTotal={payoutTotal}
|
||||
lineItems={lineItems}
|
||||
userRole="provider"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
16
src/index.js
16
src/index.js
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Decimal from 'decimal.js';
|
||||
import { createInstance, types } from './util/sdkLoader';
|
||||
import { ClientApp, renderApp } from './app';
|
||||
import configureStore from './store';
|
||||
|
|
@ -25,6 +26,8 @@ import routeConfiguration from './routesConfiguration';
|
|||
|
||||
import './marketplace.css';
|
||||
|
||||
const { BigDecimal } = types;
|
||||
|
||||
const render = store => {
|
||||
// If the server already loaded the auth information, render the app
|
||||
// immediately. Otherwise wait for the flag to be loaded and render
|
||||
|
|
@ -54,7 +57,18 @@ if (typeof window !== 'undefined') {
|
|||
// eslint-disable-next-line no-underscore-dangle
|
||||
const preloadedState = window.__PRELOADED_STATE__ || '{}';
|
||||
const initialState = JSON.parse(preloadedState, types.reviver);
|
||||
const sdk = createInstance({ clientId: config.sdk.clientId, baseUrl: config.sdk.baseUrl });
|
||||
const sdk = createInstance({
|
||||
clientId: config.sdk.clientId,
|
||||
baseUrl: config.sdk.baseUrl,
|
||||
typeHandlers: [
|
||||
{
|
||||
type: BigDecimal,
|
||||
customType: Decimal,
|
||||
writer: v => new BigDecimal(v.toString()),
|
||||
reader: v => new Decimal(v.value),
|
||||
},
|
||||
],
|
||||
});
|
||||
const store = configureStore(sdk, initialState);
|
||||
|
||||
setupStripe();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue