Skip to content

Commit 639b6cf

Browse files
simonhampclaude
andcommitted
Add plugin developer terms and conditions for paid plugin marketplace
Introduces formal developer terms covering 30% platform fee, developer responsibility/liability, listing criteria, and pricing discretion. Requires terms acceptance during developer onboarding before Stripe Connect. Updates general ToS and Privacy Policy for third-party plugins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 345a4c1 commit 639b6cf

11 files changed

Lines changed: 951 additions & 7 deletions

File tree

app/Http/Controllers/DeveloperOnboardingController.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Models\DeveloperAccount;
56
use App\Services\StripeConnectService;
67
use Illuminate\Http\RedirectResponse;
78
use Illuminate\Http\Request;
@@ -29,13 +30,27 @@ public function show(Request $request): View|RedirectResponse
2930

3031
public function start(Request $request): RedirectResponse
3132
{
33+
$request->validate([
34+
'accepted_plugin_terms' => ['required', 'accepted'],
35+
], [
36+
'accepted_plugin_terms.required' => 'You must accept the Plugin Developer Terms and Conditions.',
37+
'accepted_plugin_terms.accepted' => 'You must accept the Plugin Developer Terms and Conditions.',
38+
]);
39+
3240
$user = $request->user();
3341
$developerAccount = $user->developerAccount;
3442

3543
if (! $developerAccount) {
3644
$developerAccount = $this->stripeConnectService->createConnectAccount($user);
3745
}
3846

47+
if (! $developerAccount->hasAcceptedCurrentTerms()) {
48+
$developerAccount->update([
49+
'accepted_plugin_terms_at' => now(),
50+
'plugin_terms_version' => DeveloperAccount::CURRENT_PLUGIN_TERMS_VERSION,
51+
]);
52+
}
53+
3954
try {
4055
$onboardingUrl = $this->stripeConnectService->createOnboardingLink($developerAccount);
4156

app/Models/DeveloperAccount.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class DeveloperAccount extends Model
1212
{
1313
use HasFactory;
1414

15+
public const CURRENT_PLUGIN_TERMS_VERSION = '1.0';
16+
1517
protected $guarded = [];
1618

1719
/**
@@ -53,13 +55,25 @@ public function hasCompletedOnboarding(): bool
5355
return $this->onboarding_completed_at !== null;
5456
}
5557

58+
public function hasAcceptedPluginTerms(): bool
59+
{
60+
return $this->accepted_plugin_terms_at !== null;
61+
}
62+
63+
public function hasAcceptedCurrentTerms(): bool
64+
{
65+
return $this->hasAcceptedPluginTerms()
66+
&& $this->plugin_terms_version === self::CURRENT_PLUGIN_TERMS_VERSION;
67+
}
68+
5669
protected function casts(): array
5770
{
5871
return [
5972
'stripe_connect_status' => StripeConnectStatus::class,
6073
'payouts_enabled' => 'boolean',
6174
'charges_enabled' => 'boolean',
6275
'onboarding_completed_at' => 'datetime',
76+
'accepted_plugin_terms_at' => 'datetime',
6377
];
6478
}
6579
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Database\Factories;
4+
5+
use App\Enums\StripeConnectStatus;
6+
use App\Models\DeveloperAccount;
7+
use App\Models\User;
8+
use Illuminate\Database\Eloquent\Factories\Factory;
9+
10+
/**
11+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\DeveloperAccount>
12+
*/
13+
class DeveloperAccountFactory extends Factory
14+
{
15+
/**
16+
* Define the model's default state.
17+
*
18+
* @return array<string, mixed>
19+
*/
20+
public function definition(): array
21+
{
22+
return [
23+
'user_id' => User::factory(),
24+
'stripe_connect_account_id' => 'acct_'.fake()->unique()->regexify('[a-zA-Z0-9]{16}'),
25+
'stripe_connect_status' => StripeConnectStatus::Pending,
26+
'payouts_enabled' => false,
27+
'charges_enabled' => false,
28+
];
29+
}
30+
31+
public function onboarded(): static
32+
{
33+
return $this->state(fn (array $attributes) => [
34+
'stripe_connect_status' => StripeConnectStatus::Active,
35+
'payouts_enabled' => true,
36+
'charges_enabled' => true,
37+
'onboarding_completed_at' => now(),
38+
]);
39+
}
40+
41+
public function withAcceptedTerms(?string $version = null): static
42+
{
43+
return $this->state(fn (array $attributes) => [
44+
'accepted_plugin_terms_at' => now(),
45+
'plugin_terms_version' => $version ?? DeveloperAccount::CURRENT_PLUGIN_TERMS_VERSION,
46+
]);
47+
}
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('developer_accounts', function (Blueprint $table) {
15+
$table->timestamp('accepted_plugin_terms_at')->nullable()->after('onboarding_completed_at');
16+
$table->string('plugin_terms_version')->nullable()->after('accepted_plugin_terms_at');
17+
});
18+
}
19+
20+
/**
21+
* Reverse the migrations.
22+
*/
23+
public function down(): void
24+
{
25+
Schema::table('developer_accounts', function (Blueprint $table) {
26+
$table->dropColumn(['accepted_plugin_terms_at', 'plugin_terms_version']);
27+
});
28+
}
29+
};

resources/views/components/footer.blade.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,14 @@ class="inline-block px-px py-1.5 transition duration-300 will-change-transform h
281281
Terms of Service
282282
</a>
283283
</li>
284+
<li>
285+
<a
286+
href="{{ route('developer-terms') }}"
287+
class="inline-block px-px py-1.5 transition duration-300 will-change-transform hover:translate-x-1 hover:text-gray-700 dark:hover:text-gray-300"
288+
>
289+
Developer Terms
290+
</a>
291+
</li>
284292
</ul>
285293
</section>
286294

resources/views/customer/developer/onboarding.blade.php

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,86 @@
115115
</div>
116116
@endif
117117

118-
{{-- CTA Button --}}
118+
{{-- Developer Terms Agreement --}}
119119
<div class="mt-8">
120-
<form action="{{ route('customer.developer.onboarding.start') }}" method="POST">
120+
<form action="{{ route('customer.developer.onboarding.start') }}" method="POST" x-data="{ termsAccepted: {{ ($developerAccount?->hasAcceptedCurrentTerms()) ? 'true' : 'false' }} }">
121121
@csrf
122+
123+
@if ($developerAccount?->hasAcceptedCurrentTerms())
124+
{{-- Already accepted terms, just include hidden field --}}
125+
<input type="hidden" name="accepted_plugin_terms" value="1" />
126+
127+
<div class="mb-6 rounded-lg border border-emerald-200 bg-emerald-50 p-4 dark:border-emerald-800 dark:bg-emerald-900/20">
128+
<div class="flex items-center gap-3">
129+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5 shrink-0 text-emerald-500">
130+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
131+
</svg>
132+
<p class="text-sm text-emerald-800 dark:text-emerald-200">
133+
You accepted the <a href="{{ route('developer-terms') }}" class="font-medium underline" target="_blank">Plugin Developer Terms and Conditions</a> on {{ $developerAccount->accepted_plugin_terms_at->format('F j, Y') }}.
134+
</p>
135+
</div>
136+
</div>
137+
@else
138+
{{-- Terms acceptance required --}}
139+
<div class="mb-6 rounded-lg border border-gray-200 bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-700/50">
140+
<h3 class="font-semibold text-gray-900 dark:text-white">Plugin Developer Terms and Conditions</h3>
141+
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
142+
Before you can sell plugins on the Marketplace, you must agree to the following key terms:
143+
</p>
144+
145+
<ul class="mt-4 space-y-3 text-sm text-gray-600 dark:text-gray-400">
146+
<li class="flex items-start gap-3">
147+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mt-0.5 size-5 shrink-0 text-indigo-500">
148+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
149+
</svg>
150+
<span><strong class="text-gray-900 dark:text-white">30% Platform Fee</strong> &mdash; NativePHP retains 30% of each sale to cover payment processing, hosting, and platform maintenance</span>
151+
</li>
152+
<li class="flex items-start gap-3">
153+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mt-0.5 size-5 shrink-0 text-indigo-500">
154+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
155+
</svg>
156+
<span><strong class="text-gray-900 dark:text-white">Your Responsibility</strong> &mdash; You are solely responsible for your plugin's quality, performance, and customer support</span>
157+
</li>
158+
<li class="flex items-start gap-3">
159+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mt-0.5 size-5 shrink-0 text-indigo-500">
160+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75" />
161+
</svg>
162+
<span><strong class="text-gray-900 dark:text-white">Listing Criteria</strong> &mdash; NativePHP sets and may change listing standards at any time, and may remove plugins at its discretion</span>
163+
</li>
164+
<li class="flex items-start gap-3">
165+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mt-0.5 size-5 shrink-0 text-indigo-500">
166+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" />
167+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
168+
</svg>
169+
<span><strong class="text-gray-900 dark:text-white">Pricing & Discounts</strong> &mdash; NativePHP sets plugin prices and may offer discounts at its discretion</span>
170+
</li>
171+
</ul>
172+
173+
<div class="mt-6 border-t border-gray-200 pt-4 dark:border-gray-600">
174+
<label class="flex items-start gap-3 cursor-pointer">
175+
<input
176+
type="checkbox"
177+
name="accepted_plugin_terms"
178+
value="1"
179+
x-model="termsAccepted"
180+
class="mt-0.5 size-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-700"
181+
/>
182+
<span class="text-sm text-gray-700 dark:text-gray-300">
183+
I have read and agree to the
184+
<a href="{{ route('developer-terms') }}" class="font-medium text-indigo-600 underline hover:text-indigo-500 dark:text-indigo-400" target="_blank">Plugin Developer Terms and Conditions</a>
185+
</span>
186+
</label>
187+
@error('accepted_plugin_terms')
188+
<p class="mt-2 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
189+
@enderror
190+
</div>
191+
</div>
192+
@endif
193+
122194
<button
123195
type="submit"
124-
class="w-full rounded-lg bg-indigo-600 px-6 py-3 text-center text-base font-semibold text-white shadow-sm transition hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
196+
class="w-full rounded-lg bg-indigo-600 px-6 py-3 text-center text-base font-semibold text-white shadow-sm transition hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
197+
:disabled="!termsAccepted"
125198
>
126199
@if ($hasExistingAccount)
127200
Continue Onboarding

0 commit comments

Comments
 (0)