Added instore payment type.
parent
f2e6b32384
commit
eafb690445
19
README.md
19
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
![expressCart](https://raw.githubusercontent.com/mrvautin/expressCart/master/public/images/logo.png)
|
||||
|
||||
`expressCart` is a fully functional shopping cart built in Node.js (Express, MongoDB) with Stripe, PayPal, Authorize.net and Adyen payments.
|
||||
`expressCart` is a fully functional shopping cart built in Node.js (Express, MongoDB) with Stripe, PayPal, Authorize.net, Adyen and Instore payments.
|
||||
|
||||
[![Github stars](https://img.shields.io/github/stars/mrvautin/expressCart.svg?style=social&label=Star)](https://github.com/mrvautin/expressCart)
|
||||
[![Build Status](https://travis-ci.org/mrvautin/expressCart.svg?branch=master)](https://travis-ci.org/mrvautin/expressCart)
|
||||
|
@ -308,6 +308,19 @@ The Adyen config file is located: `/config/adyen.json`. A example Adyen settings
|
|||
|
||||
Note: The `publicKey`, `apiKey` and `merchantAccount` is obtained from your Adyen account dashboard.
|
||||
|
||||
##### Instore (Payments)
|
||||
|
||||
The Instore config file is located: `/config/instore.json`. A example Instore settings file is provided:
|
||||
|
||||
```
|
||||
{
|
||||
"orderStatus": "Pending",
|
||||
"buttonText": "Place order, pay instore",
|
||||
"resultMessage": "The order is place. Please pay for your order instore on pickup."
|
||||
}
|
||||
```
|
||||
Note: No payment is actually processed. The order will move to the `orderStatus` set and the payment is completed instore.
|
||||
|
||||
## Email settings
|
||||
|
||||
You will need to configure your SMTP details for expressCart to send email receipts to your customers.
|
||||
|
@ -362,9 +375,7 @@ New static pages are setup via `/admin/settings/pages`.
|
|||
|
||||
## TODO
|
||||
|
||||
- Add some tests...
|
||||
- Separate API and frontend
|
||||
- Modernize the frontend
|
||||
- Modernize the frontend of the admin
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
27
app.js
27
app.js
|
@ -66,6 +66,12 @@ switch(config.paymentGateway){
|
|||
process.exit(2);
|
||||
}
|
||||
break;
|
||||
case'instore':
|
||||
if(ajv.validate(require('./config/instoreSchema'), require('./config/instore.json')) === false){
|
||||
console.log(colors.red(`instore config is incorrect: ${ajv.errorsText()}`));
|
||||
process.exit(2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// require the routes
|
||||
|
@ -79,6 +85,7 @@ const paypal = require('./routes/payments/paypal');
|
|||
const stripe = require('./routes/payments/stripe');
|
||||
const authorizenet = require('./routes/payments/authorizenet');
|
||||
const adyen = require('./routes/payments/adyen');
|
||||
const instore = require('./routes/payments/instore');
|
||||
|
||||
const app = express();
|
||||
|
||||
|
@ -259,6 +266,25 @@ handlebars = handlebars.create({
|
|||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
},
|
||||
paymentMessage: (status) => {
|
||||
if(status === 'Paid'){
|
||||
return'<h2 class="text-success">Your payment has been successfully processed</h2>';
|
||||
}
|
||||
if(status === 'Pending'){
|
||||
const paymentConfig = common.getPaymentConfig();
|
||||
if(config.paymentGateway === 'instore'){
|
||||
return`<h2 class="text-warning">${paymentConfig.resultMessage}</h2>`;
|
||||
}
|
||||
return'<h2 class="text-warning">The payment for this order is pending. We will be in contact shortly.</h2>';
|
||||
}
|
||||
return'<h2 class="text-success">Your payment has failed. Please try again or contact us.</h2>';
|
||||
},
|
||||
paymentOutcome: (status) => {
|
||||
if(status === 'Paid' || status === 'Pending'){
|
||||
return'<h3 class="text-success">Please retain the details above as a reference of payment</h3>';
|
||||
}
|
||||
return'';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -338,6 +364,7 @@ app.use('/paypal', paypal);
|
|||
app.use('/stripe', stripe);
|
||||
app.use('/authorizenet', authorizenet);
|
||||
app.use('/adyen', adyen);
|
||||
app.use('/instore', instore);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use((req, res, next) => {
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
},
|
||||
"paymentGateway": {
|
||||
"type": "string",
|
||||
"enum": ["paypal", "stripe", "authorizenet", "adyen"]
|
||||
"enum": ["paypal", "stripe", "authorizenet", "adyen", "instore"]
|
||||
},
|
||||
"databaseConnectionString": {
|
||||
"type": "string"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"orderStatus": "Pending",
|
||||
"buttonText": "Place order, pay instore",
|
||||
"resultMessage": "The order is place. Please pay for your order instore on pickup."
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"properties": {
|
||||
"orderStatus": {
|
||||
"type": "string",
|
||||
"enum": ["Completed", "Paid", "Pending"]
|
||||
},
|
||||
"buttonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"resultMessage": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"orderStatus",
|
||||
"buttonText",
|
||||
"resultMessage"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
|
@ -98,6 +98,7 @@
|
|||
"stripe",
|
||||
"authorise.net",
|
||||
"adyen",
|
||||
"instore",
|
||||
"lunr",
|
||||
"cart",
|
||||
"shopping"
|
||||
|
|
|
@ -55,7 +55,7 @@ router.get('/payment/:orderId', async (req, res, next) => {
|
|||
await hooker(order);
|
||||
};
|
||||
|
||||
res.render(`${config.themeViews}payment_complete`, {
|
||||
res.render(`${config.themeViews}payment-complete`, {
|
||||
title: 'Payment complete',
|
||||
config: req.app.config,
|
||||
session: req.session,
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
const express = require('express');
|
||||
const common = require('../../lib/common');
|
||||
const { indexOrders } = require('../../lib/indexing');
|
||||
const router = express.Router();
|
||||
|
||||
// The homepage of the site
|
||||
router.post('/checkout_action', async (req, res, next) => {
|
||||
const db = req.app.db;
|
||||
const config = req.app.config;
|
||||
const instoreConfig = common.getPaymentConfig();
|
||||
|
||||
const orderDoc = {
|
||||
orderPaymentId: common.getId(),
|
||||
orderPaymentGateway: 'Instore',
|
||||
orderPaymentMessage: 'Your payment was successfully completed',
|
||||
orderTotal: req.session.totalCartAmount,
|
||||
orderItemCount: req.session.totalCartItems,
|
||||
orderProductCount: req.session.totalCartProducts,
|
||||
orderEmail: req.session.customerEmail,
|
||||
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: instoreConfig.orderStatus,
|
||||
orderDate: new Date(),
|
||||
orderProducts: req.session.cart
|
||||
};
|
||||
|
||||
// insert order into DB
|
||||
try{
|
||||
const newDoc = await db.orders.insertOne(orderDoc);
|
||||
|
||||
// get the new ID
|
||||
const newId = newDoc.insertedId;
|
||||
|
||||
// add to lunr index
|
||||
indexOrders(req.app)
|
||||
.then(() => {
|
||||
// set the results
|
||||
req.session.messageType = 'success';
|
||||
req.session.message = 'Your order was successfully placed. Payment for your order will be completed instore.';
|
||||
req.session.paymentEmailAddr = newDoc.ops[0].orderEmail;
|
||||
req.session.paymentApproved = true;
|
||||
req.session.paymentDetails = `<p><strong>Order ID: </strong>${newId}</p>
|
||||
<p><strong>Transaction ID: </strong>${orderDoc.orderPaymentId}</p>`;
|
||||
|
||||
// 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){
|
||||
req.session.cart = null;
|
||||
req.session.orderId = null;
|
||||
req.session.totalCartAmount = 0;
|
||||
}
|
||||
|
||||
// send the email with the response
|
||||
// TODO: Should fix this to properly handle result
|
||||
common.sendEmail(req.session.paymentEmailAddr, `Your order with ${config.cartTitle}`, common.getEmailTemplate(paymentResults));
|
||||
|
||||
// redirect to outcome
|
||||
res.redirect('/payment/' + newId);
|
||||
});
|
||||
}catch(ex){
|
||||
console.log('Error sending payment to API', ex);
|
||||
res.status(400).json({ err: 'Your order declined. Please try again' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,3 @@
|
|||
<div class="instore_button col-sm-12 text-center">
|
||||
<button id="checkout_instore" class="btn btn-outline-success" type="submit">{{@root.paymentConfig.buttonText}}</button>
|
||||
</div>
|
|
@ -53,6 +53,9 @@
|
|||
{{#ifCond config.paymentGateway '==' 'adyen'}}
|
||||
{{> partials/payments/adyen}}
|
||||
{{/ifCond}}
|
||||
{{#ifCond config.paymentGateway '==' 'instore'}}
|
||||
{{> partials/payments/instore}}
|
||||
{{/ifCond}}
|
||||
{{/if}}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<div class="col-md-10 offset-md-1 col-sm-12 top-pad-50">
|
||||
<div class="row">
|
||||
<div class="text-center col-md-10 offset-md-1">
|
||||
{{#paymentMessage result.orderStatus}}{{/paymentMessage}}
|
||||
<div>
|
||||
<p><strong>{{ @root.__ "Order ID" }}:</strong> {{result._id}}</p>
|
||||
<p><strong>{{ @root.__ "Payment ID" }}:</strong> {{result.orderPaymentId}}</p>
|
||||
</div>
|
||||
{{#paymentOutcome result.orderStatus}}{{/paymentOutcome}}
|
||||
<a href="/" class="btn btn-outline-warning">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,21 +0,0 @@
|
|||
<div class="col-md-10 offset-md-1 col-sm-12 top-pad-50">
|
||||
<div class="row">
|
||||
<div class="text-center col-md-10 offset-md-1">
|
||||
{{#ifCond result.orderStatus '==' 'Paid'}}
|
||||
<h2 class="text-success">{{ @root.__ "Your payment has been successfully processed" }}</h2>
|
||||
{{else}}
|
||||
<h2 class="text-danger">{{ @root.__ "Your payment has failed. Please try again or contact us." }}</h2>
|
||||
{{/ifCond}}
|
||||
{{#if result}}
|
||||
<div>
|
||||
<p><strong>{{ @root.__ "Order ID" }}:</strong> {{result._id}}</p>
|
||||
<p><strong>{{ @root.__ "Payment ID" }}:</strong> {{result.orderPaymentId}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#ifCond result.orderStatus '==' 'Paid'}}
|
||||
<h3 class="text-warning">{{ @root.__ "Please retain the details above as a reference of payment." }}</h3>
|
||||
{{/ifCond}}
|
||||
<a href="/" class="btn btn-outline-warning">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue