-
Notifications
You must be signed in to change notification settings - Fork 329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Stripe Payment #679
base: main
Are you sure you want to change the base?
[WIP] Stripe Payment #679
Changes from 26 commits
634b095
182cdd6
3b33496
d5cee46
019153e
84df0f8
808c4da
46b2a2f
62c2ed4
040cec2
728231d
c76fe9e
f40ae36
f14f932
d421bf7
9c825b8
7f8a7cd
f67e487
452ab15
d0fd417
5936d60
641b5b7
ccf0d7c
0213224
b18b4e8
f7c8f02
2936343
c851879
208629b
088cf45
00a3acc
d341b54
c5ed0c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,118 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
namespace App\Http\Controllers\Forms; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use App\Http\Controllers\Controller; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use App\Models\OAuthProvider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Http\Request; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Support\Facades\Log; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use Stripe\Stripe; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
use Stripe\PaymentIntent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class FormPaymentController extends Controller | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public function getAccount(Request $request) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$form = $request->form; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Get payment block (only one allowed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$paymentBlock = collect($form->properties)->first(fn ($prop) => $prop['type'] === 'payment'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!$paymentBlock) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::warning('Form without payment block', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Form does not have a payment block.']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Get provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$provider = OAuthProvider::find($paymentBlock['stripe_account_id']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ($provider === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::error('Failed to find Stripe account', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'stripe_account_id' => $paymentBlock['stripe_account_id'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Failed to find Stripe account']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->success(['stripeAccount' => $provider->provider_user_id]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public function createIntent(Request $request) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$form = $request->form; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Verify form exists and is accessible | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ($form->workspace === null || $form->visibility !== 'public') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::warning('Attempt to create payment for invalid form', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Form not found.'], 404); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Get payment block (only one allowed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$paymentBlock = collect($form->properties)->first(fn ($prop) => $prop['type'] === 'payment'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!$paymentBlock) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::warning('Attempt to create payment for form without payment block', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Form does not have a payment block.']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Get provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$provider = OAuthProvider::find($paymentBlock['stripe_account_id']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ($provider === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::error('Failed to find Stripe account', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'stripe_account_id' => $paymentBlock['stripe_account_id'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Failed to find Stripe account']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::info('Creating payment intent', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'amount' => $paymentBlock['amount'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'currency' => $paymentBlock['currency'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Stripe::setApiKey(config('cashier.secret')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
$intent = PaymentIntent::create([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'description' => 'Form - ' . $form->title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'amount' => (int) ($paymentBlock['amount'] * 100), // Stripe requires amount in cents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'currency' => strtolower($paymentBlock['currency']), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'payment_method_types' => ['card'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'metadata' => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'workspace_id' => $form->workspace_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_name' => $form->title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
], [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'stripe_account' => $provider->provider_user_id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::info('Payment intent created', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'intent' => $intent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ($intent->id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->success([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'intent' => ['id' => $intent->id, 'secret' => $intent->client_secret] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Failed to create payment intent']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (\Stripe\Exception\CardException $e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::warning('Failed to create payment intent', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'message' => $e->getMessage() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => $e->getMessage()]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (\Exception $e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Log::error('Failed to create payment intent', [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'form_id' => $form->id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'error' => $e->getMessage() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return $this->error(['message' => 'Failed to initialize payment.']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+104
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve error handling granularity. The catch blocks could be more specific to handle different Stripe exceptions differently. - } catch (\Stripe\Exception\CardException $e) {
+ } catch (\Stripe\Exception\CardException $e) {
Log::warning('Failed to create payment intent', [
'form_id' => $form->id,
'message' => $e->getMessage()
]);
return $this->error(['message' => $e->getMessage()]);
+ } catch (\Stripe\Exception\InvalidRequestException $e) {
+ Log::error('Invalid Stripe request', [
+ 'form_id' => $form->id,
+ 'error' => $e->getMessage()
+ ]);
+ return $this->error(['message' => 'Invalid payment configuration.']);
+ } catch (\Stripe\Exception\AuthenticationException $e) {
+ Log::critical('Stripe authentication failed', [
+ 'form_id' => $form->id,
+ 'error' => $e->getMessage()
+ ]);
+ return $this->error(['message' => 'Payment service configuration error.']);
} catch (\Exception $e) { 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,78 @@ | ||||||||||||||||||||||||||||||||||||||||||||
<?php | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
namespace App\Integrations\OAuth\Drivers; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
use App\Integrations\OAuth\Drivers\Contracts\OAuthDriver; | ||||||||||||||||||||||||||||||||||||||||||||
use Laravel\Socialite\Contracts\User; | ||||||||||||||||||||||||||||||||||||||||||||
use Laravel\Socialite\Facades\Socialite; | ||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Support\Facades\Auth; | ||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Support\Facades\Log; | ||||||||||||||||||||||||||||||||||||||||||||
use SocialiteProviders\Stripe\Provider as StripeProvider; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
class OAuthStripeDriver implements OAuthDriver | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
private ?string $redirectUrl = null; | ||||||||||||||||||||||||||||||||||||||||||||
private ?array $scopes = []; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
protected StripeProvider $provider; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function __construct() | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
$this->provider = Socialite::driver('stripe'); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function getRedirectUrl(): string | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
$user = Auth::user(); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
$params = [ | ||||||||||||||||||||||||||||||||||||||||||||
'stripe_user[email]' => $user->email, | ||||||||||||||||||||||||||||||||||||||||||||
'stripe_user[url]' => config('app.url'), | ||||||||||||||||||||||||||||||||||||||||||||
'stripe_user[business_name]' => $user->name, | ||||||||||||||||||||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
Log::info('Initiating Stripe Connect flow', [ | ||||||||||||||||||||||||||||||||||||||||||||
'user_id' => $user->id | ||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid logging sensitive user information. The log message includes user ID which could be considered PII. Consider using a hash or removing it if not essential for debugging. - Log::info('Initiating Stripe Connect flow', [
- 'user_id' => $user->id
- ]);
+ Log::info('Initiating Stripe Connect flow'); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return $this->provider | ||||||||||||||||||||||||||||||||||||||||||||
->scopes($this->scopes ?? []) | ||||||||||||||||||||||||||||||||||||||||||||
->stateless() | ||||||||||||||||||||||||||||||||||||||||||||
->redirectUrl($this->redirectUrl ?? config('services.stripe.redirect')) | ||||||||||||||||||||||||||||||||||||||||||||
->with($params) | ||||||||||||||||||||||||||||||||||||||||||||
->redirect() | ||||||||||||||||||||||||||||||||||||||||||||
->getTargetUrl(); | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+38
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for Stripe API calls. The redirect URL generation doesn't handle potential exceptions from the Stripe API. This could result in unhandled exceptions reaching the user. + try {
return $this->provider
->scopes($this->scopes ?? [])
->stateless()
->redirectUrl($this->redirectUrl ?? config('services.stripe.redirect'))
->with($params)
->redirect()
->getTargetUrl();
+ } catch (\Exception $e) {
+ Log::error('Failed to generate Stripe redirect URL', [
+ 'error' => $e->getMessage()
+ ]);
+ throw new \RuntimeException('Failed to connect to Stripe. Please try again later.');
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function getUser(): User | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
return $this->provider | ||||||||||||||||||||||||||||||||||||||||||||
->stateless() | ||||||||||||||||||||||||||||||||||||||||||||
->redirectUrl($this->redirectUrl ?? config('services.stripe.redirect')) | ||||||||||||||||||||||||||||||||||||||||||||
->user(); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function canCreateUser(): bool | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function setRedirectUrl(string $url): OAuthDriver | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
$this->redirectUrl = $url; | ||||||||||||||||||||||||||||||||||||||||||||
return $this; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+60
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate redirect URL to prevent open redirect vulnerability. The setRedirectUrl method accepts any URL without validation, which could lead to open redirect vulnerabilities. + private function isValidRedirectUrl(string $url): bool
+ {
+ $allowedDomains = [parse_url(config('app.url'), PHP_URL_HOST)];
+ $urlHost = parse_url($url, PHP_URL_HOST);
+ return in_array($urlHost, $allowedDomains);
+ }
public function setRedirectUrl(string $url): OAuthDriver
{
+ if (!$this->isValidRedirectUrl($url)) {
+ throw new \InvalidArgumentException('Invalid redirect URL');
+ }
$this->redirectUrl = $url;
return $this;
}
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function setScopes(array $scopes): OAuthDriver | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
$this->scopes = $scopes; | ||||||||||||||||||||||||||||||||||||||||||||
return $this; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
public function fullScopes(): OAuthDriver | ||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||
return $this->setScopes([ | ||||||||||||||||||||||||||||||||||||||||||||
'read_write', | ||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
use Illuminate\Support\Facades\Storage; | ||
use Illuminate\Support\Facades\URL; | ||
use Illuminate\Support\Facades\Validator; | ||
use Illuminate\Support\Facades\Event; | ||
use Illuminate\Support\ServiceProvider; | ||
use Laravel\Cashier\Cashier; | ||
use Laravel\Dusk\DuskServiceProvider; | ||
|
@@ -40,6 +41,10 @@ public function boot() | |
} | ||
|
||
Validator::includeUnvalidatedArrayKeys(); | ||
|
||
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { | ||
$event->extendSocialite('stripe', \SocialiteProviders\Stripe\Provider::class); | ||
}); | ||
Comment on lines
+44
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Missing Stripe OAuth Environment Variables Documentation The Stripe OAuth configuration is properly structured in Please add these required variables to
🔗 Analysis chainLGTM! Verify Stripe OAuth configuration. The Stripe OAuth provider is correctly integrated using Laravel Socialite's event listener pattern. Run this script to verify the OAuth configuration: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify Stripe OAuth configuration
# Test: Check if required OAuth config exists in services.php
echo "Checking services.php for Stripe OAuth configuration..."
rg "stripe.*=>.*\[" config/services.php -A 5
# Test: Check if required environment variables are documented
echo "Checking .env.example for Stripe OAuth variables..."
rg "STRIPE.*(CLIENT_ID|CLIENT_SECRET|REDIRECT)" .env.example
Length of output: 456 Script: #!/bin/bash
# Search for config files and env examples
echo "Searching for config files..."
fd -t f "services" -E "node_modules"
echo -e "\nSearching for env files..."
fd -t f ".env" -E "node_modules"
echo -e "\nSearching for Stripe-related configuration in any file..."
rg -l "STRIPE" -t php -t env
Length of output: 475 Script: #!/bin/bash
echo "Checking services.php content..."
cat api/config/services.php
echo -e "\nChecking environment files for Stripe configuration..."
echo "=== client/.env.example ==="
cat client/.env.example
echo -e "\n=== client/.env.docker ==="
cat client/.env.docker
echo -e "\n=== scripts/setup-env.sh ==="
cat scripts/setup-env.sh
Length of output: 7910 |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,73 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
<?php | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
namespace App\Rules; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
use App\Models\OAuthProvider; | ||||||||||||||||||||||||||||||||||||||||||||||
use Closure; | ||||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Support\Facades\Log; | ||||||||||||||||||||||||||||||||||||||||||||||
use Illuminate\Contracts\Validation\ValidationRule; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
class PaymentBlockConfigurationRule implements ValidationRule | ||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||
protected array $properties; | ||||||||||||||||||||||||||||||||||||||||||||||
protected array $field; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
public function __construct(array $properties) | ||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||
$this->properties = $properties; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
public function validate(string $attribute, mixed $value, Closure $fail): void | ||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||
// Set the field | ||||||||||||||||||||||||||||||||||||||||||||||
$fieldIndex = explode('.', $attribute)[1]; | ||||||||||||||||||||||||||||||||||||||||||||||
$this->field = $this->properties[$fieldIndex]; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
if ($this->field['type'] !== 'payment') { | ||||||||||||||||||||||||||||||||||||||||||||||
return; // If not a payment block, validation passes | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+20
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add array access validation to prevent undefined index errors. The field index extraction assumes the attribute string contains a dot and the extracted index exists in the properties array. This could lead to undefined index errors. public function validate(string $attribute, mixed $value, Closure $fail): void
{
// Set the field
- $fieldIndex = explode('.', $attribute)[1];
- $this->field = $this->properties[$fieldIndex];
+ $parts = explode('.', $attribute);
+ if (count($parts) < 2 || !isset($this->properties[$parts[1]])) {
+ $fail('Invalid field configuration');
+ return;
+ }
+ $this->field = $this->properties[$parts[1]]; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Only one payment block allowed | ||||||||||||||||||||||||||||||||||||||||||||||
$paymentBlocks = collect($this->properties) | ||||||||||||||||||||||||||||||||||||||||||||||
->filter(fn ($prop) => $prop['type'] === 'payment') | ||||||||||||||||||||||||||||||||||||||||||||||
->count(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
if ($paymentBlocks > 1) { | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Only one payment block allowed'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Amount validation | ||||||||||||||||||||||||||||||||||||||||||||||
if (!isset($this->field['amount']) || !is_numeric($this->field['amount']) || $this->field['amount'] < 0.5) { | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Amount must be a number greater than 0.5'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Currency validation | ||||||||||||||||||||||||||||||||||||||||||||||
if (!isset($this->field['currency']) || !in_array(strtoupper($this->field['currency']), array_keys(config('services.stripe.currencies')))) { | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Currency must be a valid currency'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Stripe account validation | ||||||||||||||||||||||||||||||||||||||||||||||
if (!isset($this->field['stripe_account_id']) || empty($this->field['stripe_account_id'])) { | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Stripe account is required'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||
$provider = OAuthProvider::find($this->field['stripe_account_id']); | ||||||||||||||||||||||||||||||||||||||||||||||
if ($provider === null) { | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Failed to validate Stripe account'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} catch (\Exception $e) { | ||||||||||||||||||||||||||||||||||||||||||||||
Log::error('Failed to validate Stripe account', [ | ||||||||||||||||||||||||||||||||||||||||||||||
'error' => $e->getMessage(), | ||||||||||||||||||||||||||||||||||||||||||||||
'account_id' => $this->field['stripe_account_id'] | ||||||||||||||||||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||||||||||||||||||
$fail('Failed to validate Stripe account'); | ||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use round or int to handle fractional amounts.
Casting using
(int)
floors the amount. If a user enters$10.99
, it will become1099
which is correct, but$10.999
becomes1099
instead of1100
. Decide whether to round or floor intentionally.📝 Committable suggestion