Building a Mobile Application with Subscription Billing Using Flutter and the Rapyd API
Published: 2024-01-09 11:00:00

**By: Dedan Ndungu**

The [Rapyd Collect API](https://www.rapyd.net/products/payments/api/) helps facilitate efficient and seamless payment collection processes. It allows businesses to accept customer payments through various payment methods, offering a range of capabilities that enhance the payment collection experience. You can provide instant checkouts for your customers from around the globe, allowing you to access new markets in a highly secure way. Furthermore, businesses handling recurring payments and subscription-based services can easily automate invoice processing and subscription management via the Rapyd Collect API. Rapyd is an API first company to accept, send and hold funds globally.

In this article, you will learn how to leverage the Rapyd API to collect subscription-based payments from a Flutter application. You will learn how to subscribe a user to your service via a hosted checkout page and the Rapyd API.

## What Is the Rapyd Collect API? 

Receiving payments from customers across the globe can be a headache as you need to handle different currencies, regulations, and payment methods. It's particularly challenging if you're planning to expand into different geographic markets. The Rapyd Collect API integrates a wide variety of payment networks and technologies from around the world into a single platform. The API provides a single place to manage all your payment integration needs while guaranteeing seamless customer payment collection.

The Rapyd API also ensures your customers have a simple and intuitive checkout facilitated by Rapyd's hosted checkout experience. This also means you don't need to spend time building this experience manually. You can generate a payment request, and in return, Rapyd returns a checkout page. The page will collect all the payment details from the customer and present all available payment methods to them. Once the customer is done, Rapyd informs you of the request status via the endpoints you provide. The hosted checkout experience can also collect subscription billing payments worldwide and supports various payment methods, such as debit and credit cards.

## Implementing Payments Using the Rapyd API in a Flutter Application

Following this tutorial, you'll integrate a payment solution via the Rapyd Collect API in a Flutter application. You'll build a Flutter app that provides memes as a service. Users can access a few memes and subscribe to a plan to view unlimited memes.

### Prerequisites

Before proceeding, you need to have the following:

- A [Rapyd](https://dashboard.rapyd.net/sign-up) account
- The [Flutter](https://docs.flutter.dev/get-started/install) software development kit (SDK) installed

### The Subscription Service

To offer a [subscription service](https://docs.rapyd.net/en/subscriptions.html), you must first set it up on Rapyd. Before continuing to set up the subscription, please download the [Postman collection](https://bit.ly/3pi2RtU), install it, and open the **Pre-request Script**.

![Pre-request Script](https://i.imgur.com/gJdMKqv.png)

If you experience any errors when evaluating the Pre-request Script, replace its contents with the code below, which generates a [request signature](https://docs.rapyd.net/en/request-signatures.html) needed to authorize your requests to the Rapyd API :

```js
var  timestamp  = (Math.floor(new  Date().getTime() /  1000) -  10).toString();
pm.globals.set("rapyd_request_timestamp", timestamp);
var  signature_salt  =  CryptoJS.lib.WordArray.random(12).toString();
pm.globals.set("rapyd_signature_salt", signature_salt);

var  body  =  '';
if (JSON.stringify(request.data) !==  '{}'  &&  request.data !==  ''  &&  typeof  request.data !=='object' ){
body  =  JSON.stringify(JSON.parse(request.data));
}
var  secret  =  pm.collectionVariables.get('rapyd_secret_key');
var  to_sign  =  request.method.toLowerCase() +  request.url.replace('{{base_uri}}','/v1') +  signature_salt  +  timestamp  +  pm.collectionVariables.get('rapyd_access_key') +  secret  +  body;

var  rapyd_signature  =  CryptoJS.enc.Hex.stringify(CryptoJS.HmacSHA256(to_sign, secret));
rapyd_signature  =  CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(rapyd_signature));
pm.globals.set("rapyd_signature", rapyd_signature);
```

Open the **Variables** section, where you will input the Rapyd access key and secret key and the API endpoint:

![Variables](https://i.imgur.com/jruf6rc.png)

With the above configuration on Postman, you can easily make requests to the Rapyd API and carry out the necessary operations.

#### Create a Product Service

To establish a subscription service in Rapyd, you need to [create a product](https://docs.rapyd.net/en/create-product.html) for the type of service you're offering. Open the **Create Services** API on the Postman collection and input the following JSON code to create a product:

```json
{
    "id": "",
    "name": "Online Memery Service",
    "type": "services",
    "active": true,
    "description": "Get access to hundreds of Memes online"
}
```

![Product](https://i.imgur.com/bwOpIGn.png)

Please note the product ID from the response for use in the next section.

#### Create Subscription Plans

A [plan](https://docs.rapyd.net/en/plan.html) defines the pricing structure of your product. A product can have multiple plans, depending on your needs. Use the **Create Plan** endpoint on the Postman Collection to create a plan, which will be billed monthly and charged in the currency you specify. The billing scheme will be per unit, while the usage type will be licensed. Use the following JSON to create the plan:

```json
{
	"currency": "usd",
   	 "amount":"10",
	"interval": "month",
	"product": "product_xxxxxxxxxxxxxxxxxxxxxx",
	"billing_scheme": "per_unit",
	"nickname": "Gold Memery Plan",
	"usage_type": "licensed"
}
```

![Service plan](https://i.imgur.com/0wS3HNv.png)

You can create any number of plans. Once you create a plan, you can then integrate the subscription service into your app.

### Integrate the Subscription Service into the Flutter App

With the subscription service ready, you can now build the app for your meme-loving users. In this app, users have a quota of fifty memes; if they want to view more, they should subscribe to a  plan. To get started, please grab the starter app in this [GitHub repository branch](https://github.com/Rapyd-Samples/rapyd-flutter-tutorial) and run it. You should see a screen similar to the screenshot below:

![App home screen](https://i.imgur.com/b8WLHNq.png)

#### Generate a Request Signature

Like the Postman collection, you must generate a [request signature](https://docs.rapyd.net/en/request-signatures.html) to authorize your requests to the Rapyd API endpoint. To facilitate this, open the **pubspec.yaml** file and add the `crypto:` package.

In the **lib/network** folder, create the file **rapyd_connection.dart** and add the following content:

```dart
import 'dart:convert';
import 'dart:math';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';

class RapydConnection {
  final Dio dio = Dio(BaseOptions(
    connectTimeout: const Duration(seconds: 30),
    receiveTimeout: const Duration(seconds: 30),
  ));
  final String baseUrl = 'https://sandboxapi.rapyd.net';
  final String secretKey = ''; // The secret key received from Rapyd.
  final String accessKey = ''; // The access key received from Rapyd.

  RapydConnection() {
    dio.options.baseUrl = baseUrl;
  }

  String _getRandString(int len) {
    var values = List<int>.generate(len, (i) => Random.secure().nextInt(256));
    return base64Url.encode(values);
  }

  String _getSignature(String httpMethod, String urlPath, String salt,
      String timestamp, String bodyString) {
    String sigString = httpMethod +
        urlPath +
        salt +
        timestamp +
        accessKey +
        secretKey +
        bodyString;
    Hmac hmac = Hmac(sha256, utf8.encode(secretKey));
    Digest digest = hmac.convert(utf8.encode(sigString));
    var ss = hex.encode(digest.bytes);
    return base64UrlEncode(ss.codeUnits);
  }

  Map<String, String> _getHeaders(String method, String urlEndpoint,
      {String body = ""}) {
    String salt = _getRandString(16);

    String timestamp = (DateTime.now().toUtc().millisecondsSinceEpoch / 1000)
        .round()
        .toString();

    String signature =
        _getSignature(method.toLowerCase(), urlEndpoint, salt, timestamp, body);

    return <String, String>{
      "access_key": accessKey,
      "signature": signature,
      "salt": salt,
      "timestamp": timestamp,
    };
  }

  Future<Map<String, dynamic>> makeRequest(
      String method, String path, String body) async {
    print(body);
    final headers = _getHeaders(method, path, body: body);
    Response response;

    if (method == 'get') {
      print(baseUrl + path);
      response = await dio.get(path, options: Options(headers: headers));
    } else if (method == 'put') {
      response =
          await dio.put(path, data: body, options: Options(headers: headers));
    } else if (method == 'delete') {
      response = await dio.delete(baseUrl + path,
          data: body, options: Options(headers: headers));
    } else {
      response = await dio.post(baseUrl + path,
          data: body, options: Options(headers: headers));
    }
    print(response);
    if (response.statusCode != 200) {
      throw Exception('Request failed: ${response.statusCode}');
    }
    return response.data;
  }
}

```

The above code calculates the request signature by hashing a concatenation of values, including the Rapyd access key, secret key, timestamp, HTTP method, URL path, and the sent body request. The `makeRequest` function makes all the Rapyd-related API requests.

Add the following line of code to the **NetworkConnection** file to include the class above.

```dart
  final  RapydConnection  rapyd  =  RapydConnection();
  ```

#### Generate JSON Models

You need to generate JSON models to decode the Rapyd API response data. Open the **app_models.dart** file and add the code in [this file](https://github.com/Rapyd-Samples/rapyd-flutter-tutorial/blob/main/lib/models/app_models.dart).

*Note:* The Rapyd API has numerous JSON responses, and manually creating models would be time-consuming. [Json to Dart Model](https://marketplace.visualstudio.com/items?itemName=hirantha.json-to-dart) automates this and is a useful VS Code extension. Most JSON models in the code above have been redacted for brevity. You can check the Postman responses for the complete data.

Run the command below to generate the JSON models:

```dart
dart run build_runner build --delete-conflicting-outputs
```

#### Fetch Subscription Plans

You'll now add functions to the app to present users with various plans and ask them to select the country where they are located so Rapyd can present supported Payment methods in that region.

Add the code below to the **NetworkConnection** class to fetch the plans you created earlier and the countries supported by Rapyd:

```dart
  Future<List<Country>> getSupportedCountries() async {
    try {
      final response = await rapyd.makeRequest('get', '/v1/data/countries', '');
      return CountryResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return [];
  }

  Future<List<Plan>> getSubscriptionPlans() async {
    try {
      final response = await rapyd.makeRequest('get', '/v1/plans', '');
      return PlansResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return [];
  }
```

To present the plans and the country to the user for selection, open the **home.dart** file and replace the `_bottomSheet` and `initState` functions in the class `_HomeScreenState` with the code below:

```dart
  List<Country> countries = [];
  List<Plan> plans = [];
  void _bottomSheet(BuildContext context) {
    showModalBottomSheet(
      barrierColor: Colors.transparent,
      isScrollControlled: true,
      useSafeArea: true,
      elevation: 5,
      context: context,
      builder: (ctx) => Container(
          padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
          child: (countries.isEmpty || plans.isEmpty)
              ? const Center(
                  child: CircularProgressIndicator(),
                )
              : SelectPlanScreen(
                  countries: countries,
                  plans: plans,
                )),
    );
  }

  Future<void> loadSubscriptionPlans() async {
    countries = await NetworkConnection().getSupportedCountries();
    plans = await NetworkConnection().getSubscriptionPlans();
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void initState() {
    super.initState();
    refresh();
    loadSubscriptionPlans();
  }
```

Create a new file in the `lib` folder named **select_plan_screen.dart** and add the following content:

```dart
import 'package:flutter/material.dart';
import 'package:rapyd/hosted_checkout_screen.dart';
import 'models/app_models.dart';

class SelectPlanScreen extends StatefulWidget {
  final List<Country> countries;
  final List<Plan> plans;
  const SelectPlanScreen(
      {required this.countries, required this.plans, Key? key})
      : super(key: key);

  @override
  State<SelectPlanScreen> createState() => _SelectPlanScreenState();
}

class _SelectPlanScreenState extends State<SelectPlanScreen> {
  List<Country> countries = [];
  List<Plan> plans = [];
  late Plan selectedPlan;
  late Country selectedCountry;
  final nameController = TextEditingController();

  @override
  void initState() {
    plans = widget.plans;
    countries = widget.countries;
    selectedCountry = countries.first;
    selectedPlan = plans.first;

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          "To Continue Enjoying More Memes, Please Subscribe for a Plan.",
          textAlign: TextAlign.center,
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(
          height: 10,
        ),
        const Text(
          "Select Country",
          textAlign: TextAlign.center,
        ),
        const SizedBox(
          height: 10,
        ),
        DropdownButtonFormField<Country>(
            value: selectedCountry,
            isExpanded: true,
            items: countries
                .map((e) =>
                    DropdownMenuItem<Country>(value: e, child: Text(e.name)))
                .toList(),
            onChanged: (country) {
              selectedCountry = country!;
            }),
        const SizedBox(
          height: 10,
        ),
        const Text(
          'Provide a name to use the Hosted Checkout method otherwise leave it blank to use the Subscriptions API method',
          style: TextStyle(color: Colors.red),
        ),
        const SizedBox(
          height: 5,
        ),
        TextFormField(
          controller: nameController,
          decoration: const InputDecoration(hintText: "Your Name"),
        ),
        const SizedBox(
          height: 10,
        ),
        const Text(
          "Select Plan",
          textAlign: TextAlign.center,
        ),
        const SizedBox(
          height: 10,
        ),
        ...plans
            .map((e) => RadioListTile.adaptive(
                title: Text(e.nickname!),
                subtitle: Text("${e.currency} ${e.amount}"),
                value: e,
                groupValue: selectedPlan,
                onChanged: (plan) {
                  setState(() {
                    selectedPlan = plan!;
                  });
                }))
            .toList(),
        Center(
          child: ElevatedButton.icon(
              onPressed: () {
                Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => HostedCheckoutScreen(
                        customerName: nameController.text.trim(),
                        plan: selectedPlan,
                        country: selectedCountry,
                      ),
                    ));
              },
              icon: const Icon(Icons.subscriptions_rounded),
              label: Text('Subscribe To ${selectedPlan.nickname}')),
        ),
      ],
      ),
    );
  }
}
```

The above file will display some errors related to missing files; you'll add the relevant files in the next sections.

### Hosted Checkout Page

A hosted checkout page facilitates collecting payment information from customers interested in subscribing to your service. The hosted page is generated via the Rapyd API, which returns a web page URL the user can open.

You can customize the page displayed to the customer by opening your Rapyd dashboard, navigating to the **Settings** tab, and selecting the **Branding** option. Here, you can modify the color theme, the payment methods to be displayed, the company logo, and many other options.

![Branding](https://i.imgur.com/khkt7n5.png)

To allow users to open the web page within the app, add the package `webview_flutter: ^4.2.3` into your **pubspec.yaml** file.

You need a product, a plan, and a customer to generate the hosted checkout page. You already created the product and the plan, so now you'll create a customer before continuing to generate the hosted checkout page.

Open the **NetworkConnection** file and add the following code:

```dart
  Future<Customer?> createCustomer(
      {required String name, Map<String, dynamic>? paymentMethod}) async {
    try {
      final response = await rapyd.makeRequest('post', '/v1/customers',
          jsonEncode({"name": name, "payment_method": paymentMethod}));
      return CustomerResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return null;
  }

  Future<HostedCheckout?> createCheckoutPage(
      {required Customer customer,
      required Plan plan,
      required Country country}) async {
    try {
      final response = await rapyd.makeRequest(
          'post',
          '/v1/checkout/subscription',
          jsonEncode({
            "customer": customer.id,
            "billing": "pay_automatically",
            "country": country.isoAlpha2,
            "subscription_items": [
              {"plan": plan.id, "quantity": 1}
            ]
          }));
      return HostedCheckoutResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return null;
  }
```

To generate and display the hosted checkout page, create a new file named **hosted_checkout_screen.dart** in the `lib` folder and add the following contents:

```dart
import 'package:flutter/material.dart';

import 'package:rapyd/network/network_connection.dart';
import 'package:webview_flutter/webview_flutter.dart';

import 'models/app_models.dart';

class HostedCheckoutScreen extends StatefulWidget {
  final Plan plan;
  final Country country;
  final String customerName;
  const HostedCheckoutScreen(
      {required this.plan,
      required this.customerName,
      required this.country,
      Key? key})
      : super(key: key);

  @override
  State<HostedCheckoutScreen> createState() => _HostedCheckoutScreenState();
}

class _HostedCheckoutScreenState extends State<HostedCheckoutScreen> {
  late final WebViewController controller;
  HostedCheckout? hostedCheckoutData;

  @override
  void initState() {
    createCustomerAndCreateHostedPage(
        widget.customerName, widget.plan, widget.country);

    super.initState();
  }

  Future<void> createCustomerAndCreateHostedPage(
      String name, Plan plan, Country country) async {
    Customer? customer = await NetworkConnection().createCustomer(name: name);
    if (customer != null) {
      hostedCheckoutData = await NetworkConnection()
          .createCheckoutPage(customer: customer, plan: plan, country: country);
    }
    if (hostedCheckoutData != null) {
      controller = WebViewController()
        ..setJavaScriptMode(JavaScriptMode.unrestricted)
        ..setBackgroundColor(const Color(0x00000000))
        ..setNavigationDelegate(
          NavigationDelegate(
            onProgress: (int progress) {},
            onPageStarted: (String url) {
              print(url);
            },
            onPageFinished: (String url) {},
            onWebResourceError: (WebResourceError error) {
              print(error);
            },
          ),
        )
        ..loadRequest(Uri.parse(hostedCheckoutData!.redirectUrl!));
    }
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Hosted Checkout"),
      ),
      body: hostedCheckoutData == null
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : WebViewWidget(controller: controller),
    );
  }
}
```

The code above creates a new customer on the Rapyd platform and then generates a checkout page for the customer to complete payment. Kindly run the app to experience this functionality.

![Hosted checkout](https://i.imgur.com/IwgJ74O.gif)

### Integrate Rapyd API for Subscription Checkout

You can also create the subscription directly via the Rapyd API. However, you need to collect the payment details from the user and save them against their profile. Each payment method has its own [requirements](https://docs.rapyd.net/en/creating-a-payment-using-customer-profile-information.html) from the user, so make sure to check.

First, you need to check which payment methods Rapyd supports in the country where the user is located and present them to the user.

Open the **NetworkConnection** file and add the following code:

```dart
  Future<List<PaymentMethod>> getCountryPaymentMethods(
      {required String countryCode, required String currency}) async {
    try {
      final response = await rapyd.makeRequest(
          'get',
          '/v1/payment_methods/country?country=$countryCode&currency=$currency',
          '');
      return PaymentMethodsResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return [];
  }
  Future<Subscription?> createPlanSubscription(
      {required Customer customer,
      required Plan plan,
      required Country country}) async {
    try {
      final response = await rapyd.makeRequest(
          'post',
          '/v1/payments/subscriptions',
          jsonEncode({
            "customer": customer.id,
            "billing": "pay_automatically",
            "country": country.isoAlpha2,
            "subscription_items": [
              {"plan": plan.id, "quantity": 1}
            ]
          }));
      return SubscriptionResponse.fromJson(response).data;
    } catch (e) {
      print(e);
    }
    return null;
  }
```

Rapyd returns all available payment methods in a country but not all support subscription payments, so filter them out.

Create a new file in the `lib` folder named **payment_method_screen.dart**, which will enable you to present all available payment methods to the user:

```dart
import 'package:flutter/material.dart';
import 'package:rapyd/models/app_models.dart';
import 'package:rapyd/network/network_connection.dart';
import 'package:rapyd/plan_checkout_screen.dart';

class PaymentMethodScreen extends StatefulWidget {
  final Plan selectedPlan;
  final Country selectedCountry;
  const PaymentMethodScreen(
      {required this.selectedCountry, required this.selectedPlan, Key? key})
      : super(key: key);

  @override
  State<PaymentMethodScreen> createState() => _PaymentMethodScreenState();
}

class _PaymentMethodScreenState extends State<PaymentMethodScreen> {
  late final Plan selectedPlan;
  late final Country selectedCountry;
  String errorText = '';
  List<PaymentMethod> paymentMethods = [];

  @override
  void initState() {
    selectedPlan = widget.selectedPlan;
    selectedCountry = widget.selectedCountry;
    super.initState();
    loadPaymentMethods();
  }

  Future<void> loadPaymentMethods() async {
    var paymethods = await NetworkConnection().getCountryPaymentMethods(
        countryCode: selectedCountry.isoAlpha2,
        currency: selectedCountry.currencyCode);

    // Filter unsupported and non-card payment methods
    paymentMethods.addAll(paymethods
        .where((element) => element.category == 'card')
        .where((element) => element.supportsSubscription));

    if (mounted) {
      setState(() {
        if (paymentMethods.isEmpty) {
          errorText = "Payment Methods Not Found";
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(selectedPlan.nickname!),
      ),
      body: errorText.isNotEmpty
          ? Center(
              child: Text(errorText),
            )
          : paymentMethods.isEmpty
              ? const Center(
                  child: CircularProgressIndicator(),
                )
              : ListView.builder(
                  itemCount: paymentMethods.length,
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    PaymentMethod paymentMethodDatum =
                        paymentMethods.elementAt(index);
                    return ListTile(
                      title: Text(paymentMethodDatum.category),
                      subtitle: Text(paymentMethodDatum.name),
                      onTap: () {
                        print(paymentMethodDatum.type);
                        Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) => PlanCheckoutScreen(
                                    plan: selectedPlan,
                                    country: selectedCountry,
                                    paymentMethod: paymentMethodDatum)));
                      },
                    );
                  },
                ),
    );
  }
}

```

Check if the user provided a name when selecting a plan, and if they have, navigate to the `HostedCheckoutScreen`. If the user didn't provide a name, navigate to the `PaymentMethodScreen` screen built by the code above to utilize the Subscriptions API checkout method. Please update the button in the **select_plan_screen.dart** file with the code below:

```dart
        Center(
          child: ElevatedButton.icon(
              onPressed: () {
                // If the name is not empty, use the Hosted Checkout method to subscribe the user
                // otherwise use the Subscriptions API method
                if (nameController.text.isNotEmpty) {
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => HostedCheckoutScreen(
                          customerName: nameController.text.trim(),
                          plan: selectedPlan,
                          country: selectedCountry,
                        ),
                      ));
                } else {
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => PaymentMethodScreen(
                          selectedCountry: selectedCountry,
                          selectedPlan: selectedPlan,
                        ),
                      ));
                }
              },
              icon: const Icon(Icons.subscriptions_rounded),
              label: Text('Subscribe To ${selectedPlan.nickname}')),
        ),
```

After the user selects their payment method, you must collect the required details. To facilitate this, add the `flutter_credit_card: ^3.0.6` package in your **pubspec.yaml** file.

Once the information is collected, you can create the customer with their payment details and then create a subscription for the service. In the `lib` folder, create the file **plan_checkout_screen.dart** file and add the following contents:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_credit_card/credit_card_brand.dart';
import 'package:flutter_credit_card/flutter_credit_card.dart';
import 'package:rapyd/models/app_models.dart';
import 'package:rapyd/network/network_connection.dart';

class PlanCheckoutScreen extends StatefulWidget {
  final Plan plan;
  final Country country;
  final PaymentMethod paymentMethod;
  const PlanCheckoutScreen(
      {required this.plan,
      required this.country,
      required this.paymentMethod,
      Key? key})
      : super(key: key);

  @override
  State<PlanCheckoutScreen> createState() => _PlanCheckoutScreenState();
}

class _PlanCheckoutScreenState extends State<PlanCheckoutScreen> {
  String name = '';
  String cardNo = '';
  String expiry = '';
  String cvv = '';
  final _formKey = GlobalKey<FormState>();

  late Country country;
  late Plan plan;
  late PaymentMethod paymentMethod;
  @override
  void initState() {
    plan = widget.plan;
    country = widget.country;
    paymentMethod = widget.paymentMethod;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(plan.nickname!),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            CreditCardWidget(
              cardNumber: cardNo,
              expiryDate: expiry,
              cardHolderName: name,
              cvvCode: cvv,
              showBackView: false,
              obscureCardCvv: true,
              isHolderNameVisible: true,
              onCreditCardWidgetChange: (CreditCardBrand creditCardBrand) {},
            ),
            CreditCardForm(
              formKey: _formKey,
              obscureCvv: true,
              cardNumber: cardNo,
              cvvCode: cvv,
              isHolderNameVisible: true,
              isCardNumberVisible: true,
              isExpiryDateVisible: true,
              cardHolderName: name,
              expiryDate: expiry,
              themeColor: Colors.purple,
              onCreditCardModelChange: (creditCard) {
                setState(() {
                  name = creditCard.cardHolderName;
                  cardNo = creditCard.cardNumber;
                  cvv = creditCard.cvvCode;
                  expiry = creditCard.expiryDate;
                });
              },
            ),
            ElevatedButton.icon(
                icon: const Icon(Icons.subscriptions),
                onPressed: () async {
                  if (_formKey.currentState!.validate()) {
                    Customer? customer =
                        await NetworkConnection().createCustomer(
                      name: name,
                      paymentMethod: {
                        "type": paymentMethod.type,
"name": name,
                        "fields": {
                          "number": cardNo.replaceAll(' ', ''),
                          "expiration_month": expiry.split('/').first,
                          "expiration_year": expiry.split('/').last,
                          "cvv": cvv
                        },
                        "complete_payment_url": "https://complete.rapyd.net/",
                        "error_payment_url": "https://error.rapyd.net/"
                      },
                    );
                    if (customer != null) {
                      Subscription? subscriptionData = await NetworkConnection()
                          .createPlanSubscription(
                              customer: customer, plan: plan, country: country);
                      if (subscriptionData != null) {
                        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                            elevation: 0,
                            backgroundColor: Colors.transparent,
                            content: Text("${plan.nickname}Subscribed")));
                      }
                    }
                  }
                },
                label: const Text("Subscribe"))
          ],
        ),
      ),
    );
  }
}
```

Run the app to subscribe to a plan via the Rapyd Subscription API. Leave the name blank when selecting the plan to subscribe via the Subscriptions Rapyd API.

![Subscription API](https://i.imgur.com/JS6RTAx.gif)

Please refer to this [GitHub repository](https://github.com/Rapyd-Samples/rapyd-flutter-tutorial) for the complete code for the above app.

## Conclusion

In this article, you learned how to integrate the [Rapyd Collect API](https://www.rapyd.net/products/payments/api/)  into your Flutter application for payment processing. You've defined a product within the Rapyd platform, crafted user subscription plans for the product, and used the hosted checkout and subscription API methods to collect payments from your users within a Flutter application.

Empower your app's subscription management through the Rapyd Subscription API, which streamlines the process with advanced features like automated billing cycles and trial periods while ensuring the security and integrity of sensitive payment information. Additionally, the cutting-edge hosted checkout page simplifies payment collection by seamlessly guiding users through the payment process while supporting various payment methods and currencies.

Enhance your app's capabilities by seamlessly integrating diverse global payment options, leveraging today's intuitive Rapyd API.