Filter checkout session by metadata Stripe - javascript

I'm building a marketplace (you can imagine it like an Ebay where there are buyer and seller). And i want to get an items that bought customer (and vice versa with seller).
Here is my checkout session service:
async charge(userId: number, dto: CreateChargeDto) {
//Get an item by id
const item = await this.prisma.item.findUnique({
where: {
id: dto.itemId,
},
});
if (!item) {
throw new BadRequestException('There is no item with this id');
}
// Search for user
const user = await this.prisma.user.findUnique({
where: {
id: userId,
},
});
//update customer
await this.updateCustomer(user.stripeCustomerId, dto);
const session = await this.stripe.checkout.sessions.create({
success_url: 'http://localhost:3000/success?message=Succesful+payment',
cancel_url: 'http://localhost:3000/404?message=Payment+canceled',
mode: 'payment',
currency: 'pln',
customer: user.stripeCustomerId,
customer_update: {
address: 'auto',
},
metadata: {
seller_id: item.userId, // here is where i save seller id
},
line_items: [
{
quantity: 1,
price_data: {
currency: 'pln',
unit_amount: item.price * 100,
product_data: {
name: item.name,
images: item.images,
metadata: {
id: item.id,
},
},
},
},
],
});
return session.id;
}
async getCheckoutList(userId: number) {
const user = await this.prisma.user.findUnique({
where: {
id: userId,
},
});
const checkouts = await this.stripe.
return checkouts.data;
}
And now i wanna filter this session checkouts so i can display them in buyer (or seller) personal page.
const checkouts = await this.stripe.checkout.sessions.list({
where: {
metadata: {
seller_id: // seller id
}
}
How can i do it?

There's currently no way to filter for Checkout Sessions by metadata.
Some other options are :
Listen for the checkout.session.completed webhook event, save the data to your DB and then perform your own filtering. See https://stripe.com/docs/webhooks for more information on handling webhooks.
Add the metadata to the PaymentIntent also using the following parameter : https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-payment_intent_data-metadata. You can then use the Search API to filter for the
corresponding PaymentIntent with https://stripe.com/docs/search. Once
you have the PaymentIntent, retrieve the corresponding
Checkout Session with
https://stripe.com/docs/api/checkout/sessions/list#list_checkout_sessions-payment_intent
To filter for Checkout Sessions by Customer, you can use https://stripe.com/docs/api/checkout/sessions/list#list_checkout_sessions-customer

So the best in my opinion in this situation is to use a webhook.
The perfect example in Nestjs you can read here: https://wanago.io/2021/07/05/api-nestjs-stripe-events-webhooks/

Related

Cannot charge a customer that has no active card

So I am getting the error in Stripe that "Cannot charge a customer that has no active cards".
I am using node js for this
Payment Code is given below
try {
const customer = await stripe.customers.create({
email: req.body.email,
source: req.body.id,
});
const payment = await stripe.charges.create(
{
amount: req.body.totalAmount * 100,
currency: "inr",
customer: customer.id,
receipt_email: req.body.email,
confirm: true,
off_session: true,
},
{
idempotencyKey: uuidv4(),
}
);
And I am getting the following error
type: 'StripeCardError',
raw: {
code: 'missing',
doc_url: 'https://stripe.com/docs/error-codes/missing',
message: 'Cannot charge a customer that has no active card',
param: 'card',
type: 'card_error',
}
Log out req.body.id as that might be null and then investigate why your frontend is not passing that parameter to your backend.
Second, confirm and off_session are not supported parameters on the /v1/charges endpoint.

Loop custom array from mongoose returned empty

here is my problem
I want to loop data from two entities Project and User, my Project entity have a field with User id called creatorId.
I looped it and tried to add my user data _id, displayname and avatarUrl.
For now that work, but when I send it from the backend to the frontend nothing appears, like the array is empty.
const projectList = [];
Project.find({}, function(err, projects) {
projects.map(project => {
User.findById(project.creatorId, function(err, creator){
projectList.push({
_id: project._id,
title: project.title,
description: project.description,
avatarUrl: project.avatarUrl,
creationDate: project.creationDate,
projectCreator: {
_id: creator._id,
displayname: creator.displayname,
avatarUrl: creator.avatarUrl
},
git: project.git
})
})
})
});
res.send(projectList);

How to increase / decrease the quantity of a product in a shopping cart?

I have some difficulty implementing the logic to add or remove a product from my cart.
First of all at the level of my database:
Each basket consists of: a basket ID, a product ID, a customer ID and a quantity.
This means that the user has in fact "several baskets" (One product = One basket)
BACK-END
Here is how I created my models / relation with sequelize:
// Inside db.config.js
db.paniers = sequelize.define('panier', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
field: 'PAN_ID'
},
userId: {
type: Sequelize.INTEGER,
references: {
model: db.users,
key: 'USE_ID'
},
primaryKey: true,
allowNull: false,
field: 'USE_ID'
},
produitId : {
type: Sequelize.INTEGER,
references: {
model: db.produits,
key: 'PRO_ID'
},
primaryKey: true,
allowNull: false,
field: 'PRO_ID'
},
quantite: {
type: Sequelize.INTEGER,
allowNull: false,
field: 'PAN_QUANTITE'
}
}, {
tableName: 'PANIER'
});
Then for queries, two queries are made:
One to display information about the cart (cart ID, product ID, product name, product price, product image, cart quantity)
Another one to update the quantity.
Here is how I made my axios queries and the result under POSTMAN
const APIURL = 'http://localhost:8090/api';
// Get the details of the cart
export const getDetails = (userId) => axios.get(`${APIURL}/panier/details/${userId}`,
{
userId: userId,
});
// Update the quantity of the cart
export const updateQuantite = (produitId) => axios.put(`${APIURL}/panier/${produitId}`,
{
produitId: produitId,
});
// Result for the userId 1 (getDetails)
{
"PRO_ID": 1,
"PRO_NOM": "Un immeuble",
"PRO_PRIX": "1515",
"PRO_URL": "58afa4f2-41b1-42f7-a371-6d267784c44e.jpg",
"PAN_QUANTITE": 1,
"PAN_ID": 1
},
{
"PRO_ID": 2,
"PRO_NOM": "Model",
"PRO_PRIX": "102",
"PRO_URL": "a76fbe76-a183-49fa-84ee-40d5da08b91f.png",
"PAN_QUANTITE": 1,
"PAN_ID": 2
}
And here are my two controllers managing his routes :
// Display the informations of the basket
exports.getDetails = (req, res) => {
const queryResult = db.sequelize.query(
'SELECT P.PRO_ID, PRO_NOM, PRO_PRIX, PRO_URL, PA.PAN_QUANTITE, PA.PAN_ID\n' +
'FROM panier AS PA INNER JOIN produit AS P ON PA.PRO_ID = P.PRO_ID\n' +
'WHERE USE_ID = :id',
{
replacements: { id: req.params.userId },
type: QueryTypes.SELECT
}
).then(panier => {
res.json(panier);
}).catch(err => res.status(400).send(err));
}
// Modify the quantity of a basket
exports.update = (req, res) => {
Paniers.update({
quantite: req.body.quantite
}, {
where: {
produitId: req.params.produitId
}
}).then(panier => {
res.json(panier);
}).catch(err => res.status(400).send(err));
}
FRONT-END
This is how my information is displayed (Still under development, that's why it really doesn't look like anything ^^' )
This is where I get lost ...
Here is my shopping cart pagePage.js :
import React, { useState, useEffect } from 'react';
import { Card, CardHeader, CardMedia, Grid, ButtonGroup, Button} from '#material-ui/core';
import PayPal from '../services/PayPal/paypal'
import {getDetails, updateQuantite, getAllPanier, get} from '../services/API/panier'
export default function PanierPage() {
// Récupération des détails des paniers
const [paniers, setPaniers] = useState([])
const getPaniersDetails = () => [
getDetails(JSON.parse(localStorage.getItem('User')).id).then(response => {
setPaniers(response.data)
console.log(response)
}).catch(err => console.log(err))
]
const handleIncrement = (id) => {
updateQuantite(id).then(response => {
// ???
}).catch(err => console.log(err))
}
const handleDecrement = () => {
}
// Affichage des détails des paniers
const paniersAffichage = paniers.map((panier) => (
<Grid container>
<Card key={panier.PAN_ID}>
<CardHeader title={panier.PRO_NOM}/>
<CardMedia image={`http://localhost:8090/${panier.PRO_URL}`}/>
<Button onClick={() => handleIncrement(panier.PRO_ID)}> + </Button>
{panier.PAN_QUANTITE}
<Button onClick={handleDecrement}> - </Button>
</Card>
</Grid>
));
// Chargement des produits
useEffect(() => {
getPaniersDetails();
}, [])
return (
<>
<Grid>
{paniersAffichage}
</Grid>
<PayPal/>
</>
);
}
For explanations:
I get my basket information in 'getPaniersDetails' where I indicate the user id then I load it in my useEffect.
basketsDisplay allows me to display the baskets of the user concerned.
I give in each card the ID of the cart for the mapping, then I display the information ...
When clicking on "+" I want to increase my quantity, so I give it the product ID.
handleIncrement will therefore handle this action, using 'updateQuantite'.
This is where I block, I have the impression of mixing myself between my different IDs. Particularly between the cart ID of the table and the cart ID of my query (SELECT)
I'm sure it's something very simple to set up but in my head it seems complicated to me ...
If I missed any important points tell me, I will do my best to change my post
Your object model doesn't make much sense.
I would change it to make it more dynamic.
If you instead have users, and they have a basket property, this approach makes more sense.
NOTE: The code below is a rough outline, but should give you an idea of how to do it.
interface Database {
users: User[]; // Array of users.
}
interface User {
id: number;
username: string;
passwordHash: string;
baskets: Basket[];
}
interface Basket {
id: number;
items: Item[]; // array of items;
date: string;
}
interface Item {
id: number; // ID of the item.
name: string;
imgURL: string;
description: string[];
quantity: number;
}
Now if we want to receive and send data to the database, we would do so like this.
interface Basket {
items: string[]; // list of item id's.
}
// You want to use a session token instead of the user id so noone but the user can access their basket.
// Normaly baskets will be local to the browser or app and not sored on a servers database.
// Only past orders should be stored. But in this example, we are storing the current basket too.
async function getBasket(sessionToken: string){
return await axios.get(`${api.host}/basket`, {
headers: {
Session-Token: sessionToken, // used to identify the user
Content-Type: "application/json",
}
}).data;
}
// we send the basket object, which is just a list of IDs, and the session token.
async function setBasket(basket: Basket, sessionToken: string){
return await axios.put(`${api.host}/basket`, {
headers: {
Session-Token: sessionToken, // used to identify the user
Content-Type: "application/json",
}
}).data;
}
Now on the server side, with express we can handle the requests.
To implement sessions with express, there is the npm module express-session which is a middleware for express. When the user logs in, they will be given a header that they will save as a cookie to use on their future requests. When they logout, the session is removed from your server and the cookie is deleted on the client.
For added security, you can set an expiration on the session. The user will have to re-login and get a new session.
// check the documentation to tune it to what you need.
app.use(session({
secret: 'mySuperSecret',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
app.get("/basket", async(req, res) => {
// here we first check if the session exists.
if(req.session.views){
// if it does, then we return the list of baskets.
const { baskets } = await getUser(req.session.userID);
res.sendStatus(200).send([success: true, data: baskets]);
}
// if not, then we will return a 403 error.
// we also send a response that matches the layout of the normal response.
res.sendStatus(403).send([success: false, data: []]);
})
app.put("/basket", (req, res) => {
// here we first check if the session exists.
if(req.session.views){
// if it does, then we add the basket to the user.
addBasket(req.session.userID, basket)
.then(res.sendStatus(204).send([success: true, data: []]))
}
// if not, then we will return a 403 error.
// we also send a response that matches the layout of the normal response.
res.sendStatus(403).send([success: false, data: []]);
})
If you have any question, just ask in the comment section. I will respond when I am free.

Dynamically return config value (node.js)

I'm building a content middleware which gather contents from our external publishers. The publishers will share their contents either in rss or json and the key/value field would be different from each other. To make thing easier, I created a config file where I can pre-defined the key/value and the feed type. The problem is, how can I dynamically return this config value based on publishers name.
Example: To get Publisher #1 feed type, I just can use config.articles.rojak_daily.url_feed
my config file /config/index.js
module.exports = {
batch:100,
mysql: {
database: process.env.database,
host: process.env.host,
username: process.env.username,
password: process.env.password
},
articles:{
rojak_daily:{ // Publisher 1
url: 'xxx',
url_feed: 'rss',
id: null,
Name: 'title',
Description: 'description',
Link: 'link',
DatePublishFrom: 'pubDate',
LandscapeImage: 's3image',
SiteName: 'Rojak Daily',
SiteLogo: null
},
rojak_weekly:{ // publisher 2
url: 'xxx',
url_feed: 'json',
id: null,
Name: 'Name',
Description: 'Desc',
Link: 'link',
DatePublishFrom: 'pubDate',
LandscapeImage: 's3image',
SiteName: 'Rojak Weekly',
SiteLogo: null
}
}
}
my main application script
const config = require('#config'); // export from config file
class Main {
constructor(){
this.publishers = ['rojak_daily','rojak_weekly'];
}
// Main process
async startExport(){
try{
for(let publisher of this.publishers){
const feedType = await this.getFeedType(publisher)
const result = (feedType == 'rss')? await this.rss.start(publisher): await this.json.start(publisher)
return result
}
}catch(err){
console.log("Error occured: ", err)
}
}
// Get feed type from config
async getFeedType(publisher){
return await config.articles.rojak_daily.url_feed;
// this only return publisher 1 url feed.
// my concern is to dynamically passing variable
// into this config file (example: config.articles.<publisher>.url_feed)
}
}
module.exports = Main
async getFeedType(publisher){
return await config.articles[publisher].url_feed;
}
You can access properties of objects by variable
You could either loop over the articles by using Object.entries(articles) or Object.values(articles) in conjunction with Array.prototype.forEach(), or since you already have the name of the publisher you could access that entry with config.articles[publisher].url_feed, like so:
const config = {
articles: {
rojak_daily: { // Publisher 1
url: 'xxx',
url_feed: 'rss',
id: null,
Name: 'title',
Description: 'description',
Link: 'link',
DatePublishFrom: 'pubDate',
LandscapeImage: 's3image',
SiteName: 'Rojak Daily',
SiteLogo: null
},
rojak_weekly: { // publisher 2
url: 'xxx',
url_feed: 'json',
id: null,
Name: 'Name',
Description: 'Desc',
Link: 'link',
DatePublishFrom: 'pubDate',
LandscapeImage: 's3image',
SiteName: 'Rojak Weekly',
SiteLogo: null
}
}
}
const publishers = ['rojak_daily', 'rojak_weekly']
function getFeedType(publisher) {
return config.articles[publisher].url_feed;
}
publishers.forEach(publisher => console.log(getFeedType(publisher)));

Upload Intent function Dialogflow V2

I am trying to develop an API to upload the intent to Dialogflow V2. I have tried below snippet, which it is not working however if trying to communicate with Dialogflow it does work (detect intent)and does get a reply from the Dialogflow for queries.
PERMISSION
I AM & ADMIN > SERVICE ACCOUNTS > DIALOGFLOW ADMIN
ERROR
Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.entityTypes.create' on 'projects/dexter-47332/agent' denied.
BLOGS/ REFERENCES
Dialogflow easy way for authorization
https://github.com/dialogflow/dialogflow-nodejs-client-v2/blob/master/samples/resource.js#L26
https://www.npmjs.com/package/dialogflow
https://developers.google.com/apis-explorer/
https://cloud.google.com/docs/authentication/production
//------- keys.json (test 1)
{
"type": "service_account",
"project_id": "mybot",
"private_key_id": "123456asd",
"private_key": "YOURKEY",
"client_email": "yourID#mybot.iam.gserviceaccount.com",
"client_id": "098091234",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/yourID%40mybot.iam.gserviceaccount.com"
}
//--------------------- ** (test 2) ** ---------
let privateKey = 'key';
let clientEmail = "email";
let config = {
credentials: {
private_key: privateKey,
client_email: clientEmail
}
}
function createEntityTypes(projectId) {
// [START dialogflow_create_entity]
// Imports the Dialogflow library
const dialogflow = require('dialogflow');
// ******** Instantiates clients (Test 1)********
const entityTypesClient = new dialogflow.EntityTypesClient({
'keyFilename': './keys.json'
});
const intentsClient = new dialogflow.IntentsClient({
'keyFilename': './keys.json'
});
// ******** Instantiates clients (Test 2)********
const entityTypesClient = new dialogflow.EntityTypesClient(config);
const intentsClient = new dialogflow.IntentsClient(config);
// The path to the agent the created entity type belongs to.
const agentPath = intentsClient.projectAgentPath(projectId);
const promises = [];
// Create an entity type named "size", with possible values of small, medium
// and large and some synonyms.
const sizeRequest = {
parent: agentPath,
entityType: {
displayName: 'test',
kind: 'KIND_MAP',
autoExpansionMode: 'AUTO_EXPANSION_MODE_UNSPECIFIED',
entities: [{
value: 'small',
synonyms: ['small', 'petit']
},
{
value: 'medium',
synonyms: ['medium']
},
{
value: 'large',
synonyms: ['large', 'big']
},
],
},
};
promises.push(
entityTypesClient
.createEntityType(sizeRequest)
.then(responses => {
console.log('Created size entity type:');
logEntityType(responses[0]);
})
.catch(err => {
console.error('Failed to create size entity type ----->:', err);
})
);
}
createEntityTypes(projectId);
You can use JWT(JSON Web Tokens) for authenticating with service accounts like in this example
const serviceAccount = { }; // JSON key contents {"type": "service_account",...
const serviceAccountAuth = new google.auth.JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: 'https://www.googleapis.com/auth/calendar'
});
For more OAuth2.0 scopes for Google APIs you can see the full list here.
I encountered the same error. I corrected it by deleting the current service account and creating a new one and selected the "owner" option for the role.
The associated service-account has to have the role "Dialogflow API Admin" to be able to create intents and entities.
I think you must provide a name parameter there in the sizeRequest and make it equal to an empty string.
Take a look at the code snippet.
let request = {
parent: `projects/${PROJECID}/agent`,
entityType: {
name: '',
autoExpansionMode: 'AUTO_EXPANSION_MODE_DEFAULT',
displayName: 'size_type',
enableFuzzyExtraction: false,
entities: [
{
value: 'Big',
synonyms: ['big', 'large', 'huge']
},
{
value: 'Medium',
synonyms: ['medium', 'not big']
}
],
kind: 'KIND_MAP'
},
languageCode: 'en'
};
Please let me know if this helps.

Categories