The Problem and the Context
Need: I need to sign up users and check if they are Pro or not. If not, allow them to subscribe to a Pro plan and pay via a credit card.
My project: https://unicornplatform.com – the landing page generator for startups.
Requirements: things must be done ASAP since it was the MVP stage.
Country: Russia (no Stripe or Braintree 👀)
My pick was Paddle and Google Firebase Auth and so far the experience is absolutely smooth.
Let’s dig deeper into the details! :>
What is Google Firebase Auth
Google Firebase is a mobile and web application development platform. It offers many cool modules such as static pages hosting, files storage, real-time database, and authentication.
The only thing I needed is Authentication. Let’s add it to my project:
import firebase from "firebase/app";
import 'firebase/auth';import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
initAuth(){
var config = {
apiKey: "<key>",
authDomain: "unicorn-platform-app.firebaseapp.com",
databaseURL: "https://unicorn-platform-app.firebaseio.com",
projectId: "unicorn-platform-app",
storageBucket: "unicorn-platform-app.appspot.com",
messagingSenderId: "<senderId>"
};
firebase.initializeApp(config);
firebase.auth().onAuthStateChanged(function(user) {
// if (user) {
// User is signed in and currentUser will no longer return null.
// } else {
// No user is signed in.
// }
});
}
Easy as a pie.
Good thing is that the Google Firebase Auth script draws all needed UI for me.
So I only needed to add the ReactJS component:
Now I can invoke the initAuth() when a user opens the “sign up/in” window.
What about signing up with social networks? Google Firebase does that for me. Select preferred sign-in methods in the dashboard:
Google Firebase Auth Dashboard
Then declared them in your uiConfig and all the buttons will appear in the UI:
Google Firebase Auth UI (slightly customized)
Signing out is as simple as calling firebase.auth().signOut(). It returns a promise so you can do all the necessary things after a user signed out.
Google Firebase Auth also takes care of password reset, combining two accounts in one, and email address change. No servers, no databases – you just plug in a .js file and it works. Wow!
No servers, no databases – you just plug in a .js script and it works.
The future is now ha-ha.
Although the auth process with Google Firebase is smooth, it’s not recommended to be used as your users database because the users table they offer is not extensible. You can not add additional fields and, for example, mark a paying user as Pro and give him benefits.
I will show how I solved that a bit later.
What is Paddle
Paddle allows selling your software worldwide. It plays a role of a reseller of your software. You can start collecting money by selling digital goods before incorporation – perfect for a beginning startup.
It’s a powerful platform that offers plenty of features but I will cover only the ones that I used to build my SaaS.
First, it’s a ready-to-use checkout – I don’t need to code anything. I just plugged their JS and added a “purchase” button in my template:
So how to integrate Paddle?
Unfortunately, their JS plugin is not wrapped in a module and doesn’t provide any initiation callbacks. So the ol’ good setInterval() buddy is here to help:
initPaddle(){
//Init the Paddle after it's loaded from their server.
//There is no callback or an event so we use intervals to wait until it's loaded.
//We also must load their script from their servers to get the freshest code.
let interval = setInterval(() => {
if (typeof window.Paddle !== 'undefined') {
//Credentials: https://vendors.paddle.com/account
window.Paddle.Setup({
vendor: 11111,
debug: false
});
clearInterval(interval);
}
}, 500);
}
After initiation, the Paddle checkout window is simply invoked via the window.Paddle.Checkout.open() function. You can additionally pass user’s email so they don’t need to re-enter it after signing up. See more checkout props in the docs.
Btw, I only showed the checkout window to signed up users to avoid misunderstanding at the checkout step.
So now users can open a checkout and pay.
After a successful purchase is made, the email of the buyer is placed in the Paddle users database. The window.Paddle.Checkout.open() function also accepts a callback as a function so I upgrade the purchaser instantly after the money are received.
So now we can:
- Authenticate users: a user can sign up with a social network profile or with his email.
- Accept payment from users: a signed up user can subscribe with his email and I can give his premium access right away.
But what if a user closes my app and clears cookies? I need to recognize Pro users when they sign in back.
Pairing Paddle and Google Firebase Auth
As you may remember, Google Firebase Auth doesn’t allow custom fields in their user’s database table, thus I can not mark my users as Pro anyhow. So what do we do? 🤔
One option was to go the traditional way: create a database, start a server and provide some hooks to unite the Google Firebase Auth database, the Paddle database and flavour it up with my own custom fields.
But I was making an MVP, not a spaceship.
To move forward faster, I’ve decided not to store any custom info about a user. When a user signs in, I simply send a cURL request to the Paddle database and check if he is a Pro user or not.
If he is a Pro, I give him full access to the tool and show some billing info. If not, I only offer to upgrade his subscription.
So how to do that? Let’s crunch some code more stuff.
First, I needed a JS function checkIfPro() that will send a request to the Paddle server:
checkIfPro(){
// Send email and PRO_PRODUCT_ID to check.php.
// check.php must get the list of all users from Paddle, then see if current user is among Pro users.
const userEmail = "[email protected]";
const PRODUCT_ID = 11111; //See this ID in the Paddle dashboard.
const URL = 'https://unicornplatform.com/api/check.php';
const data = {
email: userEmail,
productID: PRODUCT_ID
};
const PARAMS = {
headers: {
"Accept": "application/json",
"Content-Type": "application/json; charset=UTF-8"
},
body: JSON.stringify(data),
method: 'POST'
};
fetch(URL, params)
.then(res => res.text())
.then(response => {
let res = JSON.parse(response);
//PHP script returns an object.
if(res.isPro === 'true'){
// It's a Pro user.
}else{
// It's a free-plan user.
}
})
.catch(error => console.error('Error:', error));
}
So I simply take a users’ email which he entered after signing in via Google Firebase Auth, the product ID (a constant string – unique for each subscription plan) and send an AJAX request to my check.php endpoint:
<?php
//Checks if user a Pro. If Pro send also his last/next payment info.
$checking_user_email = json_decode(file_get_contents('php://input'))->email;
$product_id = json_decode(file_get_contents('php://input'))->productID;
curl_init('https://vendors.paddle.com/api/2.0/subscription/users');
//https://paddle.com/docs/api-list-users/
// Generated by curl-to-PHP: http://incarnate.github.io/curl-to-php/
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://vendors.paddle.com/api/2.0/subscription/users");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "vendor_id=11111&vendor_auth_code=11111111111111111111111111&plan=" . $product_id);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = array();
$headers[] = "Content-Type: application/x-www-form-urlencoded";
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:' . curl_error($ch);
}
curl_close ($ch);
$users = json_decode($result)->response;
$is_pro = 'false';
$last_payment = null;
$next_payment = null;
$subscription_id = null;
foreach($users as $user) {
if($user->user_email === $checking_user_email && $user->state === 'active'){
// If the given email is in the subscribers list (means he paid), we tell it back.
// We also send billing info.
$is_pro = 'true';
$last_payment = $user->last_payment;
$next_payment = $user->next_payment;
$subscription_id = $user->subscription_id;
}
}
echo json_encode(array("isPro" => $is_pro, "lastPayment" => $last_payment, "nextPayment" => $next_payment, "subscriptionID" => $subscription_id));
The check.php function checks if my Paddle users table contains the received email. If yes, Paddle will return ‘true’ and some additional info such as a last/next payment date and currency. My checkIfPro() JS function will mark the current user as Pro and unlock premium components.
If the sent email is not in the Paddle users table, the user will not be promoted and stay on his current (free) plan.
So now the app can:
- Sign up users.
- Accept payments.
- Check signed in users if they are Pro or not.
Yes, this set looks very flimsy and unprofessional It’s because it is flimsy and unprofessional.
But it doesn’t matter.
It works flawlessly for all the clients I currently serve. And I reached my goal pretty quickly: validated the demand (it’s huge btw).
Awesome!