Fix agenda
This commit is contained in:
parent
113573866d
commit
e17b1bfc1c
157
WISE_INTEGRATION_UPDATES.md
Normal file
157
WISE_INTEGRATION_UPDATES.md
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# Wise Payment Integration - Frontend Updates
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Updated the KSA-ORACLE project's cardSelection component to support both Stripe and Wise payment methods, matching the implementation in the oracle project.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. **cardSelection.vue** (`resources/js/pages/cards/cardSelection.vue`)
|
||||||
|
|
||||||
|
#### Added Wise Payment Support
|
||||||
|
- Updated `handleSelection()` function to accept a `provider` parameter (`'stripe' | 'wise'`)
|
||||||
|
- Added new `redirectToWisePayment()` function that:
|
||||||
|
- Creates a payment record via `/create-wise-payment` endpoint
|
||||||
|
- Stores the `clientSessionId` in sessionStorage
|
||||||
|
- Redirects to the Wise payment link
|
||||||
|
|
||||||
|
#### Updated UI
|
||||||
|
- Changed single payment buttons to dual payment buttons for 6-card and 18-card options
|
||||||
|
- **6-card option (9.99 €)**: Now shows two buttons side-by-side:
|
||||||
|
- "Stripe" button (outlined style)
|
||||||
|
- "Wise" button (filled style)
|
||||||
|
- **18-card option (99€)**: Now shows two buttons side-by-side:
|
||||||
|
- "Stripe" button (outlined style)
|
||||||
|
- "Wise" button (filled style)
|
||||||
|
|
||||||
|
### 2. **WiseController.php** (`app/Http/Controllers/WiseController.php`)
|
||||||
|
|
||||||
|
Added complete Wise payment controller with:
|
||||||
|
- `createPaymentSession()` - Creates payment records and returns Wise payment links
|
||||||
|
- `handleWebhook()` - Processes Wise webhook notifications
|
||||||
|
- `validatePayment()` - Manual payment validation for redirect flow
|
||||||
|
- Webhook signature verification
|
||||||
|
- Support for different Wise event types
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
- Uses environment variables for payment links per draw count
|
||||||
|
- Stores payments with `payment_provider = 'wise'`
|
||||||
|
- Tracks payments via `client_session_id` and `wise_session_id`
|
||||||
|
|
||||||
|
### 3. **Routes** (`routes/web.php`)
|
||||||
|
|
||||||
|
Added new Wise payment routes:
|
||||||
|
```php
|
||||||
|
Route::post('/create-wise-payment', [WiseController::class, 'createPaymentSession']);
|
||||||
|
Route::post('/wise/webhook', [WiseController::class, 'handleWebhook']);
|
||||||
|
Route::get('/wise/validate-payment', [WiseController::class, 'validatePayment']);
|
||||||
|
Route::get('/wise/verify', function () {
|
||||||
|
return Inertia::render('wise/VerifyPayment');
|
||||||
|
})->name('wise.verify');
|
||||||
|
```
|
||||||
|
|
||||||
|
Updated `/success` route to:
|
||||||
|
- Check for pending Wise payments
|
||||||
|
- Redirect to Pending page if payment is still processing
|
||||||
|
- Pass `paymentProvider` prop to success page
|
||||||
|
|
||||||
|
### 4. **Frontend Views**
|
||||||
|
|
||||||
|
Added new Vue components:
|
||||||
|
- **Pending.vue** (`resources/js/pages/payments/Pending.vue`)
|
||||||
|
- Shows "Payment is being processed" message
|
||||||
|
- Polls payment status for Wise payments
|
||||||
|
- Auto-redirects when payment succeeds
|
||||||
|
|
||||||
|
- **VerifyPayment.vue** (`resources/js/pages/wise/VerifyPayment.vue`)
|
||||||
|
- Manual payment verification page
|
||||||
|
- Allows users to check payment status
|
||||||
|
|
||||||
|
### 5. **TypeScript Actions**
|
||||||
|
|
||||||
|
Added **WiseController.ts** (`resources/js/actions/App/Http/Controllers/WiseController.ts`)
|
||||||
|
- Type-safe route helpers for Wise endpoints
|
||||||
|
- Auto-generated from Laravel routes
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
Add these variables to your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Wise Payment Links (Simplified Integration)
|
||||||
|
WISE_PAYMENT_LINK_6_CARDS=https://wise.com/pay/r/YOUR_6_CARDS_LINK
|
||||||
|
WISE_PAYMENT_LINK_18_CARDS=https://wise.com/pay/r/YOUR_18_CARDS_LINK
|
||||||
|
|
||||||
|
# Wise Webhook Configuration
|
||||||
|
WISE_WEBHOOK_SECRET=your_webhook_secret_here
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Experience Flow
|
||||||
|
|
||||||
|
### Stripe Flow (unchanged)
|
||||||
|
1. User clicks "Stripe" button
|
||||||
|
2. Backend creates Stripe checkout session
|
||||||
|
3. User redirects to Stripe checkout page
|
||||||
|
4. After payment, redirects back to `/success`
|
||||||
|
|
||||||
|
### Wise Flow (new)
|
||||||
|
1. User clicks "Wise" button
|
||||||
|
2. Backend creates payment record with `status='pending'`
|
||||||
|
3. Returns Wise payment link URL
|
||||||
|
4. Frontend stores `client_session_id` in sessionStorage
|
||||||
|
5. User redirects to Wise payment page
|
||||||
|
6. After payment on Wise, user returns to `/success`
|
||||||
|
7. If payment still pending, shows Pending page
|
||||||
|
8. Pending page polls backend until payment confirmed via webhook
|
||||||
|
9. When confirmed, redirects to success page
|
||||||
|
|
||||||
|
## Webhook Setup
|
||||||
|
|
||||||
|
To receive payment confirmations:
|
||||||
|
|
||||||
|
1. Go to https://wise.com/settings/webhooks
|
||||||
|
2. Create a new webhook
|
||||||
|
3. Set URL to: `https://yourdomain.com/wise/webhook`
|
||||||
|
4. Select event types:
|
||||||
|
- `transfer_state_change`
|
||||||
|
- `balance_credit`
|
||||||
|
5. Copy the webhook secret to your `.env` file as `WISE_WEBHOOK_SECRET`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To test the integration:
|
||||||
|
|
||||||
|
1. Configure test payment links in `.env`
|
||||||
|
2. Start the development server
|
||||||
|
3. Navigate to the card selection page
|
||||||
|
4. Click "Wise" button for either 6 or 18 cards
|
||||||
|
5. Complete payment on Wise
|
||||||
|
6. Verify redirect back to success page
|
||||||
|
|
||||||
|
## Payment Links Configuration
|
||||||
|
|
||||||
|
Get your Wise payment links:
|
||||||
|
|
||||||
|
1. Log into your Wise business account
|
||||||
|
2. Go to "Request payment" or "Payment Links"
|
||||||
|
3. Create payment links for:
|
||||||
|
- 9.99 EUR (6 cards)
|
||||||
|
- 15.90 EUR (18 cards) - *Update this value based on your pricing*
|
||||||
|
4. Copy the links and add to `.env`
|
||||||
|
|
||||||
|
## Database Fields Used
|
||||||
|
|
||||||
|
The following Payment model fields are used for Wise:
|
||||||
|
- `payment_provider` = 'wise'
|
||||||
|
- `wise_session_id` - Unique session identifier
|
||||||
|
- `client_session_id` - Client-side tracking ID
|
||||||
|
- `wise_payment_id` - Wise transaction ID (from webhook)
|
||||||
|
- `status` - Payment status (pending, succeeded, failed, etc.)
|
||||||
|
- `amount` - Payment amount
|
||||||
|
- `currency` - Payment currency (EUR)
|
||||||
|
- `draw_count` - Number of cards (6 or 18)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Integration Status**: ✅ Complete
|
||||||
|
**Last Updated**: October 14, 2025
|
||||||
|
**Project**: KSA-ORACLE (/media/creator/6226b912-8ba7-45dc-88a2-4b10d3dd1655/kandra/successkey/KSA-ORACLE)
|
||||||
276
app/Http/Controllers/WiseController.php
Normal file
276
app/Http/Controllers/WiseController.php
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Payment;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class WiseController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a Wise payment session for card draws
|
||||||
|
* Simplified version - just redirects to pre-configured Wise payment links
|
||||||
|
*/
|
||||||
|
public function createPaymentSession(Request $request)
|
||||||
|
{
|
||||||
|
$count = $request->input('count');
|
||||||
|
$clientSessionId = Str::uuid();
|
||||||
|
|
||||||
|
// Define pricing and payment links for different draw counts
|
||||||
|
$paymentOptions = [
|
||||||
|
6 => [
|
||||||
|
'amount' => 9.99,
|
||||||
|
'currency' => 'EUR',
|
||||||
|
'description' => 'Profilage - 6 cartes',
|
||||||
|
'payment_link' => env('WISE_PAYMENT_LINK_6_CARDS', 'https://wise.com/pay/r/JVNRSE21VZTj8rw'),
|
||||||
|
],
|
||||||
|
18 => [
|
||||||
|
'amount' => 15.90,
|
||||||
|
'currency' => 'EUR',
|
||||||
|
'description' => 'Quadrige Doré - 18 cartes',
|
||||||
|
'payment_link' => env('WISE_PAYMENT_LINK_18_CARDS','https://wise.com/pay/r/W2k1NqQySdc9HW8'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! isset($paymentOptions[$count])) {
|
||||||
|
return response()->json(['error' => 'Invalid product selected.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$option = $paymentOptions[$count];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Store payment in database with pending status
|
||||||
|
Payment::create([
|
||||||
|
'amount' => $option['amount'],
|
||||||
|
'currency' => $option['currency'],
|
||||||
|
'wise_session_id' => $clientSessionId,
|
||||||
|
'client_session_id' => $clientSessionId,
|
||||||
|
'draw_count' => $count,
|
||||||
|
'status' => 'pending',
|
||||||
|
'payment_provider' => 'wise',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Log::info('Wise payment created', [
|
||||||
|
'client_session_id' => $clientSessionId,
|
||||||
|
'amount' => $option['amount'],
|
||||||
|
'draw_count' => $count,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Return the payment link URL
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'paymentUrl' => $option['payment_link'],
|
||||||
|
'clientSessionId' => $clientSessionId,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Wise payment creation failed: '.$e->getMessage());
|
||||||
|
|
||||||
|
return response()->json(['error' => 'Could not create payment session.'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Wise webhook notifications
|
||||||
|
*/
|
||||||
|
public function handleWebhook(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->all();
|
||||||
|
$signature = $request->header('X-Signature-SHA256');
|
||||||
|
|
||||||
|
// Verify webhook signature
|
||||||
|
if (! $this->verifyWebhookSignature($request->getContent(), $signature)) {
|
||||||
|
Log::error('Wise webhook signature verification failed');
|
||||||
|
|
||||||
|
return response()->json(['error' => 'Invalid signature'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$eventType = $payload['event_type'] ?? $payload['type'] ?? null;
|
||||||
|
|
||||||
|
Log::info('Wise webhook received', ['event_type' => $eventType, 'payload' => $payload]);
|
||||||
|
|
||||||
|
// Handle different Wise event types
|
||||||
|
switch ($eventType) {
|
||||||
|
case 'transfer_state_change':
|
||||||
|
case 'transfers#state-change':
|
||||||
|
$this->handleTransferStateChange($payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'balance_credit':
|
||||||
|
$this->handleBalanceCredit($payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log::info('Unhandled Wise webhook event type: '.$eventType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success'], 200);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Wise webhook processing error: '.$e->getMessage(), ['exception' => $e]);
|
||||||
|
|
||||||
|
return response()->json(['error' => 'Server error'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle transfer state change events
|
||||||
|
*/
|
||||||
|
private function handleTransferStateChange(array $payload)
|
||||||
|
{
|
||||||
|
$transferId = $payload['data']['resource']['id'] ?? null;
|
||||||
|
$currentState = $payload['data']['current_state'] ?? $payload['data']['resource']['status'] ?? null;
|
||||||
|
|
||||||
|
if (! $transferId) {
|
||||||
|
Log::warning('Transfer ID not found in Wise webhook payload');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find payment by Wise transfer ID
|
||||||
|
$payment = Payment::where('wise_payment_id', $transferId)
|
||||||
|
->orWhere('wise_session_id', $payload['data']['resource']['customerTransactionId'] ?? null)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $payment) {
|
||||||
|
Log::warning('No payment record found for Wise transfer ID: '.$transferId);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update payment status based on transfer state
|
||||||
|
switch ($currentState) {
|
||||||
|
case 'outgoing_payment_sent':
|
||||||
|
case 'funds_converted':
|
||||||
|
case 'incoming_payment_waiting':
|
||||||
|
// Payment is being processed
|
||||||
|
$payment->update(['status' => 'processing']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'funds_refunded':
|
||||||
|
// Payment was refunded
|
||||||
|
$payment->update(['status' => 'refunded']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bounced_back':
|
||||||
|
case 'charged_back':
|
||||||
|
// Payment failed or was charged back
|
||||||
|
$payment->update(['status' => 'failed']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log::info('Unhandled Wise transfer state: '.$currentState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle balance credit events (payment received)
|
||||||
|
*/
|
||||||
|
private function handleBalanceCredit(array $payload)
|
||||||
|
{
|
||||||
|
$amount = $payload['data']['amount'] ?? null;
|
||||||
|
$currency = $payload['data']['currency'] ?? null;
|
||||||
|
$transactionId = $payload['data']['transaction_id'] ?? null;
|
||||||
|
|
||||||
|
// Find payment by amount and currency (less reliable, but works for balance credits)
|
||||||
|
$payment = Payment::where('amount', $amount)
|
||||||
|
->where('currency', $currency)
|
||||||
|
->where('status', '!=', 'succeeded')
|
||||||
|
->where('payment_provider', 'wise')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($payment) {
|
||||||
|
$payment->update([
|
||||||
|
'status' => 'succeeded',
|
||||||
|
'wise_payment_id' => $transactionId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Log::info('Wise payment succeeded', ['payment_id' => $payment->id, 'transaction_id' => $transactionId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Wise webhook signature
|
||||||
|
*/
|
||||||
|
private function verifyWebhookSignature(string $payload, ?string $signature): bool
|
||||||
|
{
|
||||||
|
if (! $signature) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$webhookSecret = env('WISE_WEBHOOK_SECRET');
|
||||||
|
if (! $webhookSecret) {
|
||||||
|
Log::warning('WISE_WEBHOOK_SECRET not configured');
|
||||||
|
|
||||||
|
return true; // Allow in development if secret not set
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);
|
||||||
|
|
||||||
|
return hash_equals($expectedSignature, $signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate payment status manually (for redirect flow)
|
||||||
|
*/
|
||||||
|
public function validatePayment(Request $request)
|
||||||
|
{
|
||||||
|
$clientSessionId = $request->query('client_session_id');
|
||||||
|
|
||||||
|
$payment = Payment::where('client_session_id', $clientSessionId)
|
||||||
|
->where('payment_provider', 'wise')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $payment) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payment not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if payment is succeeded
|
||||||
|
if ($payment->status === 'succeeded') {
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'drawCount' => $payment->draw_count,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If payment is still pending, check with Wise API
|
||||||
|
if ($payment->status === 'pending' && $payment->wise_payment_id) {
|
||||||
|
try {
|
||||||
|
$wiseApiUrl = env('WISE_API_URL', 'https://api.wise.com');
|
||||||
|
$wiseApiToken = env('WISE_API_TOKEN');
|
||||||
|
|
||||||
|
$response = Http::withToken($wiseApiToken)
|
||||||
|
->get("{$wiseApiUrl}/v1/transfers/{$payment->wise_payment_id}");
|
||||||
|
|
||||||
|
if ($response->successful()) {
|
||||||
|
$transferStatus = $response->json('status');
|
||||||
|
|
||||||
|
if ($transferStatus === 'outgoing_payment_sent') {
|
||||||
|
$payment->update(['status' => 'succeeded']);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'drawCount' => $payment->draw_count,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Wise payment validation failed: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payment not validated.',
|
||||||
|
'status' => $payment->status,
|
||||||
|
], 402);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,11 +23,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative z-10 flex flex-col items-center">
|
<div class="relative z-10 flex flex-col items-center">
|
||||||
<span
|
|
||||||
class="rounded-fullbg-gradient-to-br mb-4 inline-flex items-center from-black/80 to-[#1a0b2e] p-8 shadow-lg transition-all duration-500 hover:-translate-y-2 hover:shadow-xl"
|
|
||||||
>Consultation</span
|
|
||||||
>
|
|
||||||
|
|
||||||
<h2 class="text-4xl font-black text-white drop-shadow-md md:text-5xl">Amplify Your Oracle</h2>
|
<h2 class="text-4xl font-black text-white drop-shadow-md md:text-5xl">Amplify Your Oracle</h2>
|
||||||
<p class="mx-auto mt-4 max-w-2xl text-lg text-white/90 drop-shadow-md">
|
<p class="mx-auto mt-4 max-w-2xl text-lg text-white/90 drop-shadow-md">
|
||||||
For deeper guidance, book a personalized consultation with Kris.
|
For deeper guidance, book a personalized consultation with Kris.
|
||||||
@ -35,23 +30,23 @@
|
|||||||
|
|
||||||
<div class="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
<div class="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
||||||
<button
|
<button
|
||||||
@click="goToBooking"
|
@click="goToOffers"
|
||||||
class="gold-trail-btn relative inline-flex h-12 min-w-[160px] items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-[#f59e0b] to-[#eab308] px-8 font-bold tracking-wide text-[#1e1b4b] shadow-lg transition-all duration-300 hover:shadow-[#f59e0b]/40"
|
class="gold-trail-btn relative inline-flex h-12 min-w-[160px] items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-[#f59e0b] to-[#eab308] px-8 font-bold tracking-wide text-[#1e1b4b] shadow-lg transition-all duration-300 hover:shadow-[#f59e0b]/40"
|
||||||
>
|
>
|
||||||
<span class="relative z-10">Book a Consultation Discover the Readings</span>
|
<span class="relative z-10">Book a Consultation</span>
|
||||||
<span
|
<span
|
||||||
class="absolute inset-0 translate-x-[-100%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[100%]"
|
class="absolute inset-0 translate-x-[-100%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[100%]"
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="goToOffers"
|
@click="goToBooking"
|
||||||
class="inline-flex h-12 min-w-[160px] items-center justify-center rounded-full border-2 border-[#4c1d95] bg-transparent px-8 font-bold text-white backdrop-blur-sm transition-all hover:bg-[#4c1d95]"
|
class="inline-flex h-12 min-w-[160px] items-center justify-center rounded-full border-2 border-[#4c1d95] bg-transparent px-8 font-bold text-white backdrop-blur-sm transition-all hover:bg-[#4c1d95]"
|
||||||
>
|
>
|
||||||
Book a Consultation
|
Book a Consultation
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-6 text-xs text-white/70 backdrop-blur-sm">Secure payment.</p>
|
<p class="mt-6 text-xs text-white/70 backdrop-blur-sm">Secure payment</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,13 +9,11 @@
|
|||||||
|
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<h2 class="transform text-4xl font-bold text-[var(--midnight-blue)] transition-all duration-700 md:text-5xl">
|
<h2 class="transform text-4xl font-bold text-[var(--midnight-blue)] transition-all duration-700 md:text-5xl">
|
||||||
<span class="inline-block cursor-default transition-all duration-300 hover:scale-105 hover:text-[var(--midnight-blue)]/90">
|
<span class="inline-block cursor-default transition-all duration-300 hover:text-[var(--midnight-blue)]/90"> Beyond a Guide, </span>
|
||||||
Beyond a Guide,
|
|
||||||
</span>
|
|
||||||
<span class="citadel-script mt-2 block from-yellow-400 to-purple-500 text-5xl md:mt-0 md:ml-2 md:inline-block md:text-6xl">
|
<span class="citadel-script mt-2 block from-yellow-400 to-purple-500 text-5xl md:mt-0 md:ml-2 md:inline-block md:text-6xl">
|
||||||
<span class="inline-block cursor-default transition-all duration-500 hover:scale-110">A</span>
|
<span class="inline-block cursor-default transition-all duration-500">A </span>
|
||||||
<span class="mx-2 inline-block cursor-default transition-all duration-500 hover:scale-110">Strategic</span>
|
<span class="mx-2 inline-block cursor-default transition-all duration-500">Strategic </span>
|
||||||
<span class="inline-block cursor-default transition-all duration-500 hover:scale-110">Manuscript</span>
|
<span class="inline-block cursor-default transition-all duration-500">Manuscript</span>
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ onMounted(() => {
|
|||||||
<!-- Enhanced Header with Bigger Font -->
|
<!-- Enhanced Header with Bigger Font -->
|
||||||
<header
|
<header
|
||||||
class="fixed top-0 z-50 w-full transition-all duration-300"
|
class="fixed top-0 z-50 w-full transition-all duration-300"
|
||||||
:class="isScrolled ? 'bg-black/90 py-2 shadow-xl backdrop-blur-md' : 'bg-transparent py-4'"
|
:class="isScrolled ? 'bg-gradient-to-t from-[var(--c-deep-navy)] to-black py-2 shadow-xl backdrop-blur-md' : 'bg-transparent py-4'"
|
||||||
>
|
>
|
||||||
<div class="container mx-auto px-4 sm:px-6">
|
<div class="container mx-auto px-4 sm:px-6">
|
||||||
<div class="flex items-center justify-between px-2 py-2 whitespace-nowrap sm:px-6">
|
<div class="flex items-center justify-between px-2 py-2 whitespace-nowrap sm:px-6">
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DatePicker from '@/components/DatePicker.vue';
|
|
||||||
import LandingLayout from '@/layouts/app/LandingLayout.vue';
|
import LandingLayout from '@/layouts/app/LandingLayout.vue';
|
||||||
import { loadStripe } from '@stripe/stripe-js';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
interface UserForm {
|
interface UserForm {
|
||||||
@ -14,211 +11,271 @@ const userForm = ref<UserForm>({
|
|||||||
fullname: '',
|
fullname: '',
|
||||||
email: '',
|
email: '',
|
||||||
});
|
});
|
||||||
const selectedDate = ref(new Date());
|
|
||||||
|
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
|
const appointmentConfirmed = ref<boolean>(false);
|
||||||
|
|
||||||
const handleAppointment = () => {
|
const handleAppointment = () => {
|
||||||
redirectToStipeCheckout();
|
|
||||||
};
|
|
||||||
|
|
||||||
const redirectToStipeCheckout = async () => {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
|
||||||
const res = await axios.post('/checkout-rendez-vous', {
|
|
||||||
userForm: userForm.value,
|
|
||||||
selectedDate: selectedDate.value,
|
|
||||||
});
|
|
||||||
const sessionId = res.data.sessionId;
|
|
||||||
const stripe = await loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!);
|
|
||||||
if (stripe) {
|
|
||||||
const { error } = await stripe.redirectToCheckout({ sessionId });
|
|
||||||
|
|
||||||
if (error) {
|
// Simulate processing delay
|
||||||
console.error('Stripe redirect error:', error.message);
|
setTimeout(() => {
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initiating Stripe checkout:', error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
appointmentConfirmed.value = true;
|
||||||
|
|
||||||
|
// Reset form after confirmation
|
||||||
|
userForm.value = {
|
||||||
|
fullname: '',
|
||||||
|
email: '',
|
||||||
|
};
|
||||||
|
}, 1500);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LandingLayout>
|
<LandingLayout>
|
||||||
<!-- Background container with dark mystic theme -->
|
<!-- Background container with dark violet theme - isolated to main content only -->
|
||||||
<div class="relative min-h-screen bg-gradient-to-br from-purple-900 via-violet-900 to-indigo-900">
|
<div class="booking-page-background relative">
|
||||||
<!-- Animated background elements -->
|
<!-- Dark violet background with gradients -->
|
||||||
<div class="absolute inset-0 overflow-hidden">
|
<div class="absolute inset-0 overflow-hidden bg-gradient-to-br from-purple-900 via-violet-900 to-indigo-900">
|
||||||
<!-- Floating particles -->
|
<!-- Animated background elements -->
|
||||||
<div
|
<div class="absolute inset-0">
|
||||||
v-for="i in 12"
|
<!-- Floating particles -->
|
||||||
:key="'particle-' + i"
|
<div
|
||||||
class="floating-particle absolute rounded-full bg-white/10"
|
v-for="i in 12"
|
||||||
:style="{
|
:key="'particle-' + i"
|
||||||
width: `${Math.random() * 6 + 2}px`,
|
class="floating-particle absolute rounded-full bg-white/10"
|
||||||
height: `${Math.random() * 6 + 2}px`,
|
:style="{
|
||||||
top: `${Math.random() * 100}%`,
|
width: `${Math.random() * 6 + 2}px`,
|
||||||
left: `${Math.random() * 100}%`,
|
height: `${Math.random() * 6 + 2}px`,
|
||||||
animationDelay: `${Math.random() * 5}s`,
|
top: `${Math.random() * 100}%`,
|
||||||
}"
|
left: `${Math.random() * 100}%`,
|
||||||
></div>
|
animationDelay: `${Math.random() * 5}s`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- Glow effects -->
|
<!-- Glow effects -->
|
||||||
<div class="glow-effect glow-1"></div>
|
<div class="glow-effect glow-1"></div>
|
||||||
<div class="glow-effect glow-2"></div>
|
<div class="glow-effect glow-2"></div>
|
||||||
<div class="glow-effect glow-3"></div>
|
<div class="glow-effect glow-3"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
<main class="relative z-10 flex flex-1 justify-center px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-20">
|
<main class="relative z-10 flex flex-1 justify-center px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-20">
|
||||||
<div class="layout-content-container flex w-full max-w-6xl flex-col items-center gap-12 lg:gap-16">
|
<div class="layout-content-container flex w-full max-w-6xl flex-col items-center gap-12 lg:gap-16">
|
||||||
<!-- Header Section -->
|
<!-- Enhanced Header Section -->
|
||||||
<div class="max-w-3xl text-center">
|
<div class="max-w-4xl text-center">
|
||||||
<h1 class="mb-6 text-4xl font-bold text-white sm:text-5xl lg:text-6xl">Réservez votre consultation</h1>
|
<h1 class="mb-6 text-4xl font-bold text-white sm:text-5xl lg:text-6xl">Your Spiritual Transformation Starts Here</h1>
|
||||||
<p class="mx-auto max-w-2xl text-lg text-white/80 sm:text-xl">
|
<p class="mx-auto max-w-3xl text-lg text-white/80 sm:text-xl">
|
||||||
Choisissez la date de votre consultation spirituelle et laissez-vous guider vers l'éclaircissement
|
Do you feel something calling you toward a more aligned life?
|
||||||
|
<span class="font-semibold text-purple-300">Your soul is seeking answers</span> and I'm here to guide you.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content Container -->
|
<!-- Benefits Section -->
|
||||||
<div class="flex w-full flex-col items-center justify-center gap-12 lg:flex-row lg:items-start lg:gap-16 xl:gap-20">
|
<div class="grid max-w-5xl grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
<!-- Date Picker Card -->
|
<!-- Benefit 1 -->
|
||||||
<div class="w-full max-w-md lg:max-w-lg">
|
<div class="rounded-2xl border border-white/20 bg-black/30 p-6 text-center backdrop-blur-sm">
|
||||||
<div class="rounded-2xl border border-white/20 bg-black/30 p-6 shadow-2xl backdrop-blur-sm">
|
<div class="mb-4 flex justify-center">
|
||||||
<div class="mb-6 text-center">
|
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
|
||||||
<h2 class="text-2xl font-bold text-white">Choisissez votre date</h2>
|
<span class="text-xl">🔮</span>
|
||||||
<p class="mt-2 text-white/70">Sélectionnez le moment parfait pour votre guidance</p>
|
|
||||||
</div>
|
</div>
|
||||||
<date-picker v-model:selectedDate="selectedDate" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="mb-3 text-xl font-bold text-white">Immediate Clarity</h3>
|
||||||
|
<p class="text-white/70">
|
||||||
|
Get clear answers to your deepest questions and free yourself from the doubts holding you back
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Card -->
|
<!-- Benefit 2 -->
|
||||||
<div class="w-full max-w-md">
|
<div class="rounded-2xl border border-white/20 bg-black/30 p-6 text-center backdrop-blur-sm">
|
||||||
<div class="rounded-2xl border border-white/20 bg-black/30 p-8 shadow-2xl backdrop-blur-sm">
|
<div class="mb-4 flex justify-center">
|
||||||
<div class="mb-8 text-center">
|
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
|
||||||
<div class="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
|
<span class="text-xl">💫</span>
|
||||||
<svg class="h-6 w-6 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
</div>
|
||||||
<path
|
</div>
|
||||||
stroke-linecap="round"
|
<h3 class="mb-3 text-xl font-bold text-white">Personalized Guidance</h3>
|
||||||
stroke-linejoin="round"
|
<p class="text-white/70">A unique approach tailored to your energy and life path for transformative results</p>
|
||||||
stroke-width="2"
|
</div>
|
||||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
||||||
/>
|
<!-- Benefit 3 -->
|
||||||
</svg>
|
<div class="rounded-2xl border border-white/20 bg-black/30 p-6 text-center backdrop-blur-sm">
|
||||||
|
<div class="mb-4 flex justify-center">
|
||||||
|
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
|
||||||
|
<span class="text-xl">🌙</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 class="mb-3 text-xl font-bold text-white">Lasting Transformation</h3>
|
||||||
|
<p class="text-white/70">Don't settle for temporary solutions. Create the profound changes your soul is calling for</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Call to Action Text -->
|
||||||
|
<div class="max-w-3xl text-center">
|
||||||
|
<div class="rounded-2xl border border-purple-500/30 bg-purple-900/20 p-8 backdrop-blur-sm">
|
||||||
|
<h2 class="mb-4 text-2xl font-bold text-purple-300">The Time Has Come to Say "Yes" to Your Inner Journey</h2>
|
||||||
|
<p class="mb-4 text-lg text-white/80">
|
||||||
|
<span class="font-semibold">Hundreds of people</span> have already transformed their lives through this guidance. They
|
||||||
|
found <span class="font-semibold text-purple-300">inner peace</span>,
|
||||||
|
<span class="font-semibold text-purple-300">mental clarity</span> and the
|
||||||
|
<span class="font-semibold text-purple-300">confidence</span> to move forward.
|
||||||
|
</p>
|
||||||
|
<p class="text-lg text-white/80">
|
||||||
|
<span class="font-bold">What about you?</span> What are you waiting for to discover what the universe has in store for
|
||||||
|
you?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Message -->
|
||||||
|
<div v-if="appointmentConfirmed" class="w-full max-w-2xl">
|
||||||
|
<div class="rounded-2xl border border-green-500/30 bg-green-900/20 p-8 text-center shadow-xl backdrop-blur-sm">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-green-500/20">
|
||||||
|
<svg class="h-8 w-8 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 class="mb-4 text-2xl font-bold text-green-400">Wonderful! Your Request Is Registered</h2>
|
||||||
|
<p class="mb-4 text-green-300"><strong>Congratulations on taking this first step toward your transformation!</strong></p>
|
||||||
|
<p class="mb-6 text-green-300">
|
||||||
|
I will contact you within 24 hours to discuss your needs and arrange the best time for your personalized consultation.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
@click="appointmentConfirmed = false"
|
||||||
|
class="rounded-xl bg-green-600 px-6 py-3 font-semibold text-white transition-colors hover:bg-green-500"
|
||||||
|
>
|
||||||
|
Schedule a new appointment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form Card -->
|
||||||
|
<div v-else class="w-full max-w-md">
|
||||||
|
<div class="rounded-2xl border border-white/20 bg-black/30 p-8 shadow-xl backdrop-blur-sm">
|
||||||
|
<div class="mb-8 text-center">
|
||||||
|
<div class="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-purple-500/20">
|
||||||
|
<svg class="h-6 w-6 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl font-bold text-white">Book Your Discovery Call</h2>
|
||||||
|
<p class="mt-3 text-white/70">
|
||||||
|
<strong>Free:</strong> 15 minutes to identify your blocks and create a personalized action plan
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="flex flex-col gap-6" @submit.prevent="handleAppointment">
|
||||||
|
<!-- Name Input -->
|
||||||
|
<div class="group">
|
||||||
|
<label class="mb-3 block text-sm font-semibold text-white" for="name"> Your First and Last Name </label>
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-xl border-2 border-purple-500/30 bg-black/40 p-4 text-base text-white transition-all duration-300 placeholder:text-white/40 focus:border-purple-400 focus:bg-black/60 focus:shadow-lg focus:ring-2 focus:ring-purple-400/20"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder="What would you like me to call you?"
|
||||||
|
type="text"
|
||||||
|
v-model="userForm.fullname"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-2xl font-bold text-white">Vos informations</h2>
|
|
||||||
<p class="mt-2 text-white/70">Remplissez vos coordonnées pour finaliser la réservation</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="flex flex-col gap-6" @submit.prevent="handleAppointment">
|
<!-- Email Input -->
|
||||||
<!-- Name Input -->
|
<div class="group">
|
||||||
<div class="group">
|
<label class="mb-3 block text-sm font-semibold text-white" for="email"> Your Best Email </label>
|
||||||
<label class="mb-3 block text-sm font-semibold text-white" for="name"> Nom complet </label>
|
<div class="relative">
|
||||||
<div class="relative">
|
<input
|
||||||
<input
|
class="w-full rounded-xl border-2 border-purple-500/30 bg-black/40 p-4 text-base text-white transition-all duration-300 placeholder:text-white/40 focus:border-purple-400 focus:bg-black/60 focus:shadow-lg focus:ring-2 focus:ring-purple-400/20"
|
||||||
class="w-full rounded-xl border-2 border-purple-500/30 bg-black/40 p-4 text-base text-white transition-all duration-300 placeholder:text-white/40 focus:border-purple-400 focus:bg-black/60 focus:shadow-lg focus:ring-2 focus:ring-purple-400/20"
|
id="email"
|
||||||
id="name"
|
name="email"
|
||||||
name="name"
|
placeholder="Where can I send the details?"
|
||||||
placeholder="Votre nom complet"
|
type="email"
|
||||||
type="text"
|
v-model="userForm.email"
|
||||||
v-model="userForm.fullname"
|
required
|
||||||
required
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Email Input -->
|
<!-- Urgency CTA -->
|
||||||
<div class="group">
|
<div class="rounded-xl border border-purple-500/30 bg-purple-900/20 p-4 text-center">
|
||||||
<label class="mb-3 block text-sm font-semibold text-white" for="email"> Adresse e-mail </label>
|
<p class="text-sm font-semibold text-purple-300">
|
||||||
<div class="relative">
|
⚡ Limited spots - Don't let doubt deprive you of your transformation
|
||||||
<input
|
|
||||||
class="w-full rounded-xl border-2 border-purple-500/30 bg-black/40 p-4 text-base text-white transition-all duration-300 placeholder:text-white/40 focus:border-purple-400 focus:bg-black/60 focus:shadow-lg focus:ring-2 focus:ring-purple-400/20"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="Votre adresse e-mail"
|
|
||||||
type="email"
|
|
||||||
v-model="userForm.email"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Selected Date Display -->
|
|
||||||
<div class="rounded-xl border border-purple-500/20 bg-purple-900/20 p-4">
|
|
||||||
<p class="mb-1 text-sm font-medium text-white">Date sélectionnée :</p>
|
|
||||||
<p class="text-lg font-semibold text-purple-300">
|
|
||||||
{{
|
|
||||||
selectedDate.toLocaleDateString('fr-FR', {
|
|
||||||
weekday: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit Button -->
|
|
||||||
<button
|
|
||||||
class="group relative mt-4 flex h-14 w-full cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 px-8 text-lg font-bold tracking-wide text-white shadow-lg transition-all duration-500 hover:from-purple-500 hover:to-violet-500 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
type="submit"
|
|
||||||
:disabled="loading || !userForm.fullname || !userForm.email"
|
|
||||||
>
|
|
||||||
<!-- Shine effect -->
|
|
||||||
<span
|
|
||||||
class="absolute inset-0 -translate-x-full -skew-x-12 transform bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-full"
|
|
||||||
></span>
|
|
||||||
|
|
||||||
<!-- Button content -->
|
|
||||||
<span class="relative z-10 flex items-center gap-3">
|
|
||||||
<svg v-if="loading" class="h-5 w-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path
|
|
||||||
class="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<span>{{ loading ? 'Traitement...' : 'Confirmer et Payer' }}</span>
|
|
||||||
<svg
|
|
||||||
v-if="!loading"
|
|
||||||
class="h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Security Note -->
|
|
||||||
<p class="mt-4 text-center text-xs text-white/60">
|
|
||||||
🔒 Paiement sécurisé via Stripe. Vos informations sont protégées.
|
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<button
|
||||||
|
class="group relative mt-2 flex h-14 w-full cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 px-8 text-lg font-bold tracking-wide text-white shadow-lg transition-all duration-500 hover:from-purple-500 hover:to-violet-500 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
type="submit"
|
||||||
|
:disabled="loading || !userForm.fullname || !userForm.email"
|
||||||
|
>
|
||||||
|
<!-- Shine effect -->
|
||||||
|
<span
|
||||||
|
class="absolute inset-0 -translate-x-full -skew-x-12 transform bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-full"
|
||||||
|
></span>
|
||||||
|
|
||||||
|
<!-- Button content -->
|
||||||
|
<span class="relative z-10 flex items-center gap-3">
|
||||||
|
<svg v-if="loading" class="h-5 w-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span>{{ loading ? 'Sending...' : '✨ Yes, I want my free consultation!' }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Security Note -->
|
||||||
|
<p class="mt-4 text-center text-xs text-white/60">
|
||||||
|
🔒 Absolute confidentiality • No commitment • 100% personalized guidance
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Testimonial Section -->
|
||||||
|
<div class="max-w-4xl text-center">
|
||||||
|
<div class="rounded-2xl border border-white/20 bg-black/30 p-8 backdrop-blur-sm">
|
||||||
|
<h3 class="mb-6 text-2xl font-bold text-white">They Took the Decisive Step</h3>
|
||||||
|
<div class="grid gap-6 md:grid-cols-2">
|
||||||
|
<div class="text-left">
|
||||||
|
<p class="mb-4 text-white/80 italic">
|
||||||
|
"I had been carrying uncertainties for years. In just one session, I found the clarity that changed my life.
|
||||||
|
Thank you!"
|
||||||
|
</p>
|
||||||
|
<p class="font-semibold text-purple-300">- Marie, 34 years old</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
<p class="mb-4 text-white/80 italic">
|
||||||
|
"The guidance I received transformed my relationship with myself and others. I finally feel aligned with my
|
||||||
|
true self."
|
||||||
|
</p>
|
||||||
|
<p class="font-semibold text-purple-300">- Thomas, 41 years old</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Additional Information -->
|
<!-- Final CTA -->
|
||||||
<div class="mt-8 max-w-2xl text-center">
|
<div class="max-w-2xl text-center">
|
||||||
<div
|
<div class="rounded-2xl bg-gradient-to-r from-purple-500/20 to-violet-500/20 p-6">
|
||||||
class="inline-flex items-center gap-4 rounded-2xl border border-white/20 bg-black/30 px-6 py-4 text-sm text-white/70 backdrop-blur-sm"
|
<h3 class="mb-4 text-xl font-bold text-white">🌟 Your Future Awaits You</h3>
|
||||||
>
|
<p class="text-white/80">
|
||||||
<svg class="h-5 w-5 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
Don't let daily life suffocate your soul's call.
|
||||||
<path
|
<strong>Your transformation begins with a simple "yes"</strong>.
|
||||||
stroke-linecap="round"
|
</p>
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>Consultation confidentielle • Guidance personnalisée • Support après séance</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -228,6 +285,15 @@ const redirectToStipeCheckout = async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* Isolate the background to only this component */
|
||||||
|
.booking-page-background {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking-page-background > * {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* Floating particles animation */
|
/* Floating particles animation */
|
||||||
@keyframes float {
|
@keyframes float {
|
||||||
0%,
|
0%,
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'selectDraw', count: number): void;
|
(e: 'selectDraw', count: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const handleSelection = async (count: number) => {
|
const handleSelection = async (count: number, provider: 'stripe' | 'wise' = 'stripe') => {
|
||||||
drawCount.value = count;
|
drawCount.value = count;
|
||||||
|
|
||||||
if (count == 1 && tarotStore.freeDrawsRemaining <= 0) {
|
if (count == 1 && tarotStore.freeDrawsRemaining <= 0) {
|
||||||
@ -23,7 +23,11 @@ const handleSelection = async (count: number) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (count > 1 && tarotStore.paidDrawsRemaining < count) {
|
if (count > 1 && tarotStore.paidDrawsRemaining < count) {
|
||||||
await redirectToStripeCheckout(count);
|
if (provider === 'wise') {
|
||||||
|
await redirectToWisePayment(count);
|
||||||
|
} else {
|
||||||
|
await redirectToStripeCheckout(count);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit('selectDraw', count);
|
emit('selectDraw', count);
|
||||||
@ -56,6 +60,30 @@ const redirectToStripeCheckout = async (count: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const redirectToWisePayment = async (count: number) => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 1. Create payment record in backend
|
||||||
|
const res = await axios.post('/create-wise-payment', { count });
|
||||||
|
const { paymentUrl, clientSessionId } = res.data;
|
||||||
|
|
||||||
|
// 2. Store client session ID in sessionStorage to verify later
|
||||||
|
sessionStorage.setItem('wise_client_session_id', clientSessionId);
|
||||||
|
|
||||||
|
// 3. Redirect to Wise payment link
|
||||||
|
if (paymentUrl) {
|
||||||
|
window.location.href = paymentUrl;
|
||||||
|
} else {
|
||||||
|
alert('Payment link not configured. Please contact support.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initiating Wise payment:', error);
|
||||||
|
alert('Payment processing failed. Please try again.');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Computed to disable the free draw button if used
|
// Computed to disable the free draw button if used
|
||||||
const isFreeDrawUsed = computed(() => tarotStore.freeDrawsRemaining <= 0);
|
const isFreeDrawUsed = computed(() => tarotStore.freeDrawsRemaining <= 0);
|
||||||
|
|
||||||
@ -145,9 +173,7 @@ const clearHover = () => {
|
|||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
>
|
></svg>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -191,25 +217,15 @@ const clearHover = () => {
|
|||||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">9.99 €</p>
|
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">9.99 €</p>
|
||||||
<p class="mt-4 text-sm text-gray-200 md:text-base">six‑card Reading Personalized Analysis Tailored Recommendations</p>
|
<p class="mt-4 text-sm text-gray-200 md:text-base">six‑card Reading Personalized Analysis Tailored Recommendations</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div class="mt-2 flex gap-2">
|
||||||
class="group relative mt-2 flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-[var(--c-gold)] px-6 font-bold tracking-wide text-black transition-all duration-300 hover:bg-white hover:shadow-lg"
|
<button
|
||||||
@click="handleSelection(6)"
|
class="group relative flex h-12 flex-1 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--c-gold)] bg-transparent px-4 font-bold tracking-wide text-[var(--c-gold)] transition-all duration-300 hover:bg-[var(--c-gold)] hover:text-black"
|
||||||
>
|
@click="handleSelection(6, 'wise')"
|
||||||
<!-- Button shine effect -->
|
title="Pay with wise"
|
||||||
<span
|
|
||||||
class="absolute inset-0 -translate-x-full -skew-x-12 transform bg-gradient-to-r from-transparent via-white/40 to-transparent transition-transform duration-700 group-hover:translate-x-full"
|
|
||||||
></span>
|
|
||||||
<span class="relative z-10">Discover</span>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="relative z-10 ml-2 h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
<span class="relative text-sm">Discover</span>
|
||||||
</svg>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Premium plus option -->
|
<!-- Premium plus option -->
|
||||||
@ -254,24 +270,15 @@ const clearHover = () => {
|
|||||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">99€</p>
|
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">99€</p>
|
||||||
<p class="mt-4 text-sm text-gray-300 md:text-base">Eigtheen‑card reading In‑depth exploration Complete strategy</p>
|
<p class="mt-4 text-sm text-gray-300 md:text-base">Eigtheen‑card reading In‑depth exploration Complete strategy</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div class="mt-2 flex gap-2">
|
||||||
class="group relative mt-2 flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-6 font-bold tracking-wide text-white transition-all duration-300 hover:from-[var(--c-purple)] hover:to-[var(--c-purple)]/80 hover:text-white"
|
<button
|
||||||
@click="handleSelection(18)"
|
class="group relative flex h-12 flex-1 items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-4 font-bold tracking-wide text-white transition-all duration-300 hover:bg-gray-800"
|
||||||
>
|
@click="handleSelection(18, 'wise')"
|
||||||
<span class="relative z-10">EXPLORE</span>
|
title="Pay with Wise"
|
||||||
<div
|
|
||||||
class="absolute inset-0 bg-gradient-to-r from-[var(--c-gold)]/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
|
||||||
></div>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="relative z-10 ml-2 h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
<span class="relative text-sm">Explore</span>
|
||||||
</svg>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
148
resources/js/pages/payments/Pending.vue
Normal file
148
resources/js/pages/payments/Pending.vue
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { router } from '@inertiajs/vue3';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
message: string;
|
||||||
|
clientSessionId: string;
|
||||||
|
paymentProvider: 'stripe' | 'wise';
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const checking = ref(true);
|
||||||
|
const pollCount = ref(0);
|
||||||
|
const maxPolls = 30; // Poll for 5 minutes (30 * 10 seconds)
|
||||||
|
|
||||||
|
// Poll payment status every 10 seconds
|
||||||
|
const checkPaymentStatus = async () => {
|
||||||
|
try {
|
||||||
|
const endpoint = props.paymentProvider === 'wise' ? '/wise/validate-payment' : '/stripe/validate-payment';
|
||||||
|
|
||||||
|
const response = await axios.get(endpoint, {
|
||||||
|
params: { client_session_id: props.clientSessionId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
// Payment succeeded! Redirect to success page
|
||||||
|
window.location.href = `/success?client_session_id=${props.clientSessionId}`;
|
||||||
|
} else {
|
||||||
|
pollCount.value++;
|
||||||
|
|
||||||
|
if (pollCount.value >= maxPolls) {
|
||||||
|
// Stop polling after max attempts
|
||||||
|
checking.value = false;
|
||||||
|
} else {
|
||||||
|
// Continue polling
|
||||||
|
setTimeout(checkPaymentStatus, 10000); // Check again in 10 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking payment status:', error);
|
||||||
|
pollCount.value++;
|
||||||
|
|
||||||
|
if (pollCount.value < maxPolls) {
|
||||||
|
setTimeout(checkPaymentStatus, 10000);
|
||||||
|
} else {
|
||||||
|
checking.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Start polling after 2 seconds
|
||||||
|
setTimeout(checkPaymentStatus, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshPage = () => {
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
router.visit('/');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex min-h-screen items-center justify-center bg-gradient-to-br from-[var(--light-ivory)] via-white to-[var(--linen)] px-4">
|
||||||
|
<div class="w-full max-w-md rounded-2xl border border-[var(--linen)] bg-white/90 p-8 shadow-xl backdrop-blur-sm">
|
||||||
|
<!-- Animated Loading Icon -->
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="relative h-20 w-20">
|
||||||
|
<svg class="animate-spin text-[var(--subtle-gold)]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<h1 class="mb-4 text-center text-2xl font-bold text-[var(--midnight-blue)] md:text-3xl">Payment en cours</h1>
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<p class="mb-6 text-center text-base text-[var(--spiritual-earth)]">
|
||||||
|
{{ message }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Provider Info -->
|
||||||
|
<div class="mb-6 rounded-lg bg-[var(--light-ivory)] p-4">
|
||||||
|
<p class="text-center text-sm text-[var(--midnight-blue)]">
|
||||||
|
<span class="font-semibold">Fournisseur de paiement:</span>
|
||||||
|
<span class="ml-2 capitalize">{{ paymentProvider }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="checking" class="mt-2 text-center text-xs text-[var(--spiritual-earth)]">
|
||||||
|
Vérification automatique en cours... ({{ pollCount }}/{{ maxPolls }})
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Message -->
|
||||||
|
<div v-if="!checking" class="mb-6 rounded-lg border border-yellow-300 bg-yellow-50 p-4">
|
||||||
|
<p class="text-center text-sm text-yellow-800">
|
||||||
|
Le paiement prend plus de temps que prévu. Cela peut prendre quelques minutes pour que {{ paymentProvider }} traite votre
|
||||||
|
paiement.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<button
|
||||||
|
v-if="!checking"
|
||||||
|
@click="refreshPage"
|
||||||
|
class="w-full rounded-full bg-[var(--subtle-gold)] px-6 py-3 font-semibold text-[var(--midnight-blue)] shadow-md transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
Vérifier à nouveau
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="goHome"
|
||||||
|
class="w-full rounded-full border-2 border-[var(--midnight-blue)] bg-transparent px-6 py-3 font-semibold text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--midnight-blue)] hover:text-white"
|
||||||
|
>
|
||||||
|
Retour à l'accueil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Help Text -->
|
||||||
|
<p class="mt-6 text-center text-xs text-[var(--spiritual-earth)]">
|
||||||
|
Si le problème persiste, veuillez contacter notre support avec votre ID de session:
|
||||||
|
<br />
|
||||||
|
<code class="mt-1 block rounded bg-gray-100 px-2 py-1 text-[10px]">{{ clientSessionId }}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
185
resources/js/pages/wise/VerifyPayment.vue
Normal file
185
resources/js/pages/wise/VerifyPayment.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { router } from '@inertiajs/vue3';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const checking = ref(true);
|
||||||
|
const paymentStatus = ref<'pending' | 'succeeded' | 'failed' | null>(null);
|
||||||
|
const errorMessage = ref('');
|
||||||
|
const clientSessionId = ref('');
|
||||||
|
|
||||||
|
// Check payment status
|
||||||
|
const checkPayment = async () => {
|
||||||
|
try {
|
||||||
|
// Get client session ID from sessionStorage
|
||||||
|
const storedSessionId = sessionStorage.getItem('wise_client_session_id');
|
||||||
|
|
||||||
|
if (!storedSessionId) {
|
||||||
|
errorMessage.value = 'Session non trouvée. Veuillez recommencer le processus de paiement.';
|
||||||
|
checking.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSessionId.value = storedSessionId;
|
||||||
|
|
||||||
|
// Check payment status via API
|
||||||
|
const response = await axios.get('/wise/validate-payment', {
|
||||||
|
params: { client_session_id: storedSessionId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
paymentStatus.value = 'succeeded';
|
||||||
|
|
||||||
|
// Clear session storage
|
||||||
|
sessionStorage.removeItem('wise_client_session_id');
|
||||||
|
|
||||||
|
// Redirect to success page after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/success?client_session_id=${storedSessionId}`;
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
paymentStatus.value = response.data.status || 'pending';
|
||||||
|
errorMessage.value = response.data.message || 'Le paiement est en cours de vérification...';
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error checking payment:', error);
|
||||||
|
paymentStatus.value = 'failed';
|
||||||
|
errorMessage.value = error.response?.data?.message || 'Erreur lors de la vérification du paiement.';
|
||||||
|
} finally {
|
||||||
|
checking.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const retryCheck = () => {
|
||||||
|
checking.value = true;
|
||||||
|
paymentStatus.value = null;
|
||||||
|
errorMessage.value = '';
|
||||||
|
setTimeout(checkPayment, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
sessionStorage.removeItem('wise_client_session_id');
|
||||||
|
router.visit('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Check payment status after 1 second
|
||||||
|
setTimeout(checkPayment, 1000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex min-h-screen items-center justify-center bg-gradient-to-br from-[var(--light-ivory)] via-white to-[var(--linen)] px-4">
|
||||||
|
<div class="w-full max-w-md rounded-2xl border border-[var(--linen)] bg-white/90 p-8 shadow-xl backdrop-blur-sm">
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="checking" class="text-center">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="relative h-20 w-20">
|
||||||
|
<svg class="animate-spin text-[var(--subtle-gold)]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="mb-4 text-2xl font-bold text-[var(--midnight-blue)]">Vérification du paiement...</h1>
|
||||||
|
<p class="text-[var(--spiritual-earth)]">Veuillez patienter pendant que nous vérifions votre paiement Wise.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Success State -->
|
||||||
|
<div v-else-if="paymentStatus === 'succeeded'" class="text-center">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="flex h-20 w-20 items-center justify-center rounded-full bg-green-100">
|
||||||
|
<svg class="h-12 w-12 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="mb-4 text-2xl font-bold text-green-600">Paiement réussi ! 🎉</h1>
|
||||||
|
<p class="text-[var(--spiritual-earth)]">Votre paiement a été confirmé. Vous allez être redirigé vers vos cartes...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pending State -->
|
||||||
|
<div v-else-if="paymentStatus === 'pending'" class="text-center">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="flex h-20 w-20 items-center justify-center rounded-full bg-yellow-100">
|
||||||
|
<svg class="h-12 w-12 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="mb-4 text-2xl font-bold text-yellow-600">Paiement en cours</h1>
|
||||||
|
<p class="mb-6 text-[var(--spiritual-earth)]">
|
||||||
|
{{ errorMessage || 'Votre paiement est en cours de traitement. Cela peut prendre quelques minutes.' }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<button
|
||||||
|
@click="retryCheck"
|
||||||
|
class="w-full rounded-full bg-[var(--subtle-gold)] px-6 py-3 font-semibold text-[var(--midnight-blue)] shadow-md transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
Vérifier à nouveau
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="goHome"
|
||||||
|
class="w-full rounded-full border-2 border-[var(--midnight-blue)] bg-transparent px-6 py-3 font-semibold text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--midnight-blue)] hover:text-white"
|
||||||
|
>
|
||||||
|
Retour à l'accueil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Failed State -->
|
||||||
|
<div v-else-if="paymentStatus === 'failed'" class="text-center">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<div class="flex h-20 w-20 items-center justify-center rounded-full bg-red-100">
|
||||||
|
<svg class="h-12 w-12 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="mb-4 text-2xl font-bold text-red-600">Erreur de paiement</h1>
|
||||||
|
<p class="mb-6 text-[var(--spiritual-earth)]">
|
||||||
|
{{ errorMessage || 'Une erreur est survenue lors de la vérification du paiement.' }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<button
|
||||||
|
@click="retryCheck"
|
||||||
|
class="w-full rounded-full bg-[var(--subtle-gold)] px-6 py-3 font-semibold text-[var(--midnight-blue)] shadow-md transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
Réessayer
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="goHome"
|
||||||
|
class="w-full rounded-full border-2 border-[var(--midnight-blue)] bg-transparent px-6 py-3 font-semibold text-[var(--midnight-blue)] transition-all duration-300 hover:bg-[var(--midnight-blue)] hover:text-white"
|
||||||
|
>
|
||||||
|
Retour à l'accueil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Session ID Info -->
|
||||||
|
<div v-if="clientSessionId && !checking" class="mt-6 rounded-lg bg-gray-50 p-3">
|
||||||
|
<p class="text-center text-xs text-gray-600">
|
||||||
|
ID de session:
|
||||||
|
<br />
|
||||||
|
<code class="block font-mono text-[10px] break-all">{{ clientSessionId }}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -24,6 +24,14 @@ Route::post('/checkout-rendez-vous', [App\Http\Controllers\StripeController::cla
|
|||||||
|
|
||||||
Route::post('/stripe/webhook', [App\Http\Controllers\WebhookController::class, 'handleWebhook']);
|
Route::post('/stripe/webhook', [App\Http\Controllers\WebhookController::class, 'handleWebhook']);
|
||||||
|
|
||||||
|
// Wise payment routes
|
||||||
|
Route::post('/create-wise-payment', [App\Http\Controllers\WiseController::class, 'createPaymentSession']);
|
||||||
|
Route::post('/wise/webhook', [App\Http\Controllers\WiseController::class, 'handleWebhook']);
|
||||||
|
Route::get('/wise/validate-payment', [App\Http\Controllers\WiseController::class, 'validatePayment']);
|
||||||
|
Route::get('/wise/verify', function () {
|
||||||
|
return Inertia::render('wise/VerifyPayment');
|
||||||
|
})->name('wise.verify');
|
||||||
|
|
||||||
Route::get('/rendez-vous', [App\Http\Controllers\AppointmentController::class, 'index']);
|
Route::get('/rendez-vous', [App\Http\Controllers\AppointmentController::class, 'index']);
|
||||||
|
|
||||||
Route::get('/resultat', [App\Http\Controllers\CardController::class, 'cartResult']);
|
Route::get('/resultat', [App\Http\Controllers\CardController::class, 'cartResult']);
|
||||||
@ -46,7 +54,19 @@ Route::get('/success', function (Request $request) {
|
|||||||
if ($payment) {
|
if ($payment) {
|
||||||
return Inertia::render('payments/Success', [
|
return Inertia::render('payments/Success', [
|
||||||
'paymentSuccess' => true,
|
'paymentSuccess' => true,
|
||||||
'drawCount' => $payment->draw_count
|
'drawCount' => $payment->draw_count,
|
||||||
|
'paymentProvider' => $payment->payment_provider ?? 'stripe'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If payment not found as succeeded, check if it's pending (especially for Wise)
|
||||||
|
$pendingPayment = Payment::where('client_session_id', $clientSessionId)->first();
|
||||||
|
|
||||||
|
if ($pendingPayment && $pendingPayment->status === 'pending') {
|
||||||
|
return Inertia::render('payments/Pending', [
|
||||||
|
'message' => 'Payment is being processed. Please wait...',
|
||||||
|
'clientSessionId' => $clientSessionId,
|
||||||
|
'paymentProvider' => $pendingPayment->payment_provider ?? 'stripe'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user