class TeamEndpoint extends Endpoint {
Future<Team?> createTeam(
Session session,
String name,
SubscriptionPlan plan,
) async {
await RateLimiter.check(session, 'team-create');
if (!session.auth.authenticated) {
throw UnauthorizedException();
}
var team = Team(
name: name,
ownerId: session.auth.userId!,
plan: plan,
stripeCustomerId: await StripeService.createCustomer(session),
createdAt: DateTime.now(),
);
return await Team.db.insertRow(session, team);
}
}
class CreateTeamScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final client = ref.watch(clientProvider);
final teamAsync = ref.watch(createTeamProvider);
return Scaffold(
body: Form(
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Team Name',
),
validator: Validators.required,
),
PlanSelector(
onChanged: (plan) => selectedPlan = plan,
),
ElevatedButton(
onPressed: teamAsync.isLoading ? null : () async {
final team = await client.team.createTeam(
nameController.text,
selectedPlan,
);
if (team != null) {
context.go('/team/${team.id}');
}
},
child: teamAsync.isLoading
? CircularProgressIndicator()
: Text('Create Team'),
),
],
),
),
);
}
}
class AuthService {
static Future<void> sendMagicLink(String email) async {
await client.auth.sendMagicLink(
email: email,
redirectUrl: 'serverpodkit://auth/verify',
);
}
static Future<UserInfo?> signInWithGoogle() async {
final serverAuthCode = await GoogleSignIn().signIn();
return await client.auth.google.authenticate(serverAuthCode);
}
static Future<TOTPSetup> enableTwoFactor() async {
final setup = await client.auth.mfa.setupTOTP();
return setup;
}
static Future<bool> authenticateWithBiometrics() async {
final localAuth = LocalAuthentication();
final authenticated = await localAuth.authenticate(
localizedReason: 'Authenticate to access your account',
options: AuthenticationOptions(biometricOnly: true),
);
return authenticated;
}
}
class BillingEndpoint extends Endpoint {
Future<Subscription> createSubscription(
Session session,
String teamId,
String priceId,
) async {
final team = await Team.db.findById(session, teamId);
await PermissionService.requireTeamOwner(session, team);
final subscription = await stripe.subscriptions.create(
SubscriptionCreateParams(
customer: team.stripeCustomerId,
items: [SubscriptionItemOption(price: priceId)],
paymentBehavior: 'default_incomplete',
metadata: {
'teamId': teamId,
'userId': session.auth.userId,
},
),
);
await Subscription.db.insertRow(session, subscription);
return subscription;
}
Future<void> handleWebhook(
Session session,
String payload,
String signature,
) async {
final event = stripe.webhooks.constructEvent(
payload,
signature,
webhookSecret,
);
switch (event.type) {
case 'customer.subscription.updated':
await handleSubscriptionUpdate(session, event.data);
break;
case 'invoice.payment_succeeded':
await handlePaymentSuccess(session, event.data);
break;
}
}
}