feat(notifications): implement comprehensive telegram notifications for payment providers
- Add NotifyMe trait with centralized Telegram bot integration - Implement webhook notifications for Polar, OxaPay, and ActivationKey providers - Add subscription lifecycle notifications (create, activate, cancel, pause, resume) - Enhance Polar webhook processing with user context and error handling - Fix subscription.updated and subscription.canceled webhook column errors - Add idempotent webhook processing to prevent duplicate handling
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\PaymentProvider as PaymentProviderModel;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\User;
|
||||
use App\NotifyMe;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -15,6 +16,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OxapayProvider implements PaymentProviderContract
|
||||
{
|
||||
use NotifyMe;
|
||||
|
||||
protected array $config;
|
||||
|
||||
protected bool $sandbox;
|
||||
@@ -52,7 +55,6 @@ class OxapayProvider implements PaymentProviderContract
|
||||
try {
|
||||
$providerModel = PaymentProviderModel::where('name', 'oxapay')->first();
|
||||
|
||||
|
||||
if ($providerModel && $providerModel->configuration) {
|
||||
return $providerModel->configuration;
|
||||
}
|
||||
@@ -163,6 +165,9 @@ class OxapayProvider implements PaymentProviderContract
|
||||
'cancellation_reason' => $reason,
|
||||
]);
|
||||
|
||||
// Notify subscription cancelled
|
||||
$this->notifySubscriptionCancelled($subscription, $reason ?: 'Manual cancellation');
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -273,6 +278,13 @@ class OxapayProvider implements PaymentProviderContract
|
||||
'last_provider_sync' => now(),
|
||||
]);
|
||||
|
||||
// Notify payment created
|
||||
$this->notifyPaymentCreated($user, $plan, [
|
||||
'amount' => $paymentData['amount'] ?? $amount,
|
||||
'currency' => $paymentData['currency'] ?? $currency,
|
||||
'payment_id' => $paymentData['track_id'] ?? null,
|
||||
]);
|
||||
|
||||
Log::info('OxaPay subscription created', [
|
||||
'subscription_id' => $subscription->id,
|
||||
'track_id' => $paymentData['track_id'],
|
||||
@@ -321,11 +333,7 @@ class OxapayProvider implements PaymentProviderContract
|
||||
{
|
||||
try {
|
||||
$payload = $request->getContent();
|
||||
$signature = $request->header('HMAC');
|
||||
|
||||
if (! $this->validateWebhook($request)) {
|
||||
throw new \Exception('Invalid webhook signature');
|
||||
}
|
||||
$signature = $request->header('hmac');
|
||||
|
||||
$data = $request->json()->all();
|
||||
$status = strtolower($data['status'] ?? 'unknown');
|
||||
@@ -335,6 +343,15 @@ class OxapayProvider implements PaymentProviderContract
|
||||
if (! $trackId) {
|
||||
throw new \Exception('Missing track_id in webhook payload');
|
||||
}
|
||||
Log::debug('before send webhook status');
|
||||
// Notify webhook received
|
||||
$this->notifyWebhookReceived($status, $trackId);
|
||||
Log::debug('before send webhook status with payload'.$status.' '.$trackId);
|
||||
|
||||
if (! $this->validateWebhook($request)) {
|
||||
$this->notifyWebhookError($status, $trackId, 'Invalid webhook signature');
|
||||
throw new \Exception('Invalid webhook signature');
|
||||
}
|
||||
|
||||
Log::info('OxaPay webhook received', [
|
||||
'track_id' => $trackId,
|
||||
@@ -345,6 +362,9 @@ class OxapayProvider implements PaymentProviderContract
|
||||
// Process the webhook based on status
|
||||
$result = $this->handleWebhookStatus($status, $data);
|
||||
|
||||
// Notify webhook processed successfully
|
||||
$this->notifyWebhookProcessed($status, $trackId, $data['payment_id'] ?? null);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'event_type' => $status,
|
||||
@@ -361,6 +381,9 @@ class OxapayProvider implements PaymentProviderContract
|
||||
'payload' => $request->getContent(),
|
||||
]);
|
||||
|
||||
// Notify webhook processing error
|
||||
$this->notifyWebhookError($data['status'] ?? 'unknown', $data['track_id'] ?? 'unknown', $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'event_type' => 'error',
|
||||
@@ -543,6 +566,15 @@ class OxapayProvider implements PaymentProviderContract
|
||||
|
||||
$subscription->update($updateData);
|
||||
|
||||
// Notify payment success for paid payments
|
||||
if (in_array($status, ['paid', 'manual_accept'])) {
|
||||
$this->notifyPaymentSuccess($subscription->user, $subscription->plan, [
|
||||
'amount' => $data['amount'] ?? 0,
|
||||
'currency' => $data['currency'] ?? 'USD',
|
||||
'transaction_id' => $trackId,
|
||||
]);
|
||||
}
|
||||
|
||||
Log::info('OxaPay subscription updated', [
|
||||
'subscription_id' => $subscription->id,
|
||||
'track_id' => $trackId,
|
||||
@@ -824,4 +856,109 @@ class OxapayProvider implements PaymentProviderContract
|
||||
{
|
||||
throw new \Exception('OxaPay does not support recurring subscriptions');
|
||||
}
|
||||
|
||||
// Notification methods
|
||||
protected function notifyPaymentSuccess(User $user, Plan $plan, array $paymentDetails): void
|
||||
{
|
||||
$message = "💰 PAYMENT SUCCESS\n".
|
||||
"📧 Customer: {$user->name} ({$user->email})\n".
|
||||
"📦 Plan: {$plan->name}\n".
|
||||
'💵 Amount: $'.number_format($paymentDetails['amount'] ?? 0, 2).' '.($paymentDetails['currency'] ?? 'USD')."\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
'🆔 Transaction: '.($paymentDetails['transaction_id'] ?? 'N/A')."\n".
|
||||
'⏰ Time: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
protected function notifyWebhookReceived(string $eventType, ?string $webhookId): void
|
||||
{
|
||||
$message = "🔔 WEBHOOK RECEIVED\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
"📋 Event: {$eventType}\n".
|
||||
'🆔 Webhook ID: '.($webhookId ?? 'N/A')."\n".
|
||||
"📊 Status: Processing...\n".
|
||||
'⏰ Received: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
Log::warning($message);
|
||||
|
||||
$res = $this->sendTelegramNotification($message);
|
||||
|
||||
Log::warning('result : '.$res);
|
||||
}
|
||||
|
||||
protected function notifyWebhookProcessed(string $eventType, ?string $webhookId, ?string $paymentId = null): void
|
||||
{
|
||||
$message = "✅ WEBHOOK PROCESSED\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
"📋 Event: {$eventType}\n".
|
||||
'🆔 Webhook ID: '.($webhookId ?? 'N/A')."\n".
|
||||
($paymentId ? "💳 Payment ID: {$paymentId}\n" : '').
|
||||
"📊 Status: Completed\n".
|
||||
'⏰ Processed: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
protected function notifyWebhookError(string $eventType, ?string $webhookId, string $error): void
|
||||
{
|
||||
$message = "❌ WEBHOOK ERROR\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
"📋 Event: {$eventType}\n".
|
||||
'🆔 Webhook ID: '.($webhookId ?? 'N/A')."\n".
|
||||
"💥 Error: {$error}\n".
|
||||
'⏰ Time: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
protected function notifyProviderError(string $operation, string $error, array $context = []): void
|
||||
{
|
||||
$contextStr = '';
|
||||
if (! empty($context)) {
|
||||
$contextStr = '📝 Details: '.json_encode(array_slice($context, 0, 3, true), JSON_UNESCAPED_SLASHES)."\n";
|
||||
if (count($context) > 3) {
|
||||
$contextStr .= '📝 Additional: '.(count($context) - 3).' more items'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
$message = "🚨 PROVIDER ERROR\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
"📡 Operation: {$operation}\n".
|
||||
"💥 Error: {$error}\n".
|
||||
$contextStr.
|
||||
'⏰ Time: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
protected function notifyPaymentCreated(User $user, Plan $plan, array $paymentDetails): void
|
||||
{
|
||||
$message = "🆕 PAYMENT CREATED\n".
|
||||
"👤 User: {$user->name} ({$user->email})\n".
|
||||
"📋 Plan: {$plan->name}\n".
|
||||
'💰 Amount: $'.number_format($paymentDetails['amount'] ?? 0, 2).' '.($paymentDetails['currency'] ?? 'USD')."\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
'🆔 Payment ID: '.($paymentDetails['payment_id'] ?? 'N/A')."\n".
|
||||
'📅 Created: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
protected function notifySubscriptionCancelled(Subscription $subscription, string $reason): void
|
||||
{
|
||||
$user = $subscription->user;
|
||||
$plan = $subscription->plan;
|
||||
|
||||
$message = "❌ SUBSCRIPTION CANCELLED\n".
|
||||
"👤 User: {$user->name} ({$user->email})\n".
|
||||
"📋 Plan: {$plan->name}\n".
|
||||
"🏪 Provider: OxaPay\n".
|
||||
"💭 Reason: {$reason}\n".
|
||||
"🆔 Subscription ID: {$subscription->provider_subscription_id}\n".
|
||||
($subscription->ends_at ? '📅 Effective: '.$subscription->ends_at->format('Y-m-d')."\n" : '').
|
||||
'⏰ Cancelled: '.now()->format('Y-m-d H:i:s');
|
||||
|
||||
$this->sendTelegramNotification($message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user