Skip to content

Commit b80c7ab

Browse files
authored
Feature: Messaging tiers with eligibility checks and admin approval workflow (#986)
1 parent 04be108 commit b80c7ab

File tree

66 files changed

+1849
-65
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1849
-65
lines changed

backend/app/DomainObjects/AccountDomainObject.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class AccountDomainObject extends Generated\AccountDomainObjectAbstract
1515

1616
private ?AccountVatSettingDomainObject $accountVatSetting = null;
1717

18+
private ?AccountMessagingTierDomainObject $messagingTier = null;
19+
1820
public function getApplicationFee(): AccountApplicationFeeDTO
1921
{
2022
/** @var AccountConfigurationDomainObject $applicationFee */
@@ -56,6 +58,16 @@ public function setAccountVatSetting(AccountVatSettingDomainObject $accountVatSe
5658
$this->accountVatSetting = $accountVatSetting;
5759
}
5860

61+
public function getMessagingTier(): ?AccountMessagingTierDomainObject
62+
{
63+
return $this->messagingTier;
64+
}
65+
66+
public function setMessagingTier(AccountMessagingTierDomainObject $messagingTier): void
67+
{
68+
$this->messagingTier = $messagingTier;
69+
}
70+
5971
/**
6072
* Get the primary active Stripe platform for this account
6173
* Returns the platform with setup completed, preferring the most recent
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects;
4+
5+
class AccountMessagingTierDomainObject extends Generated\AccountMessagingTierDomainObjectAbstract
6+
{
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Enums;
4+
5+
enum MessagingEligibilityFailureEnum: string
6+
{
7+
case STRIPE_NOT_CONNECTED = 'stripe_not_connected';
8+
case NO_PAID_ORDERS = 'no_paid_orders';
9+
case EVENT_TOO_NEW = 'event_too_new';
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Enums;
4+
5+
enum MessagingTierViolationEnum: string
6+
{
7+
case MESSAGE_LIMIT_EXCEEDED = 'message_limit_exceeded';
8+
case RECIPIENT_LIMIT_EXCEEDED = 'recipient_limit_exceeded';
9+
case LINKS_NOT_ALLOWED = 'links_not_allowed';
10+
11+
public function getMessage(): string
12+
{
13+
return match ($this) {
14+
self::MESSAGE_LIMIT_EXCEEDED => __('You have reached your daily message limit. Please try again later or contact support to increase your limits.'),
15+
self::RECIPIENT_LIMIT_EXCEEDED => __('The number of recipients exceeds your account limit. Please contact support to increase your limits.'),
16+
self::LINKS_NOT_ALLOWED => __('Your account tier does not allow links in messages. Please contact support to enable this feature.'),
17+
};
18+
}
19+
}

backend/app/DomainObjects/Generated/AccountDomainObjectAbstract.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ abstract class AccountDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr
1212
final public const PLURAL_NAME = 'accounts';
1313
final public const ID = 'id';
1414
final public const ACCOUNT_CONFIGURATION_ID = 'account_configuration_id';
15+
final public const ACCOUNT_MESSAGING_TIER_ID = 'account_messaging_tier_id';
1516
final public const CURRENCY_CODE = 'currency_code';
1617
final public const TIMEZONE = 'timezone';
1718
final public const CREATED_AT = 'created_at';
@@ -29,6 +30,7 @@ abstract class AccountDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr
2930

3031
protected int $id;
3132
protected ?int $account_configuration_id = null;
33+
protected ?int $account_messaging_tier_id = null;
3234
protected string $currency_code = 'USD';
3335
protected ?string $timezone = null;
3436
protected ?string $created_at = null;
@@ -49,6 +51,7 @@ public function toArray(): array
4951
return [
5052
'id' => $this->id ?? null,
5153
'account_configuration_id' => $this->account_configuration_id ?? null,
54+
'account_messaging_tier_id' => $this->account_messaging_tier_id ?? null,
5255
'currency_code' => $this->currency_code ?? null,
5356
'timezone' => $this->timezone ?? null,
5457
'created_at' => $this->created_at ?? null,
@@ -88,6 +91,17 @@ public function getAccountConfigurationId(): ?int
8891
return $this->account_configuration_id;
8992
}
9093

94+
public function setAccountMessagingTierId(?int $account_messaging_tier_id): self
95+
{
96+
$this->account_messaging_tier_id = $account_messaging_tier_id;
97+
return $this;
98+
}
99+
100+
public function getAccountMessagingTierId(): ?int
101+
{
102+
return $this->account_messaging_tier_id;
103+
}
104+
91105
public function setCurrencyCode(string $currency_code): self
92106
{
93107
$this->currency_code = $currency_code;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Generated;
4+
5+
/**
6+
* THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY.
7+
* @package HiEvents\DomainObjects\Generated
8+
*/
9+
abstract class AccountMessagingTierDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject
10+
{
11+
final public const SINGULAR_NAME = 'account_messaging_tier';
12+
final public const PLURAL_NAME = 'account_messaging_tiers';
13+
final public const ID = 'id';
14+
final public const NAME = 'name';
15+
final public const MAX_MESSAGES_PER_24H = 'max_messages_per_24h';
16+
final public const MAX_RECIPIENTS_PER_MESSAGE = 'max_recipients_per_message';
17+
final public const LINKS_ALLOWED = 'links_allowed';
18+
final public const CREATED_AT = 'created_at';
19+
final public const UPDATED_AT = 'updated_at';
20+
final public const DELETED_AT = 'deleted_at';
21+
22+
protected int $id;
23+
protected string $name;
24+
protected int $max_messages_per_24h;
25+
protected int $max_recipients_per_message;
26+
protected bool $links_allowed = false;
27+
protected ?string $created_at = null;
28+
protected ?string $updated_at = null;
29+
protected ?string $deleted_at = null;
30+
31+
public function toArray(): array
32+
{
33+
return [
34+
'id' => $this->id ?? null,
35+
'name' => $this->name ?? null,
36+
'max_messages_per_24h' => $this->max_messages_per_24h ?? null,
37+
'max_recipients_per_message' => $this->max_recipients_per_message ?? null,
38+
'links_allowed' => $this->links_allowed ?? null,
39+
'created_at' => $this->created_at ?? null,
40+
'updated_at' => $this->updated_at ?? null,
41+
'deleted_at' => $this->deleted_at ?? null,
42+
];
43+
}
44+
45+
public function setId(int $id): self
46+
{
47+
$this->id = $id;
48+
return $this;
49+
}
50+
51+
public function getId(): int
52+
{
53+
return $this->id;
54+
}
55+
56+
public function setName(string $name): self
57+
{
58+
$this->name = $name;
59+
return $this;
60+
}
61+
62+
public function getName(): string
63+
{
64+
return $this->name;
65+
}
66+
67+
public function setMaxMessagesPer24h(int $max_messages_per_24h): self
68+
{
69+
$this->max_messages_per_24h = $max_messages_per_24h;
70+
return $this;
71+
}
72+
73+
public function getMaxMessagesPer24h(): int
74+
{
75+
return $this->max_messages_per_24h;
76+
}
77+
78+
public function setMaxRecipientsPerMessage(int $max_recipients_per_message): self
79+
{
80+
$this->max_recipients_per_message = $max_recipients_per_message;
81+
return $this;
82+
}
83+
84+
public function getMaxRecipientsPerMessage(): int
85+
{
86+
return $this->max_recipients_per_message;
87+
}
88+
89+
public function setLinksAllowed(bool $links_allowed): self
90+
{
91+
$this->links_allowed = $links_allowed;
92+
return $this;
93+
}
94+
95+
public function getLinksAllowed(): bool
96+
{
97+
return $this->links_allowed;
98+
}
99+
100+
public function setCreatedAt(?string $created_at): self
101+
{
102+
$this->created_at = $created_at;
103+
return $this;
104+
}
105+
106+
public function getCreatedAt(): ?string
107+
{
108+
return $this->created_at;
109+
}
110+
111+
public function setUpdatedAt(?string $updated_at): self
112+
{
113+
$this->updated_at = $updated_at;
114+
return $this;
115+
}
116+
117+
public function getUpdatedAt(): ?string
118+
{
119+
return $this->updated_at;
120+
}
121+
122+
public function setDeletedAt(?string $deleted_at): self
123+
{
124+
$this->deleted_at = $deleted_at;
125+
return $this;
126+
}
127+
128+
public function getDeletedAt(): ?string
129+
{
130+
return $this->deleted_at;
131+
}
132+
}

backend/app/DomainObjects/Generated/MessageDomainObjectAbstract.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ abstract class MessageDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr
2626
final public const CREATED_AT = 'created_at';
2727
final public const UPDATED_AT = 'updated_at';
2828
final public const DELETED_AT = 'deleted_at';
29+
final public const ELIGIBILITY_FAILURES = 'eligibility_failures';
2930

3031
protected int $id;
3132
protected int $event_id;
@@ -43,6 +44,7 @@ abstract class MessageDomainObjectAbstract extends \HiEvents\DomainObjects\Abstr
4344
protected string $created_at;
4445
protected ?string $updated_at = null;
4546
protected ?string $deleted_at = null;
47+
protected array|string|null $eligibility_failures = null;
4648

4749
public function toArray(): array
4850
{
@@ -63,6 +65,7 @@ public function toArray(): array
6365
'created_at' => $this->created_at ?? null,
6466
'updated_at' => $this->updated_at ?? null,
6567
'deleted_at' => $this->deleted_at ?? null,
68+
'eligibility_failures' => $this->eligibility_failures ?? null,
6669
];
6770
}
6871

@@ -241,4 +244,15 @@ public function getDeletedAt(): ?string
241244
{
242245
return $this->deleted_at;
243246
}
247+
248+
public function setEligibilityFailures(array|string|null $eligibility_failures): self
249+
{
250+
$this->eligibility_failures = $eligibility_failures;
251+
return $this;
252+
}
253+
254+
public function getEligibilityFailures(): array|string|null
255+
{
256+
return $this->eligibility_failures;
257+
}
244258
}

backend/app/DomainObjects/Status/MessageStatus.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ enum MessageStatus
88
{
99
use BaseEnum;
1010

11+
case PENDING_REVIEW;
1112
case PROCESSING;
1213
case SENT;
1314
case FAILED;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace HiEvents\Exceptions;
4+
5+
use Exception;
6+
use HiEvents\Services\Domain\Message\DTO\MessagingTierViolationDTO;
7+
8+
class MessagingTierLimitExceededException extends Exception
9+
{
10+
public function __construct(
11+
private readonly MessagingTierViolationDTO $violation,
12+
int $code = 429,
13+
?Exception $previous = null
14+
) {
15+
parent::__construct($this->violation->getFirstViolationMessage(), $code, $previous);
16+
}
17+
18+
public function getViolation(): MessagingTierViolationDTO
19+
{
20+
return $this->violation;
21+
}
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiEvents\Http\Actions\Admin\Accounts;
6+
7+
use HiEvents\DomainObjects\Enums\Role;
8+
use HiEvents\Http\Actions\BaseAction;
9+
use HiEvents\Resources\Account\AdminAccountDetailResource;
10+
use HiEvents\Services\Application\Handlers\Admin\GetAccountHandler;
11+
use HiEvents\Services\Application\Handlers\Admin\UpdateAccountMessagingTierHandler;
12+
use Illuminate\Http\JsonResponse;
13+
use Illuminate\Http\Request;
14+
15+
class UpdateAccountMessagingTierAction extends BaseAction
16+
{
17+
public function __construct(
18+
private readonly UpdateAccountMessagingTierHandler $handler,
19+
private readonly GetAccountHandler $getAccountHandler,
20+
) {
21+
}
22+
23+
public function __invoke(Request $request, int $accountId): JsonResponse
24+
{
25+
$this->minimumAllowedRole(Role::SUPERADMIN);
26+
27+
$validated = $request->validate([
28+
'messaging_tier_id' => 'required|integer|exists:account_messaging_tiers,id',
29+
]);
30+
31+
$this->handler->handle($accountId, $validated['messaging_tier_id']);
32+
33+
$account = $this->getAccountHandler->handle($accountId);
34+
35+
return $this->jsonResponse(new AdminAccountDetailResource($account), wrapInData: true);
36+
}
37+
}

0 commit comments

Comments
 (0)