diff --git a/clients/apps/web/src/components/Orders/DownloadInvoice.tsx b/clients/apps/web/src/components/Orders/DownloadInvoice.tsx index d6c9d0ab3d..840fb77a30 100644 --- a/clients/apps/web/src/components/Orders/DownloadInvoice.tsx +++ b/clients/apps/web/src/components/Orders/DownloadInvoice.tsx @@ -341,6 +341,7 @@ const DownloadInvoice = ({ country={country} value={field.value || ''} onChange={field.onChange} + disabled={order.paid} /> @@ -361,6 +362,7 @@ const DownloadInvoice = ({ value={field.value || undefined} onChange={field.onChange} allowedCountries={enums.addressInputCountryValues} + disabled={order.paid} /> diff --git a/clients/packages/ui/src/components/atoms/CountryPicker.tsx b/clients/packages/ui/src/components/atoms/CountryPicker.tsx index dc2d87866e..f1190ea744 100644 --- a/clients/packages/ui/src/components/atoms/CountryPicker.tsx +++ b/clients/packages/ui/src/components/atoms/CountryPicker.tsx @@ -28,6 +28,7 @@ const CountryPicker = ({ className, itemClassName, contentClassName, + disabled, }: { allowedCountries: readonly string[] value?: string @@ -36,11 +37,17 @@ const CountryPicker = ({ className?: string itemClassName?: string contentClassName?: string + disabled?: boolean }) => { const countryMap = getCountryList(allowedCountries as TCountryCode[]) return ( - + void country?: string autoComplete?: string + disabled?: boolean }) => { if (country === 'US' || country === 'CA') { const states = country === 'US' ? US_STATES : CA_PROVINCES @@ -100,8 +102,9 @@ const CountryStatePicker = ({ onValueChange={onChange} value={value} autoComplete={autoComplete} + disabled={disabled} > - + onChange(e.target.value)} + disabled={disabled} /> ) } diff --git a/server/polar/order/service.py b/server/polar/order/service.py index 66ce65da32..22d361ad9a 100644 --- a/server/polar/order/service.py +++ b/server/polar/order/service.py @@ -28,7 +28,7 @@ from polar.event.service import event as event_service from polar.event.system import OrderPaidMetadata, SystemEvent, build_system_event from polar.eventstream.service import publish as eventstream_publish -from polar.exceptions import PolarError +from polar.exceptions import PolarError, PolarRequestValidationError from polar.file.s3 import S3_SERVICES from polar.held_balance.service import held_balance as held_balance_service from polar.integrations.stripe.service import stripe as stripe_service @@ -339,6 +339,42 @@ async def update( order: Order, order_update: OrderUpdate | CustomerOrderUpdate, ) -> Order: + # Validate that country/state cannot be changed after order is paid + # because VAT was calculated based on the original address + if order.paid and order_update.billing_address is not None: + new_address = order_update.billing_address + existing_address = order.billing_address + + new_country = str(new_address.country) if new_address else None + new_state = new_address.state if new_address else None + existing_country = ( + str(existing_address.country) if existing_address else None + ) + existing_state = existing_address.state if existing_address else None + + if new_country != existing_country: + raise PolarRequestValidationError( + [ + { + "type": "value_error", + "loc": ("body", "billing_address", "country"), + "msg": "Cannot change country after order is paid.", + "input": new_country, + } + ] + ) + if new_state != existing_state: + raise PolarRequestValidationError( + [ + { + "type": "value_error", + "loc": ("body", "billing_address", "state"), + "msg": "Cannot change state after order is paid.", + "input": new_state, + } + ] + ) + repository = OrderRepository.from_session(session) order = await repository.update( order, update_dict=order_update.model_dump(exclude_unset=True)