updated plan, and oxapay integration
This commit is contained in:
@@ -46,6 +46,7 @@ class PlanResource extends Resource
|
||||
TextInput::make('product_id')->required(),
|
||||
TextInput::make('pricing_id')->required(),
|
||||
TextInput::make('shoppy_product_id')->nullable(),
|
||||
TextInput::make('oxapay_link')->nullable(),
|
||||
TextInput::make('price')->numeric()->required(),
|
||||
TextInput::make('mailbox_limit')->numeric()->required(),
|
||||
Select::make('monthly_billing')->options([
|
||||
@@ -60,6 +61,10 @@ class PlanResource extends Resource
|
||||
1 => 'Activate',
|
||||
0 => 'Disable',
|
||||
])->required(),
|
||||
Select::make('accept_oxapay')->options([
|
||||
1 => 'Activate',
|
||||
0 => 'Disable',
|
||||
])->required(),
|
||||
KeyValue::make('details')
|
||||
->label('Plan Details (Optional)')
|
||||
->keyPlaceholder('Name')
|
||||
|
||||
111
app/Http/Controllers/WebhookController.php
Normal file
111
app/Http/Controllers/WebhookController.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\NotifyMe;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class WebhookController extends Controller
|
||||
{
|
||||
use NotifyMe;
|
||||
public function oxapay(Request $request)
|
||||
{
|
||||
// Get the request data
|
||||
$postData = $request->getContent();
|
||||
$data = json_decode($postData, true);
|
||||
|
||||
// Validate request data
|
||||
if (!$data || !isset($data['type']) || !in_array($data['type'], ['invoice', 'payment_link', 'payout'])) {
|
||||
\Log::warning('Invalid Oxapay webhook data', ['data' => $data]);
|
||||
return response('Invalid data.type', 400);
|
||||
}
|
||||
|
||||
// Determine API secret key based on type
|
||||
$apiSecretKey = $data['type'] === 'invoice'
|
||||
? config('services.oxapay.merchant_api_key')
|
||||
: config('services.oxapay.payout_api_key');
|
||||
|
||||
// Validate HMAC signature
|
||||
$hmacHeader = $request->header('HMAC');
|
||||
$calculatedHmac = hash_hmac('sha512', $postData, $apiSecretKey);
|
||||
|
||||
if (hash_equals($calculatedHmac, $hmacHeader)) {
|
||||
// HMAC signature is valid
|
||||
try {
|
||||
if ($data['type'] === 'invoice' || $data['type'] === 'payment_link') {
|
||||
// Process invoice payment data
|
||||
$email = $data['email'] ?? 'Unknown';
|
||||
$amount = $data['amount'] ?? 'Unknown';
|
||||
$currency = $data['currency'] ?? 'Unknown';
|
||||
$trackId = $data['track_id'] ?? 'Unknown';
|
||||
$orderId = $data['order_id'] ?? 'N/A';
|
||||
$date = isset($data['date']) ? Carbon::createFromTimestamp($data['date'])->toDateTimeString() : now()->toDateTimeString();
|
||||
|
||||
\Log::info('Received Oxapay invoice payment callback', [
|
||||
'track_id' => $trackId,
|
||||
'email' => $email,
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'order_id' => $orderId,
|
||||
'date' => $date,
|
||||
]);
|
||||
|
||||
$message = "✅ Oxapay Invoice Payment Success\n" .
|
||||
"Track ID: {$trackId}\n" .
|
||||
"Email: {$email}\n" .
|
||||
"Amount: {$amount} {$currency}\n" .
|
||||
"Order ID: {$orderId}\n" .
|
||||
"Time: {$date}";
|
||||
self::sendTelegramNotification($message);
|
||||
} elseif ($data['type'] === 'payout') {
|
||||
// Process payout data
|
||||
$trackId = $data['track_id'] ?? 'Unknown';
|
||||
$amount = $data['amount'] ?? 'Unknown';
|
||||
$currency = $data['currency'] ?? 'Unknown';
|
||||
$network = $data['network'] ?? 'Unknown';
|
||||
$address = $data['address'] ?? 'Unknown';
|
||||
$txHash = $data['tx_hash'] ?? 'Unknown';
|
||||
$description = $data['description'] ?? 'N/A';
|
||||
$date = isset($data['date']) ? Carbon::createFromTimestamp($data['date'])->toDateTimeString() : now()->toDateTimeString();
|
||||
|
||||
\Log::info('Received Oxapay payout callback', [
|
||||
'track_id' => $trackId,
|
||||
'status' => $data['status'] ?? 'Unknown',
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'network' => $network,
|
||||
'address' => $address,
|
||||
'tx_hash' => $txHash,
|
||||
'description' => $description,
|
||||
'date' => $date,
|
||||
]);
|
||||
|
||||
$message = "📤 Oxapay Payout Confirmed\n" .
|
||||
"Track ID: {$trackId}\n" .
|
||||
"Amount: {$amount} {$currency}\n" .
|
||||
"Network: {$network}\n" .
|
||||
"Address: {$address}\n" .
|
||||
"Transaction Hash: {$txHash}\n" .
|
||||
"Description: {$description}\n" .
|
||||
"Date: {$date}";
|
||||
self::sendTelegramNotification($message);
|
||||
}
|
||||
|
||||
return response('OK', 200);
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Oxapay webhook processing error', ['error' => $e->getMessage(), 'data' => $data]);
|
||||
self::sendTelegramNotification("
|
||||
Failed to process Oxapay webhook\n
|
||||
Type: {$data['type']}\n
|
||||
Email/Track ID: " . ($data['type'] === 'invoice' ? ($data['email'] ?? 'Unknown') : ($data['track_id'] ?? 'Unknown')) . "\n
|
||||
Error: {$e->getMessage()}
|
||||
");
|
||||
return response('Processing error', 400);
|
||||
}
|
||||
} else {
|
||||
\Log::warning('Invalid Oxapay HMAC signature', ['hmac_header' => $hmacHeader, 'calculated_hmac' => $calculatedHmac]);
|
||||
return response('Invalid HMAC signature', 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ class Plan extends Model
|
||||
'shoppy_product_id',
|
||||
'accept_stripe',
|
||||
'accept_shoppy',
|
||||
'oxapay_link',
|
||||
'accept_oxapay',
|
||||
'price',
|
||||
'mailbox_limit',
|
||||
'monthly_billing',
|
||||
|
||||
@@ -12,11 +12,12 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Cashier\Billable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, Billable;
|
||||
use HasFactory, Notifiable, Billable, HasApiTokens;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware;
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
@@ -16,6 +17,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
]);
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'stripe/*',
|
||||
'webhook/oxapay',
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"filament/filament": "3.3",
|
||||
"laravel/cashier": "^15.6",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"livewire/flux": "^2.1",
|
||||
"livewire/livewire": "^3.6",
|
||||
|
||||
66
composer.lock
generated
66
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "972e884837f3870524619dc37aa08d0f",
|
||||
"content-hash": "c28ee3c8ad2c6071685462887cfb5ee5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -2462,6 +2462,70 @@
|
||||
},
|
||||
"time": "2025-02-11T13:34:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v4.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sanctum.git",
|
||||
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
|
||||
"reference": "a360a6a1fd2400ead4eb9b6a9c1bb272939194f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/console": "^11.0|^12.0",
|
||||
"illuminate/contracts": "^11.0|^12.0",
|
||||
"illuminate/database": "^11.0|^12.0",
|
||||
"illuminate/support": "^11.0|^12.0",
|
||||
"php": "^8.2",
|
||||
"symfony/console": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^9.0|^10.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^11.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sanctum\\SanctumServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sanctum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"laravel",
|
||||
"sanctum"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/sanctum/issues",
|
||||
"source": "https://github.com/laravel/sanctum"
|
||||
},
|
||||
"time": "2025-04-23T13:03:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v2.0.4",
|
||||
|
||||
84
config/sanctum.php
Normal file
84
config/sanctum.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort(),
|
||||
// Sanctum::currentRequestHost(),
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -34,5 +34,9 @@ return [
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
'oxapay' => [
|
||||
'merchant_api_key' => env('OXAPAY_MERCHANT_API_KEY', ''),
|
||||
'payout_api_key' => env('OXAPAY_PAYOUT_API_KEY', ''),
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('plans', function (Blueprint $table) {
|
||||
$table->string('oxapay_link')->nullable()->after('accept_shoppy');
|
||||
$table->boolean('accept_oxapay')->default(false)->after('oxapay_link');});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('plans', function (Blueprint $table) {
|
||||
$table->dropColumn(['oxapay_link', 'accept_oxapay']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="mx-auto max-w-3xl px-4 py-8 sm:px-6 sm:py-12 lg:px-8 ">
|
||||
<script src="https://shoppy.gg/api/embed.js"></script>
|
||||
{{-- <script src="https://shoppy.gg/api/embed.js"></script>--}}
|
||||
<div class="w-full mb-8 items-center flex justify-center">
|
||||
<h1 class="text-center text-3xl text-gray-900 dark:text-gray-200">Purchase Subscription</h1>
|
||||
</div>
|
||||
@@ -46,6 +46,17 @@
|
||||
Pay with crypto
|
||||
</flux:button>
|
||||
@endif
|
||||
@if($plan->accept_oxapay && $plan->oxapay_link !== null)
|
||||
<flux:button
|
||||
variant="filled"
|
||||
class="w-full mt-2 cursor-pointer"
|
||||
tag="a"
|
||||
href="{{ $plan->oxapay_link }}"
|
||||
target="_blank"
|
||||
>
|
||||
Pay with crypto
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
8
routes/api.php
Normal file
8
routes/api.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
})->middleware('auth:sanctum');
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\AppController;
|
||||
use App\Http\Controllers\WebhookController;
|
||||
use App\Http\Middleware\CheckPageSlug;
|
||||
use App\Http\Middleware\CheckUserBanned;
|
||||
use App\Livewire\AddOn;
|
||||
@@ -145,6 +146,8 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::get('settings/appearance', Appearance::class)->name('settings.appearance');
|
||||
});
|
||||
|
||||
Route::post('/webhook/oxapay', [WebhookController::class, 'oxapay'])->name('webhook.oxapay');
|
||||
|
||||
require __DIR__.'/auth.php';
|
||||
|
||||
Route::get('{slug}', Page::class)->where('slug', '.*')->name('page')->middleware(CheckPageSlug::class);
|
||||
|
||||
Reference in New Issue
Block a user