Skip to content

Commit

Permalink
Merge pull request #83 from vtex-apps/feat/product-installments-list-…
Browse files Browse the repository at this point in the history
…criteria

Add new props 'installmentsCriteria' and 'installmentOptionsFilter' to 'product-installments' block
  • Loading branch information
victorhmp authored Jul 13, 2021
2 parents e1b3cb8 + 7a05c69 commit ff17626
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `installmentsCriteria` and `installmentOptionsFilter` props to `Installments` component.

## [1.23.0] - 2021-07-13
### Added
Expand Down
9 changes: 8 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Now, you can use all the blocks exported by the `product-price` app. Check out t
| `product-list-price` | Renders the product list price. If it is equal or lower than the product selling price, this block will not be rendered. |
| `product-selling-price` | Renders the product selling price.|
| `product-spot-price` | Renders the product spot price (in case it equals the product selling price, the block is not rendered). This block finds the spot price by looking for the cheapest price of all installments options.|
| `product-installments` | Renders the product installments. If more than one option is available, the one with the biggest number of installments will be displayed. |
| `product-installments` | Renders the product installments. If more than one option is available, the one with the biggest number of installments will be displayed by default. |
| `product-installments-list` | Renders all the installments of the payment system with the biggest amount of installments options by default. |
| `product-installments-list-item` | Renders an installments option of the `product-installments-list-item` |
| `product-price-savings` | Renders the product price savings, if there is any. It can show the percentage of the discount or the value of the absolute saving. |
Expand Down Expand Up @@ -102,6 +102,13 @@ The block `product-installments-list` has two additional props:
| `paymentSystemName` | `string` | This prop enables you to filter the listed installments options by a certain payment system. If not passed, the installments of the payment system with the biggest amount of installments options will be rendered. | `undefined` |
| `installmentsToShow` | `number[]` | Which installments options you want to show the user, in terms of the number of installments. For example, if `[1, 3]` is passed as a value for this prop, only the installments options with `NumberOfInstallments` equal to 1 and 3 will be rendered. If not passed, all options will be rendered. | `undefined` |

And the block `product-installments` also has two additional props:

| Prop name | Type | Description | Default value |
| --------------------| ----------|--------------|---------------|
| `installmentsCriteria` | `max-quantity` or `max-quantity-without-interest` | When set to `max-quantity`, the block will render the installments plan with the biggest number of installments. When set to `max-quantity-without-interest`, the block will render the installments plan with the biggest number of installments and **zero interest**. Notice that, if this prop is set to `max-quantity-without-interest`, and no installments plan matches the 'without interest' criteria, the component will fallback the default behavior. | `max-quantity` |
| `installmentOptionsFilter` | `{ paymentSystemName?: string, installmentsQuantity?: number }` | Allows you to define two filtering rules that will narrow down the possible installments plans the component might render. | `undefined` |


If you are using the asynchronous price feature, you can take advantage of the `product-price-suspense` and its props:

Expand Down
40 changes: 31 additions & 9 deletions react/Installments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { useProduct } from 'vtex.product-context'
import InstallmentsRenderer, {
CSS_HANDLES,
} from './components/InstallmentsRenderer'
import {
pickMaxInstallmentsOption,
pickMaxInstallmentsOptionWithoutInterest,
} from './modules/pickInstallments'
import { getDefaultSeller } from './modules/seller'

const messages = defineMessages({
Expand All @@ -23,13 +27,20 @@ const messages = defineMessages({
interface Props {
message?: string
markers?: string[]
installmentsCriteria?: 'max-quantity' | 'max-quantity-without-interest'
installmentOptionsFilter?: {
paymentSystemName?: string
installmentsQuantity?: number
}
/** Used to override default CSS handles */
classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function Installments({
message = messages.default.id,
markers = [],
installmentsCriteria = 'max-quantity',
installmentOptionsFilter,
classes,
}: Props) {
const productContextValue = useProduct()
Expand All @@ -45,22 +56,33 @@ function Installments({
return null
}

let [maxInstallmentOption] = commercialOffer.Installments
let [installmentsOption] = commercialOffer.Installments

switch (installmentsCriteria) {
case 'max-quantity-without-interest': {
installmentsOption = pickMaxInstallmentsOptionWithoutInterest(
commercialOffer.Installments,
installmentOptionsFilter
)

break
}

default: {
installmentsOption = pickMaxInstallmentsOption(
commercialOffer.Installments,
installmentOptionsFilter
)

commercialOffer.Installments.forEach(installmentOption => {
if (
installmentOption.NumberOfInstallments >
maxInstallmentOption.NumberOfInstallments
) {
maxInstallmentOption = installmentOption
break
}
})
}

return (
<InstallmentsRenderer
message={message}
markers={markers}
installment={maxInstallmentOption}
installment={installmentsOption}
handles={handles}
handlesModifierFunction={withModifiers}
/>
Expand Down
10 changes: 5 additions & 5 deletions react/__mocks__/installments-list-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@ import { Installments } from '../components/InstallmentsContext'
export const visaInstallments: Installments[] = [
{
Value: 44,
InterestRate: 0,
InterestRate: 3,
TotalValuePlusInterestRate: 44,
NumberOfInstallments: 1,
PaymentSystemName: 'Visa',
},
{
Value: 22,
InterestRate: 0,
InterestRate: 4,
TotalValuePlusInterestRate: 44,
NumberOfInstallments: 2,
PaymentSystemName: 'Visa',
},
{
Value: 14.7,
InterestRate: 0,
InterestRate: 5,
TotalValuePlusInterestRate: 44,
NumberOfInstallments: 3,
PaymentSystemName: 'Visa',
},
{
Value: 11,
InterestRate: 0,
InterestRate: 3,
TotalValuePlusInterestRate: 44,
NumberOfInstallments: 4,
PaymentSystemName: 'Visa',
Expand Down Expand Up @@ -135,7 +135,7 @@ export const installmentsListMastercardMax = [
},
{
Value: 44,
InterestRate: 0,
InterestRate: 5,
TotalValuePlusInterestRate: 44,
NumberOfInstallments: 15,
PaymentSystemName: 'Mastercard',
Expand Down
84 changes: 81 additions & 3 deletions react/__tests__/pickInstallments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
installmentsListMastercardMax,
} from 'installments-list-mock'

import pickInstallmentsList from '../modules/pickInstallments'
import pickInstallmentsList, {
pickMaxInstallmentsOption,
pickMaxInstallmentsOptionWithoutInterest,
} from '../modules/pickInstallments'

describe('pickInstallments', () => {
it('should pick installments of the paymentmethod with the biggest amount', () => {
describe('pickInstallmentsList', () => {
it('should pick installments of the payment method with the biggest amount of options', () => {
const pickedInstallments = pickInstallmentsList(
installmentsList,
'PaymentSystemName'
Expand Down Expand Up @@ -40,3 +43,78 @@ describe('pickInstallments', () => {
expect(pickedInstallments[index].NumberOfInstallments).toBe(15)
})
})

describe('pickMaxInstallmentsOption', () => {
it('should pick the installments plan with the highest NumberOfInstallments', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOption(
installmentsListMastercardMax
)

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(15)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Mastercard')
})

it('should pick the installments plan with the highest NumberOfInstallments, from a certain payment system', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOption(installmentsList, {
paymentSystemName: 'Customer Credit',
})

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(3)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
})

it('should pick an installments plan with a certain number of installments', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOption(
installmentsListMastercardMax,
{
installmentsQuantity: 14,
}
)

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(14)
})

it('should pick the installments plan with the highest NumberOfInstallments, using all filtering options', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOption(installmentsList, {
paymentSystemName: 'Customer Credit',
installmentsQuantity: 2,
})

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(2)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
})
})

describe('pickMaxInstallmentsOptionWithoutInterest', () => {
it('should pick the installments plan with the highest NumberOfInstallments and no interest', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
installmentsListMastercardMax
)

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(14)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Visa')
expect(pickedInstallmentsPlan.InterestRate).toBe(0)
})

it('should pick the installments plan with the second highest NumberOfInstallments and no interest, due to filtering rules', () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
installmentsList,
{
paymentSystemName: 'Customer Credit',
}
)

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(2)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
expect(pickedInstallmentsPlan.InterestRate).toBe(0)
})

it("should pick the installments plan with the highest NumberOfInstallments overall, if there isn't an option with no interest", () => {
const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
visaInstallments
)

expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(4)
expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Visa')
})
})
85 changes: 80 additions & 5 deletions react/modules/pickInstallments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { ProductTypes } from 'vtex.product-context'
type ClusterBy = keyof ProductTypes.Installment

/**
* Pick which installments should be used, first it cluster all installments
* by the value of clusterBy, then pick the cluster with the biggest amount of
* Pick which installments should be used. First it clusters all installments
* by the value of clusterBy, then picks the cluster with the biggest amount of
* installments options and then return this list sorted by the amount of installments
* @param installmentsList All installments
* @param clusterBy
Expand All @@ -15,7 +15,10 @@ export default function pickInstallmentsList(
) {
const clusteredInstallments = clusterInstallments(installmentsList, clusterBy)

const pickedInstallments = pickMaxOption(clusteredInstallments, clusterBy)
const pickedInstallments = pickMaxOptionCount(
clusteredInstallments,
clusterBy
)

return pickedInstallments.sort(
(a, b) => a.NumberOfInstallments - b.NumberOfInstallments
Expand Down Expand Up @@ -46,12 +49,12 @@ export function clusterInstallments(

/**
* Pick the cluster with the biggest amount of options, if there are multiple
* clusters witht eh biggest amount it will pick the one that has a installment
* clusters with the biggest amount it will pick the one that has an installment
* with the biggest NumberOfInstallments of all options
* @param clusteredInstallments
* @param clusterBy
*/
function pickMaxOption(
function pickMaxOptionCount(
clusteredInstallments: Record<string, ProductTypes.Installment[]>,
clusterBy: ClusterBy
) {
Expand Down Expand Up @@ -92,3 +95,75 @@ function pickMaxOption(

return clusteredInstallments[biggestInstallmentsOptionKey]
}

function applyFiltersToInstallmentsList(
installmentsList: ProductTypes.Installment[],
filteringRules: {
paymentSystemName?: string
installmentsQuantity?: number
}
) {
let filteredInstallmentsList = installmentsList

if (filteringRules.paymentSystemName) {
filteredInstallmentsList = filteredInstallmentsList.filter(
installmentsOption =>
installmentsOption.PaymentSystemName ===
filteringRules.paymentSystemName
)
}

if (filteringRules.installmentsQuantity) {
filteredInstallmentsList = filteredInstallmentsList.filter(
installmentsOption =>
installmentsOption.NumberOfInstallments ===
filteringRules.installmentsQuantity
)
}

return filteredInstallmentsList
}

export function pickMaxInstallmentsOption(
installmentsList: ProductTypes.Installment[],
filteringRules?: {
paymentSystemName?: string
installmentsQuantity?: number
}
) {
const filteredInstallmentsList = filteringRules
? applyFiltersToInstallmentsList(installmentsList, filteringRules)
: installmentsList

let [maxInstallmentOption] = filteredInstallmentsList

filteredInstallmentsList.forEach(installmentOption => {
if (
installmentOption.NumberOfInstallments >
maxInstallmentOption.NumberOfInstallments
) {
maxInstallmentOption = installmentOption
}
})

return maxInstallmentOption
}

export function pickMaxInstallmentsOptionWithoutInterest(
installmentsList: ProductTypes.Installment[],
filteringRules?: {
paymentSystemName?: string
installmentsQuantity?: number
}
) {
const installmentsWithoutInterest = installmentsList.filter(
installmentsOption => installmentsOption.InterestRate === 0
)

// There aren't any no-interest options
if (installmentsWithoutInterest.length === 0) {
return pickMaxInstallmentsOption(installmentsList, filteringRules)
}

return pickMaxInstallmentsOption(installmentsWithoutInterest, filteringRules)
}

0 comments on commit ff17626

Please sign in to comment.