diff --git a/app/lib/main/plugin_initializations.dart b/app/lib/main/plugin_initializations.dart index dad93cb6e..496ef18ca 100644 --- a/app/lib/main/plugin_initializations.dart +++ b/app/lib/main/plugin_initializations.dart @@ -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 { diff --git a/app/lib/main/sharezone_bloc_providers.dart b/app/lib/main/sharezone_bloc_providers.dart index dcce693ea..40ade3e8b 100644 --- a/app/lib/main/sharezone_bloc_providers.dart +++ b/app/lib/main/sharezone_bloc_providers.dart @@ -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'; @@ -335,8 +337,15 @@ class _SharezoneBlocProvidersState extends State { ), 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( diff --git a/app/lib/sharezone_plus/page/sharezone_plus_page_controller.dart b/app/lib/sharezone_plus/page/sharezone_plus_page_controller.dart index efd09b447..e71332920 100644 --- a/app/lib/sharezone_plus/page/sharezone_plus_page_controller.dart +++ b/app/lib/sharezone_plus/page/sharezone_plus_page_controller.dart @@ -8,12 +8,13 @@ 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/stripe_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. @@ -28,22 +29,25 @@ class SharezonePlusPageController extends ChangeNotifier { // ignore: unused_field late SubscriptionService _subscriptionService; - late StripeSharezonePlusService _stripeService; + late StripeCheckoutSession _stripeCheckoutSession; + late UserId _userId; StreamSubscription? _hasPlusSubscription; SharezonePlusPageController({ required RevenueCatPurchaseService purchaseService, required SubscriptionService subscriptionService, - required StripeSharezonePlusService stripeService, + required StripeCheckoutSession stripeCheckoutSession, + required UserId userId, }) { _purchaseService = purchaseService; _subscriptionService = subscriptionService; - _stripeService = stripeService; + _stripeCheckoutSession = stripeCheckoutSession; + _userId = userId; // Fake loading for development purposes. Future.delayed(const Duration(seconds: 1)).then((value) { - hasPlus = true; + hasPlus = false; price = fallbackPlusPrice; notifyListeners(); }); @@ -66,20 +70,26 @@ class SharezonePlusPageController extends ChangeNotifier { String? price; Future buySubscription() async { - hasPlus = true; - if (PlatformCheck.isWeb) { await _buyOnWeb(); } + hasPlus = true; notifyListeners(); } Future _buyOnWeb() async { - final checkoutUrl = await _stripeService.createCheckoutUrl(); + final checkoutUrl = await _stripeCheckoutSession.create( + userId: '$_userId', + ); + await launchUrl( Uri.parse(checkoutUrl), - // TODO: Insert ticket for async link opening + // 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", ); } diff --git a/app/lib/sharezone_plus/subscription_service/stripe_sharezone_plus_service.dart b/app/lib/sharezone_plus/subscription_service/stripe_sharezone_plus_service.dart deleted file mode 100644 index a8e009983..000000000 --- a/app/lib/sharezone_plus/subscription_service/stripe_sharezone_plus_service.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:cloud_functions/cloud_functions.dart'; -import 'package:common_domain_models/common_domain_models.dart'; -import 'package:dio/dio.dart'; - -// TODO: extra package? maybe later in a PR -class StripeSharezonePlusService { - final FirebaseFunctions functions; - final Dio dio; - - StripeSharezonePlusService({ - required this.functions, - required this.dio, - }); - - Future createCheckoutUrl({ - UserId? userId, - }) async { - const functionUrl = "TODO"; - final res = await dio.post>(functionUrl, data: { - 'userId': userId, - }); - - // TODO: What's the best way to handle the errors? - - final String? url = (res.data ?? {})['url']; - if (url == null) { - throw Exception('Could not processed'); - } - - return url; - } - - /// Returns an authenticated URL to the Stripe portal. - /// - /// This method requires an authenticated user. For unauthenticated users open - /// the default Stripe portal URL. - Future getStripePortal({ - required UserId userId, - }) async { - // TODO: use 'createPortalLink' cloud function - } -} diff --git a/app/pubspec.lock b/app/pubspec.lock index 6c5944b07..cb1038469 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1170,7 +1170,7 @@ packages: source: hosted version: "2.0.0" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" @@ -2043,6 +2043,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + stripe_checkout_session: + dependency: "direct main" + description: + path: "../lib/sharezone_plus/stripe_checkout_session" + relative: true + source: path + version: "0.0.1" sync_http: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index eeae73300..077b08607 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -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 @@ -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 diff --git a/lib/sharezone_plus/stripe_checkout_session/analysis_options.yaml b/lib/sharezone_plus/stripe_checkout_session/analysis_options.yaml new file mode 100644 index 000000000..1e445c14e --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/analysis_options.yaml @@ -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 diff --git a/lib/sharezone_plus/stripe_checkout_session/lib/src/stripe_checkout_session.dart b/lib/sharezone_plus/stripe_checkout_session/lib/src/stripe_checkout_session.dart new file mode 100644 index 000000000..0bb63c472 --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/lib/src/stripe_checkout_session.dart @@ -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 create({ + String? userId, + String? buysFor, + }) async { + assert( + userId != null || buysFor != null, + 'Either userId or buysFor must be passed', + ); + + return retry( + () 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, + ); + } +} diff --git a/lib/sharezone_plus/stripe_checkout_session/lib/stripe_checkout_session.dart b/lib/sharezone_plus/stripe_checkout_session/lib/stripe_checkout_session.dart new file mode 100644 index 000000000..ce35838b5 --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/lib/stripe_checkout_session.dart @@ -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'; diff --git a/lib/sharezone_plus/stripe_checkout_session/pubspec.lock b/lib/sharezone_plus/stripe_checkout_session/pubspec.lock new file mode 100644 index 000000000..5a4f0539f --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/pubspec.lock @@ -0,0 +1,556 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "36a321c3d2cbe01cbcb3540a87b8843846e0206df3e691fa7b23e19e78de6d49" + url: "https://pub.dev" + source: hosted + version: "65.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: dfe03b90ec022450e22513b5e5ca1f01c0c01de9c3fba2f7fd233cb57a6b9a07 + url: "https://pub.dev" + source: hosted + version: "6.3.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + url: "https://pub.dev" + source: hosted + version: "7.2.11" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" + url: "https://pub.dev" + source: hosted + version: "8.8.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f + url: "https://pub.dev" + source: hosted + version: "4.8.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 + url: "https://pub.dev" + source: hosted + version: "1.7.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter_lints: + dependency: transitive + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http: + dependency: "direct main" + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "4b693867cee1853c9d1d7ecc1871f27f39b2ef2c13c0d8d8507dfe5bebd8aaf1" + url: "https://pub.dev" + source: hosted + version: "5.4.3" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + retry: + dependency: "direct main" + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + sharezone_lints: + dependency: "direct dev" + description: + path: "../../sharezone_lints" + relative: true + source: path + version: "1.0.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f + url: "https://pub.dev" + source: hosted + version: "1.24.9" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a + url: "https://pub.dev" + source: hosted + version: "0.5.9" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.1.0 <4.0.0" diff --git a/lib/sharezone_plus/stripe_checkout_session/pubspec.yaml b/lib/sharezone_plus/stripe_checkout_session/pubspec.yaml new file mode 100644 index 000000000..5dc5857ce --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/pubspec.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2022 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 + +name: stripe_checkout_session +description: A package to create a Stripe checkout session. +version: 0.0.1 +publish_to: none + +environment: + sdk: ">=3.1.0 <4.0.0" + +dependencies: + http: ^0.13.5 + retry: ^3.1.2 + +dev_dependencies: + build_runner: ^2.1.4 + mockito: ^5.3.2 + test: ^1.23.1 + sharezone_lints: + path: ../../sharezone_lints diff --git a/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.dart b/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.dart new file mode 100644 index 000000000..6a136bb10 --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.dart @@ -0,0 +1,67 @@ +// 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:mockito/annotations.dart'; +import 'package:stripe_checkout_session/stripe_checkout_session.dart'; +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:http/http.dart' as http; + +import 'stripe_checkout_session_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec(), MockSpec()]) +void main() { + group('StripeCheckoutSession', () { + late MockClient client; + late StripeCheckoutSession service; + const testUrl = 'https://test.checkout.session'; + const functionsUrl = 'https://create-checkout.run.app'; + + setUp(() { + client = MockClient(); + service = StripeCheckoutSession( + createCheckoutSessionFunctionUrl: functionsUrl, + client: client, + ); + }); + + test('returns checkout url when passing user ID', () async { + const userId = '123'; + when( + client.post( + Uri.parse(functionsUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: '{"userId":"$userId"}', + ), + ).thenAnswer((_) async => http.Response('{"url": "$testUrl"}', 200)); + + final result = await service.create(userId: userId); + + expect(result, equals(testUrl)); + }); + + test('returns checkout url when passing buysFor ID', () async { + const buysFor = '456'; + when( + client.post( + Uri.parse(functionsUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: '{"buysFor":"$buysFor"}', + ), + ).thenAnswer((_) async => http.Response('{"url": "$testUrl"}', 200)); + + final result = await service.create(buysFor: buysFor); + + expect(result, equals(testUrl)); + }); + }); +} diff --git a/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.mocks.dart b/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.mocks.dart new file mode 100644 index 000000000..a1fc105f1 --- /dev/null +++ b/lib/sharezone_plus/stripe_checkout_session/test/stripe_checkout_session_test.mocks.dart @@ -0,0 +1,420 @@ +// Mocks generated by Mockito 5.4.3 from annotations +// in stripe_checkout_session/test/stripe_checkout_session_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:convert' as _i4; +import 'dart:typed_data' as _i6; + +import 'package:http/http.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeResponse_0 extends _i1.SmartFake implements _i2.Response { + _FakeResponse_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStreamedResponse_1 extends _i1.SmartFake + implements _i2.StreamedResponse { + _FakeStreamedResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i2.Client { + @override + _i3.Future<_i2.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future read( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), + returnValueForMissingStub: + _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future); + + @override + _i3.Future<_i6.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + returnValueForMissingStub: + _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i3.Future<_i6.Uint8List>); + + @override + _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #send, + [request], + ), + returnValue: + _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1( + this, + Invocation.method( + #send, + [request], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i3.Future<_i2.StreamedResponse>); + + @override + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [Response]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockResponse extends _i1.Mock implements _i2.Response { + @override + _i6.Uint8List get bodyBytes => (super.noSuchMethod( + Invocation.getter(#bodyBytes), + returnValue: _i6.Uint8List(0), + returnValueForMissingStub: _i6.Uint8List(0), + ) as _i6.Uint8List); + + @override + String get body => (super.noSuchMethod( + Invocation.getter(#body), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#body), + ), + returnValueForMissingStub: _i5.dummyValue( + this, + Invocation.getter(#body), + ), + ) as String); + + @override + int get statusCode => (super.noSuchMethod( + Invocation.getter(#statusCode), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + Map get headers => (super.noSuchMethod( + Invocation.getter(#headers), + returnValue: {}, + returnValueForMissingStub: {}, + ) as Map); + + @override + bool get isRedirect => (super.noSuchMethod( + Invocation.getter(#isRedirect), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + bool get persistentConnection => (super.noSuchMethod( + Invocation.getter(#persistentConnection), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); +}