Skip to content

Commit

Permalink
Merge pull request #1062 from sharetribe/custom-pricing-support
Browse files Browse the repository at this point in the history
Add support for arbitrary line item codes
  • Loading branch information
lyyder authored Apr 5, 2019
2 parents 236798d + 551dff0 commit b929493
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 41 deletions.
15 changes: 15 additions & 0 deletions src/components/BookingBreakdown/BookingBreakdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@
justify-content: space-between;
}

.subTotalLineItem {
@apply --marketplaceH4FontStyles;
font-weight: var(--fontWeightBold);

margin: 4px 0 1px 0;

@media (--viewportMedium) {
margin: 6px 0 9px 0;
}

display: flex;
flex-direction: row;
justify-content: space-between;
}

.itemLabel {
flex: 1;
margin-top: 1px; /* align with baseline */
Expand Down
132 changes: 132 additions & 0 deletions src/components/BookingBreakdown/BookingBreakdown.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,135 @@ export const UnitsType = {
}),
},
};

export const CustomPricing = {
component: BookingBreakdown,
props: {
userRole: 'customer',
unitType: LINE_ITEM_NIGHT,
transaction: exampleTransaction({
payinTotal: new Money(12800, CURRENCY),
payoutTotal: new Money(12600, CURRENCY),
lineItems: [
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(9000, CURRENCY),
reversal: false,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(5000, CURRENCY),
reversal: false,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(-10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(-1400, CURRENCY),
reversal: false,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(200, CURRENCY),
reversal: false,
},
],
}),
booking: exampleBooking({
start: new Date(Date.UTC(2017, 3, 14)),
end: new Date(Date.UTC(2017, 3, 16)),
}),
},
};

export const CustomPricingWithRefund = {
component: BookingBreakdown,
props: {
userRole: 'customer',
unitType: LINE_ITEM_NIGHT,
transaction: exampleTransaction({
payinTotal: new Money(0, CURRENCY),
payoutTotal: new Money(0, CURRENCY),
lineItems: [
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(9000, CURRENCY),
reversal: false,
},
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(-2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(-9000, CURRENCY),
reversal: true,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(5000, CURRENCY),
reversal: false,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(-1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(-5000, CURRENCY),
reversal: true,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(-10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(-1400, CURRENCY),
reversal: false,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(1400, CURRENCY),
reversal: true,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(200, CURRENCY),
reversal: false,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(-10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(-200, CURRENCY),
reversal: true,
},
],
}),
booking: exampleBooking({
start: new Date(Date.UTC(2017, 3, 14)),
end: new Date(Date.UTC(2017, 3, 16)),
}),
},
};
10 changes: 7 additions & 3 deletions src/components/BookingBreakdown/BookingBreakdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
LINE_ITEM_PROVIDER_COMMISSION,
} from '../../util/types';

import LineItemUnitPrice from './LineItemUnitPrice';
import LineItemUnitPriceMaybe from './LineItemUnitPriceMaybe';
import LineItemBookingPeriod from './LineItemBookingPeriod';
import LineItemUnitsMaybe from './LineItemUnitsMaybe';
import LineItemSubTotalMaybe from './LineItemSubTotalMaybe';
Expand All @@ -22,6 +22,8 @@ import LineItemProviderCommissionMaybe from './LineItemProviderCommissionMaybe';
import LineItemProviderCommissionRefundMaybe from './LineItemProviderCommissionRefundMaybe';
import LineItemRefundMaybe from './LineItemRefundMaybe';
import LineItemTotalPrice from './LineItemTotalPrice';
import LineItemUnknownItemsMaybe from './LineItemUnknownItemsMaybe';

import css from './BookingBreakdown.css';

export const BookingBreakdownComponent = props => {
Expand All @@ -40,17 +42,19 @@ export const BookingBreakdownComponent = props => {

return (
<div className={classes}>
<LineItemUnitPrice transaction={transaction} unitType={unitType} intl={intl} />
<LineItemUnitPriceMaybe transaction={transaction} unitType={unitType} intl={intl} />
<LineItemBookingPeriod transaction={transaction} booking={booking} unitType={unitType} />
<LineItemUnitsMaybe transaction={transaction} unitType={unitType} />

<LineItemUnknownItemsMaybe transaction={transaction} intl={intl} />

<LineItemSubTotalMaybe
transaction={transaction}
unitType={unitType}
userRole={userRole}
intl={intl}
/>
<LineItemRefundMaybe transaction={transaction} unitType={unitType} intl={intl} />
<LineItemRefundMaybe transaction={transaction} intl={intl} />

<LineItemCustomerCommissionMaybe
transaction={transaction}
Expand Down
8 changes: 4 additions & 4 deletions src/components/BookingBreakdown/BookingBreakdown.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,17 @@ describe('BookingBreakdown', () => {
{
code: 'line-item/provider-commission',
includeFor: ['provider'],
quantity: new Decimal(1),
percentage: new Decimal(-10),
lineTotal: new Money(-200, 'USD'),
unitPrice: new Money(-200, 'USD'),
unitPrice: new Money(2000, 'USD'),
reversal: false,
},
{
code: 'line-item/provider-commission',
includeFor: ['provider'],
quantity: new Decimal(-1),
percentage: new Decimal(10),
lineTotal: new Money(200, 'USD'),
unitPrice: new Money(-200, 'USD'),
unitPrice: new Money(2000, 'USD'),
reversal: true,
},
],
Expand Down
6 changes: 1 addition & 5 deletions src/components/BookingBreakdown/LineItemBookingPeriod.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,8 @@ const LineItemBookingPeriod = props => {
item => item.code === unitType && !item.reversal
);

if (!unitPurchase) {
throw new Error(`LineItemBookingPeriod: lineItem (${unitType}) missing`);
}

const useQuantityForDayCount = isNightly || isDaily;
const count = useQuantityForDayCount ? unitPurchase.quantity.toFixed() : dayCount;
const count = useQuantityForDayCount && unitPurchase ? unitPurchase.quantity.toFixed() : dayCount;

const unitCountMessage = (
<FormattedHTMLMessage
Expand Down
53 changes: 46 additions & 7 deletions src/components/BookingBreakdown/LineItemRefundMaybe.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,62 @@
import React from 'react';
import { FormattedMessage, intlShape } from 'react-intl';
import Decimal from 'decimal.js';
import { formatMoney } from '../../util/currency';
import { propTypes } from '../../util/types';
import { types as sdkTypes } from '../../util/sdkLoader';
import config from '../../config';
import {
propTypes,
LINE_ITEM_CUSTOMER_COMMISSION,
LINE_ITEM_PROVIDER_COMMISSION,
} from '../../util/types';

import css from './BookingBreakdown.css';

const LineItemRefundMaybe = props => {
const { transaction, unitType, intl } = props;
const { Money } = sdkTypes;

/**
* Calculates the total price in sub units for multiple line items.
*/
const lineItemsTotal = lineItems => {
const amount = lineItems.reduce((total, item) => {
return total.plus(item.lineTotal.amount);
}, new Decimal(0));
return new Money(amount, config.currency);
};

const refund = transaction.attributes.lineItems.find(
item => item.code === unitType && item.reversal
/**
* Checks if line item represents commission
*/
const isCommission = lineItem => {
return (
lineItem.code === LINE_ITEM_PROVIDER_COMMISSION ||
lineItem.code === LINE_ITEM_CUSTOMER_COMMISSION
);
};

/**
* Returns non-commission, reversal line items
*/
const nonCommissionReversalLineItems = transaction => {
return transaction.attributes.lineItems.filter(item => !isCommission(item) && item.reversal);
};

const LineItemRefundMaybe = props => {
const { transaction, intl } = props;

// all non-commission, reversal line items
const refundLineItems = nonCommissionReversalLineItems(transaction);

const refund = lineItemsTotal(refundLineItems);

const formattedRefund = refundLineItems.length > 0 ? formatMoney(intl, refund) : null;

return refund ? (
return formattedRefund ? (
<div className={css.lineItem}>
<span className={css.itemLabel}>
<FormattedMessage id="BookingBreakdown.refund" />
</span>
<span className={css.itemValue}>{formatMoney(intl, refund.lineTotal)}</span>
<span className={css.itemValue}>{formattedRefund}</span>
</div>
) : null;
};
Expand Down
48 changes: 39 additions & 9 deletions src/components/BookingBreakdown/LineItemSubTotalMaybe.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { string } from 'prop-types';
import { FormattedMessage, intlShape } from 'react-intl';
import Decimal from 'decimal.js';
import { formatMoney } from '../../util/currency';
import config from '../../config';
import { types as sdkTypes } from '../../util/sdkLoader';
import {
propTypes,
LINE_ITEM_CUSTOMER_COMMISSION,
Expand All @@ -10,6 +13,35 @@ import {

import css from './BookingBreakdown.css';

const { Money } = sdkTypes;

/**
* Calculates the total price in sub units for multiple line items.
*/
const lineItemsTotal = lineItems => {
const amount = lineItems.reduce((total, item) => {
return total.plus(item.lineTotal.amount);
}, new Decimal(0));
return new Money(amount, config.currency);
};

/**
* Checks if line item represents commission
*/
const isCommission = lineItem => {
return (
lineItem.code === LINE_ITEM_PROVIDER_COMMISSION ||
lineItem.code === LINE_ITEM_CUSTOMER_COMMISSION
);
};

/**
* Returns non-commission, non-reversal line items
*/
const nonCommissionNonReversalLineItems = transaction => {
return transaction.attributes.lineItems.filter(item => !isCommission(item) && !item.reversal);
};

/**
* Checks if a transaction has a commission line-item for
* a given user role.
Expand Down Expand Up @@ -40,18 +72,16 @@ const LineItemSubTotalMaybe = props => {
// PLEASE NOTE that this assumes that the transaction doesn't have other
// line item types than the defined unit type (e.g. week, month, year).
const showSubTotal = txHasCommission(transaction, userRole) || refund;
const unitPurchase = transaction.attributes.lineItems.find(
item => item.code === unitType && !item.reversal
);

if (!unitPurchase) {
throw new Error(`LineItemSubTotalMaybe: lineItem (${unitType}) missing`);
}
// all non-reversal, non-commission line items
const subTotalLineItems = nonCommissionNonReversalLineItems(transaction);
// line totals of those line items combined
const subTotal = lineItemsTotal(subTotalLineItems);

const formattedSubTotal = formatMoney(intl, unitPurchase.lineTotal);
const formattedSubTotal = subTotalLineItems.length > 0 ? formatMoney(intl, subTotal) : null;

return showSubTotal ? (
<div className={css.lineItem}>
return formattedSubTotal && showSubTotal ? (
<div className={css.subTotalLineItem}>
<span className={css.itemLabel}>
<FormattedMessage id="BookingBreakdown.subTotal" />
</span>
Expand Down
Loading

0 comments on commit b929493

Please sign in to comment.