feat: add Weeztix OAuth, coupon codes, and Mailwizz mapping

Implement Weeztix integration per documentation: database config and
subscriber coupon_code, OAuth redirect/callback, admin setup UI with
company/coupon selection via AJAX, synchronous coupon creation on public
subscribe with duplicate and rate-limit handling, Mailwizz field mapping
for coupon codes, subscriber table and CSV export, and connection hint
on the pages list.

Made-with: Cursor
This commit is contained in:
2026-04-04 14:52:41 +02:00
parent 17e784fee7
commit d3abdb7ed9
30 changed files with 2272 additions and 5 deletions

View File

@@ -4,13 +4,19 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use App\Exceptions\WeeztixCouponCodeConflictException;
use App\Http\Requests\SubscribePublicPageRequest;
use App\Jobs\SyncSubscriberToMailwizz;
use App\Models\PageBlock;
use App\Models\PreregistrationPage;
use App\Models\Subscriber;
use App\Models\WeeztixConfig;
use App\Services\WeeztixService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Throwable;
class PublicPageController extends Controller
{
@@ -52,13 +58,70 @@ class PublicPageController extends Controller
$subscriber = $publicPage->subscribers()->create($validated);
$publicPage->loadMissing('weeztixConfig');
$weeztix = $publicPage->weeztixConfig;
if ($this->weeztixCanIssueCodes($weeztix)) {
$this->tryAttachWeeztixCouponCode($subscriber, $weeztix);
}
if ($publicPage->mailwizzConfig !== null) {
SyncSubscriberToMailwizz::dispatch($subscriber);
SyncSubscriberToMailwizz::dispatch($subscriber->fresh());
}
return response()->json([
'success' => true,
'message' => $publicPage->thank_you_message ?? __('Thank you for registering!'),
'coupon_code' => $subscriber->fresh()?->coupon_code,
]);
}
private function weeztixCanIssueCodes(?WeeztixConfig $config): bool
{
if ($config === null || ! $config->is_connected) {
return false;
}
$company = $config->company_guid;
$coupon = $config->coupon_guid;
return is_string($company) && $company !== '' && is_string($coupon) && $coupon !== '';
}
private function tryAttachWeeztixCouponCode(Subscriber $subscriber, WeeztixConfig $config): void
{
$freshConfig = $config->fresh();
if ($freshConfig === null) {
return;
}
$service = new WeeztixService($freshConfig);
$maxAttempts = 5;
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
try {
$code = WeeztixService::generateUniqueCode(
is_string($freshConfig->code_prefix) && $freshConfig->code_prefix !== ''
? $freshConfig->code_prefix
: 'PREREG'
);
$service->createCouponCode($code);
$subscriber->update(['coupon_code' => $code]);
return;
} catch (WeeztixCouponCodeConflictException) {
continue;
} catch (Throwable $e) {
Log::error('Weeztix coupon creation failed', [
'subscriber_id' => $subscriber->id,
'message' => $e->getMessage(),
]);
return;
}
}
Log::warning('Weeztix coupon: exhausted duplicate retries', [
'subscriber_id' => $subscriber->id,
]);
}