-
-
Save SariSaar/b3f2d7c4b23182666f63988c7620fb50 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Fetch cart transaction line items from the local API endpoint. | |
// | |
// See `server/api/cart-transaction-line-items.js` to see what data should | |
// be sent in the body. | |
export const cartTransactionLineItems = body => { | |
return post('/api/cart-transaction-line-items', body); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cartTransactionLineItems = require('./api/cart-transaction-line-items'); | |
// ================ API router endpoints: ================ // | |
... | |
router.post('/cart-transaction-line-items', cartTransactionLineItems); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { transactionLineItems } = require('../api-util/cartLineItems'); | |
const { getSdk, handleError, serialize, fetchCommission } = require('../api-util/sdk'); | |
const { constructValidLineItems, getListingIdsFromCart } = require('../api-util/lineItemHelpers'); | |
module.exports = (req, res) => { | |
const { isOwnListing, orderData } = req.body; | |
const listingIds = getListingIdsFromCart(orderData?.cart); | |
const sdk = getSdk(req, res); | |
const listingPromise = isOwnListing | |
? () => sdk.ownListings.query({ ids: listingIds }) | |
: () => sdk.listings.query({ ids: listingIds }); | |
Promise.all([listingPromise(), fetchCommission(sdk)]) | |
.then(([showListingResponse, fetchAssetsResponse]) => { | |
const listings = showListingResponse.data.data; | |
const commissionAsset = fetchAssetsResponse.data.data[0]; | |
const { providerCommission, customerCommission } = | |
commissionAsset?.type === 'jsonAsset' ? commissionAsset.attributes.data : {}; | |
const lineItems = transactionLineItems( | |
listings, | |
orderData, | |
providerCommission, | |
customerCommission | |
); | |
// Because we are using returned lineItems directly in this template we need to use the helper function | |
// to add some attributes like lineTotal and reversal that Marketplace API also adds to the response. | |
const validLineItems = constructValidLineItems(lineItems); | |
res | |
.status(200) | |
.set('Content-Type', 'application/transit+json') | |
.send(serialize({ data: validLineItems })) | |
.end(); | |
}) | |
.catch(e => { | |
handleError(res, e); | |
console.log('e.data', JSON.stringify(e.data, null, 2)); | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
calculateTotalFromLineItems, | |
calculateShippingFee, | |
hasCommissionPercentage, | |
calculateTotalPriceFromQuantity, | |
} = require('./lineItemHelpers'); | |
const { types } = require('sharetribe-flex-sdk'); | |
const { Money } = types; | |
const Decimal = require('decimal.js'); | |
/** | |
* Get cart line items and add extra line-items that are related to delivery method | |
* | |
* @param {Object} orderData should contain a cart object with listing ids and their respective counts | |
* @param {*} listings should contain public data with shipping prices | |
* @param {*} currency should point to the currency of listing's price. | |
*/ | |
const getItemCartLineItems = (orderData, listings, currency) => { | |
const { cart } = orderData; | |
const { deliveryMethod } = cart; | |
const isShipping = deliveryMethod === 'shipping'; | |
const isPickup = deliveryMethod === 'pickup'; | |
let orderQuantity = 0; | |
let mainShippingPriceInSubunitsOneItem = 0; | |
let mainShippingPriceInSubunitsAdditionalItems = 0; | |
// Create listing line items | |
const listingLineItems = listings.map(l => { | |
const listingInCart = cart[l.id.uuid]; | |
const { | |
unitType, | |
shippingPriceInSubunitsOneItem, | |
shippingPriceInSubunitsAdditionalItems, | |
} = l.attributes.publicData; | |
const code = `line-item/${unitType}`; | |
const quan = parseFloat(listingInCart.count); | |
const unitPrice = l.attributes.price; | |
orderQuantity += quan; | |
// Set the main shipping price to be the highest shipping price across all listings | |
if (isShipping && shippingPriceInSubunitsOneItem > mainShippingPriceInSubunitsOneItem) { | |
mainShippingPriceInSubunitsOneItem = shippingPriceInSubunitsOneItem; | |
mainShippingPriceInSubunitsAdditionalItems = shippingPriceInSubunitsAdditionalItems; | |
} | |
return { | |
code, | |
unitPrice, | |
quantity: new Decimal(quan), | |
lineTotal: calculateTotalPriceFromQuantity(unitPrice, quan), | |
includeFor: ['customer', 'provider'], | |
}; | |
}); | |
// const { shippingPriceInSubunitsOneItem, shippingPriceInSubunitsAdditionalItems } = | |
// publicData || {}; | |
// Calculate shipping fee if applicable | |
const shippingFee = isShipping | |
? calculateShippingFee( | |
mainShippingPriceInSubunitsOneItem, | |
mainShippingPriceInSubunitsAdditionalItems, | |
currency, | |
orderQuantity | |
) | |
: null; | |
// Add line-item for given delivery method. | |
// Note: by default, pickup considered as free. | |
const deliveryLineItem = !!shippingFee | |
? [ | |
{ | |
code: 'line-item/shipping-fee', | |
unitPrice: shippingFee, | |
quantity: 1, | |
includeFor: ['customer', 'provider'], | |
}, | |
] | |
: isPickup | |
? [ | |
{ | |
code: 'line-item/pickup-fee', | |
unitPrice: new Money(0, currency), | |
quantity: 1, | |
includeFor: ['customer', 'provider'], | |
}, | |
] | |
: []; | |
return { listingLineItems, deliveryLineItem }; | |
}; | |
/** | |
* Returns collection of lineItems (max 50) | |
* | |
* All the line-items dedicated to _customer_ define the "payin total". | |
* Similarly, the sum of all the line-items included for _provider_ create "payout total". | |
* Platform gets the commission, which is the difference between payin and payout totals. | |
* | |
* Each line items has following fields: | |
* - `code`: string, mandatory, indentifies line item type (e.g. \"line-item/cleaning-fee\"), maximum length 64 characters. | |
* - `unitPrice`: money, mandatory | |
* - `lineTotal`: money | |
* - `quantity`: number | |
* - `percentage`: number (e.g. 15.5 for 15.5%) | |
* - `seats`: number | |
* - `units`: number | |
* - `includeFor`: array containing strings \"customer\" or \"provider\", default [\":customer\" \":provider\" ] | |
* | |
* Line item must have either `quantity` or `percentage` or both `seats` and `units`. | |
* | |
* `includeFor` defines commissions. Customer commission is added by defining `includeFor` array `["customer"]` and provider commission by `["provider"]`. | |
* | |
* @param {Object} listing | |
* @param {Object} orderData | |
* @param {Object} providerCommission | |
* @returns {Array} lineItems | |
*/ | |
exports.transactionLineItems = (listings, orderData, providerCommission, customerCommission) => { | |
const listing = listings[0]; | |
const publicData = listing.attributes.publicData; | |
const unitPrice = listing.attributes.price; | |
const currency = unitPrice.currency; | |
/** | |
* Pricing starts with order's base price: | |
* Listing's price is related to a single unit. It needs to be multiplied by quantity | |
* | |
* Initial line-item needs therefore: | |
* - code (based on unitType) | |
* - unitPrice | |
* - quantity | |
* - includedFor | |
*/ | |
// Unit type needs to be one of the following: | |
// day, night, hour or item | |
const unitType = publicData.unitType; | |
// Here "extra line-items" mean line-items that are tied to unit type | |
// E.g. by default, "shipping-fee" is tied to 'item' aka buying products. | |
// Currently only products can use a cart, so we only return line items for | |
// unitType: 'item'. | |
const cartAndExtraLineItems = | |
unitType === 'item' | |
? getItemCartLineItems(orderData, listings, currency) | |
: {}; | |
const { listingLineItems, deliveryLineItem } = cartAndExtraLineItems; | |
// Provider commission reduces the amount of money that is paid out to provider. | |
// Therefore, the provider commission line-item should have negative effect to the payout total. | |
const getNegation = percentage => { | |
return -1 * percentage; | |
}; | |
// Note: extraLineItems for product selling (aka shipping fee) | |
// is not included to commission calculation. | |
const providerCommissionMaybe = hasCommissionPercentage(providerCommission) | |
? [ | |
{ | |
code: 'line-item/provider-commission', | |
unitPrice: calculateTotalFromLineItems([...listingLineItems]), | |
percentage: getNegation(providerCommission.percentage), | |
includeFor: ['provider'], | |
}, | |
] | |
: []; | |
// The customer commission is what the customer pays for the transaction, and | |
// it is added on top of the order price to get the customer's payin price: | |
// orderPrice + customerCommission = customerPayin | |
const customerCommissionMaybe = hasCommissionPercentage(customerCommission) | |
? [ | |
{ | |
code: 'line-item/customer-commission', | |
unitPrice: calculateTotalFromLineItems([...listingLineItems]), | |
percentage: customerCommission.percentage, | |
includeFor: ['customer'], | |
}, | |
] | |
: []; | |
// Let's keep the base price (order) as first line item and provider's commission as last one. | |
// Note: the order matters only if OrderBreakdown component doesn't recognize line-item. | |
const lineItems = [ | |
...listingLineItems, | |
...deliveryLineItem, | |
...providerCommissionMaybe, | |
...customerCommissionMaybe, | |
]; | |
return lineItems; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { cartTransactionLineItems } from '../../util/api'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Return listing ids from a single vendor shopping cart, filtering | |
* out delivery method | |
* @param {*} cart | |
* @returns | |
*/ | |
exports.getListingIdsFromCart = cart => { | |
return Object.keys(cart).filter(key => key !== 'deliveryMethod'); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
const unitLineItems = lineItems.filter(li => li.code === lineItemUnitType); | |
const basePrices = unitLineItems.map((uli, idx) => ( | |
<LineItemBasePriceMaybe lineItems={[uli]} key={idx} code={lineItemUnitType} intl={intl} /> | |
)); | |
... | |
{/* replace the single LineItemBasePriceMaybe component with {basePrices} */} | |
{basePrices} | |
<LineItemShippingFeeMaybe lineItems={lineItems} intl={intl} /> | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment