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

Connect Stripe with the frontend #1169

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions app/lib/main/plugin_initializations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class PluginInitializations {
'revenuecat_api_android_key': 'goog_EyqDtrZhkswSqMAcfqawHGAqZnX',
'firebase_messaging_vapid_key':
'BNT7Da6B6wi-mUBcGrt-9HxeIJZsPTsPpmR8cae_LhgJPcSFb5j0T8o-r-oFV1xAtXVXfRPIZlgUJR3tx8mLbbA',
'stripe_checkout_session_function_url':
'https://europe-west1-sharezone-c2bd8.cloudfunctions.net/createStripeCheckoutSession'
});

try {
Expand Down
9 changes: 9 additions & 0 deletions app/lib/main/sharezone_bloc_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ import 'package:sharezone/util/notification_token_adder.dart';
import 'package:sharezone/util/platform_information_manager/flutter_platform_information_retreiver.dart';
import 'package:sharezone/util/platform_information_manager/get_platform_information_retreiver.dart';
import 'package:sharezone_common/references.dart';
import 'package:stripe_checkout_session/stripe_checkout_session.dart';
import 'package:user/user.dart';
import 'package:http/http.dart' as http;

import '../homework/parent/src/homework_page_bloc.dart' as old;
import '../notifications/is_firebase_messaging_supported.dart';
Expand Down Expand Up @@ -335,8 +337,15 @@ class _SharezoneBlocProvidersState extends State<SharezoneBlocProviders> {
),
ChangeNotifierProvider(
create: (context) => SharezonePlusPageController(
userId: UserId(api.uID),
purchaseService: RevenueCatPurchaseService(),
subscriptionService: subscriptionService,
stripeCheckoutSession: StripeCheckoutSession(
createCheckoutSessionFunctionUrl: widget
.blocDependencies.remoteConfiguration
.getString('stripe_checkout_session_function_url'),
client: http.Client(),
),
),
),
ChangeNotifierProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@

import 'dart:async';

import 'package:common_domain_models/common_domain_models.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sharezone/sharezone_plus/subscription_service/revenue_cat_sharezone_plus_service.dart';
import 'package:sharezone/sharezone_plus/subscription_service/subscription_service.dart';
import 'package:sharezone_utils/platform.dart';
import 'package:stripe_checkout_session/stripe_checkout_session.dart';
import 'package:url_launcher/url_launcher.dart';

/// A fallback price if the price cannot be fetched from the backend.
///
Expand All @@ -24,18 +29,25 @@ class SharezonePlusPageController extends ChangeNotifier {
// ignore: unused_field
late SubscriptionService _subscriptionService;

late StripeCheckoutSession _stripeCheckoutSession;
late UserId _userId;

StreamSubscription<bool>? _hasPlusSubscription;

SharezonePlusPageController({
required RevenueCatPurchaseService purchaseService,
required SubscriptionService subscriptionService,
required StripeCheckoutSession stripeCheckoutSession,
required UserId userId,
}) {
_purchaseService = purchaseService;
_subscriptionService = subscriptionService;
_stripeCheckoutSession = stripeCheckoutSession;
_userId = userId;

// Fake loading for development purposes.
Future.delayed(const Duration(seconds: 1)).then((value) {
hasPlus = true;
hasPlus = false;
price = fallbackPlusPrice;
notifyListeners();
});
Expand All @@ -58,10 +70,30 @@ class SharezonePlusPageController extends ChangeNotifier {
String? price;

Future<void> buySubscription() async {
if (PlatformCheck.isWeb) {
await _buyOnWeb();
}

hasPlus = true;
notifyListeners();
}

Future<void> _buyOnWeb() async {
final checkoutUrl = await _stripeCheckoutSession.create(
userId: '$_userId',
);

await launchUrl(
Uri.parse(checkoutUrl),
// Since the request for creating the checkout session is asynchronous, we
// can't open the checkout in a new tab due to the browser security
// policy.
//
// See https://github.com/flutter/flutter/issues/78524.
webOnlyWindowName: "_self",
);
Comment on lines +82 to +94
Copy link
Member Author

Choose a reason for hiding this comment

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

Very basis implementation since error handling, analytics, etc. for Sharezone Plus will be done later.

}

Future<void> cancelSubscription() async {
hasPlus = false;
notifyListeners();
Expand Down
9 changes: 8 additions & 1 deletion app/pubspec.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ dependencies:
path: ../lib/common_domain_models
crash_analytics:
path: ../lib/crash_analytics
stripe_checkout_session:
path: ../lib/sharezone_plus/stripe_checkout_session
cupertino_icons: ^1.0.3
date:
path: ../lib/date
Expand Down Expand Up @@ -108,6 +110,7 @@ dependencies:
path: ../lib/hausaufgabenheft_logik
holidays:
path: ../lib/holidays
http: ^0.13.5
# Used so that we can select a minute interval (only either XX:00 or XX:30)
# for the homework reminder time picker.
interval_time_picker: ^2.0.0+5
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023 Sharezone UG (haftungsbeschränkt)
# Licensed under the EUPL-1.2-or-later.
#
# You may obtain a copy of the Licence at:
# https://joinup.ec.europa.eu/software/page/eupl
#
# SPDX-License-Identifier: EUPL-1.2

include: package:sharezone_lints/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2023 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:retry/retry.dart';

class StripeCheckoutSession {
final String createCheckoutSessionFunctionUrl;
final http.Client client;

StripeCheckoutSession({
required this.client,
required this.createCheckoutSessionFunctionUrl,
});

/// Creates a new Stripe checkout session.
///
/// Either [userId] or [buysFor] must be passed to identify the user. [userId]
/// is the ID of the user who is buying the subscription. [buysFor] is the ID
/// of the user for whom the subscription is bought and is used for the
/// "Parents buy for their children" feature.
///
/// Returns the URL of the Stripe checkout session. The user must be
/// redirected to this URL to complete the payment.
Future<String> create({
String? userId,
String? buysFor,
}) async {
assert(
userId != null || buysFor != null,
'Either userId or buysFor must be passed',
);

return retry<String>(
() async {
final response = await client.post(
Uri.parse(createCheckoutSessionFunctionUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode(
{
if (userId != null) 'userId': userId,
if (buysFor != null) 'buysFor': buysFor,
},
),
);

if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
final String? url = responseData['url'];

if (url == null) {
throw Exception('Could not found url in response');
}

return url;
} else {
throw Exception(
'Request failed with status: ${response.statusCode} (${response.body})',
);
}
},
maxAttempts: 3,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2023 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

library stripe_checkout_session;

export 'src/stripe_checkout_session.dart';
Loading
Loading