Stripe is one of the most popular payment providers to accept payments online. In this tutorial, we will integrate Stripe in our Appwrite Store mobile application built with Flutter and receive payments using Stripe. We will be writing the Appwrite cloud function using Dart to process the payments using Stripe.
If you don't know what Appwrite is, Appwrite is a self-hosted backend-as-a-service platform that provides developers with all the core APIs required to build any application.
Prerequisites
To continue with this tutorial and take full advantage of it, you need the following things:
- Flutter setup and running - https://docs.flutter.dev/get-started/install
- Access to Appwrite project or permission to create one - https://appwrite.io/docs/installation
- Stripe account, you can create one for free - https://dashboard.stripe.com/register
💰 Setup Stripe
Let's start by properly setting up our Stripe account to ensure we have all secrets we need in the future. We will be using test mode for this example, but the same steps could be followed in production mode.
You start by visiting the Stripe website and signing up. Once in the dashboard, you can switch to the Developers page and enter the API keys tab. In there, copy the publishable key as well as a secret key. Click the Reveal key button to copy the secret key.
Please make sure you turn on the test mode, so we use test keys and not the real keys for testing purposes.
🔧 Setup Appwrite
Before we move on, we need to set up an Appwrite project. After following installation instructions and signing up, you can create a project with a custom project ID flutter-stripe
.
Once the project is created, hop into the Settings page and copy the endpoint, we will need this next to set up our Appwrite CLI.
🧑💻 CLI Installation
Let's now install the Appwrite CLI, which we will use in the next section to set up the database required for our application.
Install the CLI using one of the methods from our CLI installation guide. Once the CLI is installed, you need to log in to authorize CLI and gain access to your Appwrite instance.
# Initialize the client
appwrite client --endpoint https://<API endpoint>/v1
# Login, this command is interactive; login with your console email
# and password
appwrite login
The endpoint is the URL you get from the project settings page.
📜 Source Code
The source code required for this tutorial is available in the GitHub repository. Clone the repository using git or download the zip directly from GitHub and extract. The project comes with an appwrite.json
file. Next, let‘s set up collections. The collection is already defined in the appwrite.json
, and you need to deploy it. Navigate to the folder containing appwrite.json
from your terminal and run the following command to deploy the collection.
appwrite deploy collection
This command will create a new collection in your project, and you can check it via the Appwrite console if you’d like. Next, let's add some products to our products collection.
Let's set up some base products using Appwrite CLI. Still, in the same folder from your terminal, use the following command to add new products.
appwrite database createDocument --collectionId 'products' --documentId 'unique()' --data '{"name":"Appwrite Backpack","price":20.0, "imageUrl": "https://cdn.shopify.com/s/files/1/0532/9397/3658/products/all-over-print-backpack-white-front-60abc9d286d19_1100x.jpg"}' --read 'role:all'
Use the same command to add a few more products with a different name, price, and image of your choice, so that we have something to buy in our shop application. Now we have our Appwrite instance ready and some products in our collection for us to buy. Next, let's set up our mobile application built with Flutter that allows us to log in and buy stuff.
🛠 Flutter Project Set up
In the project you downloaded from GitHub, you can find another folder named flutter_stripe
that contains our Flutter shopping application. Let's set it up and run. First, let's set up the configurations. Open the project in your favorite Flutter IDE. Copy flutter_stripe/lib/utils/config.example.dart
as flutter_stripe/lib/utils/config.dart
and inside it, replace [ENDPOINT]
and [PROJECT_ID]
with your endpoint and project ID that you got from Appwrite project settings page. Replace [STRIPE_PUBLISHABLE_KEY]
with the Stripe's publishable key that you got from the Stripe dashboard starting with sk_test_
.
Now run the project in your emulator or your device, and you should see the following screen. We have already implemented registration, login, products display, and shopping cart so we can focus on the stripe integration.
You can create a new account from the registration page and log in to see the products page. There you can add products to your cart and view your cart. Tapping on the Checkout button in the cart will navigate you to an empty page. Let's implement a checkout page to allow payment with Stripe. First, add the [flutter_stripe](https://pub.dev/packages/flutter_stripe)
package as a dependency in pubspec.yaml
and run flutter pub get
to download the dependencies.
Next, let’s initialize the stripe SDK. Open flappwrite_stripe/lib/main.dart
and add the following imports.
import 'package:flutter_stripe/flutter_stripe.dart';
Inside the main function, initialize the Stripe SDK
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Stripe.publishableKey = config.publishableKey;
Stripe.merchantIdentifier = 'merchent.flappwrite.test';
Stripe.urlScheme = 'appwrite-callback-${config.projectId}';
await Stripe.instance.applySettings();
client.setEndpoint(config.endpoint).setProject(config.projectId);
runApp(....);
}
Now that Stripe is initialized let's set up the checkout page. Open flappwrite_stripe/lib/screens/checkout.dart
and add CardField
and an ElevatedButton
to confirm the payment as the following.
import 'dart:convert';
import 'package:flappwrite_stripe/providers/cart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:appwrite_auth_kit/appwrite_auth_kit.dart';
class CheckoutScreen extends ConsumerWidget {
CheckoutScreen({Key? key}) : super(key: key);
final _cardEditController = CardEditController();
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('Checkout'),
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: <Widget>[
if (context.authNotifier.user?.email != null &&
context.authNotifier.user?.email != '')
Text(
context.authNotifier.user!.email,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10.0),
CardField(
controller: _cardEditController,
),
ListTile(
title: const Text("Total Amount"),
trailing: Text(
'\$${ref.watch(cartTotalProvider).toStringAsFixed(2)}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
onPressed: () => __confirmPressed(context, ref),
child: const Text("Confirm"),
),
],
),
);
}
}
When the confirm button is pressed, we need to charge the user for the total amount of products in their shopping cart. So let's begin.
Let’s define a new method in the widget _confirmPressed
.
_confirmPressed(BuildContext context, WidgetRef ref) async {
// Woops we can't do this yet
}
The first step here is to get a payment intent by sending Stripe's amount, currency, and customer details, which can only be done from the server-side using Stripe's secret key. So before we proceed further with payment in App, we will write an Appwrite cloud function that will accept the amount to create a payment intent and return the client secret.
☁ Create Payment Cloud Function
We will be using the Appwrite CLI to initialize our function. from the project folder (the folder that contains appwrite.json
), spin up a terminal and run
appwrite init function
It's an interactive command that will ask you for a name and runtime. Give it the name createPaymentIntent
and choose Dart 2.16
as the runtime. If Dart 2.16
is not available in the list, it's probably not enabled in your Appwrite server. You can do that by updating the _APP_FUNCTIONS_RUNTIMES
in the .env
file in your Appwrite installation folder. Look at the environment docs to learn more about Appwrite’s environment variables.
Open pubspec.yaml
and add dart_appwrite
and stripedart
under dependencies. Next, we validate the environment variables required by the function to work properly.
final client = appwrite.Client();
final account = appwrite.Account(client);
Future<void> start(final request, final response) async {
if (request.env['STRIPE_SECRET_KEY'] == null ||
request.env['STRIPE_PUBLISHABLE_KEY'] == null) {
return response.send('Stripe payment keys are missing', 500);
}
if (request.env['APPWRITE_ENDPOINT'] == null) {
return response.send('Appwrite endpoint is missing', 500);
}
}
Once the environment variables are validated, we can initialize the Appwrite SDK.
final jwt = request.env['APPWRITE_FUNCTION_JWT'];
client
.setEndpoint(request.env['APPWRITE_ENDPOINT'])
.setProject(request.env['APPWRITE_FUNCTION_PROJECT_ID'])
.setJWT(jwt);
Next, let's get the details of the user executing the function from Appwrite. Let's write a function that gets the user.
Future<User> getUser() async {
return await account.get();
}
Let's update our start function to get the user details and check if stripe customer id already exists for the user in their preferences.
Future<void> start(final request, final response) async {
...
client
.setEndpoint(request.env['APPWRITE_ENDPOINT'])
.setProject(request.env['APPWRITE_FUNCTION_PROJECT_ID'])
.setJWT(jwt);
final user = await getUser();
final prefs = user.prefs.data;
String? customerId = prefs['stripeCustomerId'];
}
Next, if the customerId
doesn't exist, we will create a new account; we will set up Stripe SDK and create a customer.
var stripe = Stripe(request.env['STRIPE_SECRET_KEY']);
// create account for customer if it doesn't already exist
dynamic customer;
if (customerId == null) {
customer =
await stripe.core.customers.create(params: {"email": user.email});
customerId = customer!['id'];
if (customerId == null) {
throw (customer.toString());
} else {
prefs['stripeCustomerId'] = customerId;
await account.updatePrefs(prefs: prefs);
}
}
Notice we also save customer ID in the user's preference for next-time access.
Now that we have the customer ID, we can create a payment intent from the amount and the currency provided during function execution.
// data provided to the function during execution
final data = jsonDecode(request.env['APPWRITE_FUNCTION_DATA']);
final paymentIntent = await stripe.core.paymentIntents!.create(params: {
"amount": data['amount'],
"currency": data['currency'],
"customer": customerId,
});
response.json({
"paymentIntent": paymentIntent,
"client_secret": paymentIntent!['client_secret'],
});
Our function is now ready, and the complete function looks like this.
import 'dart:convert';
import 'package:dart_appwrite/dart_appwrite.dart' as appwrite;
import 'package:dart_appwrite/models.dart';
import 'package:stripedart/stripedart.dart';
final client = appwrite.Client();
final account = appwrite.Account(client);
Future<User> getUser() async {
return await account.get();
}
Future<void> start(final request, final response) async {
if (request.env['STRIPE_SECRET_KEY'] == null ||
request.env['STRIPE_PUBLISHABLE_KEY'] == null) {
return response.send('Stripe payment keys are missing', 500);
}
if (request.env['APPWRITE_ENDPOINT'] == null) {
return response.send('Appwrite endpoint is missing', 500);
}
// final userId = request.env['APPWRITE_FUNCTION_USER_ID'];
final jwt = request.env['APPWRITE_FUNCTION_JWT'];
client
.setEndpoint(request.env['APPWRITE_ENDPOINT'])
.setProject(request.env['APPWRITE_FUNCTION_PROJECT_ID'])
.setJWT(jwt);
final user = await getUser();
final prefs = user.prefs.data;
String? customerId = prefs['stripeCustomerId'];
var stripe = Stripe(request.env['STRIPE_SECRET_KEY']);
// create account for customer if it doesn't already exist
dynamic customer;
if (customerId == null) {
customer =
await stripe.core.customers.create(params: {"email": user.email});
customerId = customer!['id'];
if (customerId == null) {
throw (customer.toString());
} else {
prefs['stripeCustomerId'] = customerId;
await account.updatePrefs(prefs: prefs);
}
}
final data = jsonDecode(request.env['APPWRITE_FUNCTION_DATA']);
final paymentIntent = await stripe.core.paymentIntents!.create(params: {
"amount": data['amount'],
"currency": data['currency'],
"customer": customerId,
});
response.json({
"paymentIntent": paymentIntent,
"client_secret": paymentIntent!['client_secret'],
});
}
Let's deploy the function using the Appwrite CLI. Still, in the project folder containing appwrite.json
, use the following command to deploy the function.
appwrite deploy function
This interactive command allows you to choose the functions to deploy. We can use space to select that function and press return to deploy as we have only one. If you now go to your Appwrite console and tap on the Functions from the sidebar, you should see your function in the list. Tap on the Settings button for the function and in the overview, make sure your function is built and ready for execution. If the build has failed, check the logs for the build to figure out what happened.
Finally, we need to set up the function's environment variable. Open your function's settings page and scroll below to find the Variables section. Please tap on the add variable button, add three variables as STRIPE_SECRET_KEY
, STRIPE_PUBLISHABLE_KEY
, and the APPWRITE_ENDPOINT
and give them proper values obtained from Stripe and your Appwrite's endpoint, respectively.
Now let's get back to our Flutter app to set up the rest of the payment flow. Open flappwrite_stripe/lib/screens/checkout.dart
and add new method in the widget _fetchClientSecret
with the following details.
Future<String> _fetchClientSecret(BuildContext context, double total) async {
final functions = Functions(context.authNotifier.client);
final execution = await functions.createExecution(
functionId: '[FUNCTION_ID]',
data: jsonEncode({
'currency': 'usd',
'amount': (total * 100).toInt(),
}),
xasync: false);
if (execution.stdout.isNotEmpty) {
final data = jsonDecode(execution.stdout);
return data['client_secret'];
}
return '';
}
You can get the FUNCTION_ID
from your appwrite.json
or the function's settings page in the Appwrite console. Finally, we will complete the _confirmPressed
function like the following.
if (!_cardEditController.complete) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Card details not entered completely")));
return;
}
final clientSecret = await fetchClientSecret(
context, ref.watch(cartTotalProvider));
if(clientSecret.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Unable to fetch client secret")));
return;
}
// you should get proper billing details from the user
final billingDetails = BillingDetails(
email: context.authNotifier.user!.email,
phone: '+48888000888',
address: const Address(
city: 'Kathmandu',
country: 'NP',
line1: 'Chabahil, Kathmandu',
line2: '',
state: 'Bagmati',
postalCode: '55890',
),
);
try {
final paymentIntent = await Stripe.instance.confirmPayment(
clientSecret,
PaymentMethodParams.card(
billingDetails: billingDetails,
setupFutureUsage: PaymentIntentsFutureUsage.OffSession,
),
);
if (paymentIntent.status == PaymentIntentsStatus.Succeeded) {
ref.read(cartProvider.notifier).emptyCart();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Success!: The payment was confirmed successfully!"),
),
);
Navigator.pop(context);
}
} on StripeException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(e.error.localizedMessage ?? e.toString())));
}
Our Application is now ready. Run the application, add some products to the shopping cart, and tap checkout. On the checkout page, add a test card details 4242 4242 4242 4242
, and any valid validity date from the future, and any three-digit number is CCV. Finally, when you tap submit, you should be able to confirm the payment and see that you have received payment in the Stripe dashboard. That’s it; you can now build an application that can accept payment from your users.
🔗 Conclusion
I hope you enjoyed this tutorial. You can find the complete code of this project in the GitHub repository. To learn more about Appwrite, you can visit our documentation or join us on our discord.
Top comments (3)
Hey Damodar, love the post and all the work you do for the Appwrite and Flutter communities. One quick question:
How would you go about implementing infinite scrolling pagination for the 'products' collection as defined within this tutorial?
I've been spending a fair bit of time on it, and I fear I'm not making much progress as of yet.
Thanks!
Hey, here we have an official tutorial for pagination, let me know if this helps
appwrite.io/docs/pagination
Hi Damodar, it seems your article is missing some steps for deploying the environment
Here what I've done so far for trying to deploy the environment :
I've downloaded the project
I've test my endpoint and login
appwrite client --endpoint localhost:9000/v1
appwrite login
I need to init the project to link to the Appwrite project I've created : my appwrite.json is cleared and updated with only projectId and projectName
appwrite init project
I didn't create a DB but I could run appwrite deploy collection and select the Products collection
I run _appwrite database createDocument --collectionId 'products' --documentId 'unique()' --data '{"name":"Appwrite Backpack","price":20.0, "imageUrl": "cdn.shopify.com/s/files/1/0532/939...' --read 'role:all _ **and I got **Error Database not found