This commit is contained in:
Nyavokevin 2025-10-14 21:49:22 +03:00
parent 83ed2ba44c
commit b6fa37f716
5 changed files with 486 additions and 180 deletions

View File

@ -70,13 +70,21 @@ STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
VITE_STRIPE_PUBLISHABLE_KEY="${STRIPE_PUBLISHABLE_KEY}"
# Wise Payment Configuration (Simplified - Using Payment Links)
# Wise Payment Configuration
# Option 1: Simplified - Using Payment Links
# Get your payment links from your Wise business account
# Each link is for a specific product/amount
WISE_PAYMENT_LINK_6_CARDS=https://wise.com/pay/r/W2k1NqQySdc9HW8
WISE_PAYMENT_LINK_18_CARDS=https://wise.com/pay/r/YOUR_18_CARDS_LINK
# Option 2: Full API Integration (for programmatic transfers)
# Get your API key from: https://wise.com/settings/api-tokens
# Get your profile ID from: https://wise.com/settings/
WISE_API_KEY=
WISE_PROFILE_ID=
# Wise Webhook Configuration (for payment verification)
# Create webhook at: https://wise.com/settings/webhooks
# Point it to: https://yourdomain.com/wise/webhook
# Point it to: https://yourdomain.com/api/wise/webhook
WISE_WEBHOOK_SECRET=

215
WISE_PAYMENT_INTEGRATION.md Normal file
View File

@ -0,0 +1,215 @@
# Wise Payment Integration - Complete Setup
This document describes the Wise payment integration that has been added to the oracle project, mirroring the implementation from the KSA-ORACLE project.
## Overview
The Wise payment integration allows you to programmatically create international money transfers using the Wise API. This is a full API integration that enables:
- Creating recipient accounts
- Generating transfer quotes
- Initiating transfers
- Funding transfers from your Wise balance
- Tracking transfer status via webhooks
## Backend Components
### 1. WisePaymentController (`app/Http/Controllers/WisePaymentController.php`)
Main controller that handles:
- **`createTransfer()`** - Creates a complete transfer flow (recipient → quote → transfer → funding)
- **`handleWebhook()`** - Processes Wise webhook notifications for transfer status updates
- **`checkTransferStatus()`** - Manually checks the status of a transfer
### 2. Payment Model (`app/Models/Payment.php`)
Updated with the following Wise-specific fields:
- `wise_transfer_id` - The Wise transfer ID
- `wise_recipient_id` - The recipient account ID in Wise
- `wise_quote_id` - The quote ID for the transfer
- `target_currency` - The currency the recipient will receive
- `recipient_name` - Name of the transfer recipient
- `recipient_email` - Email of the recipient
- `error_message` - Stores any error messages from failed transfers
- `payment_provider` - Set to 'wise' or 'stripe'
- `wise_payment_id` - Generic Wise payment identifier
- `wise_session_id` - Session tracking for Wise payments
### 3. Database Migrations
Two migrations have been created and applied:
**Migration 1:** `2025_10_14_130637_add_wise_fields_to_payments_table.php`
- Adds: `payment_provider`, `wise_payment_id`, `wise_session_id`
**Migration 2:** `2025_10_14_145926_add_wise_transfer_fields_to_payments_table.php`
- Adds: `wise_transfer_id`, `wise_recipient_id`, `wise_quote_id`, `target_currency`, `recipient_name`, `recipient_email`, `error_message`
### 4. API Routes (`routes/api.php`)
```php
Route::post('/api/wise/transfer', [WisePaymentController::class, 'createTransfer']);
Route::post('/api/wise/webhook', [WisePaymentController::class, 'handleWebhook']);
```
## Frontend Components
### 1. TypeScript Actions (`resources/js/actions/App/Http/Controllers/WisePaymentController.ts`)
Auto-generated route helpers for type-safe API calls:
- `createTransfer.post()` - Call the transfer creation endpoint
- `handleWebhook.post()` - Webhook endpoint (for backend use)
### 2. Checkout Page (`resources/js/pages/Checkout.vue`)
A complete Vue component with a form that includes:
- Amount input
- Source and target currency selection
- Recipient information (name, email, account number)
- Payment reason
- Error and success message handling
- Loading state management
## Configuration
### Environment Variables
Add these to your `.env` file (already documented in `.env.example`):
```bash
# Wise API Integration
WISE_API_KEY=your_wise_api_token_here
WISE_PROFILE_ID=your_wise_profile_id_here
# Wise Webhook Secret (optional, for webhook signature verification)
WISE_WEBHOOK_SECRET=your_webhook_secret_here
```
### Getting Your Wise Credentials
1. **API Key**:
- Go to https://wise.com/settings/api-tokens
- Create a new token with appropriate permissions
- Use either sandbox (test) or live API key
2. **Profile ID**:
- Go to https://wise.com/settings/
- Find your business or personal profile ID
- This is usually a numeric ID
3. **Webhook Secret** (optional):
- Go to https://wise.com/settings/webhooks
- Create a webhook pointing to: `https://yourdomain.com/api/wise/webhook`
- Copy the webhook secret for signature verification
## Usage Example
### Creating a Transfer from Frontend
```javascript
import WisePaymentController from '@/actions/App/Http/Controllers/WisePaymentController'
import axios from 'axios'
const formData = {
amount: 100.00,
source_currency: 'USD',
target_currency: 'EUR',
recipient_name: 'John Doe',
recipient_email: 'john@example.com',
recipient_account_number: '1234567890',
reason: 'Payment for services'
}
const response = await axios.post('/api/wise/transfer', formData)
if (response.data.success) {
console.log('Transfer ID:', response.data.transfer_id)
console.log('Payment ID:', response.data.payment_id)
}
```
### Backend Transfer Creation Flow
1. **Validates** the request data
2. **Creates** a Payment record with status 'pending'
3. **Creates** a recipient account in Wise
4. **Generates** a quote for the transfer
5. **Creates** the transfer in Wise
6. **Funds** the transfer from your Wise balance
7. **Updates** the Payment record with transfer details and status 'processing'
8. **Returns** the transfer ID and payment ID
## Webhook Handling
When Wise sends a webhook notification:
1. The `handleWebhook()` method receives the payload
2. Extracts the transfer ID from the webhook data
3. Finds the corresponding Payment record
4. Updates the payment status based on the transfer state
5. Logs the webhook for debugging
## Database Schema
The `payments` table now includes these fields for Wise integration:
```sql
payment_provider VARCHAR(255) DEFAULT 'stripe'
wise_payment_id VARCHAR(255) NULLABLE
wise_session_id VARCHAR(255) NULLABLE
wise_transfer_id VARCHAR(255) NULLABLE
wise_recipient_id VARCHAR(255) NULLABLE
wise_quote_id VARCHAR(255) NULLABLE
target_currency VARCHAR(10) NULLABLE
recipient_name VARCHAR(255) NULLABLE
recipient_email VARCHAR(255) NULLABLE
error_message TEXT NULLABLE
```
## Payment Status Flow
1. **pending** - Payment record created
2. **processing** - Transfer created and funded in Wise
3. **succeeded** / **failed** - Final status from Wise webhook
## Dependencies
- **GuzzleHTTP** (`guzzlehttp/guzzle: ^7.10`) - Already installed
- Used for making HTTP requests to the Wise API
## Testing
To test the integration:
1. Use Wise's sandbox environment first
2. Set `WISE_API_KEY` to your sandbox API key
3. Create test transfers to verify the flow
4. Monitor the Laravel logs for any API errors
5. Test webhook delivery using Wise's webhook testing tools
## Security Considerations
1. **Never commit** your `.env` file with real API keys
2. Store `WISE_API_KEY` securely (use Laravel secrets in production)
3. Implement webhook signature verification using `WISE_WEBHOOK_SECRET`
4. Validate all user inputs before creating transfers
5. Use HTTPS in production for webhook endpoints
## Additional Resources
- [Wise API Documentation](https://api-docs.wise.com/)
- [Wise Sandbox Environment](https://sandbox.transferwise.tech/)
- [Webhook Setup Guide](https://api-docs.wise.com/#webhooks)
## Notes
- The Wise API requires your account to have sufficient balance to fund transfers
- Some recipient types require additional fields (bank codes, IBAN, etc.)
- Transfer times vary by currency corridor
- API rate limits apply based on your Wise account tier
---
**Integration Status**: ✅ Complete
**Last Updated**: October 14, 2025
**Project**: oracle (/media/creator/6226b912-8ba7-45dc-88a2-4b10d3dd1655/kandra/oracle)

View File

@ -26,6 +26,13 @@ class Payment extends Model
'payment_provider',
'wise_payment_id',
'wise_session_id',
'wise_transfer_id',
'wise_recipient_id',
'wise_quote_id',
'target_currency',
'recipient_name',
'recipient_email',
'error_message',
];
/**

View File

@ -0,0 +1,42 @@
<?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('payments', function (Blueprint $table) {
$table->string('wise_transfer_id')->nullable()->after('wise_session_id');
$table->string('wise_recipient_id')->nullable()->after('wise_transfer_id');
$table->string('wise_quote_id')->nullable()->after('wise_recipient_id');
$table->string('target_currency', 10)->nullable()->after('currency');
$table->string('recipient_name')->nullable()->after('target_currency');
$table->string('recipient_email')->nullable()->after('recipient_name');
$table->text('error_message')->nullable()->after('status');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('payments', function (Blueprint $table) {
$table->dropColumn([
'wise_transfer_id',
'wise_recipient_id',
'wise_quote_id',
'target_currency',
'recipient_name',
'recipient_email',
'error_message'
]);
});
}
};

View File

@ -1,8 +1,5 @@
<script setup lang="ts">
import DatePicker from '@/components/DatePicker.vue';
import LandingLayout from '@/layouts/app/LandingLayout.vue';
import { loadStripe } from '@stripe/stripe-js';
import axios from 'axios';
import { ref } from 'vue';
interface UserForm {
@ -14,35 +11,24 @@ const userForm = ref<UserForm>({
fullname: '',
email: '',
});
const selectedDate = ref(new Date());
const loading = ref<boolean>(false);
const appointmentConfirmed = ref<boolean>(false);
const handleAppointment = () => {
redirectToStipeCheckout();
};
const redirectToStipeCheckout = async () => {
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) {
console.error('Stripe redirect error:', error.message);
}
}
} catch (error) {
console.error('Error initiating Stripe checkout:', error);
} finally {
// Simuler un délai de traitement
setTimeout(() => {
loading.value = false;
}
appointmentConfirmed.value = true;
// Réinitialiser le formulaire après confirmation
userForm.value = {
fullname: '',
email: '',
};
}, 1500);
};
</script>
@ -73,159 +59,226 @@ const redirectToStipeCheckout = async () => {
<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">
<!-- Enhanced Header Section -->
<div class="max-w-3xl text-center">
<div class="max-w-4xl text-center">
<h1 class="mb-6 font-serif text-4xl font-bold text-[var(--midnight-blue)] sm:text-5xl lg:text-6xl">
Réservez votre consultation
Votre Transformation Spirituelle Commence Ici
</h1>
<p class="mx-auto max-w-2xl text-lg text-[var(--midnight-blue)]/80 sm:text-xl">
Choisissez la date de votre consultation spirituelle et laissez-vous guider vers l'éclaircissement
<p class="mx-auto max-w-3xl text-lg text-[var(--midnight-blue)]/80 sm:text-xl">
Vous sentez que quelque chose vous appelle vers une vie plus alignée ?
<span class="font-semibold text-[var(--spiritual-earth)]">Votre âme cherche des réponses</span> et je suis pour vous
guider.
</p>
</div>
<!-- Main Content Container -->
<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">
<!-- Date Picker Card -->
<div class="w-full max-w-md lg:max-w-lg">
<div class="rounded-2xl border border-white/50 bg-white/80 p-6 shadow-xl backdrop-blur-sm">
<div class="mb-6 text-center">
<h2 class="font-serif text-2xl font-bold text-[var(--midnight-blue)]">Choisissez votre date</h2>
<p class="mt-2 text-[var(--midnight-blue)]/70">Sélectionnez le moment parfait pour votre guidance</p>
<!-- Benefits Section -->
<div class="grid max-w-5xl grid-cols-1 gap-6 md:grid-cols-3">
<!-- Benefit 1 -->
<div class="rounded-2xl border border-white/50 bg-white/60 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-[var(--subtle-gold)]/20">
<span class="text-xl">🔮</span>
</div>
<date-picker v-model:selectedDate="selectedDate" />
</div>
<h3 class="mb-3 font-serif text-xl font-bold text-[var(--midnight-blue)]">Clarté Immédiate</h3>
<p class="text-[var(--midnight-blue)]/70">
Obtenez des réponses claires à vos questions les plus profondes et libérez-vous des doutes qui vous retiennent
</p>
</div>
<!-- Form Card -->
<div class="w-full max-w-md">
<div class="rounded-2xl border border-white/50 bg-white/80 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-[var(--subtle-gold)]/20">
<svg class="h-6 w-6 text-[var(--subtle-gold)]" 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>
<!-- Benefit 2 -->
<div class="rounded-2xl border border-white/50 bg-white/60 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-[var(--subtle-gold)]/20">
<span class="text-xl">💫</span>
</div>
</div>
<h3 class="mb-3 font-serif text-xl font-bold text-[var(--midnight-blue)]">Guidance Personnalisée</h3>
<p class="text-[var(--midnight-blue)]/70">
Une approche unique adaptée à votre énergie et votre chemin de vie pour des résultats transformateurs
</p>
</div>
<!-- Benefit 3 -->
<div class="rounded-2xl border border-white/50 bg-white/60 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-[var(--subtle-gold)]/20">
<span class="text-xl">🌙</span>
</div>
</div>
<h3 class="mb-3 font-serif text-xl font-bold text-[var(--midnight-blue)]">Transformation Durable</h3>
<p class="text-[var(--midnight-blue)]/70">
Ne vous contentez pas de solutions temporaires. Créez les changements profonds que votre âme appelle
</p>
</div>
</div>
<!-- Call to Action Text -->
<div class="max-w-3xl text-center">
<div class="rounded-2xl border border-[var(--subtle-gold)]/30 bg-[var(--light-ivory)]/50 p-8 backdrop-blur-sm">
<h2 class="mb-4 font-serif text-2xl font-bold text-[var(--spiritual-earth)]">
Le Moment Est Venu de Dire "Oui" à Votre Voyage Intérieur
</h2>
<p class="mb-4 text-lg text-[var(--midnight-blue)]/80">
<span class="font-semibold">Des centaines de personnes</span> ont déjà transformé leur vie grâce à cette guidance.
Elles ont trouvé la <span class="font-semibold text-[var(--spiritual-earth)]">paix intérieure</span>, la
<span class="font-semibold text-[var(--spiritual-earth)]">clarté mentale</span> et la
<span class="font-semibold text-[var(--spiritual-earth)]">confiance</span> pour avancer.
</p>
<p class="text-lg text-[var(--midnight-blue)]/80">
<span class="font-bold">Et vous ?</span> Qu'attendez-vous pour découvrir ce que l'univers a prévu pour vous ?
</p>
</div>
</div>
<!-- Confirmation Message -->
<div v-if="appointmentConfirmed" class="w-full max-w-2xl">
<div class="rounded-2xl border border-green-200 bg-green-50/80 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-100">
<svg class="h-8 w-8 text-green-600" 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 font-serif text-2xl font-bold text-green-800">Magnifique ! Votre Demande Est Enregistrée</h2>
<p class="mb-4 text-green-700"><strong>Félicitations pour ce premier pas vers votre transformation !</strong></p>
<p class="mb-6 text-green-700">
Je vous contacte dans les 24 heures pour échanger sur vos besoins et convenir du meilleur moment pour votre
consultation personnalisée.
</p>
<button
@click="appointmentConfirmed = false"
class="rounded-xl bg-green-600 px-6 py-3 font-semibold text-white transition-colors hover:bg-green-700"
>
Prendre un nouveau rendez-vous
</button>
</div>
</div>
<!-- Form Card -->
<div v-else class="w-full max-w-md">
<div class="rounded-2xl border border-white/50 bg-white/80 p-8 shadow-xl backdrop-blur-sm">
<div class="mb-8 text-center">
<h2 class="font-serif text-2xl font-bold text-[var(--midnight-blue)]">Réservez Votre Appel Découverte</h2>
<p class="mt-3 text-[var(--midnight-blue)]/70">
<strong>Offert :</strong> 30 minutes pour identifier vos blocages et créer un plan d'action personnalisé
</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-[var(--midnight-blue)]" for="name">
Votre Prénom et Nom
</label>
<div class="relative">
<input
class="form-input w-full rounded-xl border-2 border-[var(--linen)] bg-white/80 p-4 text-base text-[var(--midnight-blue)] transition-all duration-300 placeholder:text-[var(--midnight-blue)]/40 focus:border-[var(--subtle-gold)] focus:bg-white focus:shadow-lg focus:ring-2 focus:ring-[var(--subtle-gold)]/20"
id="name"
name="name"
placeholder="Comment souhaitez-vous que je vous appelle ?"
type="text"
v-model="userForm.fullname"
required
/>
<div
class="pointer-events-none absolute inset-0 rounded-xl border-2 border-transparent transition-all duration-300 group-focus-within:border-[var(--subtle-gold)]/30"
></div>
</div>
<h2 class="font-serif text-2xl font-bold text-[var(--midnight-blue)]">Vos informations</h2>
<p class="mt-2 text-[var(--midnight-blue)]/70">Remplissez vos coordonnées pour finaliser la réservation</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-[var(--midnight-blue)]" for="name"> Nom complet </label>
<div class="relative">
<input
class="form-input w-full rounded-xl border-2 border-[var(--linen)] bg-white/80 p-4 text-base text-[var(--midnight-blue)] transition-all duration-300 placeholder:text-[var(--midnight-blue)]/40 focus:border-[var(--subtle-gold)] focus:bg-white focus:shadow-lg focus:ring-2 focus:ring-[var(--subtle-gold)]/20"
id="name"
name="name"
placeholder="Votre nom complet"
type="text"
v-model="userForm.fullname"
required
/>
<div
class="pointer-events-none absolute inset-0 rounded-xl border-2 border-transparent transition-all duration-300 group-focus-within:border-[var(--subtle-gold)]/30"
></div>
</div>
<!-- Email Input -->
<div class="group">
<label class="mb-3 block text-sm font-semibold text-[var(--midnight-blue)]" for="email">
Votre Meilleur Email
</label>
<div class="relative">
<input
class="form-input w-full rounded-xl border-2 border-[var(--linen)] bg-white/80 p-4 text-base text-[var(--midnight-blue)] transition-all duration-300 placeholder:text-[var(--midnight-blue)]/40 focus:border-[var(--subtle-gold)] focus:bg-white focus:shadow-lg focus:ring-2 focus:ring-[var(--subtle-gold)]/20"
id="email"
name="email"
placeholder="Où puis-je vous envoyer les détails ?"
type="email"
v-model="userForm.email"
required
/>
<div
class="pointer-events-none absolute inset-0 rounded-xl border-2 border-transparent transition-all duration-300 group-focus-within:border-[var(--subtle-gold)]/30"
></div>
</div>
</div>
<!-- Email Input -->
<div class="group">
<label class="mb-3 block text-sm font-semibold text-[var(--midnight-blue)]" for="email">
Adresse e-mail
</label>
<div class="relative">
<input
class="form-input w-full rounded-xl border-2 border-[var(--linen)] bg-white/80 p-4 text-base text-[var(--midnight-blue)] transition-all duration-300 placeholder:text-[var(--midnight-blue)]/40 focus:border-[var(--subtle-gold)] focus:bg-white focus:shadow-lg focus:ring-2 focus:ring-[var(--subtle-gold)]/20"
id="email"
name="email"
placeholder="Votre adresse e-mail"
type="email"
v-model="userForm.email"
required
/>
<div
class="pointer-events-none absolute inset-0 rounded-xl border-2 border-transparent transition-all duration-300 group-focus-within:border-[var(--subtle-gold)]/30"
></div>
</div>
</div>
<!-- Selected Date Display -->
<div class="rounded-xl border border-[var(--linen)] bg-[var(--light-ivory)]/50 p-4">
<p class="mb-1 text-sm font-medium text-[var(--midnight-blue)]">Date sélectionnée :</p>
<p class="text-lg font-semibold text-[var(--spiritual-earth)]">
{{
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-[var(--midnight-blue)] to-[var(--spiritual-earth)] px-8 text-lg font-bold tracking-wide text-white shadow-lg transition-all duration-500 hover:from-[var(--spiritual-earth)] hover:to-[var(--midnight-blue)] 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-[var(--midnight-blue)]/60">
🔒 Paiement sécurisé via Stripe. Vos informations sont protégées.
<!-- Urgency CTA -->
<div class="rounded-xl border border-[var(--subtle-gold)] bg-[var(--light-ivory)]/50 p-4 text-center">
<p class="text-sm font-semibold text-[var(--spiritual-earth)]">
Places limitées - Ne laissez pas le doute vous priver de votre transformation
</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-[var(--midnight-blue)] to-[var(--spiritual-earth)] px-8 text-lg font-bold tracking-wide text-white shadow-lg transition-all duration-500 hover:from-[var(--spiritual-earth)] hover:to-[var(--midnight-blue)] 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 ? 'Envoi en cours...' : '✨ Oui, je veux ma consultation offerte !' }}</span>
</span>
</button>
<!-- Security Note -->
<p class="mt-4 text-center text-xs text-[var(--midnight-blue)]/60">
🔒 Confidentialité absolue Sans engagement Guidance 100% personnalisée
</p>
</form>
</div>
</div>
<!-- Testimonial Section -->
<div class="max-w-4xl text-center">
<div class="rounded-2xl border border-white/50 bg-white/50 p-8 backdrop-blur-sm">
<h3 class="mb-6 font-serif text-2xl font-bold text-[var(--midnight-blue)]">Ils Ont Osé le Pas Décisif</h3>
<div class="grid gap-6 md:grid-cols-2">
<div class="text-left">
<p class="mb-4 text-[var(--midnight-blue)]/80 italic">
"Je traînais des incertitudes depuis des années. En une seule séance, j'ai trouvé la clarté qui m'a changé la
vie. Merci !"
</p>
<p class="font-semibold text-[var(--spiritual-earth)]">- Marie, 34 ans</p>
</div>
<div class="text-left">
<p class="mb-4 text-[var(--midnight-blue)]/80 italic">
"La guidance reçue a transformé ma relation avec moi-même et avec les autres. Je me sens enfin alignée avec
mon vrai moi."
</p>
<p class="font-semibold text-[var(--spiritual-earth)]">- Thomas, 41 ans</p>
</div>
</div>
</div>
</div>
<!-- Additional Information -->
<div class="mt-8 max-w-2xl text-center">
<div
class="inline-flex items-center gap-4 rounded-2xl border border-white/50 bg-white/50 px-6 py-4 text-sm text-[var(--midnight-blue)]/70 backdrop-blur-sm"
>
<svg class="h-5 w-5 text-[var(--subtle-gold)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
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>
<!-- Final CTA -->
<div class="max-w-2xl text-center">
<div class="rounded-2xl bg-gradient-to-r from-[var(--subtle-gold)]/20 to-[var(--spiritual-earth)]/20 p-6">
<h3 class="mb-4 font-serif text-xl font-bold text-[var(--midnight-blue)]">🌟 Votre Avenir Vous Attend</h3>
<p class="text-[var(--midnight-blue)]/80">
Ne laissez pas le quotidien étouffer l'appel de votre âme.
<strong>Votre transformation commence par un simple "oui"</strong>.
</p>
</div>
</div>
</div>
@ -259,23 +312,4 @@ const redirectToStipeCheckout = async () => {
.relative.min-h-screen {
background: transparent;
}
/* Custom scrollbar for date picker if needed */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: var(--light-ivory);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: var(--subtle-gold);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: var(--spiritual-earth);
}
</style>