NodeJs Strimzi: KStream KTable join results in very few key matches - node.js

I am using nodefluent/kafka-streams to join a KStream and a KTable. This is with Strimzi on Kubernetes. I have set-up in such a way that, every message in KStream will definitely have a matching key in KTable. However, I see only a few (< 10) messages in the final topic. Can you please advise why the overlap is so rare?
const { KafkaStreams } = require("kafka-streams");
const kafkaBroker = process.env.KAFKA_BROKER
const kafkaConfig = {
"noptions": {
"metadata.broker.list": kafkaBroker,
"group.id": "kafka-streams-test-native",
"client.id": "kafka-streams-test-name-native",
"event_cb": true,
"compression.codec": "snappy",
"api.version.request": true,
"socket.keepalive.enable": true,
"socket.blocking.max.ms": 100,
"enable.auto.commit": false,
"auto.commit.interval.ms": 100,
"heartbeat.interval.ms": 250,
"retry.backoff.ms": 250,
"fetch.min.bytes": 100,
"fetch.message.max.bytes": 2 * 1024 * 1024,
"queued.min.messages": 100,
"fetch.error.backoff.ms": 100,
"queued.max.messages.kbytes": 50,
"fetch.wait.max.ms": 1000,
"queue.buffering.max.ms": 1000,
"batch.num.messages": 10000
},
"tconf": {
"auto.offset.reset": "earliest",
"request.required.acks": 1
},
"batchOptions": {
"batchSize": 5,
"commitEveryNBatch": 1,
"concurrency": 1,
"commitSync": false,
"noBatchCommits": false
}
}
const kafkaStreams = new KafkaStreams(kafkaConfig);
const rawData = process.env.KAFKA_TOPIC;
const lookUp = process.env.KAFKA_TOPIC_LKUP;
const alerts = process.env.KAFKA_TOPIC_ALERTS;
const dataStream = kafkaStreams.getKStream(rawData)
const lookUpTable = kafkaStreams.getKTable(lookUp, (message) => {
return {
key: message.key,
value: message.value
};
})
// I don't know, if this should be done.
lookUpTable.consumeUntilCount(2)
const alertsStream = dataStream
.innerJoin(lookUp)
.mapBufferKeyToString()
.mapStringValueToJSONObject()
.map((msg) => {
let key = msg.left.key
let data = msg.left.value
// Some logic
return `${key.toString()}|${JSON.stringify({ data:data })}`
})
.mapStringToKV("|", 0, 1)
Promise.all([
dataStream.start(),
lookUpTable.start(),
alertsStream.to(alerts)
])
.then(() => {
console.log("both consumers and the producer have connected.");
});
I am using KafkaJS to publish messages to rawData and lookUp topics as shown below.
const producer = kafka.producer()
const run = async () => {
await producer.connect()
await producer.send({
topic: process.env.KAFKA_TOPIC_LKUP,
messages: [
{ key: "k1", value: "Data 1" },
{ key: "k2", value: "Data 2" }
],
})
setInterval(async function () {
await producer.send({
topic: process.env.KAFKA_TOPIC,
messages: [
{ key: "k1", value: "Random 1" },
{ key: "k2", value: "Random 2" }
],
})
}, 1000)
}
run().catch(e => console.error(` ${e.message}`, e))

Related

Jest Mock Implementation is not working, instead original function is being called

I am trying to test an API by mocking the database function, but the imported function is being called instead of the mocked one.
Here are the code snippets
const supertest = require('supertest');
const axios = require('axios');
const querystring = require('querystring');
const { app } = require('../app');
const DEF = require('../Definition');
const tripDb = require('../database/trip');
const request = supertest.agent(app); // Agent can store cookies after login
const { logger } = require('../Log');
describe('trips route test', () => {
let token = '';
let companyId = '';
beforeAll(async (done) => {
// do something before anything else runs
logger('Jest starting!');
const body = {
username: process.env.EMAIL,
password: process.env.PASSWORD,
grant_type: 'password',
client_id: process.env.NODE_RESOURCE,
client_secret: process.env.NODE_SECRET,
};
const config = {
method: 'post',
url: `${process.env.AUTH_SERV_URL}/auth/realms/${process.env.REALM}/protocol/openid-connect/token`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: querystring.stringify(body),
};
const res = await axios(config);
token = res.data.access_token;
done();
});
const shutdown = async () => {
await new Promise((resolve) => {
DEF.COM.RCLIENT.quit(() => {
logger('redis quit');
resolve();
});
});
// redis.quit() creates a thread to close the connection.
// We wait until all threads have been run once to ensure the connection closes.
await new Promise(resolve => setImmediate(resolve));
};
afterAll(() => shutdown());
test('post correct data', async (done) => {
const createTripMock = jest.spyOn(tripDb, 'addTrip').mockImplementation(() => Promise.resolve({
pk: `${companyId}_trip`,
uid: '1667561135293773',
lsi1: 'Kotha Yatra',
lsi2: companyId,
name: 'Kotha Yatra',
companyId,
origin: {
address: 'Goa, India',
location: {
lat: 15.2993265,
lng: 74.12399599999999,
},
},
destination: {
address: 'Norway',
location: {
lat: 60.47202399999999,
lng: 8.468945999999999,
},
},
path: [
{
lat: 15.2993265,
lng: 74.12399599999999,
},
{
lat: 60.47202399999999,
lng: 8.468945999999999,
},
],
isDeleted: false,
currentVersion: 1,
geofences: [],
}));
const response = await request.post('/api/trips').set('Authorization', `Bearer ${token}`).send(tripPayload);
expect(createTripMock).toHaveBeenCalled();
expect(response.status).toEqual(200);
expect(response.body.status).toBe('success');
done();
});
});
The database function:
const addTrip = (trip) => {
// const uid = trip.uid ? trip.uid : (Date.now() * 1000) + Math.round(Math.random() * 1000);
const uid = (Date.now() * 1000) + Math.round(Math.random() * 1000);
const item = {
pk: `${trip.companyId}_trip`,
uid: `v${trip.version ? trip.version : 0}#${uid}`,
lsi1: trip.name,
lsi2: trip.companyId,
name: trip.name,
companyId: trip.companyId,
origin: trip.origin,
destination: trip.destination,
path: trip.path,
isDeleted: false,
};
if (!trip.version || trip.version === 0) {
item.currentVersion = 1;
} else {
item.version = trip.version;
}
if (trip.geofences) item.geofences = trip.geofences;
const params = {
TableName: TN,
Item: item,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('params ', params);
return new Promise((resolve, reject) => {
ddb.put(params, (err, result) => {
// console.log('err ', err);
if (err) {
if (err.code === 'ConditionalCheckFailedException') return reject(new Error('Trip id or name already exists'));
return reject(err);
}
if (!trip.version || trip.version === 0) {
const newItem = { ...item };
delete newItem.currentVersion;
newItem.version = 1;
newItem.uid = `v1#${item.uid.split('#')[1]}`;
const newParams = {
TableName: TN,
Item: newItem,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('new params ', newParams);
ddb.put(newParams, (v1Err, v1Result) => {
// console.log('v1 err ', v1Err);
if (v1Err) return reject(v1Err);
item.uid = item.uid.split('#')[1];
return resolve(item);
});
} else {
item.uid = item.uid.split('#')[1];
return resolve(item);
}
});
});
};
module.exports = {
addTrip,
};
I was mocking the above database function when I was making a request to add API, instead, the original function is being called and I was getting the result that I had written in the mock Implementation.
What should I do to just mock the result ,when the function is called and no implementation of the original function should happen.
Even this did not give an error
expect(createTripMock).toHaveBeenCalled();
Still the database function call is happening
I tried using mockReturnValue, mockReturnValueOnce, mockImplemenationOnce but not luck.
Can anyone help me with this?

Two if statements to make cmd differentiate between roles

I'm wondering if it is possible to use if statements this way so that the command gives separate messages depending on the users role. This is one attempt I've made but the second if statement is unreachable.
module.exports = {
name: "train",
description: "Train to earn some reputation!",
async execute(client, message, args, cmd, discord, profileData) {
const events1 = [
"sample event",
"sample event",
"sample event",
];
const events2 = [
"sample event",
"sample event",
"sample event",
];
const injuries = [
"sample injury",
"sample injury",
"sample injury",
];
const chosenEvent1 = events1.sort(() => Math.random() - Math.random()).slice(0, 1);
const chosenEvent2 = events2.sort(() => Math.random() - Math.random()).slice(0, 1);
const chosenInjury = injuries.sort(() => Math.random() - Math.random()).slice(0, 1);
const randomNumber1 = Math.floor(Math.random() * 29) + 5;
const randomNumber2 = Math.floor(Math.random() * 9) + 1;
if((!message.member.roles.cache.has('roleid#1'))); {
if(Math.floor(Math.random() * 3) === 0) {
await profileModel.findOneAndUpdate(
{
userID: message.author.id,
},
{
$inc: {
health: -randomNumber2,
},
}
);
return message.channel.send(`${chosenInjury} You lost ${randomNumber2} health and gained no reputation.`);
} else {
await profileModel.findOneAndUpdate(
{
userID: message.author.id,
},
{
$inc: {
reputation: randomNumber1,
},
}
);
return message.channel.send(`${chosenEvent1} You earned ${randomNumber1} reputation!`);
}
if((!message.member.roles.cache.has('roleid#2'))); {
return message.channel.send(`${chosenEvent2} You earned ${randomNumber1} reputation!`);
}
}}
};
So ideally if you have RoleID #1, you have a chance of injury or your reputation increases and you get a message with Event1 prompts. If you have RoleID #2, your reputation just increases and you get a message with Event2 prompts. I hope this is clear.
It seems the brackets are bit off and that is why the code is unreachable.
I have also attached an image of the locations where I made code changes. A key consideration is in javascript try to avoid placing semicolons right after an if statement
Please see the small bracket changes I made in your code example:
module.exports = {
name: 'train',
description: 'Train to earn some reputation!',
async execute(client, message, args, cmd, discord, profileData) {
const events1 = ['sample event', 'sample event', 'sample event'];
const events2 = ['sample event', 'sample event', 'sample event'];
const injuries = ['sample injury', 'sample injury', 'sample injury'];
const chosenEvent1 = events1.sort(() => Math.random() - Math.random()).slice(0, 1);
const chosenEvent2 = events2.sort(() => Math.random() - Math.random()).slice(0, 1);
const chosenInjury = injuries.sort(() => Math.random() - Math.random()).slice(0, 1);
const randomNumber1 = Math.floor(Math.random() * 29) + 5;
const randomNumber2 = Math.floor(Math.random() * 9) + 1;
if (!message.member.roles.cache.has('roleid#1')) {
if (Math.floor(Math.random() * 3) === 0) {
await profileModel.findOneAndUpdate(
{
userID: message.author.id,
},
{
$inc: {
health: -randomNumber2,
},
}
);
return message.channel.send(
`${chosenInjury} You lost ${randomNumber2} health and gained no reputation.`
);
} else {
await profileModel.findOneAndUpdate(
{
userID: message.author.id,
},
{
$inc: {
reputation: randomNumber1,
},
}
);
return message.channel.send(`${chosenEvent1} You earned ${randomNumber1} reputation!`);
}
} else if (!message.member.roles.cache.has('roleid#2')) {
return message.channel.send(`${chosenEvent2} You earned ${randomNumber1} reputation!`);
}
},
};

How to mock AWS TimestreamWrite by jest

This project is to record data by AWS Timestream, and it works well.
However, I'm failed to mock AWS TimestreamWrite by using jest. I tried some ways but not working. Can someone help me?
My files as below:
ledger-service.js
const AWS = require("aws-sdk");
const enums = require("./enums");
var https = require("https");
var agent = new https.Agent({
maxSockets: 5000,
});
const tsClient = new AWS.TimestreamWrite({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: agent,
},
});
module.exports = {
log: async function (audit) {
try {
if (Object.keys(audit).length !== 0) {
if (!isPresent(audit, "name")) {
throw new Error("Name shouldn't be empty");
}
if (!isPresent(audit, "value")) {
throw new Error("Value shouldn't be empty");
}
return await writeRecords(recordParams(audit));
} else {
throw new Error("Audit object is empty");
}
} catch (e) {
throw new Error(e);
}
},
};
function isPresent(obj, key) {
return obj[key] != undefined && obj[key] != null && obj[key] != "";
}
function recordParams(audit) {
const currentTime = Date.now().toString(); // Unix time in milliseconds
const dimensions = [
// { Name: "client", Value: audit["clientId"] },
{ Name: "user", Value: audit["userId"] },
{ Name: "entity", Value: audit["entity"] },
{ Name: "action", Value: audit["action"] },
{ Name: "info", Value: audit["info"] },
];
return {
Dimensions: dimensions,
MeasureName: audit["name"],
MeasureValue: audit["value"],
MeasureValueType: "VARCHAR",
Time: currentTime.toString(),
};
}
function writeRecords(records) {
try {
const params = {
DatabaseName: enums.AUDIT_DB,
TableName: enums.AUDIT_TABLE,
Records: [records],
};
return tsClient.writeRecords(params).promise();
} catch (e) {
throw new Error(e);
}
}
ledger-service.spec.js
const AWS = require("aws-sdk");
const audit = require("./ledger-service");
describe("ledger-service", () => {
beforeEach(async () => {
jest.resetModules();
});
afterEach(async () => {
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit={
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
const mockWriteRecords = jest.fn(() =>{
console.log('mock success')
return { promise: ()=> Promise.resolve()}
});
const mockTsClient={
writeRecords: mockWriteRecords
}
jest.spyOn(AWS,'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(()=>mockTsClient);
//a=new AWS.TimestreamWrite();
//a.writeRecords(); //these two lines will pass the test and print "mock success"
await audit.log(mockAudit); //this line will show "ConfigError: Missing region in config"
expect(mockWriteRecords).toHaveBeenCalled();
});
});
I just think the the AWS I mocked doesn't pass into the ledger-service.js. Is there a way to fix that?
Thanks
updates: Taking hoangdv's suggestion
I am thinking jest.resetModules(); jest.resetAllMocks(); don't work. If I put the "It should write records when all success" as the first test, it will pass the test. However, it will fail if there is one before it.
Pass
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
Failed
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
In ledger-service.js you call new AWS.TimestreamWrite "before" module.exports, this means it will be called with actual logic instead of mock.
The solution is just mock AWS before you call require("./ledger-service");
ledger-service.spec.js
const AWS = require("aws-sdk");
describe("ledger-service", () => {
let audit;
let mockWriteRecords;
beforeEach(() => {
mockWriteRecords = jest.fn(() => {
return { promise: () => Promise.resolve() }
});
jest.spyOn(AWS, 'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(() => ({
writeRecords: mockWriteRecords
}));
audit = require("./ledger-service"); // this line
});
afterEach(() => {
jest.resetModules(); // reset module to update change for each require call
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
});

How to abort an update operation with beforeUpdate Hook Sequelize

How can i abort an update operation using beforeUpdate hook on sequelize and return a object as the result from a aborted update,
if i have something like:
User.beforeUpdate(function(user, options) {
if (user.name == "example_name") {
// abort operation here
// return object to the update caller
}
}
throw from before hooks does prevent the update
E.g.:
beforeUpdate: (integerName, options) => {
if (integerName.value === 5) {
throw new Error('beforeUpdate')
}
},
and throws on .update caller.
But remember from why sequelize beforeUpdate hook doesn't work? that the before only fires if the caller uses:
Model.update({}, {individualHooks: true})
which would be annoying to remember to pass every time.
The beforeValidate hook however fires even without individualHooks, so maybe that's the way to go.
The fact that throwing works for create is documented at: https://sequelize.org/master/manual/hooks.html#instance-hooks
User.beforeCreate(user => {
if (user.accessLevel > 10 && user.username !== "Boss") {
throw new Error("You can't grant this user an access level above 10!");
}
});
The following example will throw an error:
try {
await User.create({ username: 'Not a Boss', accessLevel: 20 });
} catch (error) {
console.log(error); // You can't grant this user an access level above 10!
};
The following example will be successful:
const user = await User.create({ username: 'Boss', accessLevel: 20 });
console.log(user); // user object with username 'Boss' and accessLevel of 20
Also mentioned at: https://github.com/sequelize/sequelize/issues/11298
Minimal runnable example:
main.js
#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
sequelize = new Sequelize('tmp', undefined, undefined, {
dialect: 'postgres',
host: '/var/run/postgresql',
})
} else {
sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.sqlite',
})
}
function assertEqual(rows, rowsExpect) {
assert.strictEqual(rows.length, rowsExpect.length)
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
let rowExpect = rowsExpect[i]
for (let key in rowExpect) {
assert.strictEqual(row[key], rowExpect[key])
}
}
}
;(async () => {
const IntegerNames = sequelize.define('IntegerNames',
{
value: { type: DataTypes.INTEGER },
name: { type: DataTypes.STRING },
},
{
hooks: {
beforeCreate: (integerName, options) => {
if (integerName.value === 42) {
throw new Error('beforeCreate')
}
},
beforeValidate: (integerName, options) => {
if (integerName.value === 43) {
throw new Error('beforeValidate')
}
},
beforeUpdate: (integerName, options) => {
if (integerName.value === 5) {
throw new Error('beforeUpdate')
}
},
}
},
)
await IntegerNames.sync({ force: true })
async function reset() {
await sequelize.truncate({ cascade: true })
await IntegerNames.create({ value: 2, name: 'two' })
await IntegerNames.create({ value: 3, name: 'three' })
await IntegerNames.create({ value: 5, name: 'five' })
}
async function assertUnchanged() {
const rows = await IntegerNames.findAll()
assertEqual(rows, [
{ id: 1, value: 2, name: 'two', },
{ id: 2, value: 3, name: 'three', },
{ id: 3, value: 5, name: 'five', },
])
}
await reset()
let rows, exc
await assertUnchanged()
// beforeCreate
exc = undefined
try {
await IntegerNames.create({ value: 42, name: 'forty-two' })
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeCreate')
await assertUnchanged()
// beforeValidate
exc = undefined
try {
await IntegerNames.create({ value: 43, name: 'forty-three' })
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeValidate')
await assertUnchanged()
// beforeUpdate
exc = undefined
try {
await IntegerNames.update(
{ name: 'five hacked', },
{
where: { value: 5 },
individualHooks: true,
},
);
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeUpdate')
await assertUnchanged()
// using the beforeValidate
exc = undefined
try {
await IntegerNames.update(
{ value: 43, },
{
where: { value: 5 },
},
);
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeValidate')
await assertUnchanged()
})().finally(() => { return sequelize.close() })
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.14.0",
"sql-formatter": "4.0.2",
"sqlite3": "5.0.2"
}
}
GitHub upstream. Tested on Ubuntu 21.10, PostgreSQL 13.5.

Paypal not returning custom variable

I'm trying to complete a paypal transaction using paypal-rest-sdk, everything is set up and working, however, I need to get the clientId back from paypal in the success route in order to save it in my client_feature_payment model. I found that we can set a "custom" field where we can set anything and that'll be sent back by paypal but this feautre is available in classic paypal sdk only and not available in rest-sdk one.
Is there any workaround for this?
//Paypal objects and methods from rest-sdk:
client_page: {
args: {
clientId: {
type: GraphQLString
}
},
type: ClientType,
resolve: async (_, args) => {
if (args.clientId) {
let clientMongoId = fromGlobalId(args.clientId).id;
let client = await Client.queryOne("id")
.eq(clientMongoId)
.exec();
let clientName = client.name;
let clientSecret = client.secret;
let company = await Company.queryOne("id")
.eq(client.companyId)
.exec();
let companyName = company.name;
let service = await Service.queryOne("id")
.eq(client.serviceId)
.exec();
let serviceName = service.name;
let clientFeature = await ClientFeature.query("clientId")
.eq(clientMongoId)
.exec();
let totalFeatures = [];
let clientFeatureId = [];
for (let i = 0; i < clientFeature.length; i++) {
clientFeatureId.unshift(clientFeature[i].id);
let feature = await Feature.query("id")
.eq(clientFeature[i].featureId)
.exec();
let newFeature;
feature.map(
feature =>
(newFeature = [
feature.name,
feature.cost,
feature.trial,
feature.frequency
])
);
totalFeatures.unshift(newFeature);
}
let trial, freq;
let cost = [];
totalFeatures.map(item => {
if (item[2] && item[3]) {
trial = item[2];
freq = item[3];
}
cost.unshift(item[1]);
});
const finalCost = cost.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
let paypalFreq;
let frequencyInterval;
var isoDate = new Date(Date.now() + 1 * 60 * 1000);
switch (freq) {
case "bi-weekly":
paypalFreq = "DAY";
frequencyInterval = "7";
break;
case "monthly":
paypalFreq = "MONTH";
frequencyInterval = "1";
break;
case "3 months":
paypalFreq = "MONTH";
frequencyInterval = "3";
break;
case "6 months":
paypalFreq = "MONTH";
frequencyInterval = "6";
break;
case "1 year":
paypalFreq = "YEAR";
frequencyInterval = "1";
break;
default:
break;
}
var billingPlanAttributes = {
description:
"Create Plan for Trial & Frequency based payment for features and services used by customer",
merchant_preferences: {
auto_bill_amount: "yes",
cancel_url: "http://localhost:3000/cancel",
initial_fail_amount_action: "continue",
max_fail_attempts: "1",
return_url: "http://localhost:3000/success",
setup_fee: {
currency: "USD",
value: "0"
}
},
name: "Client Services & Features Charge",
payment_definitions: [
{
amount: {
currency: "USD",
value: finalCost
},
cycles: "0",
frequency: paypalFreq,
frequency_interval: frequencyInterval,
name: "Regular 1",
type: "REGULAR"
},
{
amount: {
currency: "USD",
value: "0"
},
cycles: "1",
frequency: "DAY",
frequency_interval: trial,
name: "Trial 1",
type: "TRIAL"
}
],
type: "INFINITE"
};
var billingPlanUpdateAttributes = [
{
op: "replace",
path: "/",
value: {
state: "ACTIVE"
}
}
];
var billingAgreementAttr = {
name: "Fast Speed Agreement",
description: "Agreement for Fast Speed Plan",
start_date: isoDate,
plan: {
id: "P-0NJ10521L3680291SOAQIVTQ"
},
payer: {
payment_method: "paypal",
payer_info: {
payer_id: clientMongoId
}
},
shipping_address: {
line1: "StayBr111idge Suites",
line2: "Cro12ok Street",
city: "San Jose",
state: "CA",
postal_code: "95112",
country_code: "US"
}
};
// Create the billing plan
let billingPlan = await new Promise((resolve, reject) => {
paypal.billingPlan.create(
billingPlanAttributes,
(error, billingPlan) => {
if (error) {
throw error;
} else {
resolve(billingPlan);
}
}
);
});
// let billingPlan = await billingPlanPromise;
// Activate the plan by changing status to Active
let billingAgreementAttributes = await new Promise(
(resolve, reject) => {
paypal.billingPlan.update(
billingPlan.id,
billingPlanUpdateAttributes,
(error, response) => {
if (error) {
throw error;
} else {
billingAgreementAttr.plan.id = billingPlan.id;
resolve(billingAgreementAttr);
}
}
);
}
);
// Use activated billing plan to create agreement
let approval_url = await new Promise((resolve, reject) => {
paypal.billingAgreement.create(
billingAgreementAttributes,
(error, billingAgreement) => {
if (error) {
throw error;
} else {
for (
var index = 0;
index < billingAgreement.links.length;
index++
) {
if (billingAgreement.links[index].rel === "approval_url") {
var approval_url = billingAgreement.links[index].href;
let newApprovalUrl =
approval_url + `&custom=${clientFeatureId}`;
resolve(newApprovalUrl);
// See billing_agreements/execute.js to see example for executing agreement
// after you have payment token
}
}
}
}
);
});
let data = {
companyId: companyName,
serviceId: serviceName,
name: clientName,
secret: clientSecret,
features: totalFeatures,
endpoint: approval_url
};
return Object.assign(data);
}
}
},
The success route:
app.get("/success", (req, res) => {
console.log("This is response", res);
let paymentToken = req.query.token;
paypal.billingAgreement.execute(paymentToken, {}, function(
error,
billingAgreement
) {
if (error) {
throw error;
} else {
console.log("Billing agreement", billingAgreement);
let date = billingAgreement.start_date;
let amountString =
billingAgreement.plan.payment_definitions[1].amount.value;
let trial =
billingAgreement.plan.payment_definitions[0].frequency_interval;
let frequencyInterval =
billingAgreement.plan.payment_definitions[1].frequency_interval;
let frequency = billingAgreement.plan.payment_definitions[1].frequency;
let totalFrequency = frequencyInterval + " " + frequency;
let period = [trial, totalFrequency];
let amount = parseInt(amountString);
try {
Payment.create({
id: uuidv1(),
date: date,
amount: amount,
period: period
});
} catch (err) {
throw new Error(err);
}
res.render("index");
}
});
});
My implementation looks very different, but I can output any payment details. My payment.execute() looks like so:
const express = require("express");
const paypal = require("paypal-rest-sdk");
const app = express;
paypal.configure({
mode: "sandbox", //sandbox or live
client_id:
"...",
client_secret:
"..."
});
app.set("view engine", "ejs");
app.get("/", (req, res) => res.render("index"));
app.post("/pay", (req, res) => {
const create_payment_json = {
intent: "sale",
payer: {
payment_method: "paypal"
},
redirect_urls: {
return_url: "http://localhost:3000/success",
cancel_url: "http://localhost:3000/cancel"
},
transactions: [
{
item_list: {
items: [
{
name: "Item",
sku: "001",
price: "3.33",
currency: "USD",
quantity: 1
}
]
},
amount: {
currency: "USD",
total: "3.33"
},
description:
"Hope this helps."
}
]
};
paypal.payment.create(create_payment_json, function(error, payment) {
if (error) {
throw error;
} else {
// console.log("Create Payment Response");
// console.log(payment.id);
// res.send('test')
for (let i = 0; i < payment.links.length; i++) {
if (payment.links[i].rel === "approval_url") {
res.redirect(payment.links[i].href);
}
}
}
});
});
app.get("/success", (req, res) => {
const payerId = req.query.PayerID;
const paymentId = req.query.paymentId;
const execute_payment_json = {
payer_id: payerId,
transactions: [
{
amount: {
currency: "USD",
total: "3.33"
}
}
]
};
paypal.payment.execute(paymentId, execute_payment_json, function(
error,
payment
) {
if (error) {
console.log(error.response);
throw error;
} else {
console.log(JSON.stringify(payment));
}
});
});
app.get("/cancel", (req, res) => {
res.send("Cancelled");
});
app.listen(3000, () => console.log("Server Started"));

Resources