forked from mrvautin/expressCart
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Catering for multiple payments * Schema fixes * Updated docs * Fix config * Payment fixes * Small payment UI changes * Stripe and instore fixes * Fixed incorrect field * Conditional GW config * Updated readme * Paypal fixes * Fix payway key * Bump version * Initial adding of Zip payments * Small fixes * Small fixes * Added supported currencies * Lookup currency and country properly * Remove debut
- Loading branch information
Showing
10 changed files
with
402 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
**/*.min.js | ||
pm2.config.js | ||
pm2.config.js | ||
jsconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"description": "Pay with Zip", | ||
"apiKey": "this_is_a_test_api_key", | ||
"publicKey": "sk_test_this_is_not_real", | ||
"privateKey": "KqtURWtVQXAA9ksD1CPpufYXgtxFe0hL9O2BF7hLXWQ=", | ||
"mode": "test", | ||
"currency": "AUD", | ||
"supportedCountries": [ | ||
"Australia", | ||
"New Zealand" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"properties": { | ||
"description": { | ||
"type": "string" | ||
}, | ||
"apiKey": { | ||
"type": "string" | ||
}, | ||
"publicKey": { | ||
"type": "string", | ||
"default": "sandbox" | ||
}, | ||
"privateKey": { | ||
"type": "string" | ||
}, | ||
"mode": { | ||
"type": "string", | ||
"default": "test", | ||
"enum": ["test", "live"] | ||
}, | ||
"currency": { | ||
"type": "string", | ||
"default": "AUD", | ||
"enum": ["AUD", "NZD"] | ||
}, | ||
"supportedCountries":{ | ||
"type": "array" | ||
} | ||
}, | ||
"required": [ | ||
"description", | ||
"apiKey", | ||
"publicKey", | ||
"privateKey", | ||
"mode", | ||
"currency", | ||
"supportedCountries" | ||
], | ||
"additionalProperties": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,6 +99,10 @@ | |
{ | ||
"type": "string", | ||
"enum": [ "instore"] | ||
}, | ||
{ | ||
"type": "string", | ||
"enum": [ "zip"] | ||
} | ||
] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
const express = require('express'); | ||
const { indexOrders } = require('../indexing'); | ||
const { getId, sendEmail, getEmailTemplate, getCountryNameToCode } = require('../common'); | ||
const { getPaymentConfig } = require('../config'); | ||
const { emptyCart } = require('../cart'); | ||
const axios = require('axios'); | ||
const ObjectId = require('mongodb').ObjectID; | ||
const paymentConfig = getPaymentConfig('zip'); | ||
const router = express.Router(); | ||
|
||
// Setup the API | ||
const payloadConfig = { | ||
headers: { | ||
Authorization: `Bearer ${paymentConfig.privateKey}`, | ||
'zip-Version': '2017-03-01', | ||
'Content-Type': 'application/json' | ||
} | ||
}; | ||
let apiUrl = 'https://api.zipmoney.com.au/merchant/v1'; | ||
if(paymentConfig.mode === 'test'){ | ||
apiUrl = 'https://api.sandbox.zipmoney.com.au/merchant/v1'; | ||
} | ||
|
||
// The homepage of the site | ||
router.post('/setup', async (req, res, next) => { | ||
const db = req.app.db; | ||
const config = req.app.config; | ||
const paymentConfig = getPaymentConfig('zip'); | ||
|
||
// Check country is supported | ||
if(!paymentConfig.supportedCountries.includes(req.session.customerCountry)){ | ||
res.status(400).json({ | ||
error: 'Unfortunately Zip is not supported in your country.' | ||
}); | ||
return; | ||
} | ||
|
||
if(!req.session.cart){ | ||
res.status(400).json({ | ||
error: 'Cart is empty. Please add something to your cart before checking out.' | ||
}); | ||
return; | ||
} | ||
|
||
// Get country code from country name | ||
const countryCode = getCountryNameToCode(req.session.customerCountry).code; | ||
|
||
// Create the payload | ||
const payload = { | ||
shopper: { | ||
first_name: req.session.customerFirstname, | ||
last_name: req.session.customerLastname, | ||
phone: req.session.customerPhone, | ||
email: req.session.customerEmail, | ||
billing_address: { | ||
line1: req.session.customerAddress1, | ||
city: req.session.customerAddress2, | ||
state: req.session.customerState, | ||
postal_code: req.session.customerPostcode, | ||
country: countryCode | ||
} | ||
}, | ||
order: { | ||
amount: req.session.totalCartAmount, | ||
currency: paymentConfig.currency, | ||
shipping: { | ||
pickup: false, | ||
address: { | ||
line1: req.session.customerAddress1, | ||
city: req.session.customerAddress2, | ||
state: req.session.customerState, | ||
postal_code: req.session.customerPostcode, | ||
country: countryCode | ||
} | ||
}, | ||
items: [ | ||
{ | ||
name: 'Shipping', | ||
amount: req.session.totalCartShipping, | ||
quantity: 1, | ||
type: 'shipping', | ||
reference: 'Shipping amount' | ||
} | ||
] | ||
}, | ||
config: { | ||
redirect_uri: 'http://localhost:1111/zip/return' | ||
} | ||
}; | ||
|
||
// Loop items in the cart | ||
Object.keys(req.session.cart).forEach((cartItemId) => { | ||
const cartItem = req.session.cart[cartItemId]; | ||
const item = { | ||
name: cartItem.title, | ||
amount: cartItem.totalItemPrice, | ||
quantity: cartItem.quantity, | ||
type: 'sku', | ||
reference: cartItem.productId.toString() | ||
}; | ||
|
||
if(cartItem.productImage && cartItem.productImage !== ''){ | ||
item.image_uri = `${config.baseUrl}${cartItem.productImage}`; | ||
} | ||
payload.order.items.push(item); | ||
}); | ||
|
||
try{ | ||
const response = await axios.post(`${apiUrl}/checkouts`, | ||
payload, | ||
payloadConfig | ||
); | ||
|
||
// Setup order | ||
const orderDoc = { | ||
orderPaymentId: response.data.id, | ||
orderPaymentGateway: 'Zip', | ||
orderTotal: req.session.totalCartAmount, | ||
orderShipping: req.session.totalCartShipping, | ||
orderItemCount: req.session.totalCartItems, | ||
orderProductCount: req.session.totalCartProducts, | ||
orderCustomer: getId(req.session.customerId), | ||
orderEmail: req.session.customerEmail, | ||
orderCompany: req.session.customerCompany, | ||
orderFirstname: req.session.customerFirstname, | ||
orderLastname: req.session.customerLastname, | ||
orderAddr1: req.session.customerAddress1, | ||
orderAddr2: req.session.customerAddress2, | ||
orderCountry: req.session.customerCountry, | ||
orderState: req.session.customerState, | ||
orderPostcode: req.session.customerPostcode, | ||
orderPhoneNumber: req.session.customerPhone, | ||
orderComment: req.session.orderComment, | ||
orderStatus: 'Pending', | ||
orderDate: new Date(), | ||
orderProducts: req.session.cart, | ||
orderType: 'Single' | ||
}; | ||
|
||
// insert order into DB | ||
const order = await db.orders.insertOne(orderDoc); | ||
|
||
// Set the order ID for the response | ||
req.session.orderId = order.insertedId; | ||
|
||
// add to lunr index | ||
indexOrders(req.app) | ||
.then(() => { | ||
// Return response | ||
res.json({ | ||
orderId: order.insertedId, | ||
redirectUri: response.data.uri | ||
}); | ||
}); | ||
}catch(ex){ | ||
console.log('ex', ex); | ||
res.status(400).json({ | ||
error: 'Failed to process payment' | ||
}); | ||
} | ||
}); | ||
|
||
router.get('/return', async (req, res, next) => { | ||
const db = req.app.db; | ||
const result = req.query.result; | ||
|
||
// If cancelled | ||
if(result === 'cancelled'){ | ||
// Update the order | ||
await db.orders.deleteOne({ | ||
_id: ObjectId(req.session.orderId) | ||
}); | ||
} | ||
|
||
res.redirect('/checkout/payment'); | ||
}); | ||
|
||
router.post('/charge', async (req, res, next) => { | ||
const db = req.app.db; | ||
const config = req.app.config; | ||
const paymentConfig = getPaymentConfig('zip'); | ||
const checkoutId = req.body.checkoutId; | ||
|
||
// grab order | ||
const order = await db.orders.findOne({ _id: ObjectId(req.session.orderId) }); | ||
|
||
// Cross check the checkoutId | ||
if(checkoutId !== order.orderPaymentId){ | ||
console.log('order check failed'); | ||
res.status(400).json({ err: 'Order not found. Please try again.' }); | ||
return; | ||
} | ||
|
||
// Create charge payload | ||
const payload = { | ||
authority: { | ||
type: 'checkout_id', | ||
value: checkoutId | ||
}, | ||
reference: order._id, | ||
amount: order.orderTotal, | ||
currency: paymentConfig.currency, | ||
capture: true | ||
}; | ||
|
||
try{ | ||
// Create charge | ||
const response = await axios.post(`${apiUrl}/charges`, | ||
payload, | ||
payloadConfig | ||
); | ||
|
||
// Set the order fields | ||
const updateDoc = { | ||
orderStatus: 'Paid', | ||
orderPaymentMessage: 'Payment completed successfully', | ||
orderPaymentId: checkoutId | ||
}; | ||
|
||
// Update result | ||
if(response.data.state === 'captured'){ | ||
updateDoc.orderStatus = 'Paid'; | ||
updateDoc.orderPaymentMessage = 'Payment completed successfully'; | ||
}else{ | ||
updateDoc.orderPaymentMessage = `Check payment: ${response.data.state}`; | ||
} | ||
|
||
// Update the order | ||
await db.orders.updateOne({ | ||
_id: ObjectId(req.session.orderId) | ||
}, | ||
{ $set: updateDoc }, | ||
{ multi: false, returnOriginal: false }); | ||
|
||
// Return decline | ||
if(updateDoc.orderStatus !== 'Paid'){ | ||
res.status(400).json({ err: 'Your payment has declined. Please try again' }); | ||
return; | ||
} | ||
|
||
// set payment results for email | ||
const paymentResults = { | ||
message: req.session.message, | ||
messageType: req.session.messageType, | ||
paymentEmailAddr: req.session.paymentEmailAddr, | ||
paymentApproved: true, | ||
paymentDetails: req.session.paymentDetails | ||
}; | ||
|
||
// clear the cart | ||
if(req.session.cart){ | ||
emptyCart(req, res, 'function'); | ||
} | ||
|
||
// send the email with the response | ||
// TODO: Should fix this to properly handle result | ||
sendEmail(req.session.paymentEmailAddr, `Your payment with ${config.cartTitle}`, getEmailTemplate(paymentResults)); | ||
|
||
// add to lunr index | ||
indexOrders(req.app) | ||
.then(() => { | ||
res.json({ | ||
message: 'Payment completed successfully', | ||
paymentId: order._id | ||
}); | ||
}); | ||
}catch(ex){ | ||
console.log('ex', ex); | ||
res.status(400).json({ | ||
error: 'Failed to process payment' | ||
}); | ||
} | ||
}); | ||
|
||
module.exports = router; |
Oops, something went wrong.