From a87d2fbf0a920296da5f7cabd723d4ae6ec357ed Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Wed, 6 Nov 2019 21:10:27 +1030 Subject: [PATCH] Adding subscriptions (#95) * Adding subscriptions * Adding in ability to use webhook for subscriptions * Add docs and ability to create subscriptions * Fixed populating value * Adding subscription tests * Language update --- README.md | 7 ++ bin/testdata.json | 11 ++ lib/common.js | 17 +++ locales/en.json | 5 +- public/javascripts/expressCart.js | 6 +- public/javascripts/expressCart.min.js | 2 +- routes/index.js | 96 +++++++++++++---- routes/payments/stripe.js | 148 +++++++++++++++++++++++++- test/test.js | 47 ++++++++ views/order.hbs | 4 +- views/partials/payments/stripe.hbs | 3 + views/product_edit.hbs | 9 ++ views/product_new.hbs | 9 ++ views/themes/Cloth/pay.hbs | 4 +- views/themes/Material/pay.hbs | 2 +- views/themes/Mono/pay.hbs | 2 +- 16 files changed, 343 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 89f044f..2ea6d9f 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,13 @@ Note: An `Options` value is not required when `Type` is set to `Checkbox`. Tags are used when indexing the products for search. It's advised to set tags (keywords) so that customers can easily find the products they are searching for. +## Subscriptions (Stripe only) + +You are able to setup product subscriptions through Stripe. First setup the `Plan` in the [Stripe dashboard](https://dashboard.stripe.com/) then enter the Plan ID (Formatted: plan_XXXXXXXXXXXXXX) when creating or editing a product. When purchasing, a customer can only add a single subscription to their cart at one time. Subscriptions cannot be combined with other products in their cart. On Checkout/Payment the customer and subscription is created in Stripe and the billing cycle commences based on the plan setup. + +##### Subscription Webhooks (Stripe only) +You are able to configure a Webhook in Stripe to receive subscription updates on successful/failed payments [here](https://dashboard.stripe.com/webhooks). The `expressCart` Webhook endpoint should be set to: `https:///stripe/subscription_update`. You will need to set the `Events to send` value to both: `invoice.payment_failed` and `invoice.payment_succeeded`. + ## Database `expressCart` uses a MongoDB for storing all the data. Setting of the database connection string is done through the `/config/settings.json` file. There are two properties relating to the database connection: diff --git a/bin/testdata.json b/bin/testdata.json index ca41b5e..612510c 100644 --- a/bin/testdata.json +++ b/bin/testdata.json @@ -69,6 +69,17 @@ "productTags" : "shirt", "productOptions" : "{\"Size\":{\"optName\":\"Size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\"]}}", "productStock": 10 + }, + { + "productPermalink" : "gym-membership", + "productTitle" : "Gym membership", + "productPrice" : "15", + "productDescription" : "This a monthly recurring Gym membership subscription.", + "productPublished": true, + "productTags" : "subscription", + "productOptions" : "", + "productStock": null, + "productSubscription": "plan_XXXXXXXXXXXXXX" } ], "customers": [ diff --git a/lib/common.js b/lib/common.js index 28d5a39..11f0168 100755 --- a/lib/common.js +++ b/lib/common.js @@ -131,6 +131,22 @@ const updateTotalCartAmount = (req, res) => { } }; +const updateSubscriptionCheck = (req, res) => { + // If cart is empty + if(!req.session.cart || req.session.cart.length === 0){ + req.session.cartSubscription = null; + return; + } + + req.session.cart.forEach((item) => { + if(item.productSubscription){ + req.session.cartSubscription = item.productSubscription; + }else{ + req.session.cartSubscription = null; + } + }); +}; + const checkDirectorySync = (directory) => { try{ fs.statSync(directory); @@ -551,6 +567,7 @@ module.exports = { addSitemapProducts, clearSessionValue, updateTotalCartAmount, + updateSubscriptionCheck, checkDirectorySync, getThemes, getImages, diff --git a/locales/en.json b/locales/en.json index bb307e1..74671e2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -160,5 +160,6 @@ "Cart contents": "Cart contents", "Shipping": "Shipping:", "Empty cart": "Empty cart", - "List": "List" -} + "List": "List", + "Order type": "Order type" +} \ No newline at end of file diff --git a/public/javascripts/expressCart.js b/public/javascripts/expressCart.js index 7bf7b2b..71050e8 100644 --- a/public/javascripts/expressCart.js +++ b/public/javascripts/expressCart.js @@ -289,6 +289,9 @@ $(document).ready(function (){ image: $('#stripeButton').data('image'), locale: 'auto', token: function(token){ + if($('#stripeButton').data('subscription')){ + $('#shipping-form').append(''); + } $('#shipping-form').append(''); $('#shipping-form').submit(); } @@ -301,7 +304,8 @@ $(document).ready(function (){ description: $('#stripeButton').data('description'), zipCode: $('#stripeButton').data('zipCode'), amount: $('#stripeButton').data('amount'), - currency: $('#stripeButton').data('currency') + currency: $('#stripeButton').data('currency'), + subscription: $('#stripeButton').data('subscription') }); } }); diff --git a/public/javascripts/expressCart.min.js b/public/javascripts/expressCart.min.js index 5cb9b7d..222bb91 100644 --- a/public/javascripts/expressCart.min.js +++ b/public/javascripts/expressCart.min.js @@ -1 +1 @@ -function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-id")}}).done(function(e){$("#cart-count").text(e.totalCartItems),0===e.totalCartItems?($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),$(".cart-contents-shipping").hide("slow",function(){$(".cart-contents-shipping").remove()}),showNotification(e.message,"success"),setTimeout(function(){window.location="/"},3700)):($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),showNotification(e.message,"success"))}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart():$(t).val(1)}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};t.push(e)}),$.ajax({method:"POST",url:"/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function updateCartDiv(){var t=window.location.pathname.split("/").length>0?window.location.pathname.split("/")[1]:"";$.ajax({method:"GET",url:"/cartPartial",data:{path:t}}).done(function(t){$("#cart").html(t)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var e=$(this).val().trim();"radio"===$(this).attr("type")&&(e=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=e}else t[$(this).val().trim()]=$(this).prop("checked")}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}$(document).ready(function(){if("Material"===$("#cartTheme").val()&&$(".materialboxed").materialbox(),$(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$(".shipping-form input").each(function(t){$(this).wrap("
");var e=$(this).attr("placeholder");$(this).after('")}),$(".shipping-form input").on("focus",function(){$(this).next().addClass("floatLabel"),$(this).next().removeClass("hidden")}),$(".shipping-form input").on("blur",function(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#productTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$('input[class="published_state"]').change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("change",".cart-product-quantity",function(t){cartUpdate(t.target)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),s=$("#searchTerm").val();""!==s&&(s+="/");var c="/"+r+"/"+s+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:c,wrapClass:"pagination",prevClass:"waves-effect",nextClass:"waves-effect",activeClass:"pag-active waves-effect"})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#btnGenerateAPIkey",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/createApiKey"}).done(function(t){$("#apiKey").val(t.apiKey),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/product/removeoption",data:{productId:$("#productId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptions").val()&&'"{}"'!==$("#productOptions").val()&&(n=JSON.parse($("#productOptions").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptions").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length)&&window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({email:$("#stripeButton").data("email"),name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency")})}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#createCustomerAccount").validator().on("click",function(t){t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length&&$.ajax({method:"POST",url:"/customer/create",data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val()}}).done(function(t){location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var e=t.customer;$("#shipEmail").val(e.email),$("#shipFirstname").val(e.firstName),$("#shipLastname").val(e.lastName),$("#shipAddr1").val(e.address1),$("#shipAddr2").val(e.address2),$("#shipCountry").val(e.country),$("#shipState").val(e.state),$("#shipPostcode").val(e.postcode),$("#shipPhoneNumber").val(e.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e),productComment:$("#product_comment").val()}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".cart-product-quantity").on("input",function(){cartUpdate()}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#productId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#productId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#productPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#productPermalink").val(),docId:$("#productId").val()}}).done(function(t){console.log("msg",t),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_customer_filter",function(t){""!==$("#customer_filter").val()?window.location.href="/admin/customers/filter/"+$("#customer_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#productPermalink").val()&&""!==$("#productTitle").val()&&$("#productPermalink").val(slugify($("#productTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}}); \ No newline at end of file +function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-id")}}).done(function(e){$("#cart-count").text(e.totalCartItems),0===e.totalCartItems?($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),$(".cart-contents-shipping").hide("slow",function(){$(".cart-contents-shipping").remove()}),showNotification(e.message,"success"),setTimeout(function(){window.location="/"},3700)):($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),showNotification(e.message,"success"))}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart():$(t).val(1)}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};t.push(e)}),$.ajax({method:"POST",url:"/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function updateCartDiv(){var t=window.location.pathname.split("/").length>0?window.location.pathname.split("/")[1]:"";$.ajax({method:"GET",url:"/cartPartial",data:{path:t}}).done(function(t){$("#cart").html(t)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var e=$(this).val().trim();"radio"===$(this).attr("type")&&(e=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=e}else t[$(this).val().trim()]=$(this).prop("checked")}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}$(document).ready(function(){if("Material"===$("#cartTheme").val()&&$(".materialboxed").materialbox(),$(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$(".shipping-form input").each(function(t){$(this).wrap("
    ");var e=$(this).attr("placeholder");$(this).after('")}),$(".shipping-form input").on("focus",function(){$(this).next().addClass("floatLabel"),$(this).next().removeClass("hidden")}),$(".shipping-form input").on("blur",function(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#productTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$('input[class="published_state"]').change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("change",".cart-product-quantity",function(t){cartUpdate(t.target)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),s=$("#searchTerm").val();""!==s&&(s+="/");var c="/"+r+"/"+s+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:c,wrapClass:"pagination",prevClass:"waves-effect",nextClass:"waves-effect",activeClass:"pag-active waves-effect"})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#btnGenerateAPIkey",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/createApiKey"}).done(function(t){$("#apiKey").val(t.apiKey),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/product/removeoption",data:{productId:$("#productId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptions").val()&&'"{}"'!==$("#productOptions").val()&&(n=JSON.parse($("#productOptions").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptions").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length)&&window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#stripeButton").data("subscription")&&$("#shipping-form").append(''),$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({email:$("#stripeButton").data("email"),name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency"),subscription:$("#stripeButton").data("subscription")})}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#createCustomerAccount").validator().on("click",function(t){t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length&&$.ajax({method:"POST",url:"/customer/create",data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val()}}).done(function(t){location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var e=t.customer;$("#shipEmail").val(e.email),$("#shipFirstname").val(e.firstName),$("#shipLastname").val(e.lastName),$("#shipAddr1").val(e.address1),$("#shipAddr2").val(e.address2),$("#shipCountry").val(e.country),$("#shipState").val(e.state),$("#shipPostcode").val(e.postcode),$("#shipPhoneNumber").val(e.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e),productComment:$("#product_comment").val()}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".cart-product-quantity").on("input",function(){cartUpdate()}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#productId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#productId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#productPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#productPermalink").val(),docId:$("#productId").val()}}).done(function(t){console.log("msg",t),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_customer_filter",function(t){""!==$("#customer_filter").val()?window.location.href="/admin/customers/filter/"+$("#customer_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#productPermalink").val()&&""!==$("#productTitle").val()&&$("#productPermalink").val(slugify($("#productTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}}); \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 9faa208..2521633 100644 --- a/routes/index.js +++ b/routes/index.js @@ -13,6 +13,7 @@ const { getPaymentConfig, getImages, updateTotalCartAmount, + updateSubscriptionCheck, getData, addSitemapProducts } = require('../lib/common'); @@ -68,6 +69,10 @@ router.get('/payment/:orderId', async (req, res, next) => { }); }); +router.get('/emptycart', async (req, res, next) => { + emptyCart(req, res, ''); +}); + router.get('/checkout', async (req, res, next) => { const config = req.app.config; @@ -105,6 +110,11 @@ router.get('/pay', async (req, res, next) => { return; } + let paymentType = ''; + if(req.session.cartSubscription){ + paymentType = '_subscription'; + } + // render the payment page res.render(`${config.themeViews}pay`, { title: 'Pay', @@ -113,6 +123,7 @@ router.get('/pay', async (req, res, next) => { pageCloseBtn: showCartCloseBtn('pay'), session: req.session, paymentPage: true, + paymentType, page: 'pay', message: clearSessionValue(req.session, 'message'), messageType: clearSessionValue(req.session, 'messageType'), @@ -220,6 +231,9 @@ router.post('/product/updatecart', (req, res, next) => { // update total cart amount updateTotalCartAmount(req, res); + // Update checking cart for subscription + updateSubscriptionCheck(req, res); + // Update cart to the DB await db.cart.updateOne({ sessionId: req.session.id }, { $set: { cart: req.session.cart } @@ -239,36 +253,42 @@ router.post('/product/updatecart', (req, res, next) => { }); // Remove single product from cart -router.post('/product/removefromcart', (req, res, next) => { +router.post('/product/removefromcart', async (req, res, next) => { const db = req.app.db; let itemRemoved = false; // remove item from cart - async.each(req.session.cart, (item, callback) => { + req.session.cart.forEach((item) => { if(item){ if(item.productId === req.body.cartId){ itemRemoved = true; req.session.cart = _.pull(req.session.cart, item); } } - callback(); - }, async () => { - // Update cart in DB - await db.cart.updateOne({ sessionId: req.session.id }, { - $set: { cart: req.session.cart } - }); - // update total cart amount - updateTotalCartAmount(req, res); - - if(itemRemoved === false){ - return res.status(400).json({ message: 'Product not found in cart' }); - } - return res.status(200).json({ message: 'Product successfully removed', totalCartItems: Object.keys(req.session.cart).length }); }); + + // Update cart in DB + await db.cart.updateOne({ sessionId: req.session.id }, { + $set: { cart: req.session.cart } + }); + // update total cart amount + updateTotalCartAmount(req, res); + + // Update checking cart for subscription + updateSubscriptionCheck(req, res); + + if(itemRemoved === false){ + return res.status(400).json({ message: 'Product not found in cart' }); + } + return res.status(200).json({ message: 'Product successfully removed', totalCartItems: Object.keys(req.session.cart).length }); }); // Totally empty the cart router.post('/product/emptycart', async (req, res, next) => { + emptyCart(req, res, 'json'); +}); + +const emptyCart = async (req, res, type) => { const db = req.app.db; // Remove from session @@ -280,8 +300,20 @@ router.post('/product/emptycart', async (req, res, next) => { // update total cart amount updateTotalCartAmount(req, res); - res.status(200).json({ message: 'Cart successfully emptied', totalCartItems: 0 }); -}); + + // Update checking cart for subscription + updateSubscriptionCheck(req, res); + + // If POST, return JSON else redirect nome + if(type === 'json'){ + res.status(200).json({ message: 'Cart successfully emptied', totalCartItems: 0 }); + return; + } + + req.session.message = 'Cart successfully emptied.'; + req.session.messageType = 'success'; + res.redirect('/'); +}; // Add item to cart router.post('/product/addtocart', async (req, res, next) => { @@ -300,13 +332,25 @@ router.post('/product/addtocart', async (req, res, next) => { req.session.cart = []; } - // Get the item from the DB + // Get the product from the DB const product = await db.products.findOne({ _id: getId(req.body.productId) }); // No product found if(!product){ return res.status(400).json({ message: 'Error updating cart. Please try again.' }); } + // If cart already has a subscription you cannot add anything else + if(req.session.cartSubscription){ + return res.status(400).json({ message: 'Subscription already existing in cart. You cannot add more.' }); + } + + // If existing cart isn't empty check if product is a subscription + if(req.session.cart.length !== 0){ + if(product.productSubscription){ + return res.status(400).json({ message: 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.' }); + } + } + // If stock management on check there is sufficient stock for this product if(config.trackStock && product.productStock){ const stockHeld = await db.cart.aggregate( @@ -346,7 +390,13 @@ router.post('/product/addtocart', async (req, res, next) => { // Doc used to test if existing in the cart with the options. If not found, we add new. let options = {}; if(req.body.productOptions){ - options = JSON.parse(req.body.productOptions); + try{ + if(typeof req.body.productOptions === 'object'){ + options = req.body.productOptions; + }else{ + options = JSON.parse(req.body.productOptions); + } + }catch(ex){} } const findDoc = { productId: req.body.productId, @@ -376,6 +426,7 @@ router.post('/product/addtocart', async (req, res, next) => { productObj.options = options; productObj.productImage = product.productImage; productObj.productComment = productComment; + productObj.productSubscription = product.productSubscription; if(product.productPermalink){ productObj.link = product.productPermalink; }else{ @@ -394,6 +445,13 @@ router.post('/product/addtocart', async (req, res, next) => { // update total cart amount updateTotalCartAmount(req, res); + // Update checking cart for subscription + updateSubscriptionCheck(req, res); + + if(product.productSubscription){ + req.session.cartSubscription = product.productSubscription; + } + // update how many products in the shopping cart req.session.cartTotalItems = req.session.cart.reduce((a, b) => +a + +b.quantity, 0); return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.cartTotalItems }); diff --git a/routes/payments/stripe.js b/routes/payments/stripe.js index c194f72..6299e61 100644 --- a/routes/payments/stripe.js +++ b/routes/payments/stripe.js @@ -52,7 +52,8 @@ router.post('/checkout_action', (req, res, next) => { orderComment: req.body.orderComment, orderStatus: paymentStatus, orderDate: new Date(), - orderProducts: req.session.cart + orderProducts: req.session.cart, + orderType: 'Single' }; // insert order into DB @@ -111,4 +112,149 @@ router.post('/checkout_action', (req, res, next) => { }); }); +// Subscription hook from Stripe +router.all('/subscription_update', async (req, res, next) => { + const db = req.app.db; + + if(!req.body.data.object.customer){ + return res.status(400).json({ message: 'Customer not found' }); + } + + const order = await db.orders.findOne({ + orderCustomer: req.body.data.object.customer, + orderType: 'Subscription' + }); + + if(!order){ + return res.status(400).json({ message: 'Order not found' }); + } + + let orderStatus = 'Paid'; + if(req.body.type === 'invoice.payment_failed'){ + orderStatus = 'Declined'; + } + + // Update order status + await db.orders.updateOne({ + _id: common.getId(order._id), + orderType: 'Subscription' + }, { + $set: { + orderStatus: orderStatus + } + }); + + return res.status(200).json({ message: 'Status successfully updated' }); +}); + +router.post('/checkout_action_subscription', async (req, res, next) => { + const db = req.app.db; + const config = req.app.config; + + try{ + const plan = await stripe.plans.retrieve(req.body.stripePlan); + if(!plan){ + req.session.messageType = 'danger'; + req.session.message = 'The plan connected to this product doesn\'t exist'; + res.redirect('/pay/'); + return; + } + }catch(ex){ + req.session.messageType = 'danger'; + req.session.message = 'The plan connected to this product doesn\'t exist'; + res.redirect('/pay/'); + return; + } + + // Create customer + const customer = await stripe.customers.create({ + source: req.body.stripeToken, + plan: req.body.stripePlan, + email: req.body.shipEmail, + name: `${req.body.shipFirstname} ${req.body.shipLastname}`, + phone: req.body.shipPhoneNumber + }); + + if(!customer){ + req.session.messageType = 'danger'; + req.session.message = 'Your subscripton has declined. Please try again'; + req.session.paymentApproved = false; + req.session.paymentDetails = ''; + res.redirect('/pay'); + return; + } + + // Check for a subscription + if(customer.subscriptions.data && customer.subscriptions.data.length === 0){ + req.session.messageType = 'danger'; + req.session.message = 'Your subscripton has declined. Please try again'; + req.session.paymentApproved = false; + req.session.paymentDetails = ''; + res.redirect('/pay'); + return; + } + + const subscription = customer.subscriptions.data[0]; + + // Create the new order document + const orderDoc = { + orderPaymentId: subscription.id, + orderPaymentGateway: 'Stripe', + orderPaymentMessage: subscription.collection_method, + orderTotal: req.session.totalCartAmount, + orderEmail: req.body.shipEmail, + orderFirstname: req.body.shipFirstname, + orderLastname: req.body.shipLastname, + orderAddr1: req.body.shipAddr1, + orderAddr2: req.body.shipAddr2, + orderCountry: req.body.shipCountry, + orderState: req.body.shipState, + orderPostcode: req.body.shipPostcode, + orderPhoneNumber: req.body.shipPhoneNumber, + orderComment: req.body.orderComment, + orderStatus: 'Pending', + orderDate: new Date(), + orderProducts: req.session.cart, + orderType: 'Subscription', + orderCustomer: customer.id + }; + + // insert order into DB + const order = await db.orders.insertOne(orderDoc); + const orderId = order.insertedId; + + indexOrders(req.app) + .then(() => { + // set the results + req.session.messageType = 'success'; + req.session.message = 'Your subscription was successfully created'; + req.session.paymentEmailAddr = req.body.shipEmail; + req.session.paymentApproved = true; + req.session.paymentDetails = '

    Order ID: ' + orderId + '

    Subscription ID: ' + subscription.id + '

    '; + + // 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.cartSubscription = null; + req.session.cart = null; + req.session.orderId = null; + req.session.totalCartAmount = 0; + } + + // send the email with the response + common.sendEmail(req.session.paymentEmailAddr, 'Your payment with ' + config.cartTitle, common.getEmailTemplate(paymentResults)); + + // redirect to outcome + res.redirect('/payment/' + orderId); + }); +}); + module.exports = router; diff --git a/test/test.js b/test/test.js index a48f069..f53c7eb 100644 --- a/test/test.js +++ b/test/test.js @@ -141,6 +141,41 @@ test.serial('[Success] Customer login with correct email', async t => { t.deepEqual(res.body.message, 'Successfully logged in'); }); +test.serial('[Success] Add subscripton product to cart', async t => { + const res = await request + .post('/product/addtocart') + .send({ + productId: products[7]._id, + productQuantity: 1, + productOptions: {} + }) + .expect(200); + const sessions = await db.cart.find({}).toArray(); + if(!sessions || sessions.length === 0){ + t.fail(); + } + t.deepEqual(res.body.message, 'Cart successfully updated'); +}); + +test.serial('[Fail] Add product to cart when subscription already added', async t => { + const res = await request + .post('/product/addtocart') + .send({ + productId: products[1]._id, + productQuantity: 100, + productOptions: JSON.stringify(products[1].productOptions) + }) + .expect(400); + t.deepEqual(res.body.message, 'Subscription already existing in cart. You cannot add more.'); +}); + +test.serial('[Success] Empty cart', async t => { + const res = await request + .post('/product/emptycart') + .expect(200); + t.deepEqual(res.body.message, 'Cart successfully emptied'); +}); + test.serial('[Success] Add product to cart', async t => { const res = await request .post('/product/addtocart') @@ -157,6 +192,18 @@ test.serial('[Success] Add product to cart', async t => { t.deepEqual(res.body.message, 'Cart successfully updated'); }); +test.serial('[Fail] Cannot add subscripton when other product in cart', async t => { + const res = await request + .post('/product/addtocart') + .send({ + productId: products[7]._id, + productQuantity: 1, + productOptions: {} + }) + .expect(400); + t.deepEqual(res.body.message, 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.'); +}); + test.serial('[Fail] Add product to cart with not enough stock', async t => { const res = await request .post('/product/addtocart') diff --git a/views/order.hbs b/views/order.hbs index e05acce..9481625 100644 --- a/views/order.hbs +++ b/views/order.hbs @@ -16,6 +16,7 @@
    +

    First setup the plan in Stripe dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX

    +
    + + {{/ifCond}}
    diff --git a/views/product_new.hbs b/views/product_new.hbs index 46baa43..9c7e613 100644 --- a/views/product_new.hbs +++ b/views/product_new.hbs @@ -106,6 +106,15 @@

    {{ @root.__ "Here you can set options for your product. Eg: Size, color, style" }}

    + {{#ifCond config.paymentGateway '==' 'stripe'}} +
    + +
    + +

    First setup the plan in Stripe dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX

    +
    +
    + {{/ifCond}}
    diff --git a/views/themes/Cloth/pay.hbs b/views/themes/Cloth/pay.hbs index 0b105e7..e1db7fe 100644 --- a/views/themes/Cloth/pay.hbs +++ b/views/themes/Cloth/pay.hbs @@ -36,7 +36,7 @@
    {{/if}} -
    + {{> partials/payments/shipping-form}} {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'paypal'}} @@ -68,4 +68,4 @@ {{> (getTheme 'cart')}}
    - + \ No newline at end of file diff --git a/views/themes/Material/pay.hbs b/views/themes/Material/pay.hbs index eef128c..c8413b2 100644 --- a/views/themes/Material/pay.hbs +++ b/views/themes/Material/pay.hbs @@ -28,7 +28,7 @@ {{/if}} - + {{> themes/Material/shipping-form}} {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'paypal'}} diff --git a/views/themes/Mono/pay.hbs b/views/themes/Mono/pay.hbs index ff0131b..9b57baf 100644 --- a/views/themes/Mono/pay.hbs +++ b/views/themes/Mono/pay.hbs @@ -28,7 +28,7 @@ {{/if}} - + {{> themes/Mono/shipping-form}} {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'paypal'}}