Search code examples
restserverwebhookssnipcart

Problem setting up node js server to listen for webhook and post to database


Good morning everyone, I'm having a bit of a struggle setting up a server to listen for webhook data and post it to a database. I'm mostly front-end, so some of this is a bit new for me. So I have a deli website that i built on snipcart. I have a receipt printer that queries an api and prints out new orders. So what I'm wanting is a server to listen for the webhook and store the info in a database. I've got it where it listens for the webhook correctly, but it refuses to post to the database. Here's the code in the app.js file.

'use strict';


require('./config/db');



const express = require('express');
const bodyParser = require('body-parser');
const fetch = require('node-fetch');



const app = express();

var routes = require('./api/routes/apiRoutes');
routes(app);

let orderToken;

app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());

app.listen(process.env.PORT || 8080);


app.post('/hook', (req, res) => {
  orderToken = req.body.content.token;
  console.log(orderToken);
  const secret = "snipcart api key";

  const apiFetch = async function(){


  };

  let buffered = new Buffer.from(secret);
  let base64data = buffered.toString('base64');

  const start = async function(){



  const request = await fetch('https://app.snipcart.com/api/orders/'+orderToken, {
      headers: {
         'Authorization': `Basic ${base64data}`,
          'Accept': 'application/json'
      }
  });

  const result = await request.json();
    console.log(result);
  };

  start();

  res.status(200).end();
});


app.get('/', (req, res) => {
  res.send('hello world')
});

Here's the code in my apiController.js file


const mongoose = require('mongoose'),
      Order = mongoose.model('apiModel');


// listAllOrders function - To list all orders
exports.listAllOrders = (req, res) => {
api.find({}, (err, api) => {
if (err) {
res.status(500).send(err);
}
res.status(200).json(api);
});
};

// createNewOrder function - To create new Order
exports.createNewOrder = (req, res) => {
let  newApi = new api (req.body);
newApi.save((err, api) => {
if (err) {
res.status(500).send(err);
}
res.status(201).json(api);
});
};

// deleteOrder function - To delete order by id
exports.deleteOrder = async ( req, res) => {
await  api.deleteOne({ _id:req.params.id }, (err) => {
if (err) {
return res.status(404).send(err);
}
res.status(200).json({ message:"Order successfully deleted"});
});
};

and my apiModel.js file


const mongoose = require('mongoose');

const Schema = mongoose.Schema;

const ApiSchema = new Schema({
  customerName: {
             type:String,
             required:true
         },
 customerPhone: {
           type:String,
           required:true
         },
  name: {
           type:String,
           required:true
       },
  orderNumber: {
           type:String,
           required:true
       },
  price: {
          type:String,
          required:true
       },
  customFields: {
        type:Array,
        required:false
        },
});

module.exports = mongoose.model("apiModel", ApiSchema);

apiRoutes.js




module.exports = function(app){
  var orderList = require('../controllers/apiController');


  app
  .route('/orders')
  .get(orderList.listAllOrders)
  .post(orderList.createNewOrder);


  app
  .route('/order/:id')
  .delete(orderList.deleteOrder);


};

and my db.js


const mongoose = require("mongoose");

//Assign MongoDB connection string to Uri and declare options settings
var  uri = "<mongodb atlas info> 
retryWrites=true&w=majority";

// Declare a variable named option and assign optional settings
const  options = {
useNewUrlParser:  true,
useUnifiedTopology:  true
};

// Connect MongoDB Atlas using mongoose connect method
mongoose.connect(uri, options).then(() => {
console.log("Database connection established!");
},
err  => {
{
console.log("Error connecting Database instance due to:", err);
}
});

and here's a sample response that I need to place into the database

{
    "token": "93c4604e-35ac-4db7-b3f1-2871476e9e6a",
    "creationDate": "2013-10-22T20:54:40.377Z",
    "modificationDate": "2013-10-22T20:55:45.617Z",
    "status": "Processed",
    "paymentMethod": "CreditCard",
    "invoiceNumber": "SNIP-1427",
    "email": "[email protected]",
    "cardHolderName": "Geeks Snipcart",
    "creditCardLast4Digits": "4242",
    "billingAddressName": "Geeks Snipcart",
    "billingAddressCompanyName": "Snipcart",
    "billingAddressAddress1": "4885 1ere Avenue",
    "billingAddressAddress2": null,
    "billingAddressCity": "Quebec",
    "billingAddressCountry": "CA",
    "billingAddressProvince": "QC",
    "billingAddressPostalCode": "G1H2T5",
    "billingAddressPhone": "1-877-301-4813",
    "notes": null,
    "shippingAddressName": "Geeks Snipcart",
    "shippingAddressCompanyName": "Snipcart",
    "shippingAddressAddress1": "4885 1ere Avenue",
    "shippingAddressAddress2": null,
    "shippingAddressCity": "Quebec",
    "shippingAddressCountry": "CA",
    "shippingAddressProvince": "QC",
    "shippingAddressPostalCode": "G1H2T5",
    "shippingAddressPhone": "1-877-301-4813",
    "shippingAddressSameAsBilling": true,
    "finalGrandTotal": 287.44,
    "shippingFees": 10,
    "shippingMethod": "Shipping",
    "items": [
        {
            "uniqueId": "1aad3398-1260-419c-9af4-d18e6fe75fbf",
            "id": "1",
            "name": "Un poster",
            "price": 300,
            "quantity": 1,
            "url": "http://snipcart.com",
            "weight": 10,
            "description": "Bacon",
            "image": "",
            "customFieldsJson": "[]",
            "stackable": true,
            "maxQuantity": null,
            "totalPrice": 300,
            "totalWeight": 10
        },
        ...
    ],
    "taxes": [
        {
            "taxName": "TPS",
            "taxRate": 0.05,
            "amount": 12.5,
            "numberForInvoice": ""
        },
        {
            "taxName": "TVQ",
            "taxRate": 0.09975,
            "amount": 24.94,
            "numberForInvoice": ""
        },
        ...
    ],
    "rebateAmount": 0,
    "subtotal": 310,
    "itemsTotal": 300,
    "grandTotal": 347.44,
    "totalWeight": 10,
    "hasPromocode": true,
    "totalRebateRate": 20,
    "promocodes": [
        {
            "code": "PROMO",
            "name": "PROMO",
            "type": "Rate",
            "rate": 20,
        },
        ...
    ],
    "willBePaidLater": false,
    "customFields": [
        {
            "name":"Slug",
            "value": "An order"
        },
        ...
    ],
    "paymentTransactionId": null,
}

I dont need all the info placed in the database, just a few key items, like customer name, phone number and the order info. but if there's more than one item in the order, I need it to take that into account and add all the items in the order. here is the docs for the printer that i'm needing to integrate https://star-m.jp/products/s_print/CloudPRNTSDK/Documentation/en/index.html Would appreciate any help that you all can give me. Thanks!


Solution

  • Snipcart will send the webhook to you endpoint for different events. I would suggest you to first filter the event by eventName, because you want to listen for only the order.completed event. After that from the body of the request message, you can extract the items that will be in the req.body.content.items. You can take from the available info what you want and store only that in the database.

    Try this:

    app.post('/hook', (req, res) => {
    
       if (req.body.eventName === 'order.completed') {
    
          const customer_name = req.body.content.cardHolderName;
          const customer_phone req.body.content.billingAddressPhone;
          const order_number = req.body.content.invoiceNumber;
          
          let items = [];
          req.body.content.items.forEach((item) => {
               items.push({
                   name: item.name,
                   price: item.price,
                   quantity: item.quantity,
                   id: item.uniqueId
               });
          })
    
    
          // Now store in database
    
          apiFetch.create({
              customerName: customer_name,
              customerPhone: customer_phone
              name: customer_name,
              orderNumber: order_number 
              customFields: items 
          }).then(()=>{
              res.status(200).json({success:true});
          }, (error)=>{
              console.log('ERROR: ', error);
          })
        
       }
    
    };