fix back card
This commit is contained in:
parent
bd7ed0c30e
commit
153e700b8a
248
app/Http/Controllers/WisePaymentController.php
Normal file
248
app/Http/Controllers/WisePaymentController.php
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Payment;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Exception\ServerException;
|
||||||
|
|
||||||
|
class WisePaymentController extends Controller
|
||||||
|
{
|
||||||
|
private $wiseClient;
|
||||||
|
private $wiseApiKey;
|
||||||
|
private $profileId;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->wiseApiKey = env('WISE_API_KEY');
|
||||||
|
$this->profileId = env('WISE_PROFILE_ID'); // Your Wise business/profile ID
|
||||||
|
|
||||||
|
$this->wiseClient = new Client([
|
||||||
|
'base_uri' => 'https://api.wise.com/v1/',
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $this->wiseApiKey,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createTransfer(Request $request)
|
||||||
|
{
|
||||||
|
// Validate the incoming request data
|
||||||
|
$validatedData = $request->validate([
|
||||||
|
'amount' => 'required|numeric|min:0.01',
|
||||||
|
'source_currency' => 'required|string|size:3',
|
||||||
|
'target_currency' => 'required|string|size:3',
|
||||||
|
'recipient_name' => 'required|string|max:255',
|
||||||
|
'recipient_email' => 'required|email',
|
||||||
|
'recipient_account_number' => 'required|string',
|
||||||
|
'recipient_bank_code' => 'sometimes|string', // Needed for some countries
|
||||||
|
'recipient_address' => 'sometimes|array',
|
||||||
|
'reason' => 'sometimes|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new Payment record
|
||||||
|
$payment = new Payment([
|
||||||
|
'amount' => $validatedData['amount'],
|
||||||
|
'currency' => $validatedData['source_currency'],
|
||||||
|
'target_currency' => $validatedData['target_currency'],
|
||||||
|
'recipient_name' => $validatedData['recipient_name'],
|
||||||
|
'recipient_email' => $validatedData['recipient_email'],
|
||||||
|
'status' => 'pending',
|
||||||
|
'client_session_id' => $request->client_session_id ?? uniqid('wise_', true)
|
||||||
|
]);
|
||||||
|
$payment->save();
|
||||||
|
|
||||||
|
// Create recipient account
|
||||||
|
$recipient = $this->createRecipient($validatedData);
|
||||||
|
|
||||||
|
// Create quote
|
||||||
|
$quote = $this->createQuote($validatedData);
|
||||||
|
|
||||||
|
// Create transfer
|
||||||
|
$transfer = $this->createTransferWise($quote, $recipient, $validatedData);
|
||||||
|
|
||||||
|
// Fund the transfer
|
||||||
|
$this->fundTransfer($transfer['id']);
|
||||||
|
|
||||||
|
// Update the Payment record
|
||||||
|
$payment->wise_transfer_id = $transfer['id'];
|
||||||
|
$payment->wise_recipient_id = $recipient['id'];
|
||||||
|
$payment->wise_quote_id = $quote['id'];
|
||||||
|
$payment->status = 'processing';
|
||||||
|
$payment->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Transfer initiated successfully.',
|
||||||
|
'transfer_id' => $transfer['id'],
|
||||||
|
'payment_id' => $payment->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
$response = $e->getResponse();
|
||||||
|
$body = json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
Log::error('Wise Client Error:', [
|
||||||
|
'code' => $e->getCode(),
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'details' => $body
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isset($payment)) {
|
||||||
|
$payment->status = 'failed';
|
||||||
|
$payment->error_message = $e->getMessage();
|
||||||
|
$payment->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Wise API failed to process the request.',
|
||||||
|
'error' => $body['errors'] ?? $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('General Wise API Error:', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isset($payment)) {
|
||||||
|
$payment->status = 'failed';
|
||||||
|
$payment->error_message = $e->getMessage();
|
||||||
|
$payment->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'An unexpected error occurred.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleWebhook(Request $request)
|
||||||
|
{
|
||||||
|
// Verify the webhook signature if needed
|
||||||
|
$payload = $request->all();
|
||||||
|
|
||||||
|
Log::info('Wise Webhook Received:', $payload);
|
||||||
|
|
||||||
|
if (isset($payload['data']['resource']['id'])) {
|
||||||
|
$transferId = $payload['data']['resource']['id'];
|
||||||
|
|
||||||
|
// Find the payment by transfer ID
|
||||||
|
$payment = Payment::where('wise_transfer_id', $transferId)->first();
|
||||||
|
|
||||||
|
if ($payment) {
|
||||||
|
$payment->status = $payload['data']['current_state'] ?? 'unknown';
|
||||||
|
$payment->save();
|
||||||
|
|
||||||
|
// You might want to trigger other actions based on status change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'ok']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods to interact with Wise API
|
||||||
|
private function createRecipient($data)
|
||||||
|
{
|
||||||
|
$response = $this->wiseClient->post('accounts', [
|
||||||
|
'json' => [
|
||||||
|
'profile' => $this->profileId,
|
||||||
|
'accountHolderName' => $data['recipient_name'],
|
||||||
|
'currency' => $data['target_currency'],
|
||||||
|
'type' => 'email', // or 'sort_code', 'aba', 'iban' etc. based on country
|
||||||
|
'details' => [
|
||||||
|
'email' => $data['recipient_email'],
|
||||||
|
// Add more details based on account type and country
|
||||||
|
'legalType' => 'PRIVATE',
|
||||||
|
// 'accountNumber' => $data['recipient_account_number'],
|
||||||
|
// 'bankCode' => $data['recipient_bank_code'] ?? null,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createQuote($data)
|
||||||
|
{
|
||||||
|
$response = $this->wiseClient->post('quotes', [
|
||||||
|
'json' => [
|
||||||
|
'profile' => $this->profileId,
|
||||||
|
'source' => $data['source_currency'],
|
||||||
|
'target' => $data['target_currency'],
|
||||||
|
'rateType' => 'FIXED', // or 'FLOAT'
|
||||||
|
'sourceAmount' => $data['amount'],
|
||||||
|
'type' => 'BALANCE_PAYOUT' // or 'BALANCE_CONVERSION'
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTransferWise($quote, $recipient, $data)
|
||||||
|
{
|
||||||
|
$response = $this->wiseClient->post('transfers', [
|
||||||
|
'json' => [
|
||||||
|
'targetAccount' => $recipient['id'],
|
||||||
|
'quote' => $quote['id'],
|
||||||
|
'customerTransactionId' => uniqid('cti_', true),
|
||||||
|
'details' => [
|
||||||
|
'reference' => $data['reason'] ?? 'Payment for services',
|
||||||
|
'transferPurpose' => $data['reason'] ?? 'Payment for services',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fundTransfer($transferId)
|
||||||
|
{
|
||||||
|
// Check if the transfer requires funding
|
||||||
|
$transfer = $this->wiseClient->get("transfers/{$transferId}");
|
||||||
|
$transferData = json_decode($transfer->getBody(), true);
|
||||||
|
|
||||||
|
if ($transferData['status'] === 'pending') {
|
||||||
|
// Fund the transfer from your balance
|
||||||
|
$this->wiseClient->post("transfers/{$transferId}/payments", [
|
||||||
|
'json' => [
|
||||||
|
'type' => 'BALANCE',
|
||||||
|
'profile' => $this->profileId
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional helper method to check transfer status
|
||||||
|
public function checkTransferStatus($paymentId)
|
||||||
|
{
|
||||||
|
$payment = Payment::findOrFail($paymentId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->wiseClient->get("transfers/{$payment->wise_transfer_id}");
|
||||||
|
$transferData = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
$payment->status = $transferData['status'];
|
||||||
|
$payment->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'status' => $transferData['status']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to check transfer status'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,7 +22,7 @@ class Payment extends Model
|
|||||||
'draw_count',
|
'draw_count',
|
||||||
'status',
|
'status',
|
||||||
'cards',
|
'cards',
|
||||||
'appointment_date'
|
'appointment_date',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"guzzlehttp/guzzle": "^7.10",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
|
|||||||
2
composer.lock
generated
2
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "7a72790164b9b6dc081f7cbfde7e67d5",
|
"content-hash": "748c4d7177ae376a830b7c336264affd",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
|
|||||||
55809
public/back-card.svg
Normal file
55809
public/back-card.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 2.8 MiB |
@ -61,14 +61,7 @@
|
|||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
||||||
so we've added these compatibility styles to make sure everything still
|
|
||||||
looks the same as it did with Tailwind CSS v3.
|
|
||||||
|
|
||||||
If we ever want to remove these styles, we need to add an explicit border
|
|
||||||
color utility to any element that depends on these defaults.
|
|
||||||
*/
|
|
||||||
@layer base {
|
@layer base {
|
||||||
*,
|
*,
|
||||||
::after,
|
::after,
|
||||||
|
|||||||
@ -75,28 +75,8 @@ defineExpose({ setDrawnCards });
|
|||||||
>
|
>
|
||||||
<div v-for="i in 8" :key="i" class="card" :style="{ transform: `rotate(${-3 + i}deg) translateZ(-${10 * i}px);` }">
|
<div v-for="i in 8" :key="i" class="card" :style="{ transform: `rotate(${-3 + i}deg) translateZ(-${10 * i}px);` }">
|
||||||
<div class="card-back">
|
<div class="card-back">
|
||||||
<div class="card-back-design">
|
<div class="card-inner-content">
|
||||||
<svg
|
<img src="cards/1.png" alt="Card Back" class="card-back-image" />
|
||||||
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
|
||||||
<path
|
|
||||||
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="0.5"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="0.5"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,43 +88,14 @@ defineExpose({ setDrawnCards });
|
|||||||
<div v-for="(card, index) in drawnCards" :key="index" class="card-result-wrapper">
|
<div v-for="(card, index) in drawnCards" :key="index" class="card-result-wrapper">
|
||||||
<div class="result-card" :class="{ flipped: isFlipped[index] }" @click="flipCard(index)">
|
<div class="result-card" :class="{ flipped: isFlipped[index] }" @click="flipCard(index)">
|
||||||
<div class="card-face card-unknown-front">
|
<div class="card-face card-unknown-front">
|
||||||
<div class="card-back-design">
|
<div class="card-inner-content">
|
||||||
<svg
|
<img src="cards/1.png" alt="Card Back" class="card-back-image" />
|
||||||
class="h-16 w-16 text-[var(--subtle-gold)] opacity-80"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path d="M12 4v16m8-8H4" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"></path>
|
|
||||||
<path
|
|
||||||
d="M14.828 7.172a4 4 0 015.656 5.656l-5.656 5.657a4 4 0 01-5.657-5.657l5.657-5.656z"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="0.5"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M9.172 7.172a4 4 0 00-5.657 5.656l5.657 5.657a4 4 0 005.656-5.657L9.172 7.172z"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="0.5"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-face card-known-back">
|
<div class="card-face card-known-back">
|
||||||
<img :src="card.image_url!" :alt="card.name" class="card-image" />
|
<img :src="card.image_url!" :alt="card.name" class="card-image" />
|
||||||
<div class="card-description-overlay">
|
<div class="card-description-overlay">
|
||||||
<h3>{{ card.name }}</h3>
|
<h3>{{ card.name }}</h3>
|
||||||
<p class="description">{{ card.description }}</p>
|
|
||||||
<p v-if="card.orientation" class="orientation">
|
|
||||||
{{ card.orientation === 'reversed' ? 'Inversée' : 'Droite' }}
|
|
||||||
</p>
|
|
||||||
<div v-if="card.symbolism" class="symbolism">
|
|
||||||
<p><strong>Numéro:</strong> {{ card.symbolism.numéro }}</p>
|
|
||||||
<p><strong>Planète:</strong> {{ card.symbolism.planète }}</p>
|
|
||||||
<p><strong>Élément:</strong> {{ card.symbolism.élément }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -158,6 +109,7 @@ defineExpose({ setDrawnCards });
|
|||||||
<span class="truncate">Retourner à la sélection des cartes</span>
|
<span class="truncate">Retourner à la sélection des cartes</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
v-if="clientSessionId"
|
||||||
@click="goToResult"
|
@click="goToResult"
|
||||||
class="mt-8 flex h-12 max-w-[480px] min-w-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-full bg-[var(--midnight-blue)] px-8 text-base font-bold tracking-wide text-[var(--pure-white)] transition-all duration-300 hover:bg-[var(--spiritual-earth)] hover:shadow-[var(--spiritual-earth)]/30 hover:shadow-lg disabled:cursor-not-allowed disabled:bg-gray-400 disabled:hover:shadow-none"
|
class="mt-8 flex h-12 max-w-[480px] min-w-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-full bg-[var(--midnight-blue)] px-8 text-base font-bold tracking-wide text-[var(--pure-white)] transition-all duration-300 hover:bg-[var(--spiritual-earth)] hover:shadow-[var(--spiritual-earth)]/30 hover:shadow-lg disabled:cursor-not-allowed disabled:bg-gray-400 disabled:hover:shadow-none"
|
||||||
>
|
>
|
||||||
@ -273,11 +225,28 @@ defineExpose({ setDrawnCards });
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
display: flex;
|
display: flex; /* Keep flexbox for centering if needed */
|
||||||
|
align-items: center; /* For centering .card-inner-content if it's smaller */
|
||||||
|
justify-content: center; /* For centering .card-inner-content if it's smaller */
|
||||||
|
background-color: var(--subtle-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner-content {
|
||||||
|
width: calc(100% - 10px); /* Adjust 10px to control border thickness (e.g., 5px border on each side) */
|
||||||
|
height: calc(100% - 10px); /* Adjust this value as well */
|
||||||
|
background-color: white; /* Or whatever color you want inside the gold border */
|
||||||
|
border-radius: 14px; /* Slightly smaller to match outer radius with padding */
|
||||||
|
display: flex; /* Use flex to center the image if it's an <img> tag */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid var(--subtle-gold);
|
overflow: hidden; /* Ensure image doesn't bleed out */
|
||||||
background: radial-gradient(circle, var(--midnight-blue) 0%, #121a2c 100%);
|
}
|
||||||
|
|
||||||
|
.card-back-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover; /* Or 'cover' depending on how you want the image to fill the inner content */
|
||||||
|
border-radius: 14px; /* Match inner content border-radius */
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-result-wrapper {
|
.card-result-wrapper {
|
||||||
@ -324,16 +293,22 @@ defineExpose({ setDrawnCards });
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-unknown-front {
|
.card-unknown-front {
|
||||||
background: radial-gradient(circle, var(--midnight-blue) 0%, #121a2c 100%);
|
background-color: var(--subtle-gold);
|
||||||
display: flex;
|
display: flex; /* Keep flexbox for centering its *own* content */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid var(--subtle-gold);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-known-back {
|
.card-known-back {
|
||||||
|
background-color: var(--subtle-gold);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
transform: rotateY(180deg); /* This face starts rotated, so it's hidden */
|
transform: rotateY(180deg); /* This face starts rotated, so it's hidden */
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 16px; /* Ensure this matches the outer card radius */
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-back-design::before {
|
.card-back-design::before {
|
||||||
@ -410,7 +385,11 @@ defineExpose({ setDrawnCards });
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-back-info {
|
.card-back-info {
|
||||||
background: linear-gradient(145deg, var(--midnight-blue), #121a2c);
|
/* Replace the existing background property */
|
||||||
|
background-image: url('back-card.svg'); /* background-size: cover; /* This makes the image cover the entire element */
|
||||||
|
background-position: center; /* This centers the image in the element */
|
||||||
|
background-repeat: no-repeat; /* This prevents the image from repeating */
|
||||||
|
|
||||||
color: var(--pure-white);
|
color: var(--pure-white);
|
||||||
transform: rotateY(180deg);
|
transform: rotateY(180deg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -444,22 +423,39 @@ defineExpose({ setDrawnCards });
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.6); /* Semi-transparent overlay for readability */
|
|
||||||
color: white;
|
/* Adjust background for a brighter, more subtle overlay */
|
||||||
|
background: rgba(255, 255, 255, 0.2); /* Lighter, more transparent white overlay */
|
||||||
|
/* You could also use a subtle gradient if you prefer: */
|
||||||
|
/* background: linear-gradient(to top, rgba(0,0,0,0.5) 0%, rgba(255,255,255,0) 50%); */
|
||||||
|
|
||||||
|
color: white; /* Keep text white for contrast */
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
|
/* Center the content (just the name) in the middle of the card */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end; /* Align content to the bottom */
|
justify-content: center; /* Center vertically */
|
||||||
align-items: center;
|
align-items: center; /* Center horizontally */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
pointer-events: none; /* Make overlay non-interactive so clicks go through to flip */
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-description-overlay h3,
|
.card-description-overlay h3 {
|
||||||
.card-description-overlay p {
|
margin: 0; /* Remove default margin */
|
||||||
margin: 0.2rem 0;
|
font-size: 1.8rem; /* Make the name larger */
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); /* Add subtle shadow for readability */
|
||||||
|
color: var(--pure-white); /* Ensure it's clearly white */
|
||||||
|
letter-spacing: 1px; /* Add some letter spacing */
|
||||||
|
}
|
||||||
|
/* Hide these elements completely */
|
||||||
|
.card-description-overlay p.description,
|
||||||
|
.card-description-overlay p.orientation,
|
||||||
|
.card-description-overlay div.symbolism {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.orientation {
|
.orientation {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
|||||||
170
resources/js/pages/Checkout.vue
Normal file
170
resources/js/pages/Checkout.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wise-payment-form">
|
||||||
|
<form @submit.prevent="submitTransfer">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="amount">Amount</label>
|
||||||
|
<input type="number" id="amount" v-model="formData.amount" step="0.01" min="0.01" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="source_currency">Source Currency</label>
|
||||||
|
<select id="source_currency" v-model="formData.source_currency" required>
|
||||||
|
<option value="USD">USD</option>
|
||||||
|
<option value="EUR">EUR</option>
|
||||||
|
<option value="GBP">GBP</option>
|
||||||
|
<!-- Add more currencies as needed -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="target_currency">Target Currency</label>
|
||||||
|
<select id="target_currency" v-model="formData.target_currency" required>
|
||||||
|
<option value="USD">USD</option>
|
||||||
|
<option value="EUR">EUR</option>
|
||||||
|
<option value="GBP">GBP</option>
|
||||||
|
<!-- Add more currencies as needed -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="recipient_name">Recipient Name</label>
|
||||||
|
<input type="text" id="recipient_name" v-model="formData.recipient_name" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="recipient_email">Recipient Email</label>
|
||||||
|
<input type="email" id="recipient_email" v-model="formData.recipient_email" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="recipient_account_number">Account Number</label>
|
||||||
|
<input type="text" id="recipient_account_number" v-model="formData.recipient_account_number" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="reason">Payment Reason</label>
|
||||||
|
<input type="text" id="reason" v-model="formData.reason" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" :disabled="loading">
|
||||||
|
{{ loading ? 'Processing...' : 'Create Transfer' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div v-if="error" class="error-message">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="success" class="success-message">Transfer initiated successfully! Transfer ID: {{ transferId }}</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
success: false,
|
||||||
|
transferId: null,
|
||||||
|
formData: {
|
||||||
|
amount: '',
|
||||||
|
source_currency: 'USD',
|
||||||
|
target_currency: 'EUR',
|
||||||
|
recipient_name: '',
|
||||||
|
recipient_email: '',
|
||||||
|
recipient_account_number: '',
|
||||||
|
reason: 'Payment for services',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submitTransfer() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
this.success = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/wise/transfer', this.formData);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
this.success = true;
|
||||||
|
this.transferId = response.data.transfer_id;
|
||||||
|
// You might want to redirect or show additional info
|
||||||
|
} else {
|
||||||
|
this.error = response.data.message || 'An error occurred';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
this.error = error.response.data.message || 'An error occurred';
|
||||||
|
|
||||||
|
// Display detailed errors if available
|
||||||
|
if (error.response.data.error) {
|
||||||
|
if (Array.isArray(error.response.data.error)) {
|
||||||
|
this.error += ': ' + error.response.data.error.map((e) => e.message).join(', ');
|
||||||
|
} else if (typeof error.response.data.error === 'object') {
|
||||||
|
this.error += ': ' + JSON.stringify(error.response.data.error);
|
||||||
|
} else {
|
||||||
|
this.error += ': ' + error.response.data.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.error = 'Network error or server unavailable';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wise-payment-form {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #cccccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #d9534f;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: #5cb85c;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -10,3 +10,7 @@ Route::get('/user', function (Request $request) {
|
|||||||
|
|
||||||
Route::get('/validate-payment', [App\Http\Controllers\StripeController::class, 'validatePayment']);
|
Route::get('/validate-payment', [App\Http\Controllers\StripeController::class, 'validatePayment']);
|
||||||
Route::get('/get-cards', [App\Http\Controllers\StripeController::class, 'getCards']);
|
Route::get('/get-cards', [App\Http\Controllers\StripeController::class, 'getCards']);
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/wise/transfer', [App\Http\Controllers\WisePaymentController::class, 'createTransfer']);
|
||||||
|
Route::post('/wise/webhook', [App\Http\Controllers\WisePaymentController::class, 'handleWebhook']);
|
||||||
|
|||||||
@ -27,6 +27,11 @@ Route::get('/rendez-vous', [App\Http\Controllers\AppointmentController::class, '
|
|||||||
|
|
||||||
Route::get('/resultat', [App\Http\Controllers\CardController::class, 'cartResult']);
|
Route::get('/resultat', [App\Http\Controllers\CardController::class, 'cartResult']);
|
||||||
|
|
||||||
|
Route::get('paiement', function () {
|
||||||
|
return Inertia::render('Checkout');
|
||||||
|
})->name('paiement');
|
||||||
|
|
||||||
|
|
||||||
Route::get('/success', function (Request $request) {
|
Route::get('/success', function (Request $request) {
|
||||||
$clientSessionId = $request->query('client_session_id');
|
$clientSessionId = $request->query('client_session_id');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user