Stripe Payment with Flutter and Appwrite
Tags: flutter, appwrite, stripe, programming
Published: 2022-06-22 01:49:22

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](https://appwrite.io) 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](https://docs.flutter.dev/get-started/install)
- Access to Appwrite project or permission to create one - [https://appwrite.io/docs/installation](https://appwrite.io/docs/installation)
- Stripe account, you can create one for free - [https://dashboard.stripe.com/register](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.

![Stripe Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j06f7ljzygt4j5v26wf.png)
 

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`.

![Create Appwrite Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5x9wtmzphdlnc7he034x.png)

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](https://appwrite.io/docs/command-line#installation). Once the CLI is installed, you need to log in to authorize CLI and gain access to your Appwrite instance.

```bash
# 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](https://github.com/lohanidamodar/flappwrite-stripe). 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.

```bash
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.

```bash
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.

![App screenshots](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzjx5a9dbk6fx60ttm3h.png)
 
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.

```dart
import 'package:flutter_stripe/flutter_stripe.dart';
```

Inside the main function, initialize the Stripe SDK

```dart
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.

```dart
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`.

```dart
_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

```bash
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]([https://appwrite.io/docs/installation/environment](https://appwrite.io/docs/environment-variables)-variables#functions) 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.

```dart
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.

```dart
  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.

```dart
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.

```dart
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.

```dart
	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.

```dart
	 // 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.

```dart
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.

```dart
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.

```dart
	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.

```dart
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](https://github.com/lohanidamodar/flappwrite-stripe/tree/final). To learn more about Appwrite, you can visit our documentation or join us on our discord.

- [Appwrite Documentation](https://appwrite.io/docs)
- [Appwrite Discord](https://appwrite.io/discord)
- [Appwrite Github](https://github.com/appwrite)