Remove card payment moms selector (#232)

## Summary
- Removes the visible POS card-payment moms/tax selector from mobile and
desktop Stripe terminal payment flows.
- Keeps Stripe terminal payment intents at 25% moms by default and
updates the POS desktop E2E assertion for the removed selector.

## Testing
- `npx playwright test tests/e2e/pos-desktop-card-payments.spec.js
--project=chromium-desktop --grep "idle state renders grouped terminal
statuses"`
- Visual preview capture on `origin/master`:
`POS_CARD_PAYMENT_PREVIEW_DIR=.../before PLAYWRIGHT_WORKERS=1 npx
playwright test tests/e2e/pos-card-payment-visual-preview.spec.js
--project=chromium-desktop`
- Visual preview capture on this branch:
`POS_CARD_PAYMENT_PREVIEW_DIR=output/visual-previews/after
PLAYWRIGHT_WORKERS=1 npx playwright test
tests/e2e/pos-card-payment-visual-preview.spec.js
--project=chromium-desktop`

## Visual change previews

### View: POS card payment
**Description:** The POS card-payment step no longer shows the moms
selector. The terminal selector and card-payment action remain
available; 25% moms is still reflected in the payment summary where
shown.

#### Mobile (390x844)
**Before:** ![Before mobile POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-mobile-390x844.png)

**After:** ![After mobile POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-mobile-390x844.png)

#### Tablet (768x1024)
**Before:** ![Before tablet POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-tablet-768x1024.png)

**After:** ![After tablet POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-tablet-768x1024.png)

#### Desktop (1440x900)
**Before:** ![Before desktop POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-desktop-1440x900.png)

**After:** ![After desktop POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-desktop-1440x900.png)

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
This commit is contained in:
Jeppe B
2026-07-28 22:31:26 +02:00
committed by GitHub
co-authored by Jeppe Bundgaard
parent 5204536f92
commit 1da6fbd1c4
3 changed files with 7 additions and 66 deletions
@@ -16,6 +16,7 @@ import {
const POLLING_INTERVAL_MS = 5000;
const STRIPE_TERMINAL_SETUP_REQUIRED_CODE = 'stripe_terminal_setup_required';
const CARD_PAYMENT_TAX_PERCENTAGE = 25;
const props = defineProps({
departmentId: {
@@ -61,12 +62,6 @@ const autoCapturePaymentIntentId = ref(null);
const operationState = ref('idle');
const pollingEnabled = ref(true);
const taxRates = computed(() => ([
{ id: 1, display_name: stripeText('tax_rates.danish_customer'), percentage: 25 },
{ id: 2, display_name: stripeText('tax_rates.eu_customer'), percentage: 0 },
]));
const selectedTaxRate = ref(1);
const paymentIntent = computed(() => StripeModule.paymentIntents.paymentIntent.value);
const isTerminalPaymentCaptured = computed(() => StripeModule.paymentIntents.isPaymentIntentAmountReceived(paymentIntent.value));
const shouldShowDesktopCompletion = computed(() => isTerminalPaymentCaptured.value);
@@ -93,11 +88,6 @@ const normalizeErrorState = (errorLike, fallbackMessage, source) => {
};
};
const getTaxRatePercentage = (taxRateId) => {
const taxRate = taxRates.value.find((rate) => rate.id === taxRateId);
return taxRate ? taxRate.percentage : 0;
};
const clearErrorState = (...sources) => {
const sourceSet = new Set(sources);
if (sourceSet.has('readers')) {
@@ -135,7 +125,7 @@ const paymentIntentReaderId = computed(() => {
const paymentIntentTaxPercentage = computed(() => {
const value = paymentIntent.value?.metadata?.tax_percentage;
return value === undefined || value === null
? getTaxRatePercentage(selectedTaxRate.value)
? CARD_PAYMENT_TAX_PERCENTAGE
: Number(value);
});
@@ -267,18 +257,7 @@ const selectedReaderLabel = computed(() => {
return stripeText('reader.no_selection');
});
const formatTaxRateLabel = (taxRate) => {
return stripeText('tax_rates.option', {
name: taxRate.display_name,
percentage: taxRate.percentage,
});
};
const paymentTaxSummary = computed(() => {
const matchingRate = taxRates.value.find((rate) => Number(rate.percentage) === Number(paymentIntentTaxPercentage.value));
if (matchingRate) {
return formatTaxRateLabel(matchingRate);
}
return stripeText('tax_rates.fallback', { percentage: paymentIntentTaxPercentage.value });
});
@@ -345,7 +324,6 @@ const canCancelPaymentIntent = computed(() => {
});
const shouldShowReaderSelection = computed(() => paymentIntent.value === null);
const shouldShowTaxSelection = computed(() => paymentIntent.value === null);
const attemptAutomaticReaderSelection = (readerList) => {
selectedReaderId.value = selectPreferredStripeTerminalReaderId(
@@ -432,7 +410,7 @@ const onClickCreatePaymentIntent = async () => {
const response = await StripeModule.paymentIntents.createPaymentIntent(
selectedReader.value.reader,
props.order_id,
getTaxRatePercentage(selectedTaxRate.value),
CARD_PAYMENT_TAX_PERCENTAGE,
);
if (response?.status !== 200) {
@@ -689,19 +667,6 @@ onUnmounted(() => {
/>
</div>
<div v-if="shouldShowTaxSelection" class="field">
<label class="label is-size-7">{{ stripeText('tax_profile') }}</label>
<div class="select is-fullwidth">
<select v-model="selectedTaxRate" data-testid="pos-stripe-tax-select">
<template v-for="taxRate in taxRates" :key="taxRate.id">
<option :value="taxRate.id">
{{ formatTaxRateLabel(taxRate) }}
</option>
</template>
</select>
</div>
</div>
<div v-if="currentErrorMessage" class="notification is-danger is-light" data-testid="pos-stripe-error-message">
{{ currentErrorMessage }}
</div>
@@ -794,20 +759,6 @@ onUnmounted(() => {
</div>
<template v-else>
<template v-if="shouldShowTaxSelection">
<div class="pos-stripe-desktop__item" data-testid="pos-stripe-desktop-tax-field">
<div class="select is-fullwidth">
<select v-model="selectedTaxRate" data-testid="pos-stripe-tax-select">
<template v-for="taxRate in taxRates" :key="taxRate.id">
<option :value="taxRate.id">
{{ formatTaxRateLabel(taxRate) }}
</option>
</template>
</select>
</div>
</div>
</template>
<template v-if="shouldShowReaderSelection">
<div class="pos-stripe-desktop__item" data-testid="pos-stripe-desktop-reader-field">
<StripeTerminalReaderSelect
@@ -94,7 +94,7 @@ const requestPaymentIntent = async (url, method, payload) => {
return response;
};
const createPaymentIntent = async (reader, orderLike, tax_percentage = 0) => {
const createPaymentIntent = async (reader, orderLike, tax_percentage = 25) => {
const orderId = normalizeOrderId(orderLike);
if (!orderId) {
throw new Error('Order ID is required');
+3 -13
View File
@@ -308,28 +308,18 @@ test.describe("POS desktop card payments", () => {
"background-color",
"rgb(22, 163, 74)"
);
const taxSelect = stepThree.getByTestId("pos-stripe-tax-select");
await expect(taxSelect).toHaveValue("1");
await expect(taxSelect).not.toContainText("pos.stripe");
await expect(stepThree.getByTestId("pos-stripe-tax-select")).toHaveCount(0);
await expect(stepThree.getByTestId("pos-stripe-desktop-tax-field")).toHaveCount(0);
await expect(stepThree).not.toContainText("pos.pay_with_stripe");
const taxField = stepThree.getByTestId("pos-stripe-desktop-tax-field");
const readerField = stepThree.getByTestId("pos-stripe-desktop-reader-field");
const actionField = stepThree.getByTestId("pos-stripe-desktop-action-field");
const [taxBox, readerBox, actionBox] = await Promise.all([
taxField.boundingBox(),
readerField.boundingBox(),
actionField.boundingBox(),
]);
const [readerBox, actionBox] = await Promise.all([readerField.boundingBox(), actionField.boundingBox()]);
expect(taxBox).not.toBeNull();
expect(readerBox).not.toBeNull();
expect(actionBox).not.toBeNull();
expect(taxBox.y).toBeLessThan(readerBox.y);
expect(readerBox.y).toBeLessThan(actionBox.y);
expect(Math.abs(taxBox.width - readerBox.width)).toBeLessThan(2);
expect(Math.abs(readerBox.width - actionBox.width)).toBeLessThan(2);
expect(Math.abs(taxBox.x - readerBox.x)).toBeLessThan(2);
expect(Math.abs(readerBox.x - actionBox.x)).toBeLessThan(2);
await stepThree.getByTestId("pos-stripe-terminal-trigger").click();