Compare commits
11 Commits
2deaa14dcf
...
c199e0a9dd
| Author | SHA1 | Date |
|---|---|---|
|
|
c199e0a9dd | |
|
|
9e8e046985 | |
|
|
9cf128a887 | |
|
|
9d6c12ac5f | |
|
|
fcac34908e | |
|
|
7394593e57 | |
|
|
81cb53736d | |
|
|
c1dd36d7fd | |
|
|
f626157ab4 | |
|
|
30dddcf725 | |
|
|
e1a989b2a6 |
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\PaymentMethod;
|
||||
use App\Enums\PaymentStatus;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use Illuminate\Http\Request;
|
||||
use Stripe\Stripe;
|
||||
|
||||
class StripeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('stripe.index');
|
||||
}
|
||||
|
||||
public function checkout(Invoice $invoice)
|
||||
{
|
||||
Stripe::setApiKey(config('services.stripe.secret'));
|
||||
|
||||
$session = \Stripe\Checkout\Session::create([
|
||||
'payment_method_types' => ['card'],
|
||||
'line_items' => [
|
||||
[
|
||||
'price_data' => [
|
||||
'currency' => 'usd',
|
||||
'product_data' => [
|
||||
'name' => "Invoice {$invoice->invoice_number}",
|
||||
],
|
||||
'unit_amount' => (int) ($invoice->balance_due * 100),
|
||||
],
|
||||
'quantity' => 1,
|
||||
],
|
||||
],
|
||||
'mode' => 'payment',
|
||||
'success_url' => route('stripe.success', $invoice),
|
||||
'cancel_url' => route('invoices.show', $invoice),
|
||||
'metadata' => [
|
||||
'invoice_id' => $invoice->id,
|
||||
],
|
||||
]);
|
||||
|
||||
return redirect($session->url);
|
||||
}
|
||||
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
$payload = $request->getContent();
|
||||
$signature = $request->header('Stripe-Signature');
|
||||
|
||||
try {
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$payload,
|
||||
$signature,
|
||||
config('services.stripe.webhook_secret')
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return response('Invalid signature', 400);
|
||||
}
|
||||
|
||||
match ($event->type) {
|
||||
'checkout.session.completed' => $this->handleCheckoutSessionCompleted($event->data->object),
|
||||
'charge.updated' => $this->handleChargeUpdated($event->data->object),
|
||||
default => null,
|
||||
};
|
||||
|
||||
return response('OK', 200);
|
||||
}
|
||||
|
||||
protected function handleCheckoutSessionCompleted($session): void
|
||||
{
|
||||
$invoice = Invoice::find($session->metadata->invoice_id);
|
||||
|
||||
if (! $invoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stripe::setApiKey(config('services.stripe.secret'));
|
||||
|
||||
$paymentIntent = \Stripe\PaymentIntent::retrieve([
|
||||
'id' => $session->payment_intent,
|
||||
'expand' => ['latest_charge.balance_transaction'],
|
||||
]);
|
||||
|
||||
$feeAmount = $paymentIntent->latest_charge?->balance_transaction?->fee ?? 0;
|
||||
|
||||
$contact = $this->findOrCreateContact($session, $invoice);
|
||||
|
||||
Payment::create([
|
||||
'invoice_id' => $invoice->id,
|
||||
'contact_id' => $contact?->id,
|
||||
'payment_date' => now(),
|
||||
'status' => PaymentStatus::COMPLETED,
|
||||
'payment_method' => PaymentMethod::CARD,
|
||||
'reference' => $session->payment_intent,
|
||||
'stripe_payment_intent_id' => $session->payment_intent,
|
||||
'amount' => $session->amount_total / 100,
|
||||
'fee_amount' => $feeAmount / 100,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleChargeUpdated($charge): void
|
||||
{
|
||||
if (! $charge->payment_intent || ! $charge->balance_transaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment = Payment::where('stripe_payment_intent_id', $charge->payment_intent)->first();
|
||||
|
||||
if (! $payment) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stripe::setApiKey(config('services.stripe.secret'));
|
||||
|
||||
$balanceTransaction = \Stripe\BalanceTransaction::retrieve($charge->balance_transaction);
|
||||
|
||||
// Always update the fee - handles both initial capture and any corrections
|
||||
$payment->updateQuietly([
|
||||
'fee_amount' => $balanceTransaction->fee / 100,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function findOrCreateContact($session, Invoice $invoice): ?Contact
|
||||
{
|
||||
$email = $session->customer_details?->email;
|
||||
|
||||
if (! $email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contact = Contact::where('email', $email)->first();
|
||||
|
||||
if ($contact) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
$name = $session->customer_details?->name ?? '';
|
||||
$lastSpacePos = strrpos($name, ' ');
|
||||
|
||||
if ($lastSpacePos !== false) {
|
||||
$firstName = substr($name, 0, $lastSpacePos);
|
||||
$lastName = substr($name, $lastSpacePos + 1);
|
||||
} else {
|
||||
$firstName = $name;
|
||||
$lastName = '';
|
||||
}
|
||||
|
||||
$contact = Contact::create([
|
||||
'first_name' => $firstName,
|
||||
'last_name' => $lastName,
|
||||
'email' => $email,
|
||||
]);
|
||||
|
||||
$invoice->client->contacts()->attach($contact);
|
||||
|
||||
return $contact;
|
||||
}
|
||||
|
||||
public function checkoutTutorial()
|
||||
{
|
||||
Stripe::setApiKey(config('stripe.sk'));
|
||||
|
||||
$session = \Stripe\Checkout\Session::create([
|
||||
'line_items' => [
|
||||
[
|
||||
'price_data' => [
|
||||
'currency' => 'usd',
|
||||
'product_data' => [
|
||||
'name' => 'send me money',
|
||||
],
|
||||
'unit_amount' => 3250, // in cents
|
||||
],
|
||||
'quantity' => 1,
|
||||
],
|
||||
],
|
||||
'mode' => 'payment',
|
||||
'success_url' => route('stripe.success'),
|
||||
'cancel_url' => route('stripe.index'),
|
||||
]);
|
||||
|
||||
return redirect()->away($session->url);
|
||||
}
|
||||
|
||||
public function success(Invoice $invoice)
|
||||
{
|
||||
return view('stripe.success', compact('invoice'));
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,12 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
//
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'stripe/webhook',
|
||||
]);
|
||||
})
|
||||
|
||||
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
})->create();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@
|
|||
"laravel/framework": "^12.49",
|
||||
"laravel/tinker": "^2.11.0",
|
||||
"livewire/flux": "^2.11.1",
|
||||
"livewire/livewire": "^4.1"
|
||||
"livewire/livewire": "^4.1",
|
||||
"stripe/stripe-php": "^19.3",
|
||||
"symfony/clock": "^7.0",
|
||||
"symfony/css-selector": "^7.0",
|
||||
"symfony/event-dispatcher": "^7.0",
|
||||
"symfony/string": "^7.0",
|
||||
"symfony/translation": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "aa16a4acb2dc4a2a7bb2eb1dd04b1907",
|
||||
"content-hash": "a8cbc03ab467c1313282b2c05b78def7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
|
|
@ -3722,22 +3722,82 @@
|
|||
"time": "2025-12-14T04:43:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v8.0.0",
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v19.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/clock.git",
|
||||
"reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f"
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "462272ae7560ee29bb891763fd0967d5a77784e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
|
||||
"reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/462272ae7560ee29bb891763fd0967d5a77784e5",
|
||||
"reference": "462272ae7560ee29bb891763fd0967d5a77784e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"psr/clock": "^1.0"
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.72.0",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Stripe PHP Library",
|
||||
"homepage": "https://stripe.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"payment processing",
|
||||
"stripe"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v19.3.0"
|
||||
},
|
||||
"time": "2026-01-28T21:15:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/clock.git",
|
||||
"reference": "9169f24776edde469914c1e7a1442a50f7a4e110"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110",
|
||||
"reference": "9169f24776edde469914c1e7a1442a50f7a4e110",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"psr/clock": "^1.0",
|
||||
"symfony/polyfill-php83": "^1.28"
|
||||
},
|
||||
"provide": {
|
||||
"psr/clock-implementation": "1.0"
|
||||
|
|
@ -3776,7 +3836,7 @@
|
|||
"time"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/clock/tree/v8.0.0"
|
||||
"source": "https://github.com/symfony/clock/tree/v7.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3796,7 +3856,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-12T15:46:48+00:00"
|
||||
"time": "2025-11-12T15:39:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
|
|
@ -3898,20 +3958,20 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v8.0.0",
|
||||
"version": "v7.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b"
|
||||
"reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/6225bd458c53ecdee056214cb4a2ffaf58bd592b",
|
||||
"reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135",
|
||||
"reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4"
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -3943,7 +4003,7 @@
|
|||
"description": "Converts CSS selectors to XPath expressions",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/v8.0.0"
|
||||
"source": "https://github.com/symfony/css-selector/tree/v7.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3963,7 +4023,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-30T14:17:19+00:00"
|
||||
"time": "2025-10-30T13:39:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
|
|
@ -4116,24 +4176,24 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v8.0.4",
|
||||
"version": "v7.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
|
||||
"reference": "dc2c0eba1af673e736bb851d747d266108aea746"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
|
||||
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746",
|
||||
"reference": "dc2c0eba1af673e736bb851d747d266108aea746",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"php": ">=8.2",
|
||||
"symfony/event-dispatcher-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/security-http": "<7.4",
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/service-contracts": "<2.5"
|
||||
},
|
||||
"provide": {
|
||||
|
|
@ -4142,14 +4202,14 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/config": "^7.4|^8.0",
|
||||
"symfony/dependency-injection": "^7.4|^8.0",
|
||||
"symfony/error-handler": "^7.4|^8.0",
|
||||
"symfony/expression-language": "^7.4|^8.0",
|
||||
"symfony/framework-bundle": "^7.4|^8.0",
|
||||
"symfony/http-foundation": "^7.4|^8.0",
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||
"symfony/error-handler": "^6.4|^7.0|^8.0",
|
||||
"symfony/expression-language": "^6.4|^7.0|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0|^8.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/stopwatch": "^7.4|^8.0"
|
||||
"symfony/stopwatch": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -4177,7 +4237,7 @@
|
|||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4197,7 +4257,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-05T11:45:55+00:00"
|
||||
"time": "2026-01-05T11:45:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
|
|
@ -5785,34 +5845,35 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v8.0.4",
|
||||
"version": "v7.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194"
|
||||
"reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f",
|
||||
"reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-intl-grapheme": "^1.33",
|
||||
"symfony/polyfill-intl-normalizer": "^1.0",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3.0",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-intl-grapheme": "~1.33",
|
||||
"symfony/polyfill-intl-normalizer": "~1.0",
|
||||
"symfony/polyfill-mbstring": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/translation-contracts": "<2.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/emoji": "^7.4|^8.0",
|
||||
"symfony/http-client": "^7.4|^8.0",
|
||||
"symfony/intl": "^7.4|^8.0",
|
||||
"symfony/emoji": "^7.1|^8.0",
|
||||
"symfony/http-client": "^6.4|^7.0|^8.0",
|
||||
"symfony/intl": "^6.4|^7.0|^8.0",
|
||||
"symfony/translation-contracts": "^2.5|^3.0",
|
||||
"symfony/var-exporter": "^7.4|^8.0"
|
||||
"symfony/var-exporter": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -5851,7 +5912,7 @@
|
|||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/string/tree/v7.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5871,31 +5932,38 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-12T12:37:40+00:00"
|
||||
"time": "2026-01-12T10:54:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v8.0.4",
|
||||
"version": "v7.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10"
|
||||
"reference": "bfde13711f53f549e73b06d27b35a55207528877"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/db70c8ce7db74fd2da7b1d268db46b2a8ce32c10",
|
||||
"reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877",
|
||||
"reference": "bfde13711f53f549e73b06d27b35a55207528877",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-mbstring": "^1.0",
|
||||
"symfony/translation-contracts": "^3.6.1"
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/translation-contracts": "^2.5.3|^3.3"
|
||||
},
|
||||
"conflict": {
|
||||
"nikic/php-parser": "<5.0",
|
||||
"symfony/config": "<6.4",
|
||||
"symfony/console": "<6.4",
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/http-client-contracts": "<2.5",
|
||||
"symfony/service-contracts": "<2.5"
|
||||
"symfony/http-kernel": "<6.4",
|
||||
"symfony/service-contracts": "<2.5",
|
||||
"symfony/twig-bundle": "<6.4",
|
||||
"symfony/yaml": "<6.4"
|
||||
},
|
||||
"provide": {
|
||||
"symfony/translation-implementation": "2.3|3.0"
|
||||
|
|
@ -5903,17 +5971,17 @@
|
|||
"require-dev": {
|
||||
"nikic/php-parser": "^5.0",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/config": "^7.4|^8.0",
|
||||
"symfony/console": "^7.4|^8.0",
|
||||
"symfony/dependency-injection": "^7.4|^8.0",
|
||||
"symfony/finder": "^7.4|^8.0",
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/console": "^6.4|^7.0|^8.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||
"symfony/finder": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-client-contracts": "^2.5|^3.0",
|
||||
"symfony/http-kernel": "^7.4|^8.0",
|
||||
"symfony/intl": "^7.4|^8.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0|^8.0",
|
||||
"symfony/intl": "^6.4|^7.0|^8.0",
|
||||
"symfony/polyfill-intl-icu": "^1.21",
|
||||
"symfony/routing": "^7.4|^8.0",
|
||||
"symfony/routing": "^6.4|^7.0|^8.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/yaml": "^7.4|^8.0"
|
||||
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -5944,7 +6012,7 @@
|
|||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/translation/tree/v7.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5964,7 +6032,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-13T13:06:50+00:00"
|
||||
"time": "2026-01-13T10:40:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
|
|
|
|||
|
|
@ -35,4 +35,9 @@ return [
|
|||
],
|
||||
],
|
||||
|
||||
'stripe' => [
|
||||
'secret' => env('STRIPE_SK'),
|
||||
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'sk' => env('STRIPE_SK'),
|
||||
'pk' => env('STRIPE_PK'),
|
||||
];
|
||||
|
|
@ -47,7 +47,7 @@ new class extends Component {
|
|||
<flux:table.row>
|
||||
<flux:table.cell>{{ $payment->payment_date->local()->format('m/d/Y') }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $payment->invoice->invoice_number }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $payment->contact->full_name }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $payment->contact?->full_name }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $payment->status->label() }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $payment->payment_method->label() }}<br>{{ $payment->reference }}</flux:table.cell>
|
||||
<flux:table.cell>{{ formatMoney($payment->fee_amount) }}</flux:table.cell>
|
||||
|
|
|
|||
|
|
@ -137,12 +137,33 @@
|
|||
@endif
|
||||
|
||||
@if($invoice->balance_due != 0)
|
||||
<div class="mb-12 p-4 bg-gray-50 rounded">
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase mb-2">Payment</h2>
|
||||
<p class="text-gray-700">Please make payment to:</p>
|
||||
<p class="text-gray-800 font-medium mt-1">eBandroom</p>
|
||||
<p class="text-gray-600">540 W. Louse Ave.</p>
|
||||
<p class="text-gray-600">Vinita, OK 74301</p>
|
||||
<div class="mb-12 p-6 bg-gray-50 rounded-lg">
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase mb-4">Payment Options</h2>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-6">
|
||||
<div class="flex-1">
|
||||
<h3 class="font-medium text-gray-800 mb-2">Pay Online</h3>
|
||||
<p class="text-gray-600 text-sm mb-4">Pay securely with your credit or debit card.</p>
|
||||
<form action="{{ route('stripe.checkout', $invoice) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"></path>
|
||||
</svg>
|
||||
Pay {{ formatMoney($invoice->balance_due) }} Now
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<h3 class="font-medium text-gray-800 mb-2">Pay by Mail</h3>
|
||||
<p class="text-gray-600 text-sm mb-2">Make check payable to:</p>
|
||||
<p class="text-gray-800 font-medium">eBandroom</p>
|
||||
<p class="text-gray-600">540 W. Louse Ave.</p>
|
||||
<p class="text-gray-600">Vinita, OK 74301</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<form action="/checkout" method="POST">
|
||||
@csrf
|
||||
<button type="submit">Checkout</button>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payment Received - Invoice {{ $invoice->invoice_number }}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen flex items-center justify-center p-8">
|
||||
<div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full text-center">
|
||||
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-2">Payment Received</h1>
|
||||
<p class="text-gray-600 mb-6">Thank you for your payment.</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-6">
|
||||
<p class="text-sm text-gray-500">Invoice</p>
|
||||
<p class="text-lg font-semibold text-gray-800">{{ $invoice->invoice_number }}</p>
|
||||
<p class="text-sm text-gray-500 mt-2">{{ $invoice->client->name }}</p>
|
||||
</div>
|
||||
|
||||
<a href="{{ route('invoices.show', $invoice) }}"
|
||||
class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors">
|
||||
View Invoice
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\CustomerInvoiceController;
|
||||
use App\Http\Controllers\StripeController;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -16,16 +17,14 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||
Route::view('invoices', 'invoices.index')->name('invoices');
|
||||
Route::get('invoices/{invoice}/edit',
|
||||
fn(Invoice $invoice) => view('invoices.edit', compact('invoice')))->name('invoices.edit');
|
||||
Route::get('invoices/{invoice}', CustomerInvoiceController::class)->name('invoices.show');
|
||||
Route::view('payments', 'payments.index')->name('payments');
|
||||
});
|
||||
|
||||
// Route::view('dashboard', 'dashboard')
|
||||
// ->middleware(['auth', 'verified'])
|
||||
// ->name('dashboard');
|
||||
|
||||
// Route::view('clients', 'clients.index')
|
||||
// ->middleware(['auth', 'verified'])
|
||||
// ->name('clients.index');
|
||||
Route::get('invoices/{invoice}', CustomerInvoiceController::class)->name('invoices.show');
|
||||
|
||||
// Stripe
|
||||
Route::get('stripe', [StripeController::class, 'index'])->name('stripe.index');
|
||||
Route::post('/stripe/checkout/{invoice}', [StripeController::class, 'checkout'])->name('stripe.checkout');
|
||||
Route::get('/stripe/success/{invoice}', [StripeController::class, 'success'])->name('stripe.success');
|
||||
Route::post('stripe/webhook', [StripeController::class, 'webhook'])->name('stripe.webhook');
|
||||
require __DIR__.'/settings.php';
|
||||
|
|
|
|||
Loading…
Reference in New Issue