How to substitute Sendgrid template variables? (in node-js) - javascript

It seems to be a simple parameter I'm missing but I'm having trouble figuring out exactly what it is.
This is the request I'm sending with '#sendgrid/mail':
email.js:
const sgMail = require('#sendgrid/mail');
function emailRequest() {
msg = {
to: 'test+10#gmail.com
from: 'info#owner.io',
subject: 'Receipt for Business Expenses',
template_id: 'db6d11ae-41e4-4e1a-a71b-f5368eb21c9c',
personalizations: [
{
to: 'test+10#gmail.com,
from: 'info#ownr.io,
subject: 'Receipt for Business Expenses,
template_id: 'db6d11ae-41e4-4e1a-a71b-f5368eb21c9c',
substitutions: {
':firstname': 'Bobba',
':ordernumber': 'WHAAA',
':orderdate': 'today',
':ordertime': 'NOW!',
},
custom_args: {
':firstname': 'Bobba',
':ordernumber': 'WHAAA',
':orderdate': 'today',
':ordertime': 'NOW!',
},
},
],
sub: {
':firstname': 'Bobba',
':ordernumber': 'WHAAA',
':orderdate': 'today',
':ordertime': 'NOW!',
},
substitutions: {
':firstname': 'Bobba',
':ordernumber': 'WHAAA',
':orderdate': 'today',
':ordertime': 'NOW!',
},
};
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
return sgMail
.send(msg)
.then(response => {
return response;
})
.catch(err => {
throw err;
});
}
The email sends, but I'm still getting unsubstituted templates:
The source code for sendgrid-nodejs mail.js seems to say that as long as there is 'substitutions', it will initialize the mailing class with those substitutions but it's not working:
https://github.com/sendgrid/sendgrid-nodejs/blob/master/packages/helpers/classes/mail.js
https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html
How do you properly substitute variables into templates? Am I using the wrong package?

After a bit more digging, I found the answer in the issues section of their github. I was missing 'substitutionWrappers'. To get it working, all I had to do was add 'substitutionWrappers' to the message along with 'substitutions':
const msg = {
to: 'test#email.com'
from: 'info#gmail.io',
subject: 'Receipt for Business Expenses',
template_id: 'da6db3ae-41e4-4e1a-a71b-f5368ab41c9c',
substitutionWrappers: [':', ''],
substitutions: {
firstname: 'Bobba',
ordernumber: 'WHAAA',
orderdate: 'today',
ordertime: 'NOW!',
},
};

There's no need to specify the substitution wrappers as it will assume that you're using Handlebars templating by default.
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'recipient#example.org',
from: 'sender#example.org',
templateId: 'd-f43daeeaef504760851f727007e0b5d0',
dynamicTemplateData: {
subject: 'Testing Templates',
name: 'Some One',
city: 'Denver',
},
};
sgMail.send(msg);
Check documentation to learn more

Related

DiscordJS v13 Invalid Form Body

I am trying to make a toggle able slash command, if they pick the disable option it turns it off but when if you pick the enable option it asks to pick a channel but it gives this error
Error:
DiscordAPIError[50035]: Invalid Form Body
23.name[BASE_TYPE_REQUIRED]: This field is required
rawError: {
code: 50035,
errors: { '23': [Object] },
message: 'Invalid Form Body'
},
code: 50035,
status: 400,
method: 'put',
url: 'https://discord.com/api/v9/applications/971024098098569327/commands'
Code:
module.exports = {
name: 'welcomer',
permissions: 'MANAGE_CHANNELS',
description: 'Set Where Welcome Messages Get Sent To.',
options: [
{
name: 'toggle',
description: 'Toggle On/Off The Welcomer',
type: 3,
required: true,
choices: [
{
name: 'disable',
value: 'off',
},
{
name: 'enable',
value: 'on',
choices: [
{
name: 'channel',
description: 'Select channel to send welcome messages to',
type: 7,
required: true,
},
]
},
],
},
],
Those would be an example of a subcommand and need to be indicated as such and will need descriptions in a couple places.
module.exports = {
name: 'welcomer',
permissions: 'MANAGE_CHANNELS',
description: 'Set Where Welcome Messages Get Sent To.',
options: [{
name: 'disable',
description: `'Disable welcomer`, // Added needed description
type: 1, //converted to subcommmand
}, {
name: 'enable',
description: `'Enable welcomer`, // Added needed description
type: 1, //converted to subcommmand
options: [{
name: 'channel',
description: 'Select channel to send welcome messages to',
type: 7,
required: true,
channel_types: [0] // allows only text channels to be selected
}]
}],
// run command pick only one of the below two
// if command.execute()
async execute(client, interaction, args)
// if command.run()
run: async (client, interaction, args) =>
// command code below here assumes you have the code in your `interactionCreate` listener to set up args
{
if (args.disable) {
// Code to turn off
} else if (args.enable) {
const channel = args.channel
// Code to turn on
};
}
}

Building a gateway with nodeJs

I have to build a layer for an API using nodeJs and create some endpoints for the frontend.
I'm new to nodeJS and I've builded some small servers creating models and making some CRUD with Mongo.
This is the first time I have to make an intermediate layer to an existing API to filter the results.
I did it, and in the expected format, but I'm getting an error from node and I can't figure out how to resolve it.
The original API is kind like this:
{
id:'some id',
pagination: {...},
results: [...],
other_results: [...],
additional_info: [
{
id:'someid',
info:'someinfo',
values:[
{
id: 'someid',
name: 'some category name',
count: 999
},
{...},
{...}
],
},
{...},
{...}
]
}
and I have to "extract" the data from "results" and the first array of "additional_info".
My endpoint has to return data in this format:
{
brand: {name: "Any brand", country: "Germany"},
categories: ["category one", "category two", "category three"],
items: [
0: {id: "olmk23238777", name: "item one", imgUrl: 'img/34341.jpg', price: {total:424, currency: "USD"}, shipping: 'fast'},
1: {id: "olmk23239348", name: "item two", imgUrl: 'img/34764.jpg', price: {total:47, currency: "USD"}, shipping: 'slow'},
…]
}
I could achieved with this:
const axios = require('axios');
exports.products = async query => {
const url = `${process.env.BASE_URL}${query}`;
let productsData = {
brand: {
name: 'Any Brand',
country: 'Germany',
},
};
try {
const result = await axios({
method: 'GET',
url,
});
const data = result.data;
productsData.categories = data.additional_info[0].values.map(
({ categoryName }) => categoryName
);
productsData.items = data.results.map(item => ({
id: item.id,
name: item.name,
imgUrl: item.imgSrc,
price: {
total: parseInt(item.price),
currency: item.currency,
},
shipping: item.shipping_method,
}));
return productsData;
} catch (error) {
console.log(error);
}
};
This is the controller:
const { products } = require('../utils/products');
exports.searchItem = async (req, res) => {
const { search } = req.query;
try {
const response = await products(search);
res.send(response).json();
} catch (error) {
console.log(error);
}
};
and the endpoint look like this:
http://localhost:4000/api/products?search=shirt
This is the error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I tried different things but I can't fixe it
First of all, your error has an explanation here:
Error: Can't set headers after they are sent to the client
And for your code:
look this:
res.send(response).json();
it should be one of these:
res.send(response);
// or
res.json(response);
For the parameter type/structure, please refer to documentation of your selected library.

Authentication failed due to invalid authentication credentials or a missing Authorization header. in nodejs paypal sdk

I am trying to implement paypal subscription api in react-nodejs project.i refer https://developer.paypal.com/docs/api/subscriptions/v1/. after that i got id wit "P-*********".
What i have tried is:
in Ui Side(React) i created an event for requesting server side to create the billing plans.
in server side(nodejs) i do billingPlan create and update actions.
The code is:(nodejs)
export const paypalSubscribe = async (user, data) => {
const customerId = user.customer,
{ invoice: invoiceId } = data;
try {
const billingPlanAttributes = {
description: "Create Plan for Regular",
merchant_preferences: {
auto_bill_amount: "yes",
cancel_url: "http://www.cancel.com",
initial_fail_amount_action: "continue",
max_fail_attempts: "1",
return_url: "http://www.success.com",
setup_fee: {
currency: "USD",
value: "25"
}
},
name: "Testing1-Regular1",
payment_definitions: [
{
amount: {
currency: "USD",
value: order.price.recurringAmount
},
charge_models: [
{
amount: {
currency: "USD",
value: "10.60"
},
type: "SHIPPING"
},
{
amount: {
currency: "USD",
value: "20"
},
type: "TAX"
}
],
cycles: "0",
frequency: "MONTH",
frequency_interval: order.billingCycle,
name: "Regular 1",
type: "REGULAR"
}
],
type: "INFINITE"
};
const createdBillingPlan = await new Promise((resolve, reject) => {
Paypal.billingPlan.create(billingPlanAttributes, function (
error,
billingPlan
) {
if (error) {
reject(error);
} else {
resolve(billingPlan);
}
});
});
console.log("data123....", createdBillingPlan);
// update
var billing_plan_update_attributes = [
{
op: "replace",
path: "/",
value: {
state: "ACTIVE"
}
}
];
console.log(
"billing_plan_update_attributes",
billing_plan_update_attributes
);
const updateBillingPlan = await new Promise((resolve, reject) => {
Paypal.billingPlan.update(
createdBillingPlan.id,
billing_plan_update_attributes,
function (error, response) {
if (error) {
reject(error);
} else {
resolve(response);
}
}
);
});
const getBillingPlan = await new Promise((resolve, reject) => {
Paypal.billingPlan.get(createdBillingPlan.id, function (
error,
updatedBillingPlan
) {
if (error) {
console.log("errr", error.response);
reject(error);
} else {
console.log("updatedBillingPlan", JSON.stringify(updatedBillingPlan));
resolve(updatedBillingPlan);
updatedBillingPlan.redire
}
});
});
console.log("getBillingPlan", getBillingPlan);
return { ok: true, data: getBillingPlan };
} catch (error) {
console.log("error", error);
}
};
And i got getBillingPlan is like this:
{ id: 'P-**************',
state: 'ACTIVE',
name: 'Testing1-Regular1',
description: 'Create Plan for Regular',
type: 'INFINITE',
payment_definitions:
[ { id: 'PD-0EF41434TA3045459BCMIRMA',
name: 'Regular 1',
type: 'REGULAR',
frequency: 'Month',
amount: [Object],
cycles: '0',
charge_models: [Array],
frequency_interval: '1' } ],
merchant_preferences:
{ setup_fee: { currency: 'USD', value: '25' },
max_fail_attempts: '1',
return_url: 'http://www.success.com',
cancel_url: 'http://www.cancel.com',
auto_bill_amount: 'YES',
initial_fail_amount_action: 'CONTINUE' },
create_time: '2020-07-01T04:18:01.008Z',
update_time: '2020-07-01T04:18:02.031Z',
links:
[ { href:
'https://api.sandbox.paypal.com/v1/payments/billing-plans/P-***********',
rel: 'self',
method: 'GET' } ],
httpStatusCode: 200
}
And when i trying to open the links in links array
ie,https://api.sandbox.paypal.com/v1/payments/billing-plans/P-***********' i got the error:
"Authentication failed due to invalid authentication credentials or a missing Authorization
header."
Where i went wrong? How can i resolve this and implement subscription of paypal in my Project.
REST Api
i changed my code to rest api calls finaly i got response like this:
{ status: 'APPROVAL_PENDING',
id: 'I-1FU83BNMBCFS',
create_time: '2020-07-06T09:47:02Z',
links:
[ { href:
'https://www.sandbox.paypal.com/webapps/billing/subscriptions? ba_token=BA-3D945638N1691194P',
rel: 'approve',
method: 'GET' },
{ href:
'https://api.sandbox.paypal.com/v1/billing/subscriptions/I- 1FU83BNMBCFS',
rel: 'edit',
method: 'PATCH' },
{ href:
'https://api.sandbox.paypal.com/v1/billing/subscriptions/I-1FU83BNMBCFS',
rel: 'self',
method: 'GET' } ],
responseCode: 201 }
and in my ui side i opened the approval link in new window after submitting it shows 404. Why?
code:
window.open(URL, "_blank");
UPDATE:Subscription:
const subscriptionString = {
plan_id: result.id,
start_time: "2021-11-01T00:00:00Z",
shipping_amount: {
currency_code: "USD",
value: "10.00"
},
subscriber: {
name: {
given_name: "John",
surname: "Doe"
},
email_address: "customer#example.com",
shipping_address: {
name: {
full_name: "John Doe"
},
address: {
address_line_1: "2211 N First Street",
address_line_2: "Building 17",
admin_area_2: "San Jose",
admin_area_1: "CA",
postal_code: "95131",
country_code: "US"
}
}
},
application_context: {
brand_name: "walmart",
locale: "en-US",
shipping_preference: "SET_PROVIDED_ADDRESS",
user_action: "SUBSCRIBE_NOW",
payment_method: {
payer_selected: "PAYPAL",
payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED"
},
return_url: "https://example.com/returnUrl",
cancel_url: "https://example.com/cancelUrl"
}
},
options = {
url: "https://api.sandbox.paypal.com/v1/billing/subscriptions",
method: "POST",
headers: headers,
body: JSON.stringify(subscriptionString)
},
activateResult = await payment.callPayaplApi(options);
return {
ok: true,
data: activateResult
};
}
There is no SDK for a Subscriptions API integration, you need to implement direct REST API calls.
The PayPal-Node-SDK never supported the Subscriptions API, only the previous billing APIs which are not compatible. Also, the PayPal-Node-SDK is no longer maintained.
You particular authentication error is due to something else, but due to the above issues it is not worth troubleshooting.
Start over and integrate correctly with direct REST API calls.

inquirer package, present questions based on previous answers

I'm using NPM 'inquirer' package in order to present the user various questions. One of them is a 'choices' selection.
Is there a way to present follow up questions based on the 'choices' selection?
Here's my code:
const { prompt } = require('inquirer');
require('colors');
const questions = [
{
type: 'input',
name: 'appName',
message: 'Enter application name:'
},
{
type: 'input',
name: 'appDescription',
message: 'Enter app description:'
},
{
type: 'input',
name: 'appAuthor',
message: 'Enter app author:'
},
{
type: 'checkbox',
name: 'plugins',
message: 'Select plugins to install:',
choices: [
{
name: 'Cassandra'
}
]
}
];
module.exports.performQuestions = async () => {
console.log('\nWelcome to Chef!\n'.underline.italic.cyan);
const answers = await prompt(questions);
if (!answers.appName) {
console.error('\nPlease provide app name!\n'.red);
process.exit(1);
}
answers.appType = 'service';
return answers;
};
Here I want to present a few more questions if the user selects 'Cassandra' is that possible?
Thanks.
You can use "when" and like in the example bellow, the second question will popup only if "Cassandra" is selected:
const QUESTIONS = [
{
name: 'your-name',
type: 'list',
message: 'Your name:',
choices: ['Batman', 'Superman', 'Ultron', 'Cassandra'],
},
{
name: 'hello-cassandra',
type: 'confirm',
message: 'Oh, hello Cassandra!',
when: (answers) => answers['your-name'] === 'Cassandra',
},
];
inquirer.prompt(QUESTIONS)
.then(answers =>
{
console.log(answers);
});

Sequelize relationship query returns duplicate data

I'm querying customer orders for a specified customer using Sequelize relationships.
index.js
var results2 = await customerService.getOrders(1);
console.log(results2);
service.js
exports.getOrders = function (id) {
return customerModel.findAll({
raw: true,
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
results
[ { idcustomer: 1,
customername: 'hello world',
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 2,
'orders.orderdesc': 'Test 456',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 3,
'orders.orderdesc': 'Test 123',
'orders.customer_idcustomer': 1 } ]
expected
[ { idcustomer: 1,
customername: 'hello world',
'orders: [{
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 2,
'orders.orderdesc': 'order description 2',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 3,
'orders.orderdesc': 'order description 3',
'orders.customer_idcustomer': 1 },
}]
]
All you need is to remove raw: true, from query ,
as it will return plain/flat object , and that will convert your object as it looks now.
exports.getOrders = function (id) {
return customerModel.findAll({
// raw: true, // <------ Just remove this line
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
Note : You should put the where condition in upper level as per your
logic
exports.getOrders = function (id) {
return customerModel.findAll({
where: { id: id } ,
// raw: true, // <------ Just remove this line
include: [{
model: orderModel
}]
}).then(r => r);
};
Try removing raw key value from your query.
Finder methods are intended to query data from the database. They do
not return plain objects but instead return model instances. Because
finder methods return model instances you can call any model instance
member on the result as described in the documentation for instances.
If you want to get the data without meta/model information then map your results using
{ plain: true }
Good sequelize examples in docs
Example:
const getPlainData = records => records.map(record =>
record.get({ plain: true }));
// Your code
return customerModel.findAll({
// raw: true, <= remove
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(getPlainData);
In my case, having
raw: true
in the options didn't make any difference.
I added
distinct: true
and the issue disappeared.
I was using findAndCountAll, though.
Documentation: https://sequelize.org/master/class/lib/model.js~Model.html

Categories