update marketing
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 5m14s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 17s
Build, Push and Deploy / deploy-staging (push) Successful in 41s
Build, Push and Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-14 11:55:22 +07:00
parent f80fcc4c1b
commit 05fd3230b8
448 changed files with 17545 additions and 5128 deletions

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\MarketingAddon\App\Emails;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Mail\Mailables\Envelope;
class DynamicMail extends Mailable
{
use Queueable, SerializesModels;
private $data;
private $template;
public function __construct($template, $data)
{
$this->data = $data;
$this->template = $template;
}
public function envelope(): Envelope
{
return new Envelope(
from: new Address(
Config::get('mail.from.address'),
Config::get('mail.from.name')
),
subject: $this->template->subject,
);
}
public function content(): Content
{
return new Content(
view: 'mail.dynamic-mail',
with: [
'content' => $this->buildHtml(),
],
);
}
public function attachments(): array
{
return [];
}
protected function buildHtml()
{
$message = $this->template->message;
foreach ($this->data as $key => $value) {
$message = str_replace('[' . $key . ']', $value, $message);
}
return $message;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\MarketingAddon\App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
use Modules\MarketingAddon\App\Models\Smsgateway;
class SmsGatewayExport implements FromView
{
public function view(): View
{
$gateways = Smsgateway::with('type:id,name')
->where('business_id', auth()->user()->business_id)
->when(request('channel'), function ($q) {
$q->where('channel', request('channel'));
})
->latest()
->get();
return view('marketingaddon::sms-gateways.excel-csv', [
'gateways' => $gateways
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\MarketingAddon\App\Models\EmailSetting;
class AcnooEmailSettingController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$email_setting = EmailSetting::where('business_id', $businessId)->first() ?? new EmailSetting();
return view('marketingaddon::email-settings.index', compact('email_setting'));
}
public function store(Request $request)
{
$request->validate([
'driver' => 'required|string|max:25|in:smtp,sendmail',
'host' => 'nullable|string|max:255',
'port' => 'nullable|numeric',
'username' => 'nullable|string|max:255',
'password' => 'nullable|string|max:255',
'encryption' => 'nullable|string|max:25',
'from_name' => 'nullable|string|max:255',
'from_address' => 'nullable|string|max:255',
]);
EmailSetting::updateOrCreate(
['business_id' => auth()->user()->business_id],
$request->only(['driver', 'host', 'port', 'username', 'password', 'encryption', 'from_name', 'from_address'])
);
return response()->json(__('Email setting updated successfully.'));
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\MarketingAddon\App\Models\MessageReport;
class AcnooMessageLogController extends Controller
{
public function index(Request $request)
{
$message_logs = MessageReport::with('user:id,email')
->where('business_id', auth()->user()->business_id)
->when($request->search, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('title', 'like', '%' . $request->search . '%')
->orWhere('channel', 'like', '%' . $request->search . '%')
->orWhere('phone', 'like', '%' . $request->search . '%')
->orWhere('status', 'like', '%' . $request->search . '%')
->orWhereHas('user', function ($q) use ($request) {
$q->where('email', 'like', '%' . $request->search . '%');
});
});
})
->when($request->type, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('channel', $request->type);
});
})
->when($request->status, function ($q) use ($request) {
$q->where(function ($q) use ($request) {
$q->where('status', $request->status);
});
})
->latest()
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('marketingaddon::message-logs.datas', compact('message_logs'))->render(),
]);
}
return view('marketingaddon::message-logs.index', compact('message_logs'));
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Facades\Excel;
use Modules\MarketingAddon\App\Exports\SmsGatewayExport;
use Modules\MarketingAddon\App\Models\GatewayType;
use Modules\MarketingAddon\App\Models\Smsgateway;
class AcnooSmsGatewayController extends Controller
{
public function index(Request $request)
{
$gateways = Smsgateway::with('type:id,name')
->where('business_id', auth()->user()->business_id)
->when($request->filled('channel'), function ($q) use ($request) {
$q->where('channel', $request->channel);
})
->when($request->filled('search'), function ($q) use ($request) {
$q->where(function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->search . '%')
->orWhereHas('type', function ($type) use ($request) {
$type->where('name', 'like', '%' . $request->search . '%');
});
});
})
->latest()
->paginate($request->per_page ?? 20)
->appends($request->query());
if ($request->ajax()) {
return response()->json([
'data' => view('marketingaddon::sms-gateways.datas', compact('gateways'))->render()
]);
}
return view('marketingaddon::sms-gateways.index', compact('gateways'));
}
public function create(Request $request)
{
$types = GatewayType::where('channel', $request->channel)
->latest()
->get();
return view('marketingaddon::sms-gateways.create', compact('types'));
}
public function store(Request $request)
{
$request->validate([
'status' => 'required|integer',
'params' => 'required|array',
'params.*' => 'required|string',
'name' => [
'required',
Rule::unique('smsgateways')->where(function ($q) {
return $q->where('business_id', auth()->user()->business_id);
}),
],
'type_id' => 'required|exists:gateway_types,id',
]);
$type = GatewayType::findOrFail($request->type_id);
Smsgateway::create($request->all() + [
'driver' => $type->driver,
'namespace' => $type->namespace,
'business_id' => auth()->user()->business_id
]);
return response()->json([
'message' => __(':channel gateway created successfully.', ['channel' => ucfirst($request->channel)]),
'redirect' => route('business.sms-gateways.index', ['channel' => $request->channel])
]);
}
public function show(string $id)
{
$gateway = Smsgateway::findOrFail($id);
$gateway->update(['status' => !$gateway->status]);
return response()->json(['message' => 'Sms gateway']);
}
public function edit(Request $request, string $id)
{
$types = GatewayType::where('channel', $request->channel)->latest()->get();
$gateway = Smsgateway::where('business_id', auth()->user()->business_id)->findOrFail($id);
return view('marketingaddon::sms-gateways.edit', compact('gateway', 'types'));
}
public function update(Request $request, string $id)
{
$request->validate([
'status' => 'required|integer',
'params' => 'required|array',
'params.*' => 'required|string',
'type_id' => 'required|exists:gateway_types,id',
'name' => [
'required',
Rule::unique('smsgateways', 'name')->ignore($id)->where(function ($q) {
return $q->where('business_id', auth()->user()->business_id);
}),
],
]);
$type = GatewayType::findOrFail($request->type_id);
$gateway = Smsgateway::findOrFail($id);
$gateway->update($request->all() + [
'driver' => $type->driver,
'namespace' => $type->namespace
]);
return response()->json([
'message' => __(':channel gateway updated successfully.', ['channel' => ucfirst($request->channel)]),
'redirect' => route('business.sms-gateways.index', ['channel' => $request->channel])
]);
}
public function destroy(Request $request, string $id)
{
Smsgateway::where('id', $id)->delete();
return response()->json([
'message' => __(':channel gateway deleted successfully.', ['channel' => ucfirst($request->channel)]),
'redirect' => route('business.sms-gateways.index', ['channel' => $request->channel])
]);
}
public function deleteAll(Request $request)
{
Smsgateway::whereIn('id', $request->ids)->delete();
return response()->json([
'message' => __('Selected :channel gateway deleted successfully.', ['channel' => $request->channel]),
'redirect' => route('business.sms-gateways.index', ['channel' => $request->channel])
]);
}
public function exportExcel()
{
return Excel::download(new SmsGatewayExport, 'sms-gateway.xlsx');
}
public function exportCsv()
{
return Excel::download(new SmsGatewayExport, 'sms-gateway.csv');
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class AcnooSmsGatewaySettingController extends Controller
{
public function index()
{
$businessId = auth()->user()->business_id;
$sms_gateway_setting_key = 'sms-gateway-setting-' . $businessId;
$sms_gateway_setting = Option::where('key', $sms_gateway_setting_key)->first();
return view('marketingaddon::sms-gateway-settings.index', compact('sms_gateway_setting'));
}
public function store(Request $request)
{
$request->validate([
'sms_gateway_setting' => 'required|string|max:100|in:api,android',
]);
$key = 'sms-gateway-setting-' . auth()->user()->business_id;
Option::updateOrCreate(
['key' => $key],
['value' => $request->sms_gateway_setting]
);
Cache::forget($key);
return response()->json(__('SMS Gateway setting updated successfully.'));
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use App\Models\Option;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Modules\MarketingAddon\App\Models\Template;
class AcnooTemplateController extends Controller
{
public function index(Request $request)
{
$businessId = auth()->user()->business_id;
$type = $request->type;
$templates = Template::where('business_id', $businessId)
->where('type', $type)
->get()
->keyBy('title');
$page_data = Option::where('key', 'notification_settings_' . $businessId)->first()?->value ?? [];
return view('marketingaddon::templates.index', compact('templates', 'page_data'));
}
public function store(Request $request)
{
$businessId = auth()->user()->business_id;
$type = $request->type;
$tabs = ['new_sale', 'payment_received', 'payment_paid', 'purchase', 'payment_reminder', 'attendance'];
$request->validate(collect($tabs)
->flatMap(function ($key) use ($request, $type) {
if (!$request->has($key)) {
return [];
}
return [
"{$key}.template" => 'nullable|string|max:255',
"{$key}.subject" => $type === 'email' ? 'required|string|max:255' : 'nullable|string|max:255',
"{$key}.message" => 'required|string|max:1000',
];
})->toArray()
);
$optionKey = 'notification_settings_' . $businessId;
$option = Option::firstOrCreate(
['key' => $optionKey],
['value' => []]
);
$optionValue = $option->value ?? [];
foreach ($tabs as $tab) {
if (!$request->has($tab)) continue;
$tabData = $request->{$tab};
$optionValue[$tab][$type]['enabled'] = $tabData[$type]['enabled'] ?? 0;
$message = $tabData['message'] ?? null;
$variables = $message ? extractTemplateVariables($message) : [];
Template::updateOrCreate(
[
'business_id' => $businessId,
'title' => $tab,
'type' => $type,
],
[
'subject' => $tabData['subject'] ?? null,
'template' => $tabData['template'] ?? null,
'message' => $message,
'variables' => $variables,
]
);
}
$option->update(['value' => $optionValue]);
Cache::forget($optionKey);
return response()->json(__(ucfirst($type) . ' template updated successfully'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\MarketingAddon\App\Http\Controllers;
use App\Models\Party;
use App\Http\Controllers\Controller;
class PaymentReminderController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(string $party_id)
{
$party = Party::with('business')->findOrFail($party_id);
$this->paymentReminderMessage($party->business, $party);
return redirect()->back()->with('message', 'Payment reminder sent successfully.');
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Modules\MarketingAddon\App\Jobs;
use Throwable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Config;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Modules\MarketingAddon\App\Emails\DynamicMail;
use Modules\MarketingAddon\App\Models\EmailSetting;
use Modules\MarketingAddon\App\Services\SmsService;
use Modules\MarketingAddon\App\Models\MessageReport;
use Modules\MarketingAddon\App\Services\WhatsAppService;
class SendNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
protected string $channel,
protected object $template,
protected array $data,
protected int $notificationId,
protected int $businessId,
) {}
public function handle(
SmsService $sms,
WhatsAppService $whatsapp
): void {
try {
match ($this->channel) {
/** ===================== SMS ===================== */
'sms' => $sms->send(
phone: $this->data['phone'],
template: $this->template,
data: $this->data
),
/** ===================== WHATSAPP ===================== */
'whatsapp' => $whatsapp->send(
phone: $this->data['phone'],
template: $this->template,
businessId: $this->businessId,
name: $this->data['name'] ?? '',
data: $this->data
),
/** ===================== EMAIL ===================== */
'email' => $this->sendEmail(),
default => throw new \Exception('Invalid notification channel'),
};
MessageReport::where('id', $this->notificationId)->update([
'status' => 'success',
'send_at' => now(),
]);
} catch (Throwable $e) {
MessageReport::where('id', $this->notificationId)->update([
'status' => 'failed',
'send_at' => now(),
]);
throw $e;
}
}
/**
* Handle email sending with dynamic SMTP
*/
protected function sendEmail(): void
{
$smtp = EmailSetting::where('business_id', $this->businessId)->first();
if ($smtp) {
Config::set('mail.mailers.smtp.transport', $smtp->driver);
Config::set('mail.mailers.smtp.host', $smtp->host);
Config::set('mail.mailers.smtp.port', $smtp->port);
Config::set('mail.mailers.smtp.username', $smtp->username);
Config::set('mail.mailers.smtp.password', $smtp->password);
Config::set('mail.mailers.smtp.encryption', $smtp->encryption);
Config::set('mail.from.address', $smtp->from_address);
Config::set('mail.from.name', $smtp->from_name);
// IMPORTANT: reset mailer to avoid queue worker cache issues
Mail::forgetMailers();
Mail::to($this->data['email'])
->send(new DynamicMail($this->template, $this->data));
} else {
MessageReport::where('id', $this->notificationId)->update([
'status' => 'failed',
'send_at' => now(),
]);
}
}
/**
* Called automatically by Laravel after final failure
*/
public function failed(): void
{
MessageReport::where('id', $this->notificationId)->update([
'status' => 'failed',
'send_at' => now(),
]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\MarketingAddon\App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class EmailSetting extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'business_id',
'driver',
'host',
'port',
'username',
'password',
'encryption',
'from_name',
'from_address',
];
protected $casts = [
'business_id' => 'integer'
];
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\MarketingAddon\App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class GatewayType extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'inputs',
'driver',
'channel',
'status',
'namespace',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'inputs' => 'json',
];
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Modules\MarketingAddon\App\Models;
use App\Models\Business;
use App\Models\User;
use App\Traits\ImageUrlTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MessageReport extends Model
{
use HasFactory, ImageUrlTrait;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'title',
'business_id',
'template_id',
'smsgateway_id',
'user_id',
'channel',
'phone',
'message',
'attachment',
'send_at',
'status'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function business()
{
return $this->belongsTo(Business::class);
}
public function smsgateway()
{
return $this->belongsTo(Smsgateway::class, 'smsgateway_id');
}
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'user_id' => 'integer',
'device_id' => 'integer',
'business_id' => 'integer',
'send_attempts' => 'integer',
];
protected $imageFields = ['attachment'];
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\MarketingAddon\App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Smsgateway extends Model
{
use HasFactory;
protected $fillable = [
'name',
'type_id',
'business_id',
'priority',
'status',
'params',
'namespace',
'driver',
'channel',
];
protected $casts = [
'params' => 'json',
];
public function type()
{
return $this->belongsTo(GatewayType::class, 'type_id');
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\MarketingAddon\App\Models;
use App\Traits\ImageUrlTrait;
use Illuminate\Database\Eloquent\Model;
class Template extends Model
{
use ImageUrlTrait;
protected $fillable = [
'business_id',
'title',
'template',
'type',
'attachment',
'subject',
'variables',
'message',
'status',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'variables' => 'array',
];
protected $imageFields = ['attachment'];
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Modules\MarketingAddon\App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class MarketingAddonServiceProvider extends ServiceProvider
{
protected string $moduleName = 'MarketingAddon';
protected string $moduleNameLower = 'marketingaddon';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'Database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
//
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
//
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
$this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config');
$this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
$componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path'));
Blade::componentNamespace($componentNamespace, $this->moduleNameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
$paths[] = $path.'/modules/'.$this->moduleNameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Modules\MarketingAddon\App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*/
protected string $moduleNamespace = 'Modules\MarketingAddon\App\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('MarketingAddon', '/routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('MarketingAddon', '/routes/api.php'));
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Modules\MarketingAddon\App\Services;
use Modules\MarketingAddon\App\Models\Template;
use Modules\MarketingAddon\App\Models\MessageReport;
use Modules\MarketingAddon\App\Jobs\SendNotificationJob;
class NotifyService
{
public function send(string $event, array $data, string $businessId)
{
$settings = get_option('notification_settings_' . $businessId);
if (!isset($settings[$event])) {
return;
}
$array = [];
foreach (['sms', 'whatsapp', 'email'] as $channel) {
// Check enabled in settings
if (empty($settings[$event][$channel]['enabled'])) {
continue;
}
// Check required data per channel
if (in_array($channel, ['sms', 'whatsapp']) && empty($data['phone'])) {
continue;
}
if ($channel === 'email' && empty($data['email'])) {
continue;
}
$array[] = $channel;
$template = Template::where('business_id', $businessId)->where('title', $event)->where('type', $channel)->first();
if (empty($template)) {
continue;
}
$notification = MessageReport::create([
'title' => $event,
'channel' => $channel,
'business_id' => $businessId,
'template_id' => $template->id,
'message' => $template->message,
]);
// Dispatch job
SendNotificationJob::dispatch(
data: $data,
channel: $channel,
template: $template,
businessId: $businessId,
notificationId: $notification->id,
)->onQueue('high');
}
return $array;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Modules\MarketingAddon\App\Services;
use Exception;
use Modules\MarketingAddon\App\Models\Smsgateway;
class SmsService
{
public function send(string $phone, $template, array $data)
{
$gateways = Smsgateway::where('status', 1)->where('channel', 'sms')->where('business_id', $template->business_id)->get();
if (empty($gateways)) {
throw new \Exception('No active SMS gateway found for this business.');
}
$response = [
'status' => false,
'message' => 'Something went wrong, Please try again.'
];
$contents = $this->buildContent($template, $data);
foreach ($gateways as $gateway) {
$response = $gateway->namespace::send_message($gateway, $phone, $contents);
if (($response['status'] ?? false) === true) {
return;
}
}
throw new Exception('All SMS gateways failed');
}
protected function buildContent($template, array $data)
{
$message = $template->message;
foreach ($data as $key => $value) {
$message = str_replace('[' . $key . ']', $value, $message);
}
return $message;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\MarketingAddon\App\Services;
use Modules\MarketingAddon\App\Models\Smsgateway;
class WhatsAppService
{
/**
* Send WhatsApp campaign via AiSensy
*/
public function send(
string $phone,
object $template,
int $businessId,
string $name,
array $data = [],
) {
$gateways = Smsgateway::where('channel', 'whatsapp')
->where('business_id', $businessId)
->where('status', 1)
->get();
if (empty($gateways)) {
throw new \Exception('No active WhatsApp gateway found for this business.');
}
foreach ($gateways as $gateway) {
$gateway->namespace::send_message($gateway, $phone, $template, $name, $data);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\MarketingAddon\App\SmsGateways;
use Illuminate\Support\Facades\Http;
class AcnooSms
{
public static function send_message($gateway, $numbers, $contents)
{
$response = Http::post('https://acnoosms.com/send-message', [
'client_id' => $gateway->params['client_id'] ?? '',
'client_secret' => $gateway->params['client_secret'] ?? '',
'gateway_id' => $gateway->params['gateway_id'] ?? '',
'type' => 0,
'content' => $contents,
'numbers' => $numbers,
]);
if ($response['status']) {
return [
'status' => true,
];
}
return [
'status' => false,
];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\MarketingAddon\App\SmsGateways;
use Illuminate\Support\Facades\Http;
class HaDavar
{
public static function send_message($gateway, $numbers, $contents, $sender_name = null)
{
$response = Http::asForm()->{$gateway->params['api_method']}($gateway->params['api_url'], array_merge([
'Username' => $gateway->params['username'],
'Token' => $gateway->params['token'],
'Sender' => $sender_name ?? $gateway->params['sender'],
$gateway->params['number_key'] => $numbers,
$gateway->params['message_key'] => $contents,
]));
return $response->body();
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\MarketingAddon\App\SmsGateways;
use Illuminate\Support\Facades\Http;
class RouteMobile
{
public static function send_message($gateway, $numbers, $contents, $sender_name = null)
{
$response = Http::asForm()->{$gateway->params['api_method']}($gateway->params['api_url'], array_merge([
'dlr' => $gateway->params['dlr'],
'type' => $gateway->params['type'],
'source' => $sender_name ?? $gateway->params['source'],
$gateway->params['number_key'] => $numbers,
'username' => $gateway->params['username'],
'password' => $gateway->params['password'],
$gateway->params['message_key'] => $contents,
]));
$words = explode('|', $response);
$firstFourWords = array_slice($words, 0, 4);
if ($firstFourWords[0] == 1701) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\MarketingAddon\App\SmsGateways;
class TzskSms
{
public static function send_message($gateway, $numbers, $contents)
{
$numbers = is_array($numbers) ? $numbers : explode(',', $numbers);
foreach ($gateway->params as $key => $param) {
config(['sms.drivers.' . $gateway->driver . '.' . $key => $param]);
}
sms()->via($gateway->driver)->send($contents, function ($sms) use ($numbers) {
$sms->to($numbers);
});
return true;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Modules\MarketingAddon\App\WhatsappGateways;
use Illuminate\Support\Facades\Http;
class AiSensy
{
public static function send_message($gateway, $phone, $template, $name, $data)
{
$payload = [
'apiKey' => $gateway->params['api_key'],
'campaignName' => $template->template,
'destination' => $phone,
'userName' => $name,
'templateParams' => self::buildParams($template, $data),
'buttons' => self::aiSensyButtons($data['copy_text'] ?? ''),
'media' => self::buildMedia($template),
];
// 🔍 Log payload before sending
\Illuminate\Support\Facades\Log::info('AiSensy Payload', $payload);
$response = Http::withHeaders([
'Content-Type' => 'application/json',
])->post(
'https://backend.aisensy.com/campaign/t1/api/v2',
$payload
);
if ($response->successful()) {
return $response->json();
}
throw new \Exception(
'AiSensy Error: ' . $response->body()
);
}
/**
* Convert named data positional params (WhatsApp only)
*/
protected static function buildParams($template, array $data): array
{
if (empty($template->variables) || !is_array($template->variables)) {
return [];
}
$params = [];
foreach ($template->variables as $variable) {
// Skip button-related vars
if (in_array($variable, ['copy_text', 'expire_at'])) {
continue;
}
if (!isset($data[$variable])) {
continue;
}
$params[] = $data[$variable];
}
return $params;
}
protected static function aiSensyButtons(? string $copyText = null)
{
if (!$copyText) {
return [];
}
return [
[
'type' => 'button',
'sub_type' => 'url',
'index' => 0,
'parameters' => [
[
'type' => 'text',
'text' => $copyText ?? ''
]
]
]
];
}
protected static function buildMedia($template): ?array
{
if (empty($template?->attachment)) {
return null;
}
return [
'url' => asset($template->attachment),
'filename' => 'sample_media',
];
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Modules\MarketingAddon\App\WhatsappGateways;
use Illuminate\Support\Facades\Http;
class WhatsAppCloudApi
{
public static function send_message($gateway, $phone, $template, $name, $data)
{
$payload = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $phone,
'type' => 'template',
'template' => [
'name' => $template->template,
'language' => [
'code' => $gateway->params['language_code'] ?? 'en_US',
],
],
];
$components = self::buildComponents($template, $data);
if (!empty($components)) {
$payload['template']['components'] = $components;
}
$apiVersion = $gateway->params['api_version'] ?? 'v25.0';
$phoneNumberId = $gateway->params['phone_number_id'];
$url = "https://graph.facebook.com/{$apiVersion}/{$phoneNumberId}/messages";
$response = Http::withToken($gateway->params['access_token'])
->withHeaders([
'Content-Type' => 'application/json',
])
->post($url, $payload);
if ($response->successful()) {
return $response->json();
}
throw new \Exception(
'WhatsApp Cloud API Error: ' . $response->body()
);
}
protected static function buildComponents($template, array $data): array
{
$components = [];
$header = self::buildHeader($template);
$body = self::buildBody($template, $data);
$button = self::buildButton($data['copy_text'] ?? null);
if (!empty($header)) {
$components[] = $header;
}
if (!empty($body)) {
$components[] = $body;
}
if (!empty($button)) {
$components[] = $button;
}
return $components;
}
protected static function buildBody($template, array $data): ?array
{
if (empty($template->variables) || !is_array($template->variables)) {
return null;
}
$parameters = [];
foreach ($template->variables as $variable) {
if (in_array($variable, ['copy_text', 'expire_at'])) {
continue;
}
if (!isset($data[$variable])) {
continue;
}
$parameters[] = [
'type' => 'text',
'text' => (string) $data[$variable],
];
}
if (empty($parameters)) {
return null;
}
return [
'type' => 'body',
'parameters' => $parameters,
];
}
protected static function buildHeader($template): ?array
{
if (empty($template?->attachment)) {
return null;
}
$mediaType = self::mediaType($template->attachment);
$media = ['link' => asset($template->attachment)];
if ($mediaType === 'document') {
$media['filename'] = basename($template->attachment);
}
return [
'type' => 'header',
'parameters' => [
[
'type' => $mediaType,
$mediaType => $media,
],
],
];
}
protected static function buildButton(?string $copyText = null): ?array
{
if (!$copyText) {
return null;
}
return [
'type' => 'button',
'sub_type' => 'url',
'index' => 0,
'parameters' => [
[
'type' => 'text',
'text' => $copyText,
],
],
];
}
protected static function mediaType(string $attachment): string
{
$extension = strtolower(pathinfo($attachment, PATHINFO_EXTENSION));
if (in_array($extension, ['jpg', 'jpeg', 'png', 'webp'])) {
return 'image';
}
if (in_array($extension, ['mp4', '3gp'])) {
return 'video';
}
return 'document';
}
}