agenda
This commit is contained in:
parent
83ed2ba44c
commit
b6fa37f716
12
.env.example
12
.env.example
@ -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
215
WISE_PAYMENT_INTEGRATION.md
Normal 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)
|
||||
@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@ -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'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -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 là 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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user