showing premium stripe subscription after trial ends - javascript

I have an app in which I have successfully set up stripe payments, with a trial, and then after the 60 day trial, a 9.99 per month fee. However, once the 60 days is up, I want to give the user an option to have a 99.99 per year one time fee instead.
I have done this, but the problem is, if I go into the manage subscriptions page in my app, it gives the option for the 99.99 plan even if the 60 days isnt up, and then if I click update to move to that plan, it doesn't cancel the original trial, and then pretty much breaks my code after that.
code (just incase needed)
export async function createCheckoutSession(){
let user = firebase.auth().currentUser;
const checkoutSessionRef = firestore
.collection('customers')
.doc(userID)
.collection('checkout_sessions')
.add({
price: 'price_1Iav0JKDPaWWeL1yBa9F7Aht',
success_url: "http://localhost:3000/successPage",
cancel_url: "http://localhost:3000/signup",
});
// Wait for the CheckoutSession to get attached by the extension
(await checkoutSessionRef).onSnapshot(function (snap) {
const { error, sessionId } = snap.data();
if (error) {
// Show an error to your customer and
// inspect your Cloud Function logs in the Firebase console.
console.log(`An error occured: ${error.message}`);
}
if (sessionId) {
// We have a session, let's redirect to Checkout
// Init Stripe
const stripe = window.Stripe('pk_test');
stripe.redirectToCheckout({sessionId})
console.log("logged stripe")
}
});
}
manage subscription function (firebase api)
export async function goToBilliingPortal(){
const functionRef = app
.functions('us-central1')
.httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink');
const {data} = await functionRef({returnUrl : window.location.origin});
window.location.assign(data.url);
};
is there a way to hide the 99.99 plan until the trial is up?

You'll need to keep track of the trial status yourself and check it when generating Customer Portal links. While trialing, if you do not wish to allow the customer to change to another plan you need to create another Portal Configuration which disabled subscription updates (ref).
During the trial you'd use this configuration when creating portal links (ref). After the trial you can use another configuration when allows for changes.

Related

Inserting ACL after creating Google Room Resource frequently throws error

I have a Google Cloud function which first creates a Google Room Resource using resources.calendars.insert method from the Google admin sdk,
and right after I try to insert an ACL using Acl: insert method from the google calendar api.
Similar to the following code:
const AdminService = google.admin({version: 'directory_v1'});
try {
const response = await AdminService.resources.calendars.insert(options); // options omitted
} catch (error) {
console.log(`Google Room Resource FAIL`);
console.error(error.message);
}
await new Promise((r) => setTimeout(r, 10000));
const CalendarService = google.calendar({version: 'v3'});
try {
const res = await CalendarService.acl.insert(option); // options omitted
console.log(res);
} catch (error) {
console.log(error);
throw new Error(error.message);
}
As for the authentication, I am using a service account with the correct scopes which impersionates an admin user with the correct permissions. This is how I generate the required JWT token:
const generateJWT = async (scope:string[])=>{
const jwtClient = new google.auth.JWT(
client_email, // service account
undefined,
private_key,
scope,
subject // admin user
);
return jwtClient;
}
In the options parameter for each api call I directly acquire the token for the auth attribute like this:
const option = {
'calendarId': acl.calendarId,
'auth': await generateJWT('https://www.googleapis.com/auth/calendar'),
'resource': {
'role': acl.role,
'scope': {
'type': acl.scopeType,
'value': acl.scopeValue,
},
},
};
Since I await all api calls, I thought that I will only get the response back when everything is already propagated in Google Workspace but when I do not use the setTimeout in between I always get an Error: Not Found back.
First I had the timeout set to 5 seconds which worked until it didn't so I moved it up to 10 seconds. This worked quite long but now I get again sometimes the Not Found error back...
I don't like the setTimeout hack...and even less if it does not work reliable, so how should I deal with this asynchronous behavior without spinning up any other infrastructure like queues or similar?
Working with Google Workspace Calendar Resource
As a Super Admin on my organization when creating a Calendar Resource, from the API or the Web interface, it could take up to 24 hours to correctly propagate the information of the Calendar for the organizations, which generally affect the time it would take for any application to gather the ID of the newly created calendar, which could explain why you are increasing the time out.
You have already implemented the await option which is one of the best things you can do. You can also review the option to apply exponential back off to your application or similar to Google App Script a Utitlies.sleep().
There are multiple articles and references on how to utilize it for the retry process needed when the Resource itself has not fully propagated correctly.
You can also review the official Calendar API documentation that suggests that the error "Not Found" is a 404 error:
https://developers.google.com/calendar/api/guides/errors#404_not_found
With a suggested action of reviewing the option to set up exponential backoff to the application.
References:
GASRetry - Exponential backoff JavaScript implementation for Google Apps Script
Exponential backoff

how do I cancel Subscription in stripe inside react

I am using react. I want to delete old subscription when user changes his subscription plan.
this is my code.
import {loadStripe, useStripe} from '#stripe/stripe-js'
const loadCheckout = async (priceId) =>{
const docRef = await db
.collection('customers')
.doc(user.uid)
.collection('checkout_sessions')
.add({
price:priceId,
success_url:window.location.origin,
cancel_url: window.location.origin,
})
docRef.onSnapshot(async (snap) =>{
const {error, sessionId} = snap.data();
if(error){
alert(`an error occured ${error.message}`)
}
if(sessionId){
const stripe = await loadStripe("pk_test_51JG0BKLXYmn2IpSgISeCDbsCNllISGA3PvEbSzBz5bpo8WTvmqI6UKCbzpxX92LKiN0hftRrobm1J6wJZPCOWSTs0081pmQFJE")
const deleted = await stripe.subscriptions.del(
subscription.id
);
console.log(deleted)
stripe.redirectToCheckout({sessionId});
}
})
}
my code give an error saying stripe.subscriptions.del is not a function.
the code work if I remove this line the payment is successful and all.
In stripe API Docs its says to use .del() but it isnt working here.
Your React code uses Stripe.js which is a client-side library. It cannot modify Subscriptions, only secret key API calls can do that which Stripe.js does not use.
You need to make the request to update/delete the Subscription, you need to make the request server-side (in your Firebase app) using the stripe-node library and your secret API key: https://stripe.com/docs/api/subscriptions/cancel?lang=node.
You can use "customer portal" option inside Stripe to avoid backend coding.
It will generate a link to a page where your customer will be able to cancel the subscription.
Inside Stripe: Billing -> subscriptions -> Setup Customer portal

stripe: Create a 30-day trial subscription charge on a saved card but allow user to upgrade and start charging immediately?

So before a user can create an account I want to save their credit card to charge a subscription with 30 day trial AND the ability to immediately charge the card for a subscription if the user demands it.
so my logic is to
1) create a customer
2) add payment details for customer
3) create subscription with 30 day trial
4) activate subscription upon user upgrade action
I'm not clear on how 4) is possible. I get that on 3), after 30 days, they are on a subscription. but what if the customer wants to start using the full version immediately before the trial is over, how would I create a charge for the subscription?
const stripe = require('stripe')('sk_test_asdfasdf');
(async () => {
// Create a Customer:
stripe.customers.create({
email: 'jenny.rosen#example.com',
payment_method: 'pm_1FWS6ZClCIKljWvsVCvkdyWg',
invoice_settings: {
default_payment_method: 'pm_1FWS6ZClCIKljWvsVCvkdyWg',
},
}, function(err, customer) {
// asynchronously called
});
//create subscription
stripe.subscriptions.create({
customer: 'cus_4fdAW5ftNQow1a',
items: [
{
plan: 'plan_CBXbz9i7AIOTzr',
},
],
expand: ['latest_invoice.payment_intent'],
}, function(err, subscription) {
// asynchronously called
}
);
})();
I'll chime in on this really quick, to hopefully get you in the right direction; This sounds like a case for Setup Intents. You can collect payment details, with the intent to charge at a later time. Since the trial won't incur any charges at first, all is good. However you work out the logic to switch from trial to active status on the subscription, you'd update the subscription end-date to end the trial.
This is nicely summarized here, for the most part, save for updating the Subscription and setting the trial_end argument:
https://stripe.com/docs/payments/save-and-reuse
API docs entry on updating the Subscription:
https://stripe.com/docs/api/subscriptions/update#update_subscription-trial_end
Once the trial is over, whether "naturally" or by explicitly setting the end timestamp, an invoice should be sent out or default payment method charged, depending on your settings. It wouldn't hurt to work in some good flow here, concerning user-experience; For example, having a confirmation step to let the customer know they are about to be charged X amount, before actually firing off that off.
Here are other helpful docs:
https://stripe.com/docs/saving-cards
https://stripe.com/docs/payments/setup-intents
https://stripe.com/docs/billing/subscriptions/trials
https://stripe.com/docs/billing/subscriptions/payment#handling-trial
https://stripe.com/docs/api/subscriptions/update

Why am I unable to send the charge to Stripe and my Strapi.js backend using a try/catch in my React frontend?

I am trying to send a charge to stripe. I have a charge set up in my Strapi.js backend as well as a new order being created. I have created a function in my react frontend to create an entry to my 'orders' content type in strapi but it seems as though nothing in the function's try/catch is working, it is not even logging an error.
I've attempted to make change some variables here and there, but im honestly a little overwhelmed because this is the first time im using these technologies and I am somewhat of a beginner. I was somewhat watching a video for guidance through some things but this is where I faced issues. I am bringing in the script tag in the index.html file as well. I am using strapi sdk.
Here is what the create Orders function on the strapi backend looks like(stripe is being brought in at top of page using secret key):
create: async (ctx) => {
const { address, city, products, amount, token } = ctx.request.body;
// Stripe Charge
const charge = await stripe.charges.create({
amount: amount * 100,
currency: 'usd',
description: `Order ${new Date(Date.now())}`,
source: token
});
const order = await strapi.services.orders.add({
address,
city,
products,
amount,
});
return order;
}
Here is my submit function in react frontend checkout.js file
handleOrder = async () => {
const { checkOutItems, city, address } = this.state;
let token;
const amount = calculateTotalAmount(checkOutItems);
// console.log('working')
try {
// create token
const response = await this.props.stripe.createToken();
token = response.token.id;
await strapi.createEntry('order', {
address,
city,
products: checkOutItems,
amount,
token
});
} catch (error) {
console.log(error)
}
}
Well I am expecting that when i fill out the form as well as the chain of '4242' for stripe card element on the page, the charge will be sent and i can go to my stripe dashboard and see the charge, and also be able to go to my strapi admin dashboard and go to orders and see a new order created.
As of now, none of this happens. I fill the information, click submit, the form resets and then nothing. No error is logged, no console.logs within the try catch will work.

How to delete a user with UID from Real Time Database in Firebase?

The database structure looks like this
-LGw89Lx5CA9mOe1fSRQ {
uid: "FzobH6xDhHhtjbfqxlHR5nTobL62"
image: "https://pbs.twimg.com/profile_images/8950378298..."
location: "Lorem ipsum, lorem ipsum"
name: "Lorem ipsum"
provider: "twitter.com"
}
How can I delete everything, including the -LGw89Lx5CA9mOe1fSRQ key programmatically?
I looked at this, but it's outdated and deprecated Firebase: removeUser() but need to remove data stored under that uid
I've also looked at this, but this requires for user to constantly sign in (I'm saving the user ID in localStorage) and it returns null on refresh if I write firebase.auth().currentUser. Data records and user accounts are created through social network providers and I can see the data both on Authentication and Database tab in the Firebase console.
I've tried with these piece of code but it does nothing.
// currentUser has a value of UID from Firebase
// The value is stored in localStorage
databaseChild.child(currentUser).remove()
.then(res => {
// res returns 'undefined'
console.log('Deleted', res);
})
.catch(err => console.error(err));
The bottom line is, I need to delete the user (with a specific UID) from the Authentication tab and from the Database at the same time with one click.
I know that there is a Firebase Admin SDK but I'm creating a Single Page Application and I don't have any back end code. Everything is being done on the front end.
Any kind of help is appreciated.
With suggestions from #jeremyw and #peter-haddad I was able to get exactly what I want. Here is the code that is hosted on Firebase Cloud Functions
const functions = require('firebase-functions'),
admin = require('firebase-admin');
admin.initializeApp();
exports.deleteUser = functions.https.onRequest((request, response) => {
const data = JSON.parse(request.body),
user = data.uid;
// Delete user record from Authentication
admin.auth().deleteUser(user)
.then(() => {
console.log('User Authentication record deleted');
return;
})
.catch(() => console.error('Error while trying to delete the user', err));
// Delete user record from Real Time Database
admin.database().ref().child('people').orderByChild('uid').equalTo(user).once('value', snap => {
let userData = snap.val();
for (let key of Object.keys(userData)) {
admin.database().ref().child('people').child(key).remove();
}
});
response.send(200);
});
Also, if you are facing CORS errors, add the mode: 'no-cors' option to your fetch() function and it will work without any problems.
The link you already found for deleting the user-login-account client-side is your only option if you want to keep the action on the client. Usually you want to keep most of the actions for things like account creation/deletion on the server for security reasons, and Firebase forces the issue. You can only delete your account if you were recently logged in, you can't have client-side start deleting old/random accounts.
The better option is to create your own Cloud Function to handle everything related to deleting a user. You would have to use the Admin SDK that you already found for this... but you could have that Cloud Function perform as many actions as you want - it will have to delete the user from the Auth tab, and delete the matching data in the Database.

Categories