🚀 Seamless Stripe Integration in .NET MAUI: A Native Interop Approach

🚀 Seamless Stripe Integration in .NET MAUI: A Native Interop Approach

·

6 min read

Introduction

Stripe is one of the most popular payment gateways, offering a seamless checkout experience for mobile apps. In this guide, we'll walk through integrating Stripe Payments in .NET MAUI (iOS) using Native Interop. By leveraging native bindings, we ensure a smooth and native-like payment flow in our .NET MAUI applications.

Prerequisites

Before we begin, ensure you have the following:

Step 1: Clone the Native Interop Template

For ease of integration, clone the GitHub repository that includes a template for Native Interop in .NET MAUI. This will provide the base structure required for integrating Stripe.

This repository will provide necessary configurations, including binding definitions and project setup.

https://github.com/CommunityToolkit/Maui.NativeLibraryInterop

Copy Template Folder

Once cloned, copy the template folder from the repository and place it in a separate directory. Now, navigate to macios/native and open the Xcode project.

Step 2: Set Up Stripe in iOS

Follow Stripe’s official documentation on accepting payments: Stripe iOS Payment Guide.

Register with Stripe

  1. Create a Stripe account.

  2. Obtain your publishable key and secret key from the Stripe Dashboard.

Download Stripe SDK

  1. Navigate to the Manual Framework Integration section.

  2. Download Stripe.xcframework.zip from Stripe’s GitHub or official site.

  3. Extract and move StripeCore.xcframework StripeUICore.xcframework Stripe3DS.xcframework StripePayments.xcframework StripePaymentsUI.xcframework StripeApplePay.xcframework StripePaymentSheet.xcframeworkto your Xcode project.

Step 3: Configure Xcode Project

  1. Navigate to General Settings in Xcode.

  2. Locate Embedded Binaries and add all .xcframework

  3. Ensure that all required dependencies are linked correctly.

Step 4: Add Required Code in Xcode

Now, follow Stripe’s documentation and copy the necessary code snippets in DotnetNewBinding.swift.

For more ease i added code snippets for better understanding.

DotnetNewBinding.swift

import Foundation
import UIKit
import StripePaymentSheet;

@objc(DotnetNewBinding)
public class DotnetNewBinding: NSObject
{

    static var paymentSheet: PaymentSheet?
    static var paymentIntentClientSecret: String? // Store the client secret manually

 @objc(setupPaymentSheet:customerEphemeralKeySecret:clientSecret:publishableKey:)
    public static func setupPaymentSheet(
        customerId: NSString,
        customerEphemeralKeySecret: NSString,
        clientSecret: NSString,
        publishableKey: NSString
    ) -> Bool {
        print("Setting up payment sheet...")

        STPAPIClient.shared.publishableKey = publishableKey as String

        var configuration = PaymentSheet.Configuration()
        configuration.merchantDisplayName = "Native Interopp :)"
        configuration.customer = .init(id: customerId as String, ephemeralKeySecret: customerEphemeralKeySecret as String)
        configuration.allowsDelayedPaymentMethods = true

        self.paymentIntentClientSecret = clientSecret as String // Store for later use
        self.paymentSheet = PaymentSheet(paymentIntentClientSecret: clientSecret as String, configuration: configuration)

        if self.paymentSheet == nil {
            print("Error: paymentSheet failed to initialize!")
            return false
        }

        print("Payment sheet successfully initialized!")
        return true
    }

    @objc(presentPaymentSheet:completion:)
        public static func presentPaymentSheet(viewController: UIViewController, completion: @escaping (Bool, NSString?, NSString?) -> Void) {
            DispatchQueue.main.async {
                guard let paymentSheet = self.paymentSheet else {
                    print("Error: paymentSheet is nil. Did you call setupPaymentSheet() first?")
                    completion(false, nil, nil)
                    return
                }

                paymentSheet.present(from: viewController) { paymentResult in
                    switch paymentResult {
                    case .completed:
                        print("Payment Successful")
                        fetchPaymentDetails { paymentIntentId, status in
                            completion(true, paymentIntentId as NSString?, status as NSString?)
                        }
                    case .canceled:
                        print("Payment Canceled")
                        completion(false, nil, nil)
                    case .failed(let error):
                        print("Payment failed: \(error)")
                        completion(false, nil, nil)
                    }
                }
            }
        }

    // Fetch payment details using the stored paymentIntentClientSecret
    private static func fetchPaymentDetails(completion: @escaping (String?, String?) -> Void) {
        guard let clientSecret = self.paymentIntentClientSecret else {
            print("Error: paymentIntentClientSecret is nil")
            completion(nil, nil)
            return
        }

        STPAPIClient.shared.retrievePaymentIntent(withClientSecret: clientSecret) { paymentIntent, error in
            if let error = error {
                print("Error retrieving payment intent: \(error.localizedDescription)")
                completion(nil, nil)
                return
            }

            if let paymentIntent = paymentIntent {
                let paymentId = paymentIntent.stripeId
                let status = String(paymentIntent.status.rawValue) // Convert Int to String

                print("Payment Intent ID: \(paymentId), Status: \(status)")
                completion(paymentId, status)
            } else {
                completion(nil, nil)
            }
        }
    }
}

Build the Xcode project and check if it compiles successfully.

Step 5: Implement Native Interop in .NET MAUI (iOS)

Now, integrate Stripe with .NET MAUI iOS Project using Native Interop.

✨ Modify ApiDefinition.cs

In your .NET MAUI iOS project, add the following bindings in ApiDefinition.cs to expose native Stripe functionality:

ApiDefinition.cs

using Foundation;
using UIKit;
using System;

namespace NewBindingMaciOS
{
    // @interface DotnetNewBinding : NSObject
    [BaseType (typeof(NSObject))]
    interface DotnetNewBinding
    {

    [Static]
    [Export("setupPaymentSheet:customerEphemeralKeySecret:clientSecret:publishableKey:")]
    bool SetupPaymentSheet(NSString customerId, NSString customerEphemeralKeySecret, NSString clientSecret, NSString publishableKey);

     // Use Action<bool> as a callback for the result
     [Static]
     [Export("presentPaymentSheet:completion:")]
     void PresentPaymentSheet(UIViewController viewController, Action<bool, NSString, NSString> completion);

    }
}

Now copy all the following frameworks: StripeCore.xcframework, StripeUICore.xcframework, Stripe3DS.xcframework, StripePayments.xcframework, StripePaymentsUI.xcframework, StripeApplePay.xcframework, and StripePaymentSheet.xcframework. Add them to this project as well so we can include native references.

NewBinding.MaciOS.Binding.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <IsBindingProject>true</IsBindingProject>

    <!--
      Enable trim analyzers for class libraries.
      To learn more, see: https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming
    -->
    <IsTrimmable>true</IsTrimmable>
  </PropertyGroup>

  <ItemGroup>
    <ObjcBindingApiDefinition Include="ApiDefinition.cs" />
    <ObjcBindingCoreSource Include="StructsAndEnums.cs" />
  </ItemGroup>

  <!-- Reference to Xcode project -->
  <ItemGroup>
    <XcodeProject Include="../native/NewBinding/NewBinding.xcodeproj">
      <SchemeName>NewBinding</SchemeName>
      <!-- Metadata applicable to @(NativeReference) will be used if set, otherwise the following defaults will be used:
      <Kind>Framework</Kind>
      <SmartLink>true</SmartLink>
      -->
      <Visible>False</Visible>
    </XcodeProject>

     <NativeReference Include="StripePaymentSheet.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

     <NativeReference Include="Stripe3DS2.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

     <NativeReference Include="StripeApplePay.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

       <NativeReference Include="StripeCore.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

      <NativeReference Include="StripePayments.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

     <NativeReference Include="StripePaymentsUI.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

      <NativeReference Include="StripeUICore.xcframework">
        <Kind>Framework</Kind>
        <SmartLink>False</SmartLink>
        <ForceLoad>true</ForceLoad>
        <Frameworks>UIKit Foundation</Frameworks>
        <Visible>true</Visible>
    </NativeReference>

     <BundleResource Include="StripePaymentSheet.xcframework/**/Stripe_StripePaymentSheet.bundle" />

  </ItemGroup>

</Project>

Build the iOS project and check if it builds successfully. It will take some time to compile.

Step 6: Access Stripe in .NET MAUI

Now, you can call the Stripe in your .NET MAUI project:

MainPage.cs

namespace MauiSample;

#if IOS || MACCATALYST
using NewBinding = NewBindingMaciOS.DotnetNewBinding;
#elif ANDROID
using NewBinding = NewBindingAndroid.DotnetNewBinding;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using NewBinding = System.Object;
#endif

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        // Call the native binding, which will append a platform specific string to the input string
        var labelText = NewBinding.GetString("Community Toolkit");

        newBindingSampleLabel.Text = "Hello, " + labelText;
    }

    async void OnDocsButtonClicked(object sender, EventArgs e)
    {
        #if IOS || MACCATALYST

        if(await ShowPaymentSheetAsync(payment))
        {

        }
        #endif

    }

#if IOS || MACCATALYST

        public Task<bool> ShowPaymentSheetAsync(Payment payment)
{
    try
    {
        var window = UIApplication.SharedApplication.KeyWindow;
        var currentUIViewController = window?.RootViewController;

        if (currentUIViewController == null)
        {
            return Task.FromResult(false);
        }

        bool isInitialized = NewBinding.SetupPaymentSheet(
            new NSString(payment.Customer),
            new NSString(payment.EphemeralKey),
            new NSString(payment.PaymentIntent),
            new NSString(payment.PublishableKey)
        );

        if (isInitialized)
        {
            var tcs = new TaskCompletionSource<bool>();

            MainThread.BeginInvokeOnMainThread(() =>
            {
                NewBinding.PresentPaymentSheet(currentUIViewController, (success, paymentIntentId, status) =>
                {
                    if (success)
                    {
                        tcs.SetResult(true);
                    }
                    else
                    {
                        tcs.SetResult(false);
                    }
                });
            });

            return tcs.Task;
        }
    }
    catch (Exception ex)
    {
    }

    return Task.FromResult(false);
}
}

This ensures that Stripe’s payment flow is properly handled in your .NET MAUI iOS app.

GitHub Repository

You can find the complete implementation in my GitHub repository:

🔗 GitHub Repository for Stripe Integration

Conclusion

Congratulations! You have successfully integrated Stripe Payments in a .NET MAUI iOS app using Native Interop. This approach allows you to utilize Stripe’s native capabilities while keeping the .NET MAUI ecosystem intact.

🚀 Stay tuned for the next part, where we cover Stripe integration in .NET MAUI Android!

Â