Menu
+ +
+
{{/if}}
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c9c4839a..3fcc735a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,8 +9,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x, 13.x, 14.x] - mongodb-version: [4.2] + node-version: [16.x] + mongodb-version: [5.0] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} @@ -27,4 +27,4 @@ jobs: run: npm run test env: test: true - CI: true \ No newline at end of file + CI: true diff --git a/.gitignore b/.gitignore index d539dbbe..ffdfc982 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ data/ public/uploads /config/*-local.json .vscode +.idea **.DS_Store env.yaml ecosystem.config.js -bin/googleproducts.xml \ No newline at end of file +bin/googleproducts.xml +/expressCart.iml diff --git a/Dockerfile b/Dockerfile index b3754905..af1f3691 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ -FROM mhart/alpine-node:8 +FROM node:16.13.0 -ENV NODE_VERSION 8.9.4 - -RUN apk add --no-cache make gcc g++ python bash +ENV NODE_VERSION 16.13.0 WORKDIR /var/expressCart @@ -12,6 +10,7 @@ COPY config/ /var/expressCart/config/ COPY public/ /var/expressCart/public/ COPY routes/ /var/expressCart/routes/ COPY views/ /var/expressCart/views/ +COPY locales/ /var/expressCart/locales/ COPY app.js /var/expressCart/ COPY package.json /var/expressCart/ @@ -19,6 +18,8 @@ COPY deploy.js /var/expressCart/ RUN npm install +RUN npm run deploy + VOLUME /var/expressCart/data EXPOSE 1111 diff --git a/app.js b/app.js index e7615061..9522d9c0 100644 --- a/app.js +++ b/app.js @@ -214,6 +214,13 @@ handlebars = handlebars.create({ discountExpiry: (start, end) => { return moment().isBetween(moment(start), moment(end)); }, + IsHidden: (v1, operator, v2) => { + switch(operator){ + case '==': + return (v1 === v2) ? "" : "hidden" + + } + }, ifCond: (v1, operator, v2, options) => { switch(operator){ case '==': @@ -250,7 +257,7 @@ handlebars = handlebars.create({ } if(status === 'Pending'){ const paymentConfig = getPaymentConfig(); - if(config.paymentGateway === 'instore'){ + if(['instore', 'wiretransfer' , 'ondelivery'].includes(config.paymentGateway) ){ return `
Order ID: ${newId}
+Transaction ID: ${orderDoc.orderPaymentId}
`; + + // 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 order with ${config.cartTitle}`, getEmailTemplate(paymentResults)); + + // Return outcome + res.json({ paymentId: newId }); + }); + }catch(ex){ + res.status(400).json({ err: 'Your order declined. Please try again' }); + } +}); + +module.exports = router; diff --git a/lib/payments/paypal.js b/lib/payments/paypal.js index a83e2b36..560e2593 100644 --- a/lib/payments/paypal.js +++ b/lib/payments/paypal.js @@ -184,6 +184,7 @@ router.post('/checkout_action', (req, res, next) => { orderPaymentId: payment.id, orderPaymentGateway: 'Paypal', orderTotal: req.session.totalCartAmount, + orderCurrency: config.currencyISO, orderShipping: req.session.totalCartShipping, orderItemCount: req.session.totalCartItems, orderProductCount: req.session.totalCartProducts, diff --git a/lib/payments/stripe.js b/lib/payments/stripe.js index 7011e638..ddf10725 100644 --- a/lib/payments/stripe.js +++ b/lib/payments/stripe.js @@ -116,30 +116,31 @@ router.get('/checkout_action', async (req, res, next) => { // Index transactios await indexTransactions(req.app); - // new order doc - const orderDoc = { - 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: paymentStatus, - orderDate: new Date(), - orderProducts: req.session.cart, - orderType: paymentIntent.metadata.paymentType, - transaction: transactionId - }; + // new order doc + const orderDoc = { + orderTotal: req.session.totalCartAmount, + orderCurrency: config.currencyISO, + 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: paymentStatus, + orderDate: new Date(), + orderProducts: req.session.cart, + orderType: paymentIntent.metadata.paymentType, + transaction: transactionId + }; // insert order into DB const newOrder = await db.orders.insertOne(orderDoc); diff --git a/lib/payments/wiretransfer.js b/lib/payments/wiretransfer.js new file mode 100644 index 00000000..80d319d1 --- /dev/null +++ b/lib/payments/wiretransfer.js @@ -0,0 +1,90 @@ +const express = require('express'); +const { indexOrders } = require('../indexing'); +const { getId, sendEmail, getEmailTemplate } = require('../common'); +const { getPaymentConfig } = require('../config'); +const config = require('../config'); +const { emptyCart } = require('../cart'); +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 paymentConfig = getPaymentConfig('wiretransfer'); + + const orderDoc = { + orderPaymentId: getId(), + orderPaymentGateway: 'Wiretransfer', + orderPaymentMessage: 'Wiretransfer pending', + orderPaymentInstruction : `Please pay ${req.session.totalCartNetAmount} ${config.currencyISO} On account ${paymentConfig.accountNumber} within 3 days.`, + orderTotal: req.session.totalCartAmount, + orderShipping: req.session.totalCartShipping, + orderTotal: req.session.totalCartNetAmount, + orderCurrency: config.currencyISO, + 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: paymentConfig.orderStatus, + orderDate: new Date(), + orderProducts: req.session.cart, + orderType: 'Single' + }; + + // 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 done with wire transfer'; + req.session.paymentEmailAddr = newDoc.ops[0].orderEmail; + req.session.paymentApproved = true; + req.session.paymentDetails = `Order ID: ${newId}
+Transaction ID: ${orderDoc.orderPaymentId}
; +Please pay {orderDoc.orderTotal} On account "XXXXXXXXXXXXXXXX" within 3 days.
; +Transaction ID: ${orderDoc.orderPaymentId}
`; + + // 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 order with ${config.cartTitle}`, getEmailTemplate(paymentResults)); + + // Return outcome + res.json({ paymentId: newId }); + }); + }catch(ex){ + res.status(400).json({ err: 'Your order declined. Please try again' }); + } +}); + +module.exports = router; diff --git a/lib/payments/zip.js b/lib/payments/zip.js index b474419d..e7dc35b8 100644 --- a/lib/payments/zip.js +++ b/lib/payments/zip.js @@ -116,6 +116,7 @@ router.post('/setup', async (req, res, next) => { orderPaymentId: response.data.id, orderPaymentGateway: 'Zip', orderTotal: req.session.totalCartAmount, + orderCurrency: config.currencyISO, orderShipping: req.session.totalCartShipping, orderItemCount: req.session.totalCartItems, orderProductCount: req.session.totalCartProducts, diff --git a/lib/schemas/newProduct.json b/lib/schemas/newProduct.json index 3871554b..1df0d1be 100644 --- a/lib/schemas/newProduct.json +++ b/lib/schemas/newProduct.json @@ -42,6 +42,11 @@ "productStock": { "type": ["number", "null"] }, + "productDimensions" : { + "width" : {"type": "number" }, + "length" : {"type": "number" }, + "height" : {"type": "number" } + }, "productStockDisable": { "type": "boolean" } @@ -60,4 +65,4 @@ "productDescription", "productPublished" ] -} \ No newline at end of file +} diff --git a/lib/schemas/newVariant.json b/lib/schemas/newVariant.json index 303aa306..50ad744c 100644 --- a/lib/schemas/newVariant.json +++ b/lib/schemas/newVariant.json @@ -17,7 +17,19 @@ }, "stock": { "type": ["number", "null"] - } + }, + "type" : { + "type": "string" + }, + "productDimensions" :{ + "type" : "object", + "properties" : { + "length": {"type" : "string"}, + "width": {"type" : "string"}, + "height": {"type" : "string"} + } + }, + "color" : {"type": "string"} }, "errorMessage": { "isNotEmpty": "This is my custom error message", @@ -27,8 +39,7 @@ }, "required": [ "product", - "title", "price", "stock" ] -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index f2f4cdc5..b1600772 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2,6 +2,7 @@ "Languages": "Languages", "en": "English", "it": "Italiano", + "ro": "Romanian", "Cart": "Cart", "Name": "Name", "New": "New", @@ -226,6 +227,45 @@ "The Serial number, GTIN or Barcode": "The Serial number, GTIN or Barcode", "Product Brand": "Product Brand", "The brand of the product": "The brand of the product", + "Cart Empty": "Cart Empty", + "Estimated shipping": "Estimated shipping", + "Please enter a valid email address": "Please enter a valid email address", + "Address 2 (optional)": "Address 2 (optional)", + "Select Country": "Select Country", + "Post code": "Post code", + "Cancel": "Cancel", + "Add review": "Add review", + "Product review": "Product review", + "Love it": "Love it", + "Description": "Description", + "Product is great. Does everything it said it can do.": "Product is great. Does everything it said it can do.", + "Rating": "Rating", + "Price": "Price", + "Stock": "Stock", + "Product variant": "Product variant", + "Save variant": "Save variant", + "Home": "Home", + "Manage": "Manage", + "FREE shipping": "FREE shipping", + "Payment": "Payment", + "Create an account": "Create an account", + "Return to cart": "Return to cart", + "Continue to shipping": "Continue to shipping", + "Add Color": "Add Color", + "black": "black", + "white": "white", + "red": "red", + "Add variant": "Add variant", + "height*length*width": "height*length*width", + "height*width*length": "height*width*length", + "No current variants": "No current variants", + "Permalink": "Permalink", + "Dimensions": "Dimensions", + "Add Dimension": "Add Dimension", + "Shown in the variant dropdown, will be color - dimensions if let empty": "Shown in the variant dropdown, will be color - dimensions if let empty", + "Color": "Color", + "color": "color", + "The brand of the product": "The brand of the product", "Tracking number": "Tracking number", "Update order": "Update order", "Add image URL": "Add image URL", @@ -242,4 +282,4 @@ "Approved": "Approved", "Transactions": "Transactions", "Order": "Order" -} \ No newline at end of file +} diff --git a/locales/it.json b/locales/it.json index fa129cef..f7f95399 100644 --- a/locales/it.json +++ b/locales/it.json @@ -2,6 +2,7 @@ "Languages": "Lingue", "en": "English", "it": "Italiano", + "ro": "Romanian", "Cart": "Carrello", "Name": "Nome", "New": "Nuovo", @@ -184,5 +185,30 @@ "Customers can be filtered by: email, name or phone number": "Customers can be filtered by: email, name or phone number", "Password": "Password", "Create Order": "Create Order", - "Create order": "Create order" -} + "Create order": "Create order", + "Home": "Home", + "Cart Empty": "Cart Empty", + "Product review": "Product review", + "Title": "Title", + "Love it": "Love it", + "Description": "Description", + "Product is great. Does everything it said it can do.": "Product is great. Does everything it said it can do.", + "Rating": "Rating", + "Cancel": "Cancel", + "Add review": "Add review", + "Manage": "Manage", + "Reviews": "Reviews", + "Logout": "Logout", + "Shown in the variant dropdown": "Shown in the variant dropdown", + "Product variant": "Product variant", + "Price": "Price", + "Stock": "Stock", + "Save variant": "Save variant", + "View": "View", + "white": "white", + "test-jacket-variant": "test-jacket-variant", + "test-jacket-variant-black-xs": "test-jacket-variant-black-xs", + "black": "black", + "": "", + "Dimensions": "Dimensions" +} \ No newline at end of file diff --git a/locales/ro.json b/locales/ro.json new file mode 100644 index 00000000..292928d6 --- /dev/null +++ b/locales/ro.json @@ -0,0 +1,262 @@ +{ + "Languages": "Languages", + "en": "Engleză", + "it": "Italiană", + "ro": "Română", + "Cart": "Coș", + "Name": "Nume", + "New": "Nou", + "Settings": "Setări", + "General settings": "Setări generale", + "Address 1": "Adresa 1", + "Address 2": "Adresa 2", + "Country": "Țară", + "State": "Județ", + "Postcode": "Cod poștal", + "Phone number": "Număr de telefon", + "Creation date": "Data creației", + "Customers can be filtered by: email, name or phone number": "Clienții pot fi filtrați după: e-mail, nume sau număr de telefon", + "Customers": "Clienți", + "Filtered term": "Termen filtrat", + "No orders found": "Nu s-au găsit comenzi", + "Email address": "Adresa de e-mail", + "Please enter your email address": "Vă rugăm să introduceți adresa de e-mail", + "Reset": "Reset", + "Please sign in": "Vă rugăm să vă conectați", + "Sign in": "Conectați-vă", + "Update status": "Actualizați starea", + "Go Back": "Du-te înapoi", + "Order date": "Data comenzii", + "Order ID": "ID-ul comenzii", + "Payment Gateway ref": "Payment Gateway ref", + "Payment Gateway": "Payment Gateway", + "Order total amount": "Suma totală a comenzii", + "First name": "Prenume", + "Last name": "Numele de familie", + "Order comment": "Comandă comentariu", + "Products ordered": "Produse comandate", + "Options": "Opțiuni", + "Filter": "Filter", + "By status": "După statut", + "Orders can be filtered by: surname, email address or postcode/zipcode": "Comenzile pot fi filtrate după: prenume, adresă de e-mail sau cod poștal / cod poștal", + "Completed": "Finalizat", + "Paid": "Plătit", + "Created": "Creat", + "Cancelled": "Anulat", + "Declined": "Declinat", + "Shipped": "Expediat", + "Pending": "În așteptare", + "Orders": "Comenzi", + "Recent orders": "Comenzi recente", + "Status": "Status", + "Upload image": "Încarcă imaginea", + "Save product": "Salvați produsul", + "Edit product": "Editați produsul", + "Product title": "Titlul produsului", + "Product price": "Preț produs", + "Published": "Publicat", + "Draft": "Proiect", + "Stock level": "Nivel stoc", + "Product description": "Descrierea produsului", + "Validate": "Validați", + "This sets a readable URL for the product": "Aceasta setează o adresă URL lizibilă pentru produs", + "Product options": "Opțiuni produs", + "Label": "Label", + "Type": "Type", + "Add": "Adăugați", + "Remove": "Șterge", + "Here you can set options for your product. Eg: Size, color, style": "Aici puteți seta opțiuni pentru produsul dvs. De exemplu: Dimensiune, culoare, stil", + "Allow comment": "Permiteți comentariul", + "Allow free form comments when adding products to cart": "Permiteți comentarii gratuite de formular atunci când adăugați produse în coș", + "Product tag words": "Cuvinte etichetă produs", + "Tag words used to indexed products, making them easier to find and filter.": "Etichetați cuvintele folosite la produsele indexate, făcându-le mai ușor de găsit și filtrat.", + "Product images": "Imagini de produs", + "Delete": "Șterge", + "main image": "imaginea principală", + "Set as main image": "Setați ca imagine principală", + "No images have been uploaded for this product": "Nu au fost încărcate imagini pentru acest produs", + "Product image upload": "Încărcare imagine produs", + "Select file": "Selectează fișierul", + "Upload": "Încărcare", + "New product": "Produs nou", + "Products can be filtered by: product title or product description keywords": "Produsele pot fi filtrate după: titlul produsului sau cuvintele cheie cu descrierea produsului", + "Products": "Produse", + "Recent products": "Produse recente", + "Confirm": "Confirmă", + "Update": "Actualizare", + "Setting_menu_explain": "ici puteți configura un meniu afișat în coșul dvs. de cumpărături. Puteți utiliza acest meniu pentru a filtra produsele dvs. specificând un cuvânt cheie în câmpul \\\" link \\ \". De exemplu: pentru a afișa produse cu un cuvânt cheie (sau etichetă) de cizme, ați seta câmpul de meniu la \\ \"Rucsacuri \\\" și o valoare a legăturii \\ \"rucsac \\\". De asemenea, puteți utiliza acest meniu pentru a vă conecta la pagini statice, de exemplu: expediere, returnări, ajutor, despre, contact etc. . \"", + "Static page": "Pagină statică", + "Page name": "Numele paginii", + "A friendly name to manage the static page.": "Un nume prietenos pentru a gestiona pagina statică.", + "Page slug": "Page slug", + "Page_Slug_Description": "TAceasta este adresa URL relativă a paginii. De exemplu: o setare de \\\" despre \\ \"ar face pagina disponibilă la: mydomain.com/about", + "Page Enabled": "Page Enabled", + "Page content": "Conținutul paginii", + "Here you can enter the content you wish to be displayed on your static page.": "Aici puteți introduce conținutul pe care doriți să îl afișați pe pagina dvs. statică.", + "New page": "Pagină nouă", + "Static pages": "Pagini statice", + "Static_Pages_Info": "Aici puteți configura și gestiona pagini statice pentru coșul dvs. de cumpărături. Poate doriți să configurați o pagină cu un pic despre afacerea dvs. numită \\\" Despre \\ \"sau \\\" Contactați-ne \\ \"etc.", + "Edit": "Editează", + "There are currently no static pages setup. Please setup a static page.": "În prezent nu există configurarea paginilor statice. Vă rugăm să configurați o pagină statică.", + "Create new": "Creați un nou", + "Search": "Căutare", + "Cart name": "Numele coșului", + "This element is critical for search engine optimisation. Cart title is displayed if your logo is hidden.": "Acest element este esențial pentru optimizarea motorului de căutare. Ca rt title este afișat dacă sigla dvs. este ascunsă..", + "Cart description": "Descrierea coșului", + "This description shows when your website is listed in search engine results.": "Această descriere arată când site-ul dvs. web este listat în rezultatele motorului de căutare.", + "Cart image/logo": "Imagine coș / logo", + "Cart URL": "Adresă URL coș", + "This URL is used in sitemaps and when your customer returns from completing their payment.": "Această adresă URL este utilizată în sitemapuri și atunci când clientul dvs. se întoarce după finalizarea plății.", + "This is used as the \"from\" email when sending receipts to your customers.": "Aceasta este utilizată ca e-mail \\\" din \\ \"la trimiterea de chitanțe către clienții dvs.", + "Orders over this value will mean the shipped will the FREE. Set to high value if you always want to charge shipping.": "Comenzile peste această valoare vor însemna că livrarea va fi GRATUITă. Setați la valoare ridicată dacă vreți să încărcați întotdeauna livrare.", + "Payment gateway": "Gateway de plată", + "Payment_Gateway_Info": "De asemenea, va trebui să vă configurați acreditările gateway-ului de plată în fișierul` / config /{{ @root.__ "This sets a readable URL for the product" }}
{{ @root.__ "height*width*length" }}
+{{ @root.__ "This sets a readable URL for the product" }}
{{ @root.__ "height*width*length" }}
+
+
{{/if}}
- {{/if}}
+ {{#if productThumbnail}}
+
+ {{/if}}
+ {{/if}}
+ {{#if productImage}}
+
+ {{/if}}
{{/if}}
{{ @root.__ "Order ID" }}: {{result._id}}
-{{ @root.__ "Payment ID" }}: {{result.transaction}}
+{{ @root.__ "Payment ID" }}: {{result.orderPaymentId}}
+ {{#if result.orderPaymentInstruction}}{{ @root.__ "Payment Instruction" }}: {{result.orderPaymentInstruction}}
{{/if}}