I enjoy the simplicity of the stripe APIs but I'm not sure if it's possible to retrieve nested objects. I'd love to be able to retrieve all products and prices to display them publically on my website.
I can list all prices and products like this:
stripe.Price.list(limit=3)
stripe.Products.list(limit=3)
On the frontend, I then have to write some logic that associates the prices with the products, makes the distinctions for monthly and yearly prices etc. I'd love to have this nested. Is this possible?
I'm also not entirely sure if it is safe to expose the returned information publically (API key is obviously hidden). I'd love some more info on this.
Whenever an id is present in an object, the expand parameter can be called to also return that item.
This can be further explored in the Stripe docs for Expanding Responses: https://stripe.com/docs/expand
A solution in nodejs would be the following:
const productPriceData = await stripe.prices.list({
expand: ['data.product'], // 🎉 Give me the product data too!
})
I actually came across the same issue and documented how I got to this solution here: https://blog.focusotter.com/fetch-both-price-and-product-data-from-stripe-in-a-single-api-call
On the frontend, I then have to write some logic that associates the prices with the products, makes the distinctions for monthly and yearly prices etc. I'd love to have this nested. Is this possible?
The approach would be to first list the Products from the API, then iterate over them and retrieve all the Prices associated with a given Product:
List all Prices: https://stripe.com/docs/api/prices/list?lang=python
While specifying the Product ID: https://stripe.com/docs/api/prices/list?lang=python#list_prices-product
The Product object doesn't contain a list of its Prices, so there is really no way around making the two separate requests: https://stripe.com/docs/api/products/object
I'm also not entirely sure if it is safe to expose the returned information publically (API key is obviously hidden). I'd love some more info on this.
This is a good question, since generally speaking only objects retrievable with a publishable key are safe to directly consume client-side from the Stripe API. In those cases the API reference marks the properties on the object as "retrievable with a publishable key", for example:
Retrieve a PaymentIntent client-side: https://stripe.com/docs/js/payment_intents/retrieve_payment_intent
Properties retrievable: https://stripe.com/docs/api/payment_intents/object
But, as long as your server-side endpoint doesn't directly return the Product/Price objects retrieved using your secret key, and instead returns a filtered version with just the required properties you should be fine!
Do it the other way around; get all the prices, then get the corresponding product.
Create your Stripe instance first, and then request the data from your function
// index.ts
import { getStripeProduct, getStripeProducts } from './products'
import * as express from 'express'
import Stripe from 'stripe'
const router = express.Router()
const stripe = (apiKey:string) => {
return new Stripe(apiKey, {
apiVersion: "2020-08-27",
typescript: true
});
}
// GET a product
router.get('/product/:id', async (req: express.Request, res: express.Response) => {
console.log(`API call to GET /public/product/${req.params.id}`)
await getStripeProduct(stripe(STRIPE_SECRET_KEY)), req.params.id )
.then((product) => {
respond(res, {data: product})
})
.catch((error) => {
respond(res, {code: error.statusCode, message: error.message})
})
})
// GET a product list
router.get('/products', async (req: express.Request, res: express.Response) => {
console.log('API call to GET /public/products')
await getStripeProducts(stripe(STRIPE_SECRET_KEY))
.then((products) => {
respond(res, {data: products})
})
.catch((error) => {
console.error(error)
respond(res, {code: error.statusCode, message: error.message})
})
})
// products.ts
import { ProductModel } from '../../models'
import { Stripe } from 'stripe'
export async function getStripeProduct(stripe: Stripe, id: string) {
return new Promise((resolve, reject) => {
stripe.prices.retrieve(id)
.then(async (price) => {
const product: ProductModel = {
id: price.id,
name: 'Unnamed Product',
price: price.unit_amount,
recurring: price.recurring || undefined,
metadata: price.metadata
}
if(typeof(price.product) == 'string') {
await stripe.products.retrieve(price.product)
.then((stripeProduct) => {
product.name = stripeProduct.name
product.caption = stripeProduct.description || undefined
product.images = stripeProduct.images || undefined
product.active = stripeProduct.active
resolve(product)
})
}
resolve(product)
})
.catch((error:any) => {
reject(error)
})
})
}
export async function getStripeProducts(stripe: Stripe) {
return new Promise((resolve, reject) => {
stripe.prices.list()
.then(async (prices) => {
const products: ProductModel[] = []
for(let price of prices.data) {
const product: ProductModel = {
id: price.id,
name: 'Unnamed Product',
price: price.unit_amount,
recurring: price.recurring || undefined,
metadata: price.metadata
}
if(typeof price.product == 'string') {
await stripe.products.retrieve(price.product)
.then((stripeProduct) => {
product.name = stripeProduct.name
product.caption = stripeProduct.description || undefined
product.images = stripeProduct.images || undefined
product.active = stripeProduct.active
products.push(product)
})
}
}
resolve(products)
})
.catch((error:any) => {
reject(error)
})
})
}
// Product Model
export interface ProductModel {
id: string;
name: string;
caption?: string;
description?: string;
active?: boolean;
images?: string[];
price?: string | number | null;
recurring?: {
interval: string,
interval_count: number
},
metadata?: object
}
export class ProductModel implements ProductModel {
constructor(productData?: ProductModel) {
if (productData) {
Object.assign(this, productData);
}
}
}
NB: I have a utility function called respond that standarizes our API responses. You can just res.send the response
Read ttmarek's answer first. However, note that the pages do not seem to provide a full example if you need all the answers. From "List all Prices":
Each entry in the array is a separate price object. If no more prices
are available, the resulting array will be empty.
As it states, and is also stated here and in the docs, to get all of them, you need to loop until the resulting array is empty....
Here is some code that will get you all the products and all the prices...
import stripe
stripe.api_key = stripe_test_private_key
def get_all_stripe_objects(stripe_listable):
objects = []
get_more = True
starting_after = None
while get_more:
#stripe.Customer implements ListableAPIResource(APIResource):
resp = stripe_listable.list(limit=100,starting_after=starting_after)
objects.extend(resp['data'])
get_more = resp['has_more']
if len(resp['data'])>0:
starting_after = resp['data'][-1]['id']
return objects
all_stripe_products = get_all_stripe_objects(stripe.Product)
all_stripe_prices = get_all_stripe_objects(stripe.Price)
Related
I'm trying to insert a document into a collection. I want the document to have a attribute of type reference to insert into the collection. But every time I insert into the collection, it comes out as a string or an object. How can I programmatically insert a reference typed value?
It's definitely possible to do it in the UI:
Probably the simplest solution is to set the value of a reference key to a doc(collection/doc_key) because a DocumentReference is needed.
Example code:
post = {
content: "content...",
title: "impressive title",
user: db.doc('users/' + user_key),
};
db.collection('posts').add(post)
I was trying to figure this out today and the solution I came to was to use the .doc() to create a doc reference
firebase.firestore()
.collection("applications")
.add({
property: firebase.firestore().doc(`/properties/${propertyId}`),
...
})
This will store a DocumentReference type on the property field so when reading the data you will be able to access the document as so
firebase.firestore()
.collection("applications")
.doc(applicationId)
.get()
.then((application) => {
application.data().property.get().then((property) => { ... })
})
This is the model class to store in firestore.
import { AngularFirestore, DocumentReference } from '#angular/fire/firestore';
export class FlightLeg {
date: string;
type: string;
fromRef: DocumentReference; // AYT Airport object's KEY in Firestore
toRef: DocumentReference; // IST {key:"IST", name:"Istanbul Ataturk Airport" }
}
I need to store FlightLeg object with reference value. In order to do this:
export class FlightRequestComponent {
constructor(private srvc:FlightReqService, private db: AngularFirestore) { }
addFlightLeg() {
const flightLeg = {
date: this.flightDate.toLocaleString(),
type: this.flightRevenue,
fromRef: this.db.doc('/IATACodeList/' + this.flightFrom).ref,
toRef: this.db.doc('/IATACodeList/' + this.flightTo).ref,
} as FlightLeg
.
..
this.srvc.saveRequest(flightLeg);
}
The service which can save the object with referenced to another object into firestore:
export class FlightReqService {
.
..
...
saveRequest(request: FlightRequest) {
this.db.collection(this.collRequest)
.add(req).then(ref => {
console.log("Saved object: ", ref)
})
.
..
...
}
}
The value of the field must be of type DocumentReference. It looks like you're putting some other object in there that has a property called id that's a string.
It seems there may have been a recent update that has made the above answers outdated now. Funnily enough, the solution is even easier now. They've removed the .ref option, but the ref is automatically gotten now.
So you can just do:
const member = this3.$Firestore.doc('users/' + user2.user.uid);
this3.$Firestore.collection('teams').doc(this3.teamName).set({
name: this3.teamName,
members: [member],
});
Where member is the doc reference, simple as that. (Ignore the this3 lol.)
Is there a way to check if a sub collection exists in firestore for nodejs?
Currently I am using doc.exists for documents but I need to check if a subcolletion exists within a document in order to write some data or not.
Yes, there is. You can use docs.length to know if the subcollection exists.
I made a sample to guide you, hope it helps.
this.db.collection('users').doc('uid')
.get().limit(1).then(
doc => {
if (doc.exists) {
this.db.collection('users').doc('uid').collection('friendsSubcollection').get().
then(sub => {
if (sub.docs.length > 0) {
console.log('subcollection exists');
}
});
}
});
Mateus' Answer didn't help me. Probably it has been changed over the time.
.collection(..).get() returns a QuerySnapshot which has the property size, so I just did:
admin.firestore
.collection('users')
.doc('uid')
.collection('sub-collection')
.limit(1)
.get()
.then(query => query.size);
To be more precise:
const querySnapshot = await admin.firestore().collection('users').doc('uid').collection('sub-collection').limit(1).get()
if (querySnapshot.empty) {console.log('sub-collection not existed')}
This is how I was able to check if a collection exists?
I target the document path first, then if it exists, It means the collection afterwards exists and I can access it.
> db.collection("collection_name").document("doc_name").get()
> .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
> #Override
> public void onComplete(#NonNull Task<DocumentSnapshot> task) {
> if(task.isSuccessful()){
> DocumentSnapshot result = task.getResult();
> if(result.exists()){
> *//this means the document exist first, hence the
> //collection afterwards the doc_name will
> //exist...now I can access the collection*
> db.collection("collection_name").document("doc_name").collection("collection_name2").get()
> .addOnCompleteListener(task1 -> { if(task1.isSuccessful()){
> ...
> } }); } } });
isEmpty property of QuerySnapshot returns true if there are no documents in the QuerySnapshot.
Thus you can simply check if isEmpty is true or false.
const subcolRef = collection(db, "parentCollectionTitle", "parentDocId", "subcollectionTitle")
const subcolSnapshot = await getDocs(subcollectionRef)
if (!subcolSnapshot.empty) {
console.log("subcol does exists!");
} else {
console.log("subcol does NOT exist!");
}
(Firebase v9)
This is NextJS (React) code for checking if a sub-collection "history" exists or not in collection "users" > doc>user-Id,
if it exists then take data in history, else keep have-history == false.
you can then use {havehistory?<></>:<></>} for showing different info, as per data.
const [history, setHistory] = useState([])
const [havehistory, setHavehistory] = useState(false)
if(user){
onSnapshot(query(collection(db, "users", user.uid,"history")), (querySnapshot) => {
if(querySnapshot){
const historyBro = querySnapshot.docs.map((doc) => {
return { ...doc.data(), id: doc.id };
});
setHistory(historyBro)
setHavehistory(true)
}
})
}
make sure your imported the required modules. e.g.
import { useState } from "react";
import {db} from '../firebase'
import { collection,doc, query, onSnapshot} from "firebase/firestore";
import Link from "next/link";
I've recently started learning how to use python and i'm having some trouble with a graphQL api call.
I'm trying to set up a loop to grab all the information using pagination, and my first request is working just fine.
values = """
{"query" : "{organizations(ids:) {pipes {id name phases {id name cards_count cards(first:30){pageInfo{endCursor hasNextPage} edges {node {id title current_phase{name} assignees {name} due_date createdAt finished_at fields{name value filled_at updated_at} } } } } }}}"}
"""
but the second call using the end cursor as a variable isn't working for me. I assume that it's because i'm not understanding how to properly escape the string of the variable. But for the life of me I'm unable to understand how it should be done.
Here's what I've got for it so far...
values = """
{"query" : "{phase(id: """ + phaseID+ """ ){id name cards_count cards(first:30, after:"""\" + pointer + "\"""){pageInfo{endCursor hasNextPage} edges {node {id title assignees {name} due_date createdAt finished_at fields{name value datetime_value updated_at phase_field { id label } } } } } } }"}
"""
the second one as it loops just returns a 400 bad request.
Any help would be greatly appreciated.
As a general rule you should avoid building up queries using string manipulation like this.
In the GraphQL query itself, GraphQL allows variables that can be placeholders in the query for values you will plug in later. You need to declare the variables at the top of the query, and then can reference them anywhere inside the query. The query itself, without the JSON wrapper, would look something like
query = """
query MoreCards($phase: ID!, $cursor: String) {
phase(id: $phase) {
id, name, cards_count
cards(first: 30, after: $cursor) {
... CardConnectionData
}
}
}
"""
To actually supply the variable values, they get passed as an ordinary dictionary
variables = {
"phase": phaseID,
"cursor": pointer
}
The actual request body is a straightforward JSON structure. You can construct this as a dictionary too:
body = {
"query": query,
"variables": variables
}
Now you can use the standard json module to format it to a string
print(json.dumps(body))
or pass it along to something like the requests package that can directly accept the object and encode it for you.
I had a similar situation where I had to aggregate data through paginating from a GraphQL endpoint. Trying the above solution didn't work for me that well.
to start my header config for graphql was like this:
headers = {
"Authorization":f"Bearer {token}",
"Content-Type":"application/graphql"
}
for my query string, I used the triple quote with a variable placeholder:
user_query =
"""
{
user(
limit:100,
page:$page,
sort:[{field:"email",order:"ASC"}]
){
list{
email,
count
}
}
"""
Basically, I had my loop here for the pages:
for page in range(1, 9):
formatted_query = user_query.replace("$page",f'{page}')
response = requests.post(api_url, data=formatted_query,
headers=headers)
status_code, json = response.status_code, response.json()
Being very new to GraphQL, I have a graphene django implementation of a server with two models, following rather closely the graphene docs' example.
In graphiql, I can do this, and get a result back.
Following another relay tutorial, I'm intending to render the result of this query on screen.
My attempt looks like this:
class Note extends Component {
render() {
return(
<div> {this.props.store.title} </div>
)
}
}
Note = Relay.createContainer(Note, {
fragments: {
store: () => Relay.QL`
fragment on Query {
note(id: "Tm90ZU5vZGU6MQ==") {
id
title
}
}
`
}
});
class NoteRoute extends Relay.Route {
static routeName = 'NoteRoute';
static queries = {
store: Component => {
return Relay.QL`
query {
${Component.getFragment('store')}
}
`},
};
}
My browser's console shows the following error:
Uncaught Error: Relay transform error ``There are 0 fields supplied to the query named `Index`, but queries must have exactly one field.`` in file `/Users/.../src/index.js`. Try updating your GraphQL schema if an argument/field/type was recently added.
I've been trying to figure it out on my own with limited success.
Can someone point me in the right direction?
Thanks #stubailo for pointing me in the right direction. I made some adjustments, and now have a minimum example running like this:
NoteList = Relay.createContainer(NoteList, {
fragments: {
store: () => Relay.QL`
fragment N on NoteNodeConnection {
edges {
node{
id
title
note
}
}
}
`
}
});
class NoteRoute extends Relay.Route {
static routeName = 'NoteRoute';
static queries = {
store: Component => {
return Relay.QL`
query {
notes {
${Component.getFragment('store')}
}
}
`},
};
}
I am trying to figure out how to add a source to a metric in Librato when sending the information via Segment. I am using the python library and have tried creating a property for source (below) but it doesn't seem to be working properly.
Here's what I've got:
userID = '12345'
analytics.track(userID, 'event', {
'value': 1,
'integrations.Librato.source': userID
})
I've also tried 'source' and 'Librato.source' as properties, which were referenced in Segment's documentation. Any suggestions?
Similarly for ruby, using the segment gem you can specify a source like so:
require 'analytics-ruby'
segment_token = 'asdfasdf' # The secret write key for my project
Analytics.init({
secret: segment_token,
#Optional error handler
on_error: Proc.necd giw { |status, msg| print msg } })
Analytics.track(
user_id: 123,
writeKey: segment_token,
event: 'segment.librato',
properties: { value: 42 }, context: { source:'my.source.name' })
You can't set the source of the Librato metric in the properties when sending from Segment, you need to send it as part of the context meta data. Librato does not accept any properties other than 'value' so nothing else you send as a property will be recorded. To set the source using the python library, the code needs to be as follows:
userID = '12345'
analytics.track(userID, 'event', {
'value': 1
}, {
'Librato': {
'source': userID
}
})
If you are are using javascript, it would be:
analytics.track({
userId: '12345',
event: 'event'
properties: {
value: 1
},
context: {
'Librato': {
'source': userID
}
}
});