PayPal payment system - Flow of Events

159 views
Skip to first unread message

Alan Wells

unread,
Apr 18, 2019, 5:03:30 PM4/18/19
to Google Apps Script Community
This is a Flow Chart of the current PayPal payment system that I'm using.  Any thoughts or observations are welcome.


Add-on Payment Flow - PayPal.jpg


Alan Wells

unread,
Apr 18, 2019, 7:54:39 PM4/18/19
to Google Apps Script Community
PayPal payment system flow chart.  Attached is a PDF file.  Text is larger that image in previous post.
Add-on Payment Flow - PayPal.pdf

Romain Vialard

unread,
Apr 19, 2019, 4:41:21 AM4/19/19
to Google Apps Script Community
Thanks for sharing Alan.
What if a user asks for a refund ? Do you check the payment spreadsheet from time to time or once the paid plan is saved as a user property you are only relying on that ?

Alan Wells

unread,
Apr 19, 2019, 7:26:03 AM4/19/19
to Google Apps Script Community
I have very few paid users.  No one has asked for a refund.  Someone would need to contact me personally to ask for a refund.  I don't know how to do that, and haven't needed to.  The code only checks the spreadsheet if the paid to date has past, or if there is a setting indicating that a payment was made.  So, I'm relying on user properties the vast majority of the time.

Faustino Rodriguez

unread,
Apr 19, 2019, 8:59:55 AM4/19/19
to Google Apps Script Community
We are also using PayPal IPN with a GAS web app running as owner and listening (IPN server) to any IPN notifications
- The subscription details, transactions and status are stored in a Firebase database
- The user has read access to the subscription status node under its email account entry in that database
- That allow for instant subscription registration, because when the user click on Subscribe in the add-on dialog and then complete the subscription, the dialog is listening to the subscription status node in the database, so when that becomes true, the subscription becomes active
- When the subscription gets to the end-of-term and not renewed, that status becomes false and the subscription inactive
- Similar would happens if there is a refund, the IPN server gets the refund notification, makes the active status false and the subscription becomes inactive

- This approach allows us to provide Bonus and Test subscriptions to specific users, just by creating an entry in that database for them

Alan Wells

unread,
Apr 19, 2019, 9:26:01 AM4/19/19
to Google Apps Script Community
Thank you for the information about the dialog listening to the Firebase database.  I suppose that avoids the need to store any payment information in Properties Service.

I can edit my customer spreadsheet manually, and enter a new "paid-until" date.  I don't use a true of false status for whether they are paid or not, but instead always compare today's date to the "paid-until" date or trial end date.  I feel that the true or false paid status is unneeded, because I need the "paid-until" date no matter what.  And I store the dates in milliseconds.  (Properties Service can only take strings, so it needs to be converted back to a number)

By using User Properties, I'm avoiding the need to look for paid information externally, most of the time.  The code only needs to look externally if it doesn't find a date in User Properties.

User's don't have read access to my customer spreadsheet, and don't need that because the only way that the data is accessed is through the Web App.  I'm using a different spreadsheet of customers for every add-on.  Although, in the future I might want to give people access to other add-ons if they have paid for one of the add-ons.  I haven't worked on that yet.

I don't have a "built-in" refund system.  I have very few users, and no one has asked for a refund.

Faustino Rodriguez

unread,
Apr 19, 2019, 10:09:29 AM4/19/19
to Google Apps Script Community
You are right, the spreadsheet also give you the possibility to do manual entries and assign subscriptions that way
- If there is no user-property yet, I guess the script calls the GAS web app that checks the spreadsheet, using the url-fetch function
- Or no need for that either?

For the refund, would it be enough to change some value in the spreadsheet, right?

Alan Wells

unread,
Apr 19, 2019, 12:47:36 PM4/19/19
to Google Apps Script Community
The code needs to test for a few number of possible combinations.  I store 2 dates and whether a payment was made.  I don't want to constantly make a fetch call to check the spreadsheet, so I only do that if either there is no trial date or paid date in User Properties that isn't later than today.  If there is an end date in User Properties, then just use that and compare it to today's date.  If there is no end date, the check for a trial date.  If there is no trial date, and no paid-until date, then the user has never used the add-on, so generate a trial date, and save it.  Once the trial date is set, that stays forever.  You want to avoid an unaccounted for situation that would allow the user to reset the trial period end date without making a payment.  I could get rid of the trial date, but I want to display to the user whether they are "in trial" or not, and when the trial ended.  If all I had was a paid-until date, then I wouldn't know whether it's in trial or not.  If I had a Boolean for whether the app is in trial or not, that's still a setting that needs to be made.  If I'm going to make a setting, I might as well just put the trial end date in the setting.

If the payment is processed, then the property for a payment being made is reset to false.

Now that I think about it, if someone requested a refund, and the User Property status indicated that they are paid, then I'd need to have them request a refund through the add-on, so that it could reset the date.  But, I don't think many people would try to scam me like that.  I can add that into the system later if I need to.

And if a user wanted their money back because of something that I needed to fix, then I wouldn't want to lock them out anyway.  Even if I gave them their money back.  That might lure them back to give them free access if it was my fault.

As a consequence of this discussion I just re-wrote my payment status code (for the 100th time)  That's the great thing about sharing.  If you need to explain it, you might want it to actually work, or it could be embarrassing. haha

Andrew Apell

unread,
Oct 12, 2019, 1:06:56 PM10/12/19
to Google Apps Script Community
4 months on and I'm looking at this post with interest. PayPal does have a subscription button that I have been using for a few weeks. It really helps manage everything with minimal effort. I'm surprised that many of the developers here seem not to be using that option.

My 2 biggest concerns are:
  • The frequency with which the add-ons need to check the databases i.e. wait too long and you don't catch recent changes like cancellations or adjustments, a short frequency means unnecessary queries to the databases.
  • The size of the databases i.e. those who have 10000 plus users. How fast is the user data being found?
How do you gurus manage such situations?

John McGowan

unread,
Oct 13, 2019, 5:53:20 AM10/13/19
to google-apps-sc...@googlegroups.com
Caveat: I am not a guru :)
I use Firebase and Romain's awesome library to check it. I check the license value on open and store that in the Properties so it only checks every time they use it. I also use Stripe and some webhooks with a webapp to update the license in Firebase. I also have a timed private webapp for myself to reset the quota every week. 
Firebase is the glue to my subscription model and Romain's library makes it super easy to implement and I don't really need to pay for the Firebase integration.
Stripe also works well to send a webhook on other events. I do a yearly subscription and just started monetizing this past April so I don't have to worry about renewals yet but I have had about a dozen or so refunds or changes of email addresses. I have a webapp that I use to do that on the backend and write to Firebase that is updated pretty much in real-time for the user in the Add-on.
Took me about a year to get my system working well.

Andrew Apell

unread,
Oct 19, 2019, 10:11:29 AM10/19/19
to Google Apps Script Community
Can you send me a resource I can look at so that I can migrate to firebase?

John McGowan

unread,
Oct 19, 2019, 12:29:10 PM10/19/19
to google-apps-sc...@googlegroups.com
Sure thing. 
Here is the reference for Romain's Firebase Library: https://sites.google.com/site/scriptsexamples/new-connectors-to-google-services/firebase
I create and store all the info in Firebase and use his library as the glue. 
I use the Secret still and Firebase likes to try to get me to migrate telling me that it is legacy and deprecated, but that's how I roll :) (Plus that is all I know how to do :)
Access that from Settings->Service Accounts ->Database Secrets

A fun fact is that you can update to a nonexistent stem so I use update almost all the time (regardless of needing to check whether that stem exists).
It is a bit odd sharing code as nobody really ever looks at my code, so excuse any oddities. I am sure Romain and the other gurus here could improve upon it, but once I get my house of code cards standing I walk away so I don't knock them over :)

Relevant code snippet to get data from Firebase:
 
var user = Session.getEffectiveUser().getEmail();
 //before we copy over the user, get the encodeAsFirebaseKey(user) to store in UserProperties
var firebaseEmail = encodeAsFirebaseKey(user);
var firebaseUrl = "https://your-awesome-project.firebaseio.com/";
var secret = "mySuperLongDatabaseSecret";  
  //check if user is Premium and then store in userProperties
  //can check/update path even if it doesn't exist yet
 var database = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
 firebaseEmail = firebaseEmail.toLowerCase();
 var emailParts = firebaseEmail.split('@');
 var domain = emailParts[1];
 Logger.log('domain: '+domain);
//check for domain premium license
 var domainStemPremiumValue = database.getData("users/"+domain+"/premium");
 Logger.log("domainStemPremiumValue: "+domainStemPremiumValue);
 //set the UserId with the user
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty("userId", user);
userProperties.setProperty("userDomain", domain);
if(domainStemPremiumValue == "true"){
    userProperties.setProperty('premiumUser','true');
  }


Here is the encodeAsFirebaseKey function from Romain as well. There are a lot of shoutouts to Romain in my code comments :)
I also have a decodeAsFirebaseKey function that I use sometimes as well...
/**
 * Thanks to Romain Vialard: https://github.com/RomainVialard/FirebaseApp/blob/master/src/Code.gs#L60
 * Returns a valid Firebase key from a given string
 * Firebase Keys can't contain any of the following characters: . $ # [ ] /
 * https://firebase.google.com/docs/database/usage/limits#data_tree
 * https://groups.google.com/forum/#!msg/firebase-talk/vtX8lfxxShk/skzA5vQFdosJ
 *
 * @param  {string} string - the string to encode
 *
 * @return {string} the encoded string
 */
function encodeAsFirebaseKey(string) {
  return string.replace(/\%/g, '%25')
    .replace(/\./g, '%2E')
    .replace(/\#/g, '%23')
    .replace(/\$/g, '%24')
    .replace(/\//g, '%2F')
    .replace(/\[/g, '%5B')
    .replace(/\]/g, '%5D');
}

Hope that helps!
Cheers,
John

--
You received this message because you are subscribed to a topic in the Google Groups "Google Apps Script Community" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-apps-script-community/sSqHCL0Lr0Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to google-apps-script-c...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-apps-script-community/8585b431-7e43-4de8-a7c4-74e5de43d0cc%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages