Skip to content
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

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
634b095
oAuth for Stripe
chiragchhatrala Jan 8, 2025
182cdd6
Stripe Payment Frontend - WIP
chiragchhatrala Jan 9, 2025
3b33496
Payment block backend validation and new package for stripe
chiragchhatrala Jan 10, 2025
d5cee46
change stripe scopes
chiragchhatrala Jan 10, 2025
019153e
update PaymentBlockConfigurationRule
chiragchhatrala Jan 10, 2025
84df0f8
Set loader on provider modal
chiragchhatrala Jan 10, 2025
808c4da
stripe oauth
chiragchhatrala Jan 10, 2025
46b2a2f
PaymentFieldOptions as seprate component
chiragchhatrala Jan 10, 2025
62c2ed4
validate Stripe account
chiragchhatrala Jan 10, 2025
040cec2
Payment intent
chiragchhatrala Jan 13, 2025
728231d
Merge remote-tracking branch 'origin/main' into 173a6-payments
chiragchhatrala Jan 15, 2025
c76fe9e
Merge branch 'main' into 173a6-payments
chiragchhatrala Jan 16, 2025
f40ae36
Stripe Payment as composable
chiragchhatrala Jan 17, 2025
f14f932
confirmCardPayment working
chiragchhatrala Jan 20, 2025
d421bf7
Set payment errors on form.errors
chiragchhatrala Jan 21, 2025
9c825b8
Validate card other fields
chiragchhatrala Jan 21, 2025
7f8a7cd
Store payment id to database and on submission add link for view paym…
chiragchhatrala Jan 22, 2025
f67e487
FormPaymentController no need auth middleware
chiragchhatrala Jan 22, 2025
452ab15
paymentinput error display on field
chiragchhatrala Jan 22, 2025
d0fd417
Make payment block as input
chiragchhatrala Jan 22, 2025
5936d60
Refactor payment processing and error handling
chiragchhatrala Jan 23, 2025
641b5b7
Multi lang & direction support on payment
chiragchhatrala Jan 24, 2025
ccf0d7c
reset card on change direction or local
chiragchhatrala Jan 24, 2025
0213224
use connected account for loadstripe
chiragchhatrala Jan 28, 2025
b18b4e8
validate OAuthProvider before delete it
chiragchhatrala Jan 28, 2025
f7c8f02
payment improvements
chiragchhatrala Jan 28, 2025
2936343
display payment by stripe
chiragchhatrala Jan 29, 2025
c851879
Merge branch 'main' into 173a6-payments
chiragchhatrala Jan 29, 2025
208629b
use stripe_currencies.json
chiragchhatrala Jan 29, 2025
088cf45
Form Payment testcase
chiragchhatrala Jan 29, 2025
00a3acc
Enhance form auto-save behavior for payment forms
chiragchhatrala Jan 30, 2025
d341b54
Restrict payment block in self-hosted environments
chiragchhatrala Jan 30, 2025
c5ed0c4
validate form before process payment
chiragchhatrala Jan 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@ GOOGLE_AUTH_REDIRECT_URL=http://localhost:3000/oauth/google/callback
GOOGLE_FONTS_API_KEY=

ZAPIER_ENABLED=false

STRIPE_CLIENT_ID=
STRIPE_CLIENT_SECRET=
STRIPE_REDIRECT_URI=http://localhost:3000/settings/connections/callback/stripe
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function index()
'google' => [
'fonts' => !empty(config('services.google.fonts_api_key')),
'auth' => !empty(config('services.google.client_id')) && !empty(config('services.google.client_secret')),
],
]
],
'integrations' => [
'zapier' => config('services.zapier.enabled'),
Expand Down
118 changes: 118 additions & 0 deletions api/app/Http/Controllers/Forms/FormPaymentController.php
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']);
}
Comment on lines +80 to +103
Copy link
Contributor

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 become 1099 which is correct, but $10.999 becomes 1099 instead of 1100. Decide whether to round or floor intentionally.

- 'amount' => (int) ($paymentBlock['amount'] * 100),
+ 'amount' => (int) round($paymentBlock['amount'] * 100),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'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']);
}
'amount' => (int) round($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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} 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.']);
}
} 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) {
Log::error('Failed to create payment intent', [
'form_id' => $form->id,
'error' => $e->getMessage()
]);
return $this->error(['message' => 'Failed to initialize payment.']);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function handleRedirect(OAuthProviderService $service)
[
'access_token' => $driverUser->token,
'refresh_token' => $driverUser->refreshToken,
'name' => $driverUser->getName(),
'name' => ($driverUser->getName()) ? $driverUser->getName() : $driverUser->getNickname(),
'email' => $driverUser->getEmail(),
'scopes' => $driverUser->approvedScopes
]
Expand Down
3 changes: 2 additions & 1 deletion api/app/Http/Requests/UserFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Http\Requests\Workspace\CustomDomainRequest;
use App\Models\Forms\Form;
use App\Rules\FormPropertyLogicRule;
use App\Rules\PaymentBlockConfigurationRule;
use Illuminate\Validation\Rule;

/**
Expand Down Expand Up @@ -70,7 +71,7 @@ public function rules()
'properties' => 'required|array',
'properties.*.id' => 'required',
'properties.*.name' => 'required',
'properties.*.type' => 'required',
'properties.*.type' => ['required', new PaymentBlockConfigurationRule($this->properties)],
'properties.*.placeholder' => 'sometimes|nullable',
'properties.*.prefill' => 'sometimes|nullable',
'properties.*.help' => 'sometimes|nullable',
Expand Down
13 changes: 13 additions & 0 deletions api/app/Http/Resources/FormResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function toArray($request)
'max_number_of_submissions_reached' => $this->max_number_of_submissions_reached,
'form_pending_submission_key' => $this->form_pending_submission_key,
'max_file_size' => $this->max_file_size / 1000000,
'auto_save' => $this->getAutoSave(),
]);
}

Expand Down Expand Up @@ -112,4 +113,16 @@ private function getCleanigns()
{
return $this->extra?->cleanings ?? $this->cleanings;
}

private function hasPaymentBlock()
{
return array_filter($this->properties, function ($property) {
return $property['type'] === 'payment';
});
}
Comment on lines +117 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve the hasPaymentBlock() method implementation.

The current implementation has several issues:

  1. The method name implies a boolean return, but it returns an array
  2. Missing null check for $this->properties
  3. Could be more efficient

Consider this improved implementation:

     private function hasPaymentBlock()
     {
-        return array_filter($this->properties, function ($property) {
-            return $property['type'] === 'payment';
-        });
+        if (!isset($this->properties)) {
+            return false;
+        }
+        return (bool) array_search('payment', array_column($this->properties, 'type'));
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private function hasPaymentBlock()
{
return array_filter($this->properties, function ($property) {
return $property['type'] === 'payment';
});
}
private function hasPaymentBlock()
{
if (!isset($this->properties)) {
return false;
}
return (bool) array_search('payment', array_column($this->properties, 'type'));
}


private function getAutoSave()
{
return $this->hasPaymentBlock() ? true : $this->auto_save;
}
Comment on lines +124 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Fix hasPaymentBlock to return boolean instead of array

The current implementation of hasPaymentBlock() returns a filtered array instead of a boolean, which could lead to unexpected behavior. While it works in the current context due to PHP's type coercion, it's not semantically correct.

Please update the implementation:

private function hasPaymentBlock()
{
-    return array_filter($this->properties, function ($property) {
+    return collect($this->properties)->contains(function ($property) {
        return $property['type'] === 'payment';
    });
}
🔗 Analysis chain

Document the auto-save requirement for payment forms.

The implementation enforces auto-save for forms with payment blocks, but this important business rule isn't documented. Additionally, the current implementation might not work as intended due to hasPaymentBlock() returning an array.

Please:

  1. Add a PHPDoc comment explaining why payment forms require auto-save
  2. Consider this improved implementation:
     /**
+     * Get the auto_save value for the form.
+     * Forms with payment blocks must have auto_save enabled to ensure payment data persistence.
+     *
+     * @return bool
+     */
     private function getAutoSave()
     {
-        return $this->hasPaymentBlock() ? true : $this->auto_save;
+        return $this->hasPaymentBlock() || $this->auto_save;
     }
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find hasPaymentBlock implementation
echo "=== hasPaymentBlock implementation ==="
ast-grep --pattern 'function hasPaymentBlock() {
  $$$
}'

echo -e "\n=== FormResource test files ==="
fd -e php test FormResource

echo -e "\n=== Other usages of hasPaymentBlock ==="
rg "hasPaymentBlock" -A 2

Length of output: 1910


Script:

#!/bin/bash
# Get complete implementation of hasPaymentBlock method
echo "=== Complete hasPaymentBlock implementation ==="
rg -U "private function hasPaymentBlock\(\).*?\}" --multiline api/app/Http/Resources/FormResource.php

Length of output: 208


Script:

#!/bin/bash
echo "=== FormResource.php content ==="
cat api/app/Http/Resources/FormResource.php

Length of output: 4245

}
78 changes: 78 additions & 0 deletions api/app/Integrations/OAuth/Drivers/OAuthStripeDriver.php
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Log::info('Initiating Stripe Connect flow', [
'user_id' => $user->id
]);
Log::info('Initiating Stripe Connect flow');


return $this->provider
->scopes($this->scopes ?? [])
->stateless()
->redirectUrl($this->redirectUrl ?? config('services.stripe.redirect'))
->with($params)
->redirect()
->getTargetUrl();
Comment on lines +38 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return $this->provider
->scopes($this->scopes ?? [])
->stateless()
->redirectUrl($this->redirectUrl ?? config('services.stripe.redirect'))
->with($params)
->redirect()
->getTargetUrl();
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.');
}

}

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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;
     }

Committable suggestion skipped: line range outside the PR's diff.


public function setScopes(array $scopes): OAuthDriver
{
$this->scopes = $scopes;
return $this;
}

public function fullScopes(): OAuthDriver
{
return $this->setScopes([
'read_write',
]);
}
}
7 changes: 5 additions & 2 deletions api/app/Integrations/OAuth/OAuthProviderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

use App\Integrations\OAuth\Drivers\Contracts\OAuthDriver;
use App\Integrations\OAuth\Drivers\OAuthGoogleDriver;
use App\Integrations\OAuth\Drivers\OAuthStripeDriver;

enum OAuthProviderService: string
{
case Google = 'google';
case Stripe = 'stripe';

public function getDriver(): OAuthDriver
{
return match($this) {
self::Google => new OAuthGoogleDriver()
return match ($this) {
self::Google => new OAuthGoogleDriver(),
self::Stripe => new OAuthStripeDriver()
};
}
}
15 changes: 15 additions & 0 deletions api/app/Policies/OAuthProviderPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Policies;

use App\Integrations\OAuth\OAuthProviderService;
use App\Models\Integration\FormIntegration;
use App\Models\OAuthProvider;
use App\Models\User;
Expand Down Expand Up @@ -62,6 +63,20 @@ public function delete(User $user, OAuthProvider $provider)
if ($integrations->count() > 0) {
return $this->denyWithStatus(400, 'This connection cannot be removed because there is already an integration using it.');
}

if ($provider->provider->value === OAuthProviderService::Stripe->value) {
$formsUsingStripe = $user->forms()
->get()
->filter(function ($form) use ($provider) {
return collect($form->properties)
->some(fn ($prop) => ($prop['stripe_account_id'] ?? null) === $provider->id);
})
->isNotEmpty();
if ($formsUsingStripe) {
return $this->denyWithStatus(400, 'This Stripe connection cannot be removed because it is being used in a form payment field.');
}
}

return $provider->user()->is($user);
}

Expand Down
5 changes: 5 additions & 0 deletions api/app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 services.php, but the required environment variables are not documented in any .env.example file. This could cause deployment issues.

Please add these required variables to api/.env.example:

  • STRIPE_CLIENT_ID
  • STRIPE_CLIENT_SECRET
  • STRIPE_REDIRECT_URI
🔗 Analysis chain

LGTM! 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 executed

The 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

}

/**
Expand Down
Loading
Loading