From 9379ff8888b7b535129a71efa2df10c780dae277 Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Wed, 21 Jan 2026 21:08:27 +0000 Subject: [PATCH] feature: Allow bypassing application fee --- ...countConfigurationDomainObjectAbstract.php | 14 +++++++++++++ .../CreateConfigurationAction.php | 2 ++ .../UpdateConfigurationAction.php | 2 ++ .../Account/AccountConfigurationResource.php | 1 + .../StripePaymentIntentCreationService.php | 7 +++++-- ...lication_fees_to_account_configuration.php | 21 +++++++++++++++++++ frontend/src/api/admin.client.ts | 3 +++ .../routes/admin/Configurations/index.tsx | 14 ++++++++++++- 8 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 backend/database/migrations/2026_01_21_000000_add_bypass_application_fees_to_account_configuration.php diff --git a/backend/app/DomainObjects/Generated/AccountConfigurationDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/AccountConfigurationDomainObjectAbstract.php index 257f09c0a4..8baf343ba7 100644 --- a/backend/app/DomainObjects/Generated/AccountConfigurationDomainObjectAbstract.php +++ b/backend/app/DomainObjects/Generated/AccountConfigurationDomainObjectAbstract.php @@ -17,6 +17,7 @@ abstract class AccountConfigurationDomainObjectAbstract extends \HiEvents\Domain final public const CREATED_AT = 'created_at'; final public const UPDATED_AT = 'updated_at'; final public const DELETED_AT = 'deleted_at'; + final public const BYPASS_APPLICATION_FEES = 'bypass_application_fees'; protected int $id; protected string $name; @@ -25,6 +26,7 @@ abstract class AccountConfigurationDomainObjectAbstract extends \HiEvents\Domain protected ?string $created_at = null; protected ?string $updated_at = null; protected ?string $deleted_at = null; + protected bool $bypass_application_fees = false; public function toArray(): array { @@ -36,6 +38,7 @@ public function toArray(): array 'created_at' => $this->created_at ?? null, 'updated_at' => $this->updated_at ?? null, 'deleted_at' => $this->deleted_at ?? null, + 'bypass_application_fees' => $this->bypass_application_fees ?? null, ]; } @@ -115,4 +118,15 @@ public function getDeletedAt(): ?string { return $this->deleted_at; } + + public function setBypassApplicationFees(bool $bypass_application_fees): self + { + $this->bypass_application_fees = $bypass_application_fees; + return $this; + } + + public function getBypassApplicationFees(): bool + { + return $this->bypass_application_fees; + } } diff --git a/backend/app/Http/Actions/Admin/Configurations/CreateConfigurationAction.php b/backend/app/Http/Actions/Admin/Configurations/CreateConfigurationAction.php index 78d4bcde06..7c4aa208ee 100644 --- a/backend/app/Http/Actions/Admin/Configurations/CreateConfigurationAction.php +++ b/backend/app/Http/Actions/Admin/Configurations/CreateConfigurationAction.php @@ -28,12 +28,14 @@ public function __invoke(Request $request): JsonResponse 'application_fees' => 'required|array', 'application_fees.fixed' => 'required|numeric|min:0', 'application_fees.percentage' => 'required|numeric|min:0|max:100', + 'bypass_application_fees' => 'sometimes|boolean', ]); $configuration = $this->repository->create([ 'name' => $validated['name'], 'is_system_default' => false, 'application_fees' => $validated['application_fees'], + 'bypass_application_fees' => $validated['bypass_application_fees'] ?? false, ]); return $this->jsonResponse( diff --git a/backend/app/Http/Actions/Admin/Configurations/UpdateConfigurationAction.php b/backend/app/Http/Actions/Admin/Configurations/UpdateConfigurationAction.php index c83739ef3b..1d5378ddbb 100644 --- a/backend/app/Http/Actions/Admin/Configurations/UpdateConfigurationAction.php +++ b/backend/app/Http/Actions/Admin/Configurations/UpdateConfigurationAction.php @@ -27,6 +27,7 @@ public function __invoke(Request $request, int $configurationId): JsonResponse 'application_fees' => 'required|array', 'application_fees.fixed' => 'required|numeric|min:0', 'application_fees.percentage' => 'required|numeric|min:0|max:100', + 'bypass_application_fees' => 'sometimes|boolean', ]); $configuration = $this->repository->updateFromArray( @@ -34,6 +35,7 @@ public function __invoke(Request $request, int $configurationId): JsonResponse attributes: [ 'name' => $validated['name'], 'application_fees' => $validated['application_fees'], + 'bypass_application_fees' => $validated['bypass_application_fees'] ?? false, ] ); diff --git a/backend/app/Resources/Account/AccountConfigurationResource.php b/backend/app/Resources/Account/AccountConfigurationResource.php index c338691288..c1f2ea7846 100644 --- a/backend/app/Resources/Account/AccountConfigurationResource.php +++ b/backend/app/Resources/Account/AccountConfigurationResource.php @@ -20,6 +20,7 @@ public function toArray($request): array 'percentage' => $this->getPercentageApplicationFee(), 'fixed' => $this->getFixedApplicationFee(), ], + 'bypass_application_fees' => $this->getBypassApplicationFees(), ]; } } diff --git a/backend/app/Services/Domain/Payment/Stripe/StripePaymentIntentCreationService.php b/backend/app/Services/Domain/Payment/Stripe/StripePaymentIntentCreationService.php index bd38c4f463..90f835b146 100644 --- a/backend/app/Services/Domain/Payment/Stripe/StripePaymentIntentCreationService.php +++ b/backend/app/Services/Domain/Payment/Stripe/StripePaymentIntentCreationService.php @@ -66,8 +66,11 @@ public function createPaymentIntentWithClient( try { $this->databaseManager->beginTransaction(); + $accountConfiguration = $paymentIntentDTO->account->getConfiguration(); + $bypassApplicationFees = $accountConfiguration?->getBypassApplicationFees() ?? false; + $applicationFee = $this->orderApplicationFeeCalculationService->calculateApplicationFee( - accountConfiguration: $paymentIntentDTO->account->getConfiguration(), + accountConfiguration: $accountConfiguration, order: $paymentIntentDTO->order, vatSettings: $paymentIntentDTO->vatSettings, ); @@ -80,7 +83,7 @@ public function createPaymentIntentWithClient( 'automatic_payment_methods' => [ 'enabled' => true, ], - ...($applicationFee ? ['application_fee_amount' => $applicationFee->grossApplicationFee->toMinorUnit()] : []), + ...($applicationFee && !$bypassApplicationFees ? ['application_fee_amount' => $applicationFee->grossApplicationFee->toMinorUnit()] : []), ], $this->getStripeAccountData($paymentIntentDTO)); $this->logger->debug('Stripe payment intent created', [ diff --git a/backend/database/migrations/2026_01_21_000000_add_bypass_application_fees_to_account_configuration.php b/backend/database/migrations/2026_01_21_000000_add_bypass_application_fees_to_account_configuration.php new file mode 100644 index 0000000000..c02b07a7b6 --- /dev/null +++ b/backend/database/migrations/2026_01_21_000000_add_bypass_application_fees_to_account_configuration.php @@ -0,0 +1,21 @@ +boolean('bypass_application_fees')->default(false)->after('application_fees'); + }); + } + + public function down(): void + { + Schema::table('account_configuration', function (Blueprint $table) { + $table->dropColumn('bypass_application_fees'); + }); + } +}; diff --git a/frontend/src/api/admin.client.ts b/frontend/src/api/admin.client.ts index e419b4e4f0..6dfaafaad1 100644 --- a/frontend/src/api/admin.client.ts +++ b/frontend/src/api/admin.client.ts @@ -52,6 +52,7 @@ export interface AccountConfiguration { fixed: number; percentage: number; }; + bypass_application_fees: boolean; } export interface CreateConfigurationData { @@ -60,6 +61,7 @@ export interface CreateConfigurationData { fixed: number; percentage: number; }; + bypass_application_fees?: boolean; } export interface UpdateConfigurationData { @@ -68,6 +70,7 @@ export interface UpdateConfigurationData { fixed: number; percentage: number; }; + bypass_application_fees?: boolean; } export interface AssignConfigurationData { diff --git a/frontend/src/components/routes/admin/Configurations/index.tsx b/frontend/src/components/routes/admin/Configurations/index.tsx index fffc5ea30a..865d361799 100644 --- a/frontend/src/components/routes/admin/Configurations/index.tsx +++ b/frontend/src/components/routes/admin/Configurations/index.tsx @@ -1,4 +1,4 @@ -import {Container, Title, Stack, Card, Text, Group, Button, Badge, ActionIcon, Alert, NumberInput, TextInput, Skeleton} from "@mantine/core"; +import {Container, Title, Stack, Card, Text, Group, Button, Badge, ActionIcon, Alert, NumberInput, TextInput, Skeleton, Switch} from "@mantine/core"; import {t} from "@lingui/macro"; import {useGetAllConfigurations} from "../../../../queries/useGetAllConfigurations"; import {useCreateConfiguration} from "../../../../mutations/useCreateConfiguration"; @@ -16,6 +16,7 @@ interface ConfigurationFormValues { name: string; fixed_fee: number; percentage_fee: number; + bypass_application_fees: boolean; } const Configurations = () => { @@ -80,6 +81,9 @@ const Configurations = () => { {config.is_system_default && ( {t`System Default`} )} + {config.bypass_application_fees && ( + {t`Fees Bypassed`} + )}
@@ -150,6 +154,7 @@ const ConfigurationModal = ({configuration, onClose}: ConfigurationModalProps) = name: configuration?.name || '', fixed_fee: configuration?.application_fees?.fixed || 0, percentage_fee: configuration?.application_fees?.percentage || 0, + bypass_application_fees: configuration?.bypass_application_fees || false, }, validate: { name: (value) => { @@ -172,6 +177,7 @@ const ConfigurationModal = ({configuration, onClose}: ConfigurationModalProps) = fixed: values.fixed_fee, percentage: values.percentage_fee, }, + bypass_application_fees: values.bypass_application_fees, }; const mutation = isEditing ? updateMutation : createMutation; @@ -232,6 +238,12 @@ const ConfigurationModal = ({configuration, onClose}: ConfigurationModalProps) = {...form.getInputProps('percentage_fee')} /> + +