My simple template to collect payments for your software product

Doesn’t involve integrating a payment gateway.

Ganesh
7 min readMay 30, 2023

MVPs, side projects, or fun projects are a great way to test out your ideas with a couple of users, do experiments, validate your hypothesis, and stuff. Since an MVP is an MVP it may not be the best version yet and it may not contain multiple features often it might have a few core features that solve a single problem and you might want to charge your users for the product.

your project might work out and might not work out or you might want to iterate it quickly So, you might not spend too much time on writing code and you also can’t also skip the essential parts.

This was the case for me when I was building SheetSense, its a webapp where you can upload your excel sheets link and chat with it and get answers for you questions based on the excel data. You can ask it any question about your data.

And I wanted people to pay to use SheetSense. I built this with flutter and firebase and there were multiple payment gateways and platforms that I could use to collect payments, but I’ve never really integrated a payment gateway before, I mean I could do that by learning from youtube and other sources and integrate but it would take me time so, didn’t want to do that and wanted to keep it simple.

So, here is how I solved it without using one….

The Template involves using tools like zapier for automation, stripe for payments, firebase for cloud functions and database, and any frontend of your preference, I use Flutter

Step 1

This flow starts with when a user creates an account in your product, if you are using Firebase, you probably might use Firebase authentication. So, whenever a user signs up firebase will create a unique UID (user id) for that user.

After signing up the user might interact with your product and generate data and you might have to store that in a database to provide a personalized experience and provide the value you promised to deliver. Firebase has Firestore database it is a document type database, you can create a collection in the database and the collection can have multiple documents and each document can have its own fields, basically like a JSON.

Each document in a collection can have its own document id (name of the document) you can name the document id as anything but you must keep it relevant and appropriate to your use case. For example

My Firestore database for my project has two collections Users, PremiumUser. 1st column has Collections, 2nd column has documents present in that collection and 3rd column has data present in a particular document. PremiumUser collection will contain data about premium users. Currently, it’s empty and doesn’t contain any documents as I don’t have any premium users.

Users collection’s documents have data about users who have signed up and are using the product. So, to identify a user I named their document id as uid which was generated when they signed up, and in every document, there is a bool value called isPremium which is false as the user has not paid for the premium plan.

you can use this bool value to limit the user from accessing the premium features of the product, every time the user is navigating to the premium part of the product, you let them in by checking if the user isPremium by fetching that field from Firestore.

final auth = FirebaseAuth.instance;

Future<bool> checkIsPremium() async {
String userUIDd = auth.currentUser!.uid;
try {
final docRef = firestore.collection("Users").doc(userUIDd);
DocumentSnapshot doc = await docRef.get();
final data = doc.data() as Map<String, dynamic>;
bool isPremium = data['isPremium'];
return isPremium;
} catch (e) {
throw Exception();
}
}

bool isPremium = await checkIsPremium();
if(isPremium){
// let them acess premium features
}
else{
// accessnon premium part of the product
}

you can automatically set isPremium to true and let the user access premium features in app, if the user has made payment and upgraded to premium plan let me explain…

Step 2

So, the user has tried out your product and liked it and wants to upgrade to premium plan to get access to premium features, most probably they might navigate to the pricing page, they have seen the price, and hits the pay button…

The goal is to make payment and the medium to make payment can be anything that’s simple and would work.

So, you don’t have to integrate a payment gateway to collect a payment, instead you could use Stripe’s payment links, before using Stripe’s payment link you might want to sign up for Stripe, it’s a simple process, and creating a stripe payment link indeed.

after setting up the link you can provide client_referense_id as a parameter to the link.

do check out this video by Stripe that explains the client_referense_id part

Assuming you have watched the video, you can provide client_referense_id as the users’s UID So, once the user makes the payment, the id will get stored in your stripe account, and you can identify which user had made the payment

whenever a user is navigating to the payment page, simply add the user’s UID as client_referense_id tothe link.

static String stripePaymentLink =
"https://buy.stripe.com/aEU02f8nA9aG3AYdQU?client_reference_id=";

class PremiumButton extends StatefulWidget {
const PremiumButton({super.key});

@override
State<PremiumButton> createState() => _PremiumButtonState();
}

class _PremiumButtonState extends State<PremiumButton> {
String uid = auth.currentUser!.uid;
@override
Widget build(BuildContext context) {
return NavBarButton(
onPressed: () async {
//mixpanel.track("onTapPremiumPlan");
String link = Links.stripePaymentLink;
try {
String url = "$link$uid";
final Uri urii = Uri.parse(url);
if (await canLaunchUrl(urii)) {
await launchUrl(urii);
} else {
print('Could not launch $url');
}
} catch (e) {
print(e);
}
},
buttonName: "Upgrade To Premium Plan ⭐");
}
}

So, once the user makes the payment, the id will get stored in your stripe account, you can identify which user had made the payment by having a look at your stripe account, and you can now store that premium user’s uid in PremiumUser collection, you don’t have to do that manually, you can use Zapier to do that.

Here is a Zapier template that I made to accomplish that task, you could simply connect your stripe and Firebase accounts to that Zapier template and it’s done, if the user made the payment, their UID will automatically get stored in PremiumUser collection

also, this template will work under zapper's free plan.

https://zapier.com/shared/5cf984bc1332540e64a35245a690c601075aa549

But why are we doing that? Well, we do that to set isPremium of that user to true, we now know a user with a certain UID has made the payment, and we also have a Document id named afterthe user’s UID in Users collection, we can use this information set isPremium to true using firebase cloud functions

so, once the user’s isPremium to true, the user can automatically access the premium part of the app based the logic we have already set up.

bool isPremium = await checkIsPremium();
if(isPremium){
// let them acess premium features
}
else{
// access non premium part of the product
}

Firebase cloud functions are literally functions that can get executed whenever an event happens.

Zapier will create a new document in PremiumUser collection once the payment is made, that’s an event right? creating a new document in a collection is an event. So, we could write cloud functions that get triggered when a new document is created in PremiumUser collection.

and the new document will contain a field uid, which is the uid of the user who made a payment in Stripe.

firebase cloud function

const db = admin.firestore();

exports.updatePremium = functions.firestore.document('/PremiumUser/{documentId}').onCreate(async (snapshot,context)=>{
try{
//gets uid field in the newly created doument in PremiumUser collection
const uidd = snapshot.data().uid;

//sets path of the document in Users collection with the uid from PremiumUser collection
const reqDoc = db.collection("Users").doc(uidd);

//gets the document's data in that path
const docSnapshot = await reqDoc.get();

//if there is data in the doument, updates isPremium to true using update method
if (docSnapshot.exists) {
await reqDoc.update({
isPremium: true,
});
}
}
catch(e){
console.log(e);
}
return Promise.resolve();
})

functions.firestore.document(‘/PremiumUser/{documentId}’) is the path of the document and .onCreate is a firebase functions method, which basically means the piece of code inside .onCreate will get executed whenever a new document is created in that path.

Make sure to import necessary dependencies and finally deploy the functions and that’s the final step in the flow. Congrats 🎉

Now, whenever someone buys your premium plan, they will automatically get access to premium features and you can now focus on building.

This process might seem like a lengthy one because of connecting multiple tools though it’s a fairly simple process. Everything happens in the backend part, and the user will have a really simple experience.

--

--

Ganesh
Ganesh

Written by Ganesh

I write results of shit that experiment with. Mostly product, finance, tech and sometimes random shit

No responses yet