| @@ -1,5 +1,10 @@ | ||
| 1 | -import { ExperimentalOrderMeta, registerCheckoutFilters, TotalsItem } from "@woocommerce/blocks-checkout"; | |
| 1 | +import { | |
| 2 | + ExperimentalOrderMeta, | |
| 3 | + registerCheckoutFilters, | |
| 4 | + TotalsItem, | |
| 5 | + TotalsWrapper, | |
| 6 | +} from "@woocommerce/blocks-checkout"; | |
| 2 | 7 | import { FormattedMonetaryAmount } from "@woocommerce/blocks-components"; |
| 3 | 8 | import { __, sprintf } from "@wordpress/i18n"; |
| 4 | 9 | import { registerPlugin } from "@wordpress/plugins"; |
| 5 | 10 | |
| @@ -6,9 +11,8 @@ | ||
| 6 | 11 | import { formatPrice, getCurrencyFromPriceResponse } from "@woocommerce/price-format"; |
| 7 | 12 | // import { useStoreCart } from "@woocommerce/base-context/hooks"; |
| 8 | 13 | |
| 9 | 14 | const modifyCartItemPrice = (defaultValue, extensions, args, validation) => { |
| 10 | - const { sdevs_subscription } = extensions; | |
| 11 | 15 | const { cartItem } = args; |
| 12 | 16 | const { totals } = cartItem; |
| 13 | 17 | |
| 14 | 18 | if (totals === undefined) { |
| @@ -16,19 +20,10 @@ | ||
| 16 | 20 | } |
| 17 | 21 | if (totals.line_total === "0") { |
| 18 | 22 | return `<price/> ${__("Due Today", "subscription")}`; |
| 19 | 23 | } |
| 20 | - if (sdevs_subscription && sdevs_subscription.type) { | |
| 21 | - // Capitalize the first letter to match product page display | |
| 22 | - const capitalizedType = sdevs_subscription.type.charAt(0).toUpperCase() + sdevs_subscription.type.slice(1); | |
| 23 | - | |
| 24 | - // Check max_no_payment - handle string, number, null, undefined | |
| 25 | - const maxPayments = parseInt(sdevs_subscription.max_no_payment, 10); | |
| 26 | - const paymentInfo = !isNaN(maxPayments) && maxPayments > 0 ? ` x ${maxPayments}` : ""; | |
| 27 | - return `<price/> / ${ | |
| 28 | - sdevs_subscription.time && sdevs_subscription.time > 1 ? " " + sdevs_subscription.time + "-" : "" | |
| 29 | - }${capitalizedType}${paymentInfo}`; | |
| 30 | - } | |
| 24 | + // The per-unit price shows no cadence — the recurring cadence appears on the | |
| 25 | + // line total (subtotalPriceFormat) and the "Recurring totals" summary. | |
| 31 | 26 | return defaultValue; |
| 32 | 27 | }; |
| 33 | 28 | |
| 34 | 29 | const modifySubtotalPriceFormat = (defaultValue, extensions, args, validation) => { |
| @@ -70,65 +65,118 @@ | ||
| 70 | 65 | const currency = getCurrencyFromPriceResponse(cartTotals); |
| 71 | 66 | const multiplier = Math.pow(10, currency.minorUnit); |
| 72 | 67 | |
| 73 | 68 | return ( |
| 74 | - <TotalsItem | |
| 75 | - className="wc-block-components-totals-footer-item" | |
| 76 | - label={__("Recurring totals", "subscription")} | |
| 77 | - description={ | |
| 78 | - <div style={{ display: "grid" }}> | |
| 79 | - {recurrings.map((recurring) => { | |
| 80 | - // Capitalize the first letter to match product page display | |
| 81 | - const capitalizedType = recurring.type.charAt(0).toUpperCase() + recurring.type.slice(1); | |
| 69 | + <TotalsWrapper> | |
| 70 | + <TotalsItem | |
| 71 | + className="wc-block-components-totals-footer-item" | |
| 72 | + label={__("Recurring totals", "subscription")} | |
| 73 | + description={ | |
| 74 | + <div style={{ display: "grid" }}> | |
| 75 | + {recurrings.map((recurring) => { | |
| 76 | + // Capitalize the first letter to match product page display | |
| 77 | + const capitalizedType = recurring.type.charAt(0).toUpperCase() + recurring.type.slice(1); | |
| 82 | 78 | |
| 83 | - return ( | |
| 84 | - <div style={{ margin: "20px 0", float: "right" }}> | |
| 85 | - <div style={{ fontSize: "18px" }}> | |
| 86 | - <FormattedMonetaryAmount currency={currency} value={Math.round(recurring.price * multiplier)} /> | |
| 87 | - <span class="wpsubs-subscription-timing"> | |
| 88 | - / | |
| 89 | - {recurring.time && recurring.time > 1 | |
| 90 | - ? `${recurring.time + "-" + capitalizedType} ` | |
| 91 | - : capitalizedType} | |
| 92 | - </span> | |
| 93 | - </div> | |
| 94 | - <small>{recurring.description}</small> | |
| 95 | - {recurring.can_user_cancel === "yes" && | |
| 96 | - (recurring.max_no_payment === "" || parseInt(recurring.max_no_payment) === 0) && ( | |
| 79 | + const priceFormatArgs = { | |
| 80 | + currency: currency.code, | |
| 81 | + currency_symbol: currency.symbol, | |
| 82 | + decimal_separator: currency.decimalSeparator, | |
| 83 | + thousand_separator: currency.thousandSeparator, | |
| 84 | + precision: currency.minorUnit, | |
| 85 | + price_format: currency.priceFormat, | |
| 86 | + }; | |
| 87 | + | |
| 88 | + /** | |
| 89 | + * Format an amount the same way the surrounding totals are formatted. | |
| 90 | + * | |
| 91 | + * @param {number} amount Amount in major units. | |
| 92 | + * @return {string} Formatted price. | |
| 93 | + */ | |
| 94 | + const format = (amount) => formatPrice(Math.round(amount * multiplier), priceFormatArgs); | |
| 95 | + | |
| 96 | + const recurringLimit = parseInt(recurring.recurring_limit, 10) || 0; | |
| 97 | + | |
| 98 | + // Billing period as plain text, so notes can quote a price the same way the row does. | |
| 99 | + const periodLabel = | |
| 100 | + recurring.time && recurring.time > 1 ? `${recurring.time}-${capitalizedType}` : capitalizedType; | |
| 101 | + | |
| 102 | + return ( | |
| 103 | + <div style={{ margin: "8px 0px 0px", float: "right" }}> | |
| 104 | + <div style={{ fontSize: "18px" }}> | |
| 105 | + {recurring.has_recurring_discount && ( | |
| 106 | + <del aria-hidden="true" style={{ marginRight: "6px", opacity: 0.6 }}> | |
| 107 | + {format(recurring.full_price)} | |
| 108 | + </del> | |
| 109 | + )} | |
| 110 | + <FormattedMonetaryAmount currency={currency} value={Math.round(recurring.price * multiplier)} /> | |
| 111 | + <span class="wpsubs-subscription-timing"> | |
| 112 | + / | |
| 113 | + {recurring.time && recurring.time > 1 | |
| 114 | + ? `${recurring.time + "-" + capitalizedType} ` | |
| 115 | + : capitalizedType} | |
| 116 | + </span> | |
| 117 | + </div> | |
| 118 | + <small>{recurring.description}</small> | |
| 119 | + {recurring.has_one_time_discount && ( | |
| 97 | 120 | <> |
| 98 | 121 | <br /> |
| 99 | - <small>{__("You can cancel subscription at any time!", "subscription")} </small> | |
| 122 | + <small> | |
| 123 | + {sprintf( | |
| 124 | + // translators: 1: amount paid today, 2: amount charged on each renewal. | |
| 125 | + __("You pay %1$s today. %2$s will be charged from the next renewal.", "subscription"), | |
| 126 | + format(recurring.first_price), | |
| 127 | + format(recurring.price), | |
| 128 | + )} | |
| 129 | + </small> | |
| 100 | 130 | </> |
| 101 | 131 | )} |
| 102 | - {parseInt(recurring.max_no_payment) > 0 && ( | |
| 103 | - <> | |
| 104 | - <br /> | |
| 105 | - <small> | |
| 106 | - {sprintf( | |
| 107 | - // translators: 1: number of installments, 2: total amount. | |
| 108 | - __( | |
| 109 | - "This subscription will be billed in %1$s installments, for a total of %2$s.", | |
| 110 | - "subscription", | |
| 111 | - ), | |
| 112 | - recurring.max_no_payment, | |
| 113 | - formatPrice(Math.round(recurring.price * multiplier * parseInt(recurring.max_no_payment)), { | |
| 114 | - currency: currency.code, | |
| 115 | - currency_symbol: currency.symbol, | |
| 116 | - decimal_separator: currency.decimalSeparator, | |
| 117 | - thousand_separator: currency.thousandSeparator, | |
| 118 | - precision: currency.minorUnit, | |
| 119 | - price_format: currency.priceFormat, | |
| 120 | - }), | |
| 121 | - )} | |
| 122 | - </small> | |
| 123 | - </> | |
| 124 | - )} | |
| 125 | - </div> | |
| 126 | - ); | |
| 127 | - })} | |
| 128 | - </div> | |
| 129 | - } | |
| 130 | - ></TotalsItem> | |
| 132 | + {recurring.has_recurring_discount && recurringLimit > 1 && ( | |
| 133 | + <> | |
| 134 | + <br /> | |
| 135 | + <small> | |
| 136 | + {sprintf( | |
| 137 | + // translators: 1: number of discounted payments, 2: full price charged afterwards. | |
| 138 | + __("Discount applies to your first %1$s payments. After that, %2$s.", "subscription"), | |
| 139 | + recurringLimit, | |
| 140 | + `${format(recurring.full_price)} / ${periodLabel}`, | |
| 141 | + )} | |
| 142 | + </small> | |
| 143 | + </> | |
| 144 | + )} | |
| 145 | + {recurring.can_user_cancel === "yes" && | |
| 146 | + (recurring.max_no_payment === "" || parseInt(recurring.max_no_payment) === 0) && ( | |
| 147 | + <> | |
| 148 | + <br /> | |
| 149 | + <small>{__("You can cancel subscription at any time!", "subscription")} </small> | |
| 150 | + </> | |
| 151 | + )} | |
| 152 | + {parseInt(recurring.max_no_payment) > 0 && ( | |
| 153 | + <> | |
| 154 | + <br /> | |
| 155 | + <small> | |
| 156 | + {sprintf( | |
| 157 | + // translators: 1: number of installments, 2: total amount. | |
| 158 | + __( | |
| 159 | + "This subscription will be billed in %1$s installments, for a total of %2$s.", | |
| 160 | + "subscription", | |
| 161 | + ), | |
| 162 | + recurring.max_no_payment, | |
| 163 | + format( | |
| 164 | + recurring.split_total != null | |
| 165 | + ? recurring.split_total | |
| 166 | + : recurring.price * parseInt(recurring.max_no_payment), | |
| 167 | + ), | |
| 168 | + )} | |
| 169 | + </small> | |
| 170 | + </> | |
| 171 | + )} | |
| 172 | + </div> | |
| 173 | + ); | |
| 174 | + })} | |
| 175 | + </div> | |
| 176 | + } | |
| 177 | + ></TotalsItem> | |
| 178 | + </TotalsWrapper> | |
| 131 | 179 | ); |
| 132 | 180 | }; |
| 133 | 181 | |
| 134 | 182 | const render = () => { |