Several problems. To begin with I get the following error in my devtools
WebSocket connection to 'ws://localhost:3000/cable?token=ZmllcnlAc3dhZ2dlci5jb20=' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
On my rails server, I have the following output in a doc file on the project's repo: DOWNLOAD
It was rather too large for a blockquote.
To make a long story short, the connection is timing out half the time (resetting the server fixes this). However, most of the time it seems like the subscription silently fails to fire, as even making a new article from a different browser won't update the index page. I am completely lost as to what could be causing this to fail.
Relevant code:
Rails Side ApplicationCable
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = current_user
end
private
def current_user
token = request.params[:token].to_s
email = Base64.decode64(token)
User.find_by(email: email)
end
end
end
GraphQL Channel
class GraphqlChannel < ApplicationCable::Channel
def subscribed
#subscription_ids = []
end
def execute(data)
result = execute_query(data)
payload = {
result: result.subscription? ? { data: nil } : result.to_h,
more: result.subscription?
}
#subscription_ids << context[:subscription_id] if result.context[:subscription_id]
transmit(payload)
end
def unsubscribed
#subscription_ids.each do |sid|
SwyleSchema.subscriptions.delete_subscription(sid)
end
end
private
def execute_query(data)
SwyleSchema.execute(
query: data["query"],
context: context,
variables: data["variables"],
operation_name: data["operationName"]
)
end
def context
{
current_user_id: current_user.id,
current_user: current_user,
channel: self
}
end
end
Connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = current_user
end
private
def current_user
token = request.params[:token].to_s
email = Base64.decode64(token)
User.find_by(email: email)
end
end
end
Subscription Type
module Types
class SubscriptionType < GraphQL::Schema::Object
field :article_added, Types::ArticleType, null: false, description: "An article was posted"
def article_added
end
end
end
Create Article Mutation
module Mutations
class CreateArticle < BaseMutation
argument :title, String, required: true
argument :body, String, required: true
type Types::ArticleType
def resolve(title: nil, body: nil)
snippet = body[0, 300]
article = Article.new
article.title = title
article.body = body
article.snippet = snippet
article.user = context[:current_user]
if article.save
SwyleSchema.subscriptions.trigger("articleAdded", {}, article)
# { article: article}
article
else
{ errors: article.errors.full_messages }
end
end
end
end
Apollo.js
import { HttpLink } from 'apollo-link-http';
import { ApolloLink, Observable } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { onError } from 'apollo-link-error';
import { ApolloClient } from 'apollo-client';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import introspectionQueryResultData from './fragmentTypes.json';
import ActionCable from 'actioncable';
import ActionCableLink from 'graphql-ruby-client/subscriptions/ActionCableLink';
const getCableUrl = () => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.hostname;
const port = process.env.CABLE_PORT || '3000';
const authToken = localStorage.getItem('mlToken');
debugger;
return `${protocol}//${host}:${port}/cable?token=${authToken}`;
};
const createActionCableLink = () => {
const cable = ActionCable.createConsumer(getCableUrl());
return new ActionCableLink({ cable });
};
const hasSubscriptionOperation = ({ query: { definitions } }) =>
definitions.some(
({ kind, operation }) =>
kind === 'OperationDefinition' && operation === 'subscription'
);
const getTokens = async () => {
const tokens = {
"X-CSRF-Token": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content")
};
const authToken = await localStorage.getItem("mlToken");
return authToken ? { ...tokens, Authorization: authToken } : tokens;
};
const setTokenForOperation = async operation => {
return operation.setContext({
headers: {
// eslint-disable-next-line
... await getTokens(),
}
});
};
const createLinkWithToken = () =>
new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle;
Promise.resolve(operation)
.then(setTokenForOperation)
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const createHttpLink = () => new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'include',
})
const logError = (error) => console.error(error);
const createErrorLink = () => onError(({ graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
logError('GraphQL - Error', {
errors: graphQLErrors,
operationName: operation.operationName,
variables: operation.variables,
});
}
if (networkError) {
logError('GraphQL - NetworkError', networkError);
}
})
export const createClient = (cache, requestLink) => {
const client = new ApolloClient({
link: ApolloLink.from([
createErrorLink(),
createLinkWithToken(),
ApolloLink.split(
hasSubscriptionOperation,
createActionCableLink(),
createHttpLink(),
)
// createHttpLink(),
]),
cache,
});
return client;
};
export const createCache = () => {
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
const cache = new InMemoryCache({fragmentMatcher});
if (process.env.NODE_ENV === 'development') {
window.secretVariableToStoreCache = cache;
}
return cache;
};
ARticles Index Component
import React, {Component} from 'react';
import articles from './queries/articles';
import { Query } from "react-apollo";
import {Link} from 'react-router-dom';
import ArticleTags from './article_tags';
import Subscription from './subscription';
class ArticlesIndex extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
const date = Date.now();
return (
<Query query={articles}>
{({ loading, error, data, subscribeToMore }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
const articles = data.articles;
return (
<div className="article-index-page">
<h1>Newest Articles</h1>
{ articles.map((article) => (
<div className="article-index-card" key={`${article.id}${article.title}${date}`}>
<h2 className="article-index-title">{article.title}</h2>
<h3 className="article-index-subtitle">by {article.author.username}</h3>
<p className="article-index-snippet">{article.snippet}<Link className="article-index-show-link" to={`/articles/${article.id}`}>{"...more"}</Link></p>
<ArticleTags tags={["lookAtThisTag", "othertag"] } />
<h4>{article.count} Comments {article.likeCount} Likes</h4>
</div>
))}
<Subscription subscribeToMore={subscribeToMore} />
</div>
)
}}
</Query>
);
}
}
export default ArticlesIndex;
Subscription Component (as per evilmartians)
import React, { useEffect } from 'react';
import ArticleSubscription from './subscriptions/article_added';
import { graphql } from 'react-apollo';
const Subscription = ({ subscribeToMore }) => {
useEffect(() => {
return subscribeToMore({
document: ArticleSubscription,
updateQuery: (prev, { subscriptionData }) => {
console.log("subdata: ", subscriptionData.data)
if (!subscriptionData.data) return prev;
const { articleAdded } = subscriptionData.data;
if (articleAdded) {
const alreadyInList = prev.articles.find(e => e.id === articleAdded.id);
if (alreadyInList) {
return prev;
}
return { ...prev, articles: prev.articles.concat([articleAdded]) };
}
return prev;
},
});
}, []);
return null;
};
export default Subscription;
And the subscriptions themselves
import gql from 'graphql-tag';
const ArticleSubscription = gql`
subscription ArticleSubscription {
articleAdded {
id
title
body
likers
likeCount
author {
id
username
},
currentUser {
id
username
email
}
}
}`
export default ArticleSubscription;
Related
I'm using rails as backend and react native as front end, I'm trying to upload one photo using formdata in react native and using active storage in rails to save it.
using one model name Room.rb and has_one_attached :photo.
Room.rb
class Room < ApplicationRecord
has_one_attached :photo
end
here is the params received by rails, there are two (room_name and photo)
{
"room_name"=>"Guest Room",
"photo"=>
<ActionController::Parameters {
"uri"=>"file:///Users/MyName/Library/Developer/CoreSimulator/Devices/guest_room.jpg",
"name"=>"guest_room.jpg",
"type"=>"image/jpg"
} permitted: true >
}
room_controller.rb to save and receive file as follow
def create
#room = Room.create(room_params)
if #room.save
render json: RoomSerializer.new(#room).serializable_hash, status: :created
else
render json: { errors: #room.errors }, status: :unprocessable_entity
end
end
I get an error inside #room.save, saying 'TypeError - hash key "uri" is not a Symbol:'
my expectation after I choose an image from mobile phone (client) and press save button, it will automatically download an image, this is also the reason I send using FormData from react native.
Update 2:
here is part of react native that upload photo,
const preparePhoto = (uriPhoto) => {
// ImagePicker saves the taken photo to disk and returns a local URI to it
const localUri = uriPhoto;
const name = localUri.split('/').pop();
// Infer the type of the image
const match = /\.(\w+)$/.exec(name);
const type = match ? `image/${match[1]}` : `image`;
return [name, type];
};
const createRoom = dispatch => async ({ room_name, uriPhoto }) => {
const [name, type] = preparePhoto(uriPhoto);
const photo = { uri: uriPhoto, name, type };
const room = { room_name, photo };
const formData = new FormData();
formData.append('room', JSON.stringify(room));
const config = { headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
} };
try {
const response = await serverApi.post('/rooms', formData, config);
dispatch({ type: 'clear_error' });
} catch (err) {
console.log('error: ', err);
dispatch({ type: 'add_error', payload: 'Sorry we have problem' });
}
};
update 3:
source code to choose an image and send it to context
import React, { useState } from 'react';
import Constants from 'expo-constants';
import {
ActivityIndicator,
Button,
Clipboard,
Image,
Share,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from 'expo-permissions';
const RoomUploadPhoto = ({ uriPhoto, onPhotoChange }) => {
const [uploading, setUploading] = useState(false);
const renderUploadingIndicator = () => {
if (uploading) {
return <ActivityIndicator animating size="large" />;
}
};
const askPermission = async (type, failureMessage) => {
const { status, permissions } = await Permissions.askAsync(type);
if (status === 'denied') {
alert(failureMessage);
}
};
const handleImagePicked = (pickerResult) => {
onPhotoChange(pickerResult.uri);
};
const takePhoto = async () => {
await askPermission(
Permissions.CAMERA,
'We need the camera permission to take a picture...'
);
await askPermission(
Permissions.CAMERA_ROLL,
'We need the camera-roll permission to read pictures from your phone...'
);
const pickerResult = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
});
handleImagePicked(pickerResult);
};
const pickImage = async () => {
await askPermission(
Permissions.CAMERA_ROLL,
'We need the camera-roll permission to read pictures from your phone...'
);
const pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
});
handleImagePicked(pickerResult);
};
const renderControls = () => {
if (!uploading) {
return (
<View>
<View style={styles.viewSatu}>
<Button
onPress={pickImage}
title="Pick an image from camera roll"
/>
</View>
<View style={styles.viewSatu}>
<Button onPress={takePhoto} title="Take a photo" />
</View>
</View>
);
}
};
return (
<React.Fragment>
<Text>upload photo</Text>
{renderUploadingIndicator()}
{renderControls()}
</React.Fragment>
);
};
const styles = StyleSheet.create({
viewSatu: {
marginVertical: 8
}
});
export default RoomUploadPhoto;
Make sure you post File object or base64 content to backend. Your photo is just a json object at the moment contains file path and name.
Please remove the photo param from your room_params.
def room_params
params.require(:room).permit(
:room_name
)
end
And attach your photo when you create the room:
def create
#room = Room.new(room_params)
#room.attach params[:photo]
...
I am getting this error
`Stripe::InvalidRequestError (This customer has no attached payment source):
app/controllers/subscriptions_controller.rb:24:in `create`
when I try to subscribe to a plan.
here is my code
`
class SubscriptionsController < ApplicationController
layout "subscribe"
before_action :authenticate_user!, except: [:new, :create]
def new
if user_signed_in? && current_user.subscribed?
redirect_to root_path, notice: "You are already a subscriber"
end
end
def create
Stripe.api_key = Rails.application.credentials.stripe_api_key
plan_id = params[:plan_id]
plan = Stripe::Plan.retrieve(plan_id)
token = params[:stripeToken]
customer = if current_user.stripe_id?
Stripe::Customer.retrieve(current_user.stripe_id)
else
Stripe::Customer.create(email: current_user.email, source: token)
end
subscription = customer.subscriptions.create(plan: plan.id)
options = {
stripe_id: customer.id,
stripe_subscription_id: subscription.id,
subscribed: true
}
options.merge!(
card_last4: params[:user][:card_last4],
card_exp_month: params[:user][:card_exp_month],
card_exp_year: params[:user][:card_exp_year],
card_type: params[:user][:card_type]
) if params[:user][:card_last4]
current_user.update(options)
redirect_to root_path, notice: "Your subscription was setup successfully!"
end
def destroy
customer = Stripe::Customer.retrieve(current_user.stripe_id)
customer.subscriptions.retrieve(current_user.stripe_subscription_id).delete
current_user.update(stripe_subscription_id: nil)
current_user.subscribed = false
redirect_to root_path, notice: "Your subscription has been canceled."
end
end
`
stripe.js
document.addEventListener("turbolinks:load", () => {
const publishableKey = document.querySelector("meta[name='stripe-key']").content;
const stripe = Stripe(publishableKey);
const elements = stripe.elements({
fonts: [{
cssSrc: "https://rsms.me/inter/inter-ui.css"
}],
locale: "auto"
});
const style = {
base: {
color: "#32325d",
fontWeight: 500,
fontFamily: "Inter UI, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
"::placeholder": {
color: "#CFD7DF"
}
},
invalid: {
color: "#E25950"
}
};
const card = elements.create('card', {
style
});
card.mount("#card-element");
card.addEventListener('change', ({
error
}) => {
const displayError = document.getElementById('card-errors');
if (error) {
displayError.textContent = error.message;
} else {
displayError.textContent = "";
}
});
const form = document.getElementById('payment-form');
form.addEventListener('submit', async(event) => {
event.preventDefault();
const {
token,
error
} = await stripe.createToken(card);
if (error) {
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
stripeTokenHandler(token);
}
});
const stripeTokenHandler = (token) => {
const form = document.getElementById('payment-form');
const hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
["type", "last4", "exp_month", "exp_year"].forEach(function(field) {
addCardField(form, token, field);
});
form.submit();
}
function addCardField(form, token, field) {
let hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', "user[card_" + field + "]");
hiddenInput.setAttribute('value', token.card[field]);
form.appendChild(hiddenInput);
}
});
I have all the plans and stripe Api configured correctly but something is wrong with the code. i am not very good in Js. So most of the code is a copy and paste and modified to fit my needs
i have search all over i can't find a solution. i need help.
I am currently attempting to deploy an app that uses ionic + okta. I actually started with the ionic + okta template which can be found here: https://github.com/oktadeveloper/okta-ionic-auth-example
import { Component, ViewChild } from '#angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { JwksValidationHandler, OAuthService } from 'angular-oauth2-oidc';
import * as OktaAuth from '#okta/okta-auth-js';
import { TabsPage } from '../tabs/tabs';
declare const window: any;
#IonicPage()
#Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class LoginPage {
#ViewChild('email') email: any;
private username: string;
private password: string;
private error: string;
constructor(public navCtrl: NavController, private oauthService: OAuthService) {
oauthService.redirectUri = 'http://localhost:8100';
oauthService.clientId = '0oabqsotq17CoayEm0h7';
oauthService.scope = 'openid profile email';
oauthService.issuer = 'https://dev-158606.oktapreview.com/oauth2/default';
oauthService.tokenValidationHandler = new JwksValidationHandler();
// Load Discovery Document and then try to login the user
this.oauthService.loadDiscoveryDocument().then(() => {
this.oauthService.tryLogin();
});
}
ionViewDidLoad(): void {
setTimeout(() => {
this.email.setFocus();
}, 500);
}
login(): void {
this.oauthService.createAndSaveNonce().then(nonce => {
const authClient = new OktaAuth({
clientId: this.oauthService.clientId,
redirectUri: this.oauthService.redirectUri,
url: 'https://dev-158606.oktapreview.com',
issuer: 'default'
});
return authClient.signIn({
username: this.username,
password: this.password
}).then((response) => {
if (response.status === 'SUCCESS') {
return authClient.token.getWithoutPrompt({
nonce: nonce,
responseType: ['id_token', 'token'],
sessionToken: response.sessionToken,
scopes: this.oauthService.scope.split(' ')
})
.then((tokens) => {
const idToken = tokens[0].idToken;
const accessToken = tokens[1].accessToken;
const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`;
this.oauthService.tryLogin({
customHashFragment: keyValuePair,
disableOAuth2StateCheck: true
});
this.navCtrl.push(TabsPage);
});
} else {
throw new Error('We cannot handle the ' + response.status + ' status');
}
}).fail((error) => {
console.error(error);
this.error = error.message;
});
});
}
redirectLogin() {
this.oktaLogin().then(success => {
const idToken = success.id_token;
const accessToken = success.access_token;
const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`;
this.oauthService.tryLogin({
customHashFragment: keyValuePair,
disableOAuth2StateCheck: true
});
this.navCtrl.push(TabsPage);
}, (error) => {
this.error = error;
});
}
oktaLogin(): Promise<any> {
return this.oauthService.createAndSaveNonce().then(nonce => {
let state: string = Math.floor(Math.random() * 1000000000).toString();
if (window.crypto) {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
state = array.join().toString();
}
return new Promise((resolve, reject) => {
const oauthUrl = this.buildOAuthUrl(state, nonce);
const browser = window.cordova.InAppBrowser.open(oauthUrl, '_blank',
'location=no,clearsessioncache=yes,clearcache=yes');
browser.addEventListener('loadstart', (event) => {
if ((event.url).indexOf('http://localhost:8100') === 0) {
browser.removeEventListener('exit', () => {});
browser.close();
const responseParameters = ((event.url).split('#')[1]).split('&');
const parsedResponse = {};
for (let i = 0; i < responseParameters.length; i++) {
parsedResponse[responseParameters[i].split('=')[0]] =
responseParameters[i].split('=')[1];
}
const defaultError = 'Problem authenticating with Okta';
if (parsedResponse['state'] !== state) {
reject(defaultError);
} else if (parsedResponse['access_token'] !== undefined &&
parsedResponse['access_token'] !== null) {
resolve(parsedResponse);
} else {
reject(defaultError);
}
}
});
browser.addEventListener('exit', function (event) {
reject('The Okta sign in flow was canceled');
});
});
});
}
buildOAuthUrl(state, nonce): string {
return this.oauthService.issuer + '/v1/authorize?' +
'client_id=' + this.oauthService.clientId + '&' +
'redirect_uri=' + this.oauthService.redirectUri + '&' +
'response_type=id_token%20token&' +
'scope=' + encodeURI(this.oauthService.scope) + '&' +
'state=' + state + '&nonce=' + nonce;
}
}
I can build the app using ionic cordova build ios and load it into xcode. It is properly signed and loads on my phone. I can click through to the okta login page, but when I attempt to log in, I get redirected back to the login page, and get the following errors in xcode:
API error: returned 0 width
nw_socket_connect connectx SAE_ASSOCID_ANY 0, Null, 0, Null, failed
connectx failed
TIC TCP Conn Failed Err(61)
NSURLConnection finished with error - code 1004
webView:didFailLoadWithError - -1004: Could not connect to the server
ERROR: ERROR Error: Uncaught (in promise): Error: Parameter jwks expected!
ValidateSignature#http://localhost...
ProcessIdToken#http://localhost...
...
I didn't modify the login code other than to use setRoot vs push for the successful redirect. The app works when I run it in browser or the ionic lab. I noticed that the source code uses localhost as an event url. I assume that's the address of the cordova browser - does this need to change?
I feel like it's a setting in xcode. I am trying to build for iOS9 with xcode10.
When I try to make a post I'm getting a 400 (Bad Request) Error.
I'm trying to post a new Player into a Team.
My controller
def create
#teams = Team.find(params[:team_id])
#players = #teams.players.new(player_params)
render json: #players
end
private
def player_params
params.require(:player).permit(:name, :photo, :nationality)
end
The function
_handleSubmit = async (e) => {
e.preventDefault();
const teamId = this.props.match.params.id;
const payload = this.state.players;
console.log(payload);
try {
const res = await axios.post(`/api/teams/${teamId}/players`, payload);
} catch (err) {
console.log(err);
}
};
_handleChange = (e) => {
const newState = { ...this.state.players };
newState[e.target.name] = e.target.value;
this.setState({ players: newState });
};
You need to strong compare what the body you send, and what the body you expect to receive on the server?
On backend you can send not only error code, and error message, or field identifier which error occurs.
And don't forget to set await statement if you will work with query result:
const res = await axios.post(`/api/teams/${teamId}/players`, payload);
I am attempting to create a stripe user upon user creation for firebase, I keep receiving this error (error displayed below). the code for the function is also displayed below. if I need to post the database structure I will do so, I currently do not have any structure for stripe customer (this might be where issue occurs). if anyone can assist I would greatly appreciate it.
Error:
Error: Reference.child failed: First argument was an invalid path = "/stripe_customers/${data.uid}/customer_id". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"
at Object.exports.validatePathString (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/core/util/validation.js:282:15)
at Object.exports.validateRootPathString (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/core/util/validation.js:293:13)
at Reference.child (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/api/Reference.js:72:30)
at Database.ref (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/api/Database.js:60:54)
at stripe.customers.create.then (/user_code/index.js:41:29)
at tryCatcher (/user_code/node_modules/stripe/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0 (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:693:18)
at Async._drainQueue (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:133:16)
at Async._drainQueues (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:672:20)
at tryOnImmediate (timers.js:645:5)
at processImmediate [as _immediateCallback] (timers.js:617:5)
Functions:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const logging = require('#google-cloud/logging')();
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
//[START chargecustomer]
//charge the stripe customer whenever an amount is written to the realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}').onWrite((event) => {
const val = event.data.val();
if (val === null || val.id || val.error) return null;
return admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
const amount = val.amount;
const idempotency_key = event.params.id;
let charge = {amount, currency, customer};
if (val.source !== null) charge.source = val.source;
return stripe.charges.create(charge, {idempotency_key});
}).then((response) => {
return event.data.adminRef.set(response);
}).catch((error) => {
return event.data.adminRef.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: events.params.userId});
});
});
// [end chargecustomer]]
// when user is created register them with stripe
exports.createStripeCustomer = functions.auth.user().onCreate((event) => {
const data = event.data;
return stripe.customers.create({
email: data.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${data.uid}/customer_id`).set(customer.id);
});
});
// add a payment source (card) for a user by writing a stripe payment source token to realtime database
exports.addPaymentSource =. functions.database.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((event) => {
const source = event.data.val();
if (sourve === null) return null;
return admin.database.ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source});
}).then((response) => {
return event.data.adminRef.parent.set(response);
}, (error) => {
return event.data.adminRef.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: event.params.userId});
});
});
// when a user deletes their account, clean up after the
exports.cleanupUser = functions.auth.user().onDelete((event) => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.del(customer.customer_id);
}).then(() => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).remove();
});
});
function reportError(err, context = {}) {
const logName = 'errors';
const lof = logging.log(logName);
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
reject(error);
}
resolve();
});
});
}
// end [reportError]
// sanitize the error message for the user
function userFacingMessage(error) {
returnerror.type ? error.message : 'an error occurred, developers have been altered';
}
Database Structure:
In your code you have this:
ref('/stripe_customers/${event.params.userId}/customer_id')
this ${event.params.userId} should give you the value of the wildcard, but since you are using ' it is including the $ in the path also. So you need to change it like this:
ref(`/stripe_customers/${event.params.userId}/customer_id`)
by changing ' to `