Meteor 1.3 + React Stripe Subscription - meteor

I'm following along TheMeteorChef's Building a SaaS with Meteor: Stripe which is built with blaze templates. Tried to use react instead but I think I failed somewhere along the way. I've gotten to about half of the part 1 of 2 but enough to test if signing up with plan should work or not. Well, it doesn't work but also doesn't give any errors in console... I have very little experience, just started actually, so I'm hoping I could get some help. Thank you.
~/client/helpers/stripe.js
Meteor.startup(function() {
const stripeKey = Meteor.settings.public.stripe.testPublishableKey;
Stripe.setPublishableKey(stripeKey);
STRIPE = {
getToken: function(domElement, card, callback) {
Stripe.card.createToken(card, function(status, response) {
if(response.error) {
Bert.alert(response.error.message, "danger");
} else {
STRIPE.setToken(response.id, domElement, callback);
}
});
},
setToken: function(token, domElement, callback) {
$(domElement).append($('<input type="hidden" name="stripeToken" />').val(token));
callback();
}
}
});
~/client/components/SignUp.jsx
import React, {Component} from 'react';
import PlanSelectForm from '../components/PlanSelectForm.jsx';
import CreditCardForm from '../components/CreditCardForm.jsx';
export default class SignUp extends Component {
componentDidMount() {
$.validator.addMethod('usernameRegex', function(value, element) {
return this.optional(element) || /^[a-zA-Z0-9-_]+$/i.test(value);
}, "Username must contain only letters, numbers, underscores and dashes.");
$('#application-signup').validate({
rules: {
username: {
required: true,
usernameRegex: true,
minlength: 6
},
emailAddress: {
required: true,
email: true
},
password: {
required: true,
minlength: 6
}
},
messages: {
username: {
required: 'You can\'t leave this empty',
usernameRegex: 'You can use letter, numbers, underscores, and dashes.',
minlength: 'Too short. Use at least 6 characters.'
},
emailAddress: {
required: 'You can\'t leave this empty',
email: 'Email is invalid or already taken.'
},
password: {
required: 'You can\'t leave this empty',
minlength: 'Too short. Use at least 6 characters.'
}
},
handleSubmit: function() {
STRIPE.getToken('#application-signup', {
number: $('[data-stripe="cardNumber"]').val(),
exp_month: $('[data-stripe="expMo"]').val(),
exp_year: $('[data-stripe="expYr"]').val(),
cvc: $('[data-stripe="cvc"]').val()
}, function() {
const customer = {
username: $('[name="username"]').val(),
emailAddress: $('[name="emailAddress"]').val(),
password: $('[name="password"]').val(),
plan: $('[name="selectPlan"]:checked').val(),
token: $('[name="stripeToken"]').val()
};
const submitButton = $('input[type="submit"]').button('loading');
Meteor.call('createTrialCustomer', customer, function(error, response) {
if(error) {
alert(error.reason);
submitButton.button('reset');
} else {
if(response.error) {
alert(response.message);
submitButton.button('reset');
} else {
Meteor.loginWithPassword(customer.emailAddress, customer.password, function(error) {
if(error) {
alert(error.reason);
submitButton.button('reset');
} else {
Router.go('/chart');
submitButton.button('reset');
}
});
}
}
});
});
}
});
}
render() {
console.log(this);
return (
<form id="application-signup" className="signup">
<h4>Account details</h4>
<div className="form-group">
<label for="username">Username</label>
<input type="text"
name="username"
className="form-control"
placeholder="Username" />
</div>
<div className="form-group">
<label for="emailAddress">Email Address</label>
<input type="email"
name="emailAddress"
className="form-control"
placeholder="Email Address" />
</div>
<div className="form-group">
<label for="password">Password</label>
<input type="password"
name="password"
className="form-control"
placeholder="Password" />
</div>
<h4 className="page-header">Payment Information</h4>
<label>Which plan sounds <em>amazing</em>?</label>
<PlanSelectForm />
<div className="form-group">
<CreditCardForm />{/* data={signup} /> */}
</div>
<div className="form-group">
<input type="submit"
className="btn btn-success btn-block"
data-loading-text="Setting up your trial..."
value="Put me on the rocketship" />
</div>
</form>
)
}
}
Note: In the tutorial, TheMeteorChef uses a dynamic template for CreditCardForm with data="signup" context. I think he mentions the CC template will be used again after but I haven't gone that far yet. Anyways, I didn't know what "signup" means... so I left it commented out. If you know, please let me know about that as well.
~/client/components/PlanSelectForm.jsx
import React, {Component} from 'react';
export default class PlanSelectForm extends Component {
componentDidMount() {
const firstPlanItem = $('.select-plan a:first-child');
firstPlanItem.addClass('active');
firstPlanItem.find('input').prop('checked', true);
}
plans() {
return Meteor.settings.public.plans;
}
handleClickItem(e) {
const parent = $(e.target).closest('.list-group-item');
console.log(parent);
parent.addClass('active');
$('.list-group-item').not(parent).removeClass('active');
$('.list-group-item').not(parent).find('input[type="radio"]').prop('checked', false);
parent.find('input[type="radio"]').prop('checked', true);
}
render() {
let plans = this.plans();
if(!plans) {
return(<div>loading...</div>);
}
return (
<div className="list-group select-plan">
{plans.map((plan) => {
return (
<a key={plan.id}
href="#"
className="list-group-item"
onClick={this.handleClickItem.bind(this)}>
<input key={plan.id}
type="radio"
ref="selectPlan"
id={`selectPlan_${plan.id}`}
value={plan.name} />
{plan.name} {plan.amount.usd}/{plan.interval}
</a>
)
})}
</div>
)
}
}
~/client/components/CreditCardForm.jsx
import React, {Component} from 'react';
export default class CreditCardForm extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-12">
<div className="form-group">
<label className="text-success">
<i className="fa fa-lock"></i> Card Number
</label>
<input type="text"
data-stripe="cardNumber"
className="form-control card-number"
placeholder="Card Number" />
</div>
</div>
</div>
<div className="row">
<div className="col-xs-4">
<label>Exp. Mo.</label>
<input type="text"
data-stripe="expMo"
className="form-control exp-month"
placeholder="Exp. Mo." />
</div>
<div className="col-xs-4">
<label>Exp. Yr.</label>
<input type="text"
data-stripe="expYr"
className="form-control exp-year"
placeholder="Exp. Yr." />
</div>
<div className="col-xs-4">
<label>CVC</label>
<input type="text"
data-stripe="cvc"
className="form-control cvc"
placeholder="CVC" />
</div>
</div>
</div>
)
}
}
~/server/signup.js
Meteor.methods({
createTrialCustomer: function(customer) {
check(customer, {
name: String,
emailAddress: String,
password: String,
plan: String,
token: String
});
const emailRegex = new RegExp(customer.emailAddress, 'i');
const usernameRegex = new RegExp(customer.username, 'i');
const lookupEmail = Meteor.users.findOne({'emails.address': emailRegex});
const lookupUser = Meteor.users.findOne({'username': usernameRegex});
if(!lookupEmail) {
if(!lookupUser) {
const newCustomer = new Future();
Meteor.call('stripeCreateCustomer', customer.token, customer.emailAddress, function(error, stripeCustomer) {
if(error) {
console.log(error);
} else {
const customerId = stripeCustomer.id,
plan = customer.plan;
Meteor.call('stripeCreateSubscription', customerId, plan, function(error, response) {
if(error) {
console.log(error);
} else {
try {
const user = Accounts.createUser({
username: customer.username,
email: customer.emailAddress,
password: customer.password
});
const subscription = {
customerId: customerId,
subscription: {
plan: {
name: customer.plan,
used: 0
},
payment: {
card: {
type: stripeCustomer.sources.data[0].brand,
lastFour: stripeCustomer.sources.data[0].last4
},
nextPaymentDue: response.current_period_end
}
}
}
Meteor.users.update(user, {
$set: subscription
}, function(error, response) {
if(error) {
console.log(error);
} else {
newCustomer.return(user);
}
});
} catch(exception) {
newCustomer.return(exception);
}
}
});
}
});
return newCustomer.wait();
} else {
throw new Meteor.Error('username-exists', 'Sorry, that username is already active!');
}
} else {
throw new Meteor.Erro('email-exists', 'Sorry, that email is already active!')
}
},
})
~/server/stripe.js
const secret = Meteor.settings.private.stripe.testSecretKey;
const Stripe = StripeAPI(secret);
Meteor.methods({
stripeCreateCustomer: function(token, email) {
check(token, String);
check(email, String);
const stripeCustomer = new Future();
Stripe.customers.create({
source: token,
email: email
}, function(error, customer) {
if(error){
stripeCustomer.return(error);
} else {
stripeCustomer.return(customer);
}
});
return stripeCustomer.wait();
},
stripeCreateSubscription: function(customer, plan) {
check(customer, String);
check(plan, String);
const stripeSubscription = new Future();
Stripe.customers.createSubscription(customer, {
plan: plan
}, function(error, subscription) {
if(error) {
stripeSubscription.return(error);
} else {
stripeSubscription.return(subscription);
}
});
return stripeSubscription.wait();
}
})
packages
"dependencies": {
"meteor-node-stubs": "~0.2.0",
"react": "^15.0.2",
"react-addons-css-transition-group": "^15.0.2",
"react-dom": "^15.0.2",
"react-mounter": "^1.2.0"
},
accounts-base
accounts-password
session
check
random
kadira:flow-router
ultimatejs:tracker-react
meteortoys:allthings
fourseven:scss
fortawesome:fontawesome
themeteorchef:bert
themeteorchef:jquery-validation
momentjs:moment
mrgalaxy:stripe
Thanks for reading, I hope that it wasn't painful.

did you import mrgalaxy:stripe ?
it will be something like
import { StripeAPI } from 'meteor/mrgalaxy:stripe'
I guess.
anw, you can install by npm :
npm install stripe
import Stripe from 'stripe'
then
Stripe('your_secret_key')

Related

How to update the state in pinia with Vue 3

I'm trying to save the input values in the Pinia store, but the state is not updating. So I want onSubmit function to save the input values in a store.
My code :
Create.vue
<script setup>
import { reactive } from "vue";
import { useCounterStore } from '#/stores/counter';
const counter = useCounterStore();
const form = reactive({
first: "",
second: "",
email: ""
});
const onSubmit = (e) => {
e.preventDefault();
counter.$patch({ firstName: form.first.value });
counter.$patch({ secondName: form.second.value });
counter.$patch({ email: form.email.value });
}
</script>
<template>
<form #submit="onSubmit">
{{ counter.getFirst + 'MYFIRST' }} {{ counter.getSecond + 'MYSECOND' }} {{ counter.getEmail + 'MYEMAIL' }}
<div class="row mt-4">
<div class="col-6">
<label for="exampleFormControlInput1" class="form-label">First</label>
<input v-model="form.first" type="text" class="form-control" id="exampleFormControlInput1"
placeholder="First name">
</div> <div class="col-6">
<label for="exampleFormControlInput1" class="form-label">Second</label>
<input v-model="form.second" type="text" class="form-control" id="exampleFormControlInput1"
placeholder="Second name">
</div>
<div class="col-6 mt-2">
<label for="exampleFormControlInput1" class="form-label">Email</label>
<input v-model="form.email" type="email" class="form-control" id="exampleFormControlInput1"
placeholder="name#example.com">
</div>
<div class="col-12 mt-3">
<button #click="onSubmit" type="button" class="btn btn-dark push- right">Create</button>
<button type="button" class="btn btn-dark">All users</button>
</div>
</div>
</form>
</template>
Pinia store: counter.js
import { ref, computed, reactive } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
let firstName = ref('');
let secondName = ref('');
let email = ref('');
const getFirst = computed(() => firstName.value)
const getSecond = computed(() => secondName.value)
const getEmail = computed(() => email.value)
function increment() {
count.value++
}
return { count, getFirst, getSecond, getEmail, increment }
})
i am not sure how your store look like... but it should by something like:
`
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
}
})`
in your component, change the name of the store instance, lets say counterStore.
const counterStore = useCounterStore();
and now you can access the state by name:
counterStore.counter++
while this will work, it is not the best practice...
you should use actions to manipulate the state...
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
and then you can call the action like a regular method:
counterStore.increment()
I think you're in the process of learning Vue, don't give up!
Give a feedback in comment and put your post on "solved" status if this help you.
The solution :
In store file :
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', {
state: () => ({
loginForm: {
firstName: '',
secondName: '',
email: ''
}
})
});
In Create.vue :
<script setup>
import { reactive } from "vue";
import { useAuthStore } from '#/stores/auth';
const authstore = useAuthStore();
const form = reactive({
first: "",
second: "",
email: ""
});
...
const onSubmit = (e) => {
e.preventDefault();
authStore.loginForm.firstName = form.first;
authStore.loginForm.secondName = form.second;
authStore.loginForm.email = form.email;
}
...
<script/>

VUE - FIREBASE | How to get the user profile from the realtime database

I got the user profile from the Realtime database but when I have more than 1 account I get the second user profile too.
Here below you see the data from 2 users. But I want to get the user that is loggend in and that is the currentUser
The ID is the currentUser
This is the Realtime database:
This is my Profile.vue page:
<div class="container" v-for="profileData of profile" :key="profileData['.key']">
<div v-if="seen" class="row">
<div class="col">
<div class="card card-border" style="width: 30rem;">
<div class="card-body">
<h4 class="card-title text-center mb-4">Personal information</h4>
<p class="card-text">ID: {{profileData.CurrentUser}}</p>
<p class="card-text">First name: {{profileData.firstName}}</p>
<p class="card-text">Last name: {{profileData.lastName}}</p>
<p class="card-text">Phone number: {{profileData.phoneNumber}}</p>
<p class="card-text">Adress: {{profileData.adress}}</p>
<p class="card-text">Citizenship: {{profileData.citizenship}}</p>
<p class="card-text">Personal email: {{profileData.personalEmail}}</p>
</div>
</div>
</div>
<div class="col"></div>
<div class="col">
<div class="card card-border" style="width: 30rem;">
<div class="card-body">
<h4 class="card-title text-center mb-3">Business information</h4>
<p>Company name: {{profileData.companyName}}</p>
<p>Chamber Of Commerce Number: {{profileData.chamberOfCommerceNumber}}</p>
<p>Street: {{profileData.street}}</p>
<p>House number: {{profileData.houseNumber}}</p>
<p>ZIP code: {{profileData.zipCode}}</p>
<p>Location: {{profileData.location}}</p>
<p>Company email: {{profileData.companyEmail}}</p>
</div>
</div>
</div>
</div>
</div>
I added a if/else in the created() section below.
And this is the script:
<script>
import firebase from "firebase";
import { db } from '../../config/db';
export default {
data() {
return {
email: "",
password: "",
profileData: [],
isHidden: true,
seen: true,
isLoggedIn: false
}
},
firebase: {
profile: db.ref('profile')
},
methods: {
resetPassword() {
const auth = firebase.auth();
auth.sendPasswordResetEmail(auth.currentUser.email).then(() => {
console.log('Email send');
// Email sent.
}).catch((error) => {
// An error happened.
console.log(error);
});
}
},
created() {
if(firebase.auth().currentUser) {
this.isLoggedIn = true;
this.currentUser = firebase.auth().currentUser.email;
}
var user = firebase.auth().currentUser;
if (this.user == this.profileData.CurrentUser) {
this.seen = true;
} else {
this.seen = false;
}
}
};
</script>
In this Profile.vue page I have the add function:
AddProfile() {
console.log(JSON.stringify(this.profileData) + this.currentUser)
this.$firebaseRefs.profile.push({
firstName: this.profileData.firstName,
lastName: this.profileData.lastName,
phoneNumber: this.profileData.phoneNumber,
adress: this.profileData.adress,
citizenship: this.profileData.citizenship,
personalEmail: this.profileData.personalEmail,
companyName: this.profileData.companyName,
chamberOfCommerceNumber: this.profileData.chamberOfCommerceNumber,
street: this.profileData.street,
houseNumber: this.profileData.houseNumber,
zipCode: this.profileData.zipCode,
location: this.profileData.location,
companyEmail: this.profileData.companyEmail,
CurrentUser: this.currentUser
})
this.profileData.firstName = '';
this.profileData.lastName = '';
this.profileData.phoneNumber = '';
this.profileData.adress = '';
this.profileData.personalEmail = '';
this.profileData.companyName = '';
this.profileData.chamberOfCommerceNumber = '';
this.profileData.street = '';
this.profileData.houseNumber = '';
this.profileData.zipCode = '';
this.profileData.location = '';
this.profileData.companyEmail = '';
this.CurrentUser = '';
window.scrollTo(0,0, 0,0);
console.log('Added to database');
/* Waiting for 2 seconds here */
this.$router.push('/internship')
},
Apparently you are using Vuefire.
As you will see in the Vuefire documentation, by doing
firebase: {
profile: db.ref('profile')
},
you are using a declarative biding on the profile Realtime Database node and therefore it is normal that you get all the children of this node.
If you just want to display the node corresponding to the current user, you could use the programmatic binding, along the following lines:
<template>
<div class="home">
{{user}}
<ol></ol>
</div>
</template>
<script>
import { db } from "../firebase";
const profile = db.ref("profile");
export default {
name: "demo",
data() {
return {
user: null,
id: null
};
},
watch: {
id: {
immediate: true,
handler(id) {
this.$rtdbBind("user", profile.child(id));
}
}
},
created() {
if (firebase.auth().currentUser) {
this.id = currentUser.uid;
}
}
};
</script>

Firebase UpdateEmail returning updateEmail failed: First argument "email" must be a valid string

I have a email field in my vue js componont. When the component loads its taking the email value what i have added during the registration time. But when I tried to update my email to a new emai using updateEmail its retruning an error code: "auth/argument-error", message: "updateEmail failed: First argument "email" must be a valid string.".
<template>
<div>
<form #submit.prevent="onUpdateProfile">
<input type="email" v-model="profile.email" placeholder="Enter Your Email..." class="from-input" />
<button type="submit">submit</button>
</form>
</div>
</template>
data() {
return {
profile: {
email: ""
}
};
},
methods:{
onUpdateProfile() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
user.updateEmail({
email: this.profile.email
})
.then(() => {})
.catch(error => {
console.log(error);
});
}
}
},
created() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.profile.email = user.email;
}
}
}
can you try changing this
user.updateEmail({
email: this.profile.email
})
to this?
user.updateEmail(this.profile.email)

Updating user profile information with redux in firebase

I am trying to use Redux in my React application to update the user profile within my Firebase database from my react component.
This is my component:
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { firestoreConnect } from "react-redux-firebase";
import { compose } from "redux";
import { editProfile } from "../../store/actions/editProfileActions";
class UserProfile extends Component {
state = {
firstName:"",
initials:"",
lastName:""
};
onChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
onSubmit = e => {
e.preventDefault();
console.log(this.state);
this.props.editProfile(this.state);
}
render() {
const { auth, profile } = this.props;
console.log(profile);
if (auth.isEmpty) return <Redirect to="/home" />;
return (
<div className="container">
<form onSubmit={this.onSubmit} className="white">
<h5 className="grey-text text-darken-3">Edit Profile</h5>
<div className="input-field">
<label htmlFor="title">First Name: {profile.firstName}</label>
<input type="text" id="firstName" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Initials: {profile.initials}</label>
<input type="text" id="initials" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Last Name: {profile.lastName}</label>
<input type="text" id="lastName" onChange={this.onChange} />
</div>
<div className="input-field">
<button className="btn black z-depth-0">Submit</button>
{ }
</div>
</form>
</div>
)
}
};
const mapStateToProps = state => {
return {
auth: state.firebase.auth,
profile: state.firebase.profile,
};
};
const mapDispatchToProps = dispatch => {
return {
editProfile: edit => dispatch(editProfile(edit))}
}
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect([
{ collection: "profile"}
])
)(UserProfile);
The component correctly displays the current user information.
This is the action I have set up:
return async (dispatch, getState, { getFirestore, getFirebase }) => {
const firebase = getFirebase();
const user = await firebase
.auth()
.currentUser
.updateProfile({
firstName: profile.firstName
});
dispatch({ type: "EDITPROFILE_SUCCESS", user })
console.log("user = " + profile.firstName);
};
}
When I log the entered profile.firstName I get the entered data.
And my reducer:
const editProfileReducer = (state, action) => {
switch (action.type) {
case "EDITPROFILE_ERROR":
return {
...state,
editError: action.error
};
case "EDITPROFILE_SUCCESS":
return {
...state
};
default:
return state;
}
}
export default editProfileReducer;
Any idea what I am missing here?
In your reducer change the like below
case "EDITPROFILE_SUCCESS":
return {
...state,
user:action.user
};
Above is if you want to update the whole user object
If you want to change only name then
Let’s assume that profileName is in user object then
case "EDITPROFILE_SUCCESS":
return {
...state,
user:Object.assign({}, state.user, profileName:action.user.profileName)
};

The email address is badly formatted firebase vue.js

Getting an error "The email address is badly formatted." when trying to use Vue.js with firebase to create a login page.
Here's my code:
<template>
<div class = "sign-up">
<p> Let's create a new account</p>
<input type="email" v-model="email" placeholder="Email"> <br>
<input type="password" v-model="password" placeholder="Password"> <br>
<button v-on:click="signUp">Sign Up</button>
<br>
</div>
</template>
<script>
import firebase from 'firebase'
export default {
name:'Signup',
data: function() {
return {
email: '',
password: '',
}
},
methods: {
signUp: function() {
firebase.auth().createUserWithEmailAndPassword(this.email, this.password).then(
function (user) {
alert('Your account has been created')
},
function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode == 'auth/weak-password') {
alert('The password is too weak.');
} else {
alert(errorMessage);
}
console.log(error);
});
}
}
}
</script>
I did make sure that I have enabled the authentication part at the firebase console .
Don't know why still get this error
Help please
Thank God I solved it.
The problem is solved by adding
firebase.initializeApp(config);
right after
import firebase from 'firebase'
since I have already initialize Firebase in other files
the problem might be caused by javascript loading asynchronously .
This works well. I tired to solve your ones. I have brought official firebase auth sample. Your user was not defined and while importing you must have use {} to prevent .auth() error.
<template>
<div class = "sign-up">
<p> Let's create a new account</p>
<input type="email" v-model="email" placeholder="Email">
<input type="password" v-model="password" placeholder="Password">
<button v-on:click="signUp">Sign Up</button>
</div>
</template>
<script>
import {fb} from '../firebase';
export default {
name:'Signup',
data() {
return {
email: "",
password: "",
}
},
methods: {
signUp: function() {
fb.auth().createUserWithEmailAndPassword(this.email, this.password)
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode == 'auth/weak-password') {
alert('The password is too weak.');
} else {
alert(errorMessage);
}
console.log(error);
});
}
}
}
</script>

Resources