Loopback and Stripe Webhooks - javascript

I currently have a loopback project setup, and I am trying to receive webhooks from stripe.
My current Remote method looks like the following:-
Stripeconnect.stripeWebhook = function(msg, cb) {
cb(null, msg);
};
Stripeconnect.remoteMethod(
'stripeWebhook', {
description: 'This will insert the description',
http: {
path: '/stripeWebhook',
verb: 'get'
},
accepts:
{arg: 'msg', type: 'any'},
returns: {
arg: 'status',
type: 'any'
}
}
)
But in the response I receive from Stripe is:-
undefined [Function: callback]
I am unable to find any documentation online regarding Loopback and Stripe webhooks.
Would anybody be able to help, or point me in the right direction?
I have setup Stripe to point at this endpoint of the API.
Thanks in advance. If you need anymore info please let me know.

Ok so I was able to get this working by getting the response from the body:-
/**
* Receiving Webhook
* #desc Webhook EP
* #param {data} Object from Stripe.
* #return {response} response code
*/
Stripeconnect.stripeWebhook = function(request, cb) {
console.log(request.type, request.data);
};
Stripeconnect.remoteMethod(
'stripeWebhook', {
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
returns: [
{arg: 'response', type: 'any', root: true }
]
});
Which you can see from:-
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
Hopefully this helps anybody else who is having this or a similar issue.

Related

Unable to deploy the Node.js AWS Lambda function to Docker

I am developing a REST API with Node.Js. my technology stack is AWS Lambda, API Gateway and RDS (MySQL). Below is my code
roles.js
const mysql = require('mysql');
const con = mysql.createConnection({
host : "*****.rds.amazonaws.com",
user : "*****",
password : "*****",
port : 3306,
database : "******"
});
exports.lambdaHandler = (event, context, callback) => {
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "select * from role";
con.query(sql, function (err, result) {
if (err) throw err;
var response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(result),
"isBase64Encoded": false
};
callback(null, response)
});
};
exports.selectRoleByIDHandler = (event, context, callback) => {
const { id } = event.queryStringParameters;
console.log("id", id);
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "select * from role where idRole = "+id;
con.query(sql, function (err, result) {
if (err) throw err;
var response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(result),
"isBase64Encoded": false
};
callback(null, response)
});
};
Below is my template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
node2
Sample SAM Template for node2
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 100
VpcConfig:
SecurityGroupIds:
- sg-sdsdsdsd
SubnetIds:
- subnet-ssdsds
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Role: !GetAtt LambdaRole.Arn
RoleFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: roles.lambdaHandler
Runtime: nodejs14.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /roles
Method: get
Role: !GetAtt LambdaRole.Arn
SelectRolesByIDFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: roles.selectRoleByIDHandler
Runtime: nodejs14.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /selectRoleByIDHandler
Method: get
Role: !GetAtt LambdaRole.Arn
LambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:DescribeNetworkInterfaces
- ec2:CreateNetworkInterface
- ec2:DeleteNetworkInterface
- ec2:DescribeInstances
- ec2:AttachNetworkInterface
Resource: '*'
When I try to execute the sam local invoke, i get the following error
Error: You must provide a function logical ID when there are more than one functions in your template. Possible options in your template: ['HelloWorldFunction', 'RoleFunction', 'SelectRolesByIDFunction']
Now, I have 2 questions.
How to solve this issue?
Is it a bad practice to have more than one function in a single file in AWS Lambda?
Looks like you are trying to build Lambda functions via the AWS SDK for Javascipt. Have you looked at the AWS examples in the AWS SDK for JavaScript V3 DEV Guide. There are end to end instructions on how to build Lambda function using the JS API that can help you.
See this topic and the child topics in the TOC:
Cross-service examples for the AWS SDK for JavaScript
I found the issue, I had to invoke this as sam local invoke HelloWorldFunction . The HelloWorldFunction is the function name I need to deploy.

Problems integrating DynamoBD with API Gateway

I'm trying to use dynamoDb with API Gateway, but it is not working. This is what I'm trying to do
export async function handler(event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {
switch (event.httpMethod) {
case "GET":
var params = {
TableName: 'Users',
Key: {
UserID: event.queryStringParameters.UserID
}
};
dynamodb.get(params, function (err, data) {
if (err){
return{
statusCode: 200,
body: JSON.stringify({
message: "Item not found"
})
}
} else {
return {
statusCode: 200,
body: JSON.stringify({
message: data.Item,
})
};
}
})
break;
}
Every time I try to call my gateway I get a
{
"message": "Internal server error"
}
Is it a problem with my integration ?
Another doubt, how can I add other routes to my Gateway ? I'm using CloudFloration Template and it's this:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
UserAPI:
Type: AWS::Serverless::Function
Properties:
Handler: build/userAPI.handler
Runtime: nodejs14.x
Events:
Api:
Type: Api
Properties:
Path: /users
Method: ANY
How can I add a route like /user (GET) or /users (POST) ?
As requested I get the logs so the lambda is no been invoked the error I get is:
2021-06-23T12:08:37.557Z eb945ca9-a4b6-4f8e-add1-774276db2cb7 ERROR Invoke Error {
"errorType": "TypeError",
"errorMessage": "Cannot read property 'UserID' of null",
"stack": [
"TypeError: Cannot read property 'UserID' of null",
" at Runtime.handler (/var/task/build/userAPI.js:12:57)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}

Specify content-type in API Gateway method response using CDK

I am creating proxy API gateway to non-public S3 bucket using CDK.
The S3 bucket contains html, javascript, and css files.
I created an api using CDK like this:
const api = new apigw.RestApi(this, 'Test-Web')
api.root
.addResource('{file}')
.addMethod('GET', new apigw.AwsIntegration({
service: 's3',
integrationHttpMethod: 'GET',
path: `${bucket.bucketName}/{file}`,
options: {
credentialsRole: role,
requestParameters: {
'integration.request.path.file': 'method.request.path.file'
},
integrationResponses: [{
statusCode: '200'
}]
}
}), {
requestParameters: {
'method.request.path.file': true
},
methodResponses: [{
statusCode: '200'
}]
})
It works fine, but has a problem. The content type of the response is always set to application/json. I could see that the content type of integration responses (responses from S3) varies from text/html to text/css, application/javascript depending on the file.
How can I set this API to return correct content type on each file by passing the same content type header value of integration response to method response? Best if I can pass the content-type header from S3 as it already returns correctly.
CDK documentation is not great. I managed to find a solution:
I had to add responseParameters in integrationResponses to set Content-Type header from S3 to API gateway response. Please see below, especially the line marked with <<<--.
api.root
.addResource('{file}')
.addMethod(
'GET',
new apigw.AwsIntegration({
service: 's3',
integrationHttpMethod: 'GET',
path: `${bucket.bucketName}/{file}`,
options: {
credentialsRole: role,
requestParameters: {
'integration.request.path.file': 'method.request.path.file'
},
integrationResponses: [{
statusCode: '200',
selectionPattern: '2..',
responseParameters: {
'method.response.header.Content-Type': 'integration.response.header.Content-Type' // <<<--
},
}, {
statusCode: '403',
selectionPattern: '4..'
}]
}
}), {
requestParameters: {
'method.request.path.file': true
},
methodResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Content-Type': true // <<<--
}
}, {
statusCode: '404'
}]
})

How to add permissions to a new API with STRAPI (not from dashboard)

I need to do a new api in order to send an email with sendgrid. I followed the official doc and other examples so I did:
config/plugins
module.exports = ({ env }) => ({
email: {
provider: 'sendgrid',
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: 'juliasedefdjian#strapi.io',
defaultReplyTo: 'juliasedefdjian#strapi.io',
},
},
});
then I did a new folder named email in api folder
api/email/config/routes.json
{
"routes": [
{
"method": "POST",
"path": "/email",
"handler": "email.index",
"config": {
"policies": []
}
}
]
}
finally under api/email/controllers/email.js
const { default: createStrapi } = require('strapi');
module.exports = {
index: async (ctx) => {
//build email with data from ctx.request.body
await createStrapi.plugins['email'].services.email.send({
to: 'email#email.com',
from: 'email#email.com',
replyTo: 'email#email.com',
subject: 'test',
text: 'test',
});
ctx.send('Email sent!');
},
};
The real problem is that /email api returns me a 403 even if I did this from the dashboard:
I have done many APIs with strapi but I have never sent emails with it.
Is there a way to add permissions from the code? I have to say that if I use GET method it works, but I need to do it with a POST method, which doesn't. Did I miss something?

Cross-domain Update from Sencha Touch 2 to external server

Trying to create a Sencha-Touch-2 app syncing to a Node.js server; code below.
The server uses another port on the same IP, so this is cross-domain.
(The server uses Mongoose to talk to a MongoDB back-end (not shown))
Using a JSONP Proxy as shown can read data from the server but breaks when writing:
"JSONP proxies can only be used to read data".
I guess the JSONP Proxy writer config is just to write the query and isn't used to write sync (save).
Sencha docs state an Ajax proxy can't go cross-domain, even though a
Cross-domain Ext.Ajax/Ext.data.Connection is discussed in the Sencha forums:
http://www.sencha.com/forum/showthread.php?17691-Cross-domain-Ext.Ajax-Ext.data.Connection
I have found several ways to do a (cross-domain) JSON post (e.g. Mobile Application Using Sencha Touch - JSON Request Generates Syntax Error)
but don't know how to integrate this as a writer in a proxy which syncs my store.
Sencha Touch: ScriptTagProxy url for create/update functionality
seems to offer pointers, but this is ajax and apparently unsuited for cross domain.
I've been reading this forum and elsewhere for a couple of days, but I seem to be stuck. Any help would be much appreciated.
Node.js and restify server
var server = restify.createServer({
name: 'Server',
key: fs.readFileSync(root+'/'+'privatekey.pem'),
certificate: fs.readFileSync(root+'/'+'certificate.pem')
});
server.use(restify.bodyParser());
server.use(restify.queryParser());
function getMessages(req, res, next) {
Model.find(function (err,data) {
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8');
res.send(req.query["callback"] + '({"records":' + JSON.stringify(data) + '});');
});
}
function postMessage(req, res, next) {//not yet tested
var obj = new Model();
obj.name = req.params.name;
obj.description = req.params.description;
obj.date = new Date();
obj.save(function (err) {
if (err) throw err;
console.log('Saved.');
res.send('Saved.');
});
}
server.post(/^\/atapp/, postMessage);
server.get(/^\/atapp/, getMessages);
server.listen(port, ipaddr, function() {
console.log('%s: secure Node server started on %s:%d ...', Date(Date.now()), ipaddr, port);
});
Sencha Touch 2
Model
Ext.define('ATApp.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'name', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'date', type: 'date' },
{ name: '_id' }
...
Store
Ext.define('ATApp.store.Data', {
extend: 'Ext.data.Store',
requires: [
'ATApp.model.User',
'Ext.data.proxy.JsonP'
],
config: {
autoLoad: true,
model: 'ATApp.model.User',
storeId: 'Data',
proxy: {
type: 'jsonp',
model: 'ATApp.model.User',
url: 'https://192.168.2.45:13017/atapp',
reader: {
type: 'json',
idProperty: '_id',
rootProperty: 'records',
useSimpleAccessors: true
},
writer: {
type: 'json',
allowSingle: false,
encode: true,
idProperty: '_id',
rootProperty: 'records'
...
Controller
onNewDataRecord: function (view) {
console.log('newDataRecord');
var now = new Date();
var record = Ext.create('ATApp.model.User', {
date: now,
name: '..',
description: '..'
});
var store = Ext.data.StoreManager.lookup('Data')
record.setProxy(store.getProxy());
store.add(record);
this.activateEditor(record);
},
...
In Sencha-Touch-2 apps, the browser prohibits cross-domain AJAX calls (which violate the same-origin security policy). This pertains to different domains, different IP addresses and even different ports on the same IP address. JSONP circumvents this partly by fetching/reading data encapsulated in a script tag in a newly initiated HTTP GET message. In this way the Sencha-Touch-2 JSONP proxy can load a store (fetch/read) from a (cross domain) server. However, the JSONP proxy cannot write data. In 1 and 2 an approach is described which I have adapted.
My solution uses the JSONP proxy to fetch data, but not to store (which it can't). Instead, new records, and records to be saved or deleted are communicated with the server in a newly initiated HTTP GET message. Even though only HTTP GET is used, the server accepts message get (described in the question, above), put, del and new. Get is used by JSONP store/proxy load().
Node.js Server
//routes
server.get(/^\/atapp\/put/, putMessage);
server.get(/^\/atapp\/get/, getMessages);
server.get(/^\/atapp\/del/, delMessage);
server.get(/^\/atapp\/new/, newMessage);
function newMessage(req, res, next) {
var obj = new Model(); // Mongoose create new MongoDB object
obj.save(function (err,data) {
var x = err || data;
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8');
res.send(req.query["callback"] + '({"payload":' + JSON.stringify(x) + '});');
}); // send reply to Sencha-Touch 2 callback
}
function putMessage(req, res, next) {
var q = JSON.parse(req.query.data); // no reply: add error recovery separately
var obj = Model.findByIdAndUpdate(q.key,q.val);
}
function delMessage(req, res, next) {
var key = JSON.parse(req.query.data);
Model.findByIdAndRemove(key); // no reply: add error recovery separately
}
Sencha Controller
New
onNewDataRecord: function (view) {
var control = this;
Ext.Ajax.Crossdomain.request({
url: 'https://192.168.2.45:13017/atapp/new',
rootProperty: 'payload',
scriptTag: true, // see [1](http://code.google.com/p/extjsdyntran/source/browse/trunk/extjsdyntran/WebContent/js/3rdparty/Ext.lib.Ajax.js?r=203)
success: function(r) { // process synchronously after response
var obj = r.payload;
var store = Ext.data.StoreManager.lookup('Data');
var key = obj._id // MongoDB document id
store.load(function(records, operation, success) { // add new record to store
var ind = store.findBy(function(rec,id) {
return rec.raw._id==key;
}); // identify record in store
var record = store.getAt(ind);
control.onEditDataRecord(view,record);
}, this);
}
});
Save
onSaveDataRecord: function (view, record) {
rec = {key:record.data.id, val: {}} // save template
var i; for (i in record.modified) rec.val[i]=record.data[i];
var delta = Ext.encode(rec); // only save modified fields
Ext.Ajax.Crossdomain.request({
url: 'https://192.168.2.45:13017/atapp/put',
params: {
data: delta,
},
rootProperty: 'payload',
scriptTag: true, // Use script tag transport
});
},
Delete
onDelDataRecord: function (view, record) {
var key = record.data.id;
Ext.Ajax.Crossdomain.request({ // delete document in db
url: 'https://192.168.2.45:13017/atapp/del',
params: {
data: Ext.encode(key),
format: 'json'
},
rootProperty: 'payload',
scriptTag: true, // Use script tag transport
});
record.destroy(); // delete record from store
},

Categories