├── Procfile ├── blob ├── app-keys.png ├── sample_app.png ├── deploy-finish.png ├── deploy-window.png └── client-address.png ├── public ├── slack.png ├── favicon.ico ├── voucherify.png ├── voucherify_logo.png ├── voucherify_product.jpg ├── styles.css └── scripts.js ├── config └── default.json ├── index.js ├── .gitignore ├── app.json ├── LICENSE ├── package.json ├── routes └── index.js ├── lib └── voucherifyService.js ├── README.md └── views └── index.html /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | 3 | -------------------------------------------------------------------------------- /blob/app-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/blob/app-keys.png -------------------------------------------------------------------------------- /public/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/public/slack.png -------------------------------------------------------------------------------- /blob/sample_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/blob/sample_app.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /blob/deploy-finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/blob/deploy-finish.png -------------------------------------------------------------------------------- /blob/deploy-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/blob/deploy-window.png -------------------------------------------------------------------------------- /public/voucherify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/public/voucherify.png -------------------------------------------------------------------------------- /blob/client-address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/blob/client-address.png -------------------------------------------------------------------------------- /public/voucherify_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/public/voucherify_logo.png -------------------------------------------------------------------------------- /public/voucherify_product.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/HEAD/public/voucherify_product.jpg -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "voucherifyNodeJsExampleApp": { 3 | "email": "email@example.com", 4 | "applicationKeys": { 5 | "applicationId": null, 6 | "applicationSecretKey": null 7 | }, 8 | "clientSideKeys": { 9 | "clientApplicationId": null, 10 | "clientPublicKey": null 11 | }, 12 | "numberOfSuppliedTestVouchers": 5, 13 | "testVouchersCategory": "voucherify-nodejs-example" 14 | } 15 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const bodyParser = require('body-parser') 4 | const express = require('express') 5 | const swig = require('swig') 6 | 7 | const routes = require('./routes') 8 | 9 | const app = express() 10 | 11 | app.engine('html', swig.renderFile) 12 | app.set('view engine', 'html') 13 | app.set('port', (process.env.PORT || 5000)) 14 | app.use(express.static(__dirname + '/public')) 15 | app.use(bodyParser.json()) 16 | app.use(bodyParser.urlencoded({ 17 | extended: true 18 | })) 19 | 20 | app.use(routes()) 21 | 22 | app.listen(app.get('port'), () => { 23 | console.log(`Node app is running at localhost:${app.get('port')}`) 24 | }) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Jetbrains 36 | .idea 37 | 38 | # Configuration 39 | config 40 | !config/default.json 41 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Voucherify Node.js Example", 3 | "description": "A sample Node.js implementation of Voucherify SDK", 4 | "repository": "https://github.com/voucherifyio/voucherify-nodejs-example", 5 | "logo": "https://raw.githubusercontent.com/voucherifyio/voucherify-nodejs-example/master/public/voucherify.png", 6 | "keywords": ["voucherify", "node", "heroku", "express"], 7 | "env": { 8 | "EMAIL": { 9 | "description": "Leave your Email address to check the voucher tracking feature!", 10 | "value": "email@example.com" 11 | }, 12 | "APPLICATION_ID": { 13 | "description": "Voucherify Application ID", 14 | "value": "" 15 | }, 16 | "APPLICATION_SECRET_KEY": { 17 | "description": "Voucherify Application Secret Key", 18 | "value": "" 19 | }, 20 | "CLIENT_APPLICATION_ID": { 21 | "description": "Voucherify Client Application ID", 22 | "value": "" 23 | }, 24 | "CLIENT_PUBLIC_KEY": { 25 | "description": "Voucherify Client Public Key", 26 | "value": "" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 rspective 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voucherify-nodejs-example", 3 | "version": "0.1.0", 4 | "description": "A sample Node.js implementation of Voucherify SDK", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node --optimize_for_size --max_old_space_size=460 --gc_interval=100 index.js", 8 | "test": "echo 'Tests are not defined yet' && exit 0", 9 | "postinstall": "echo 'No postinstall scripts yet' && exit 0", 10 | "heroku-prebuild": "echo 'This runs before Heroku installs your dependencies' && exit 0" , 11 | "heroku-postbuild": "echo 'This runs after Heroku builds your app' && exit 0" 12 | }, 13 | "dependencies": { 14 | "bluebird": "^3.4.0", 15 | "body-parser": "^1.15.1", 16 | "config": "^1.20.1", 17 | "express": "^4.13.3", 18 | "lodash": "^4.12.0", 19 | "moment": "^2.13.0", 20 | "swig": "^1.4.2", 21 | "voucherify": "^1.10.0" 22 | }, 23 | "engines": { 24 | "node": "4.2.6" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/voucherifyio/voucherify-nodejs-example" 29 | }, 30 | "keywords": [ 31 | "voucherify", 32 | "node", 33 | "heroku", 34 | "express" 35 | ], 36 | "license": "MIT" 37 | } 38 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require('lodash') 4 | const config = require('config').get('voucherifyNodeJsExampleApp') 5 | const express = require('express') 6 | 7 | const voucherifyService = require('./../lib/voucherifyService') 8 | 9 | const clientSideKeys = config.get('clientSideKeys') 10 | 11 | const email = process.env.EMAIL || config.get('email') 12 | const clientApplicationId = process.env.CLIENT_APPLICATION_ID || clientSideKeys.get('clientApplicationId') 13 | const clientPublicKey = process.env.CLIENT_PUBLIC_KEY || clientSideKeys.get('clientPublicKey') 14 | 15 | const prepareUnhandledErrorResponse = (err, res) => { 16 | console.error('Error: %s', err) 17 | return res.status(500).json({message: 'Internal Server Error!'}) 18 | } 19 | 20 | const Routes = function () { 21 | const router = express.Router() 22 | 23 | router.get('/', (req, res) => { 24 | console.log('GET /') 25 | 26 | res.render('index', { 27 | userIdentity: email, 28 | clientConfig: { 29 | clientApplicationId: clientApplicationId, 30 | clientPublicKey: clientPublicKey 31 | } 32 | }) 33 | }) 34 | 35 | router.get('/vouchers.json', (req, res) => { 36 | console.log('GET /vouchers.json') 37 | 38 | voucherifyService.getValidVouchers() 39 | .then((result) => { 40 | res.json(result) 41 | }) 42 | .catch((err) => prepareUnhandledErrorResponse(err, res)) 43 | }) 44 | 45 | router.post('/redeem', (req, res) => { 46 | console.log('POST /redeem', req.body) 47 | 48 | const voucherCode = req.body.voucher_code 49 | const userTrackingId = req.body.tracking_id 50 | 51 | voucherifyService.redeemVoucher(voucherCode, userTrackingId) 52 | .then((result) => { 53 | res.json(result) 54 | }) 55 | .catch((err) => prepareUnhandledErrorResponse(err, res)) 56 | }) 57 | 58 | return router 59 | } 60 | 61 | module.exports = Routes 62 | -------------------------------------------------------------------------------- /lib/voucherifyService.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require('lodash') 4 | const config = require('config').get('voucherifyNodeJsExampleApp') 5 | const moment = require('moment') 6 | const Promise = require('bluebird') 7 | const voucherifyClient = require('voucherify') 8 | 9 | const applicationKeys = config.get('applicationKeys') 10 | 11 | const testVouchersCategory = config.get('testVouchersCategory') 12 | const numberOfSuppliedTestVouchers = config.get('numberOfSuppliedTestVouchers') 13 | 14 | const voucherify = voucherifyClient({ 15 | applicationId: process.env.APPLICATION_ID || applicationKeys.applicationId, 16 | clientSecretKey: process.env.APPLICATION_SECRET_KEY || applicationKeys.applicationSecretKey 17 | }) 18 | 19 | const isVoucherRedeemed = (voucher) => { 20 | return _.get(voucher, 'redemption.quantity') === _.get(voucher, 'redemption.redeemed_quantity') 21 | } 22 | 23 | function VoucherService () { 24 | this.createRandomVoucher = function () { 25 | return voucherify.create(this.generateVoucherBody()) 26 | .then((newVoucher) => { 27 | console.log('[VoucherifyService] New voucher: %s', _.get(newVoucher, 'code')) 28 | return newVoucher 29 | }) 30 | .catch((err) => { 31 | console.log('[VoucherifyService] Error: %s', err) 32 | }) 33 | } 34 | 35 | this.getVouchers = function () { 36 | return voucherify.list({category: testVouchersCategory}) 37 | } 38 | 39 | this.getValidVouchers = function () { 40 | return this.getVouchers() 41 | .then((vouchers) => _.filter(vouchers, (voucher) => !isVoucherRedeemed(voucher))) 42 | } 43 | 44 | this.redeemVoucher = function (voucherCode, userTrackingId) { 45 | return voucherify.redeem(voucherCode, userTrackingId) 46 | .then((result) => { 47 | const voucher = _.get(result, 'voucher') 48 | 49 | if (_.isEmpty(voucher)) { 50 | return Promise.reject(new Error('No voucher object after redemption')) 51 | } 52 | 53 | if (isVoucherRedeemed(voucher)) { 54 | return this.createRandomVoucher() 55 | } 56 | 57 | return voucher 58 | }) 59 | } 60 | 61 | this.generateVoucherBody = function () { 62 | const amount = { 63 | type: 'AMOUNT', 64 | amount_off: _.random(10.00, 40.00, true).toFixed(2) * 100 // 10.00 = 1000 65 | } 66 | 67 | const percent = { 68 | type: 'PERCENT', 69 | percent_off: _.random(10, 100) 70 | } 71 | 72 | const unit = { 73 | type: 'UNIT', 74 | unit_type: 'time', 75 | unit_off: 1 76 | } 77 | 78 | return { 79 | discount: _.sample([amount, percent, unit]), 80 | category: testVouchersCategory, 81 | redemption: ({ 82 | quantity: _.random(1, 5), 83 | redeemed_quantity: 0, 84 | redemption_entries: [] 85 | }), 86 | active: true, 87 | start_date: moment().toISOString() 88 | } 89 | } 90 | 91 | this.init = function () { 92 | return this.getValidVouchers() 93 | .then((vouchers) => { 94 | if (_.isEmpty(vouchers)) { 95 | return Promise.all(_.times(numberOfSuppliedTestVouchers, this.createRandomVoucher.bind(this))) 96 | } 97 | 98 | if (_.size(vouchers) < numberOfSuppliedTestVouchers){ 99 | const nbOfMissingVouchers = numberOfSuppliedTestVouchers - _.size(vouchers) 100 | return Promise.all(_.times(nbOfMissingVouchers, this.createRandomVoucher.bind(this))) 101 | } 102 | }) 103 | } 104 | 105 | this.init() 106 | 107 | return this 108 | } 109 | 110 | module.exports = new VoucherService() 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voucherify - Node.js SDK sample application 2 | 3 | ## Overview 4 | 5 | This sample application shows the [basic workflow](https://voucherify.readme.io/docs/voucher-lifecycle) of Voucherify and presents the supported [voucher types](https://voucherify.readme.io/docs/vouchers). You can try out the [Live Version](https://voucherify-sample-nodejs.herokuapp.com/) or deploy an instance bound to your [account](https://app.voucherify.io/#/signup?plan=standard) through Heroku button. 6 | 7 | Implemented with our [Node.js SDK](https://voucherify.readme.io/docs/nodejs-tutorial) and [Voucheriy.js](https://voucherify.readme.io/docs/voucherifyjs) client library. 8 | 9 | --- 10 | [Voucherify](http://voucherify.io?utm_source=github&utm_medium=demo&utm_campaign=acq) is an API-first platform for software developers who are dissatisfied with high-maintenance custom coupon software. Our product is a coupon infrastructure through API that provides a quicker way to build coupon generation, distribution and tracking. Unlike legacy coupon software we have: 11 | 12 | * an API-first SaaS platform that enables customisation of every aspect of coupon campaigns 13 | * a management console that helps cut down maintenance and reporting overhead 14 | * an infrastructure to scale up coupon activity in no time 15 | 16 | ![](blob/sample_app.png) 17 | 18 | ## Setup 19 | 20 | It is really simple to setup this app. Only what you need to do is follow the steps listed below: 21 | 22 | 1. You need a set of *Application Keys* and *Client-side Keys* to connect with **Voucherify Platform**. Visit App. 23 | 24 | 2. After signing up you need also add your domain to Voucherify's whitelist. 25 | When you go to configuration view of Voucherify account, "Your website URL" is used for allowing client requests only from given domain. You have to put there your website url or set * if you want to enable requests from any origin. 26 | 27 | ![](blob/client-address.png) 28 | 29 | 3. Press this button to create a Heroku app 30 | 31 | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/voucherifyio/voucherify-nodejs-example) 32 | 33 | Wait until the Deploy Window is open. 34 | 35 | 4. After opening the Deploy Window, please go to the [**Configuration**](https://app.voucherify.io/#/app/configuration) page. 36 | 37 | Copy App Keys from the Configuration page and paste these keys into proper input fields in the Deploy Window. 38 | 39 | ![](blob/app-keys.png) 40 | 41 | 5. In the Deploy Window after filling all required inputs click a Deploy Button located on the end of page. Wait until the Deploying Process is finish. 42 | 43 | ![](blob/deploy-window.png) 44 | 45 | 6. After finishing process you can go to the Manage Panel or visit the Voucherify Example page. 46 | 47 | ![](blob/deploy-finish.png) 48 | 49 | 50 | ## Commands 51 | 52 | * `$ npm run start` - runs the application 53 | 54 | ## Help 55 | 56 | * Found a bug? Have a suggestion for improvement? Want to tell us we're awesome? [**Submit an issue**](https://github.com/voucherifyio/voucherify-nodejs-example/issues/new) 57 | * Trouble with your integration? Contact [**Voucherify Support**](https://voucherify.readme.io/docs/support) / [**support@voucherify.io**](mailto:support@voucherify.io) 58 | * Want to contribute? [**Submit a pull request**](https://github.com/voucherifyio/voucherify-nodejs-example/compare) 59 | 60 | ## Disclaimer 61 | 62 | This code is provided as is and is only intended to be used for illustration purposes. This code is not production-ready and is not meant to be used in a production environment. This repository is to be used as a tool to help developers learn how to integrate with Voucherify. Any use of this repository or any of its code in a production environment is highly discouraged. 63 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Voucherify Sample Shop 14 | 15 | 16 |
17 | 18 |

Voucherify Sample Shop

19 |
20 | 21 |
22 |
23 |
24 |
25 | product-img 26 |
27 |
28 |
29 | 30 |
31 |
OK
32 |
33 |
34 | 35 |
36 |
37 |

 38 |           
39 |
40 | 41 |
42 |

43 | This sample application presents Voucherify's basic workflow. Follow these steps: 44 |

45 |

    46 |
  • Validate one of the voucher codes listed below in the input on the left.
  • 47 |
  • Purchase the product to redeem the voucher
  • 48 |
  • Analyze the redemption history in the Admin Panel (account required).
  • 49 |
50 | The source code is available on github. 51 |

52 | 53 |
54 | 55 | 56 | Regular Price:  57 | 0 58 | EUR 59 | 60 | 61 | 62 | Discount:  63 | 0 64 | EUR 65 | 66 | 67 |
68 |
69 | Product Name 70 | Voucherify sample product 71 |
72 | 73 |
74 | Products Count 75 | 76 | 77 | 78 |
79 | 80 |
81 | Total price 82 | 83 | 0 84 | 0 85 | EUR 86 | 87 |
88 | 89 |
90 | Shipment 91 | 92 | FREE 93 | 0 94 | EUR 95 | 96 |
97 | 98 |
99 | Summary 100 | 101 | 0 102 | EUR 103 | 104 |
105 | 106 |
107 | 108 |
109 |
110 |
111 |
112 |
113 | 114 | 117 | 118 | 119 | 120 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | /* Main styles */ 2 | 3 | *, *:before, *:after { 4 | box-sizing: border-box; 5 | } 6 | 7 | .clearfix:after { 8 | visibility: hidden; 9 | display: block; 10 | font-size: 0; 11 | content: " "; 12 | clear: both; 13 | height: 0; 14 | } 15 | 16 | .clearfix { 17 | display: inline-block; 18 | } 19 | 20 | * html .clearfix { 21 | height: 1%; 22 | } 23 | 24 | .clearfix { 25 | display: block; 26 | } 27 | 28 | body { 29 | font-family: 'Open Sans', sans-serif; 30 | font-size: 12px; 31 | } 32 | 33 | .wrapper { 34 | width: 1080px; 35 | margin: 0 auto; 36 | } 37 | 38 | /* Main Header */ 39 | 40 | .voucherify-logo { 41 | width: 80px; 42 | float: left; 43 | position: relative; 44 | margin: -12px 20px 0 0; 45 | } 46 | 47 | .site-title { 48 | color: #36b3a8; 49 | font-size: 40px; 50 | padding: 0 0 30px; 51 | } 52 | 53 | .site-title, 54 | .product-item { 55 | border-bottom: 1px solid #cccccc; 56 | margin-bottom: 20px; 57 | } 58 | 59 | .product-item .left-column { 60 | width: 50%; 61 | } 62 | 63 | /* Product section */ 64 | #products-count { 65 | text-align: right; 66 | width: 100%; 67 | } 68 | 69 | .product-image-column, 70 | .summary-column, 71 | .product-description-column { 72 | float: left; 73 | position: relative; 74 | width: 50%; 75 | } 76 | 77 | .product-image-column .voucher-checkout-box { 78 | position: absolute; 79 | bottom: 0; 80 | left: 50%; 81 | margin-left: -90px; 82 | margin-bottom: 50px; 83 | } 84 | 85 | .summary-column { 86 | display: none; 87 | } 88 | 89 | .summary-column .tick-icon { 90 | width: 50px; 91 | height: 50px; 92 | border-radius: 50%; 93 | border: 2px #08ffb9 solid; 94 | color: #08ffb9; 95 | text-align: center; 96 | padding: 10px 5px; 97 | font-size: 20px; 98 | font-weight: bold; 99 | margin: 10px auto 25px; 100 | } 101 | 102 | .summary-column .response .code { 103 | font-size: 10px; 104 | line-height: 1.4em; 105 | background-color: #EFEFEF; 106 | padding: 20px; 107 | border: 1px solid #CCC; 108 | border-radius: 5px; 109 | max-height: 450px; 110 | overflow: auto; 111 | } 112 | 113 | .vouchers-list { 114 | width: 100%; 115 | margin: 10px 0 10px; 116 | } 117 | 118 | .vouchers-list .voucher-item { 119 | position: relative; 120 | width: 19.3%; 121 | float: left; 122 | padding: 0 5px; 123 | background-color: #38B3A8; 124 | color: #FFFFFF; 125 | margin: 0.3%; 126 | text-align: center; 127 | height: 150px; 128 | max-height: 150px; 129 | } 130 | 131 | .vouchers-list .voucher-item .voucher-code { 132 | font-size: 20px; 133 | padding: 15px 0 10px; 134 | } 135 | 136 | .vouchers-list .voucher-item .voucher-percent, 137 | .vouchers-list .voucher-item .voucher-amount { 138 | font-size: 15px; 139 | line-height: 2.7em; 140 | } 141 | 142 | .vouchers-list .voucher-item .voucher-percent .percent-off { 143 | font-size: 20px; 144 | line-height: 0.5em; 145 | } 146 | 147 | .vouchers-list .voucher-item .voucher-amount .fa, 148 | .vouchers-list .voucher-item .voucher-unit .fa { 149 | margin-right: 5px; 150 | } 151 | 152 | .vouchers-list .voucher-item .voucher-unit .unit-off { 153 | font-weight: bold; 154 | margin-right: 5px; 155 | font-size: 17px; 156 | width: 100%; 157 | display: inline-block; 158 | } 159 | 160 | .vouchers-list .voucher-item .voucher-redemption { 161 | font-size: 10px; 162 | } 163 | 164 | .vouchers-list .voucher-item .voucher-expiration { 165 | position: absolute; 166 | text-align: center; 167 | font-size: 10px; 168 | bottom: 0; 169 | } 170 | 171 | .product-item .right-column { 172 | width: 50%; 173 | } 174 | 175 | .right-column { 176 | width: 70%; 177 | padding: 15px; 178 | } 179 | 180 | .product-item .left-column, 181 | .left-column { 182 | float: left; 183 | } 184 | 185 | .product-item .right-column, 186 | .right-column { 187 | float: right; 188 | } 189 | 190 | .product-item .product-img { 191 | width: 100%; 192 | margin: 0; 193 | position: relative; 194 | top: -20px 195 | } 196 | 197 | .product-item .product-img img { 198 | width: 100%; 199 | } 200 | 201 | .product-item .product-name { 202 | font-size: 30px; 203 | } 204 | 205 | .product-item .product-price { 206 | font-size: 20px; 207 | } 208 | 209 | .product-item .discount-price { 210 | font-size: 25px; 211 | } 212 | 213 | .product-item .product-price, 214 | .product-item .discount-price { 215 | display: inline-block; 216 | width: 100%; 217 | } 218 | 219 | .product-item .product-price .price-value, 220 | .product-item .product-price .price-currency, 221 | .product-item .discount-price .price-value, 222 | .product-item .discount-price .price-currency { 223 | font-weight: bold; 224 | } 225 | 226 | .old-price-value { 227 | width: 100%; 228 | font-size: 15px; 229 | font-style: italic; 230 | color: #CCC; 231 | text-decoration: line-through; 232 | } 233 | 234 | .voucher-column .label { 235 | font-size: 23px; 236 | padding: 5px 0 20px; 237 | font-weight: bold; 238 | } 239 | 240 | .price-row { 241 | padding: 10px 0 10px 0; 242 | width: 30%; 243 | } 244 | 245 | .name-row, 246 | .count-row { 247 | padding: 10px 5px 10px 0; 248 | width: 35%; 249 | } 250 | 251 | .name-row, 252 | .count-row, 253 | .price-row { 254 | float: left; 255 | text-align: right; 256 | } 257 | 258 | .name-row .label, 259 | .count-row .label, 260 | .price-row .label { 261 | font-size: 17px; 262 | margin-bottom: 3px; 263 | } 264 | 265 | .name-row .value, 266 | .count-row .input-value, 267 | .price-row .value { 268 | font-size: 17px; 269 | } 270 | 271 | .name-row .label, 272 | .count-row .label, 273 | .price-row .label { 274 | background-color: #EFFEFE; 275 | } 276 | 277 | .name-row .label, 278 | .count-row .label, 279 | .price-row .label, 280 | .name-row .value, 281 | .count-row .input-value, 282 | .price-row .value { 283 | display: inline-block; 284 | width: 100%; 285 | text-align: left; 286 | padding: 10px; 287 | } 288 | 289 | .price-row .label, 290 | .count-row .label, 291 | .count-row .input-value, 292 | .price-row .value { 293 | text-align: right; 294 | } 295 | 296 | .count-row .input-value input { 297 | width: 50px; 298 | padding: 5px 10px; 299 | border-radius: 2px; 300 | border: 1px solid #888888; 301 | position: relative; 302 | top: -4px; 303 | } 304 | 305 | .shipment-row { 306 | float: left; 307 | font-size: 17px; 308 | background-color: #EFFEFE; 309 | width: 100%; 310 | padding: 10px; 311 | } 312 | 313 | .shipment-row .label, 314 | .shipment-row .value { 315 | display: inline-block; 316 | float: left; 317 | } 318 | 319 | .shipment-row .label { 320 | width: 60%; 321 | } 322 | 323 | .shipment-row .value { 324 | width: 40%; 325 | text-align: right; 326 | } 327 | 328 | .shipment-row .price-value.free-active { 329 | text-decoration: line-through; 330 | } 331 | 332 | .shipment-row .free-label { 333 | font-weight: bold; 334 | font-size: 20px; 335 | text-transform: uppercase; 336 | } 337 | 338 | .summary-row { 339 | float: left; 340 | font-size: 20px; 341 | width: 100%; 342 | padding: 10px; 343 | } 344 | 345 | .summary-row .label, 346 | .summary-row .value { 347 | display: inline-block; 348 | float: left; 349 | } 350 | 351 | .summary-row .label { 352 | width: 60%; 353 | } 354 | 355 | .summary-row .value { 356 | width: 40%; 357 | text-align: right; 358 | } 359 | 360 | .buy-row { 361 | text-align: right; 362 | } 363 | 364 | .toolbar-row { 365 | text-align: center; 366 | } 367 | 368 | .buy-row, 369 | .toolbar-row { 370 | width: 100%; 371 | } 372 | 373 | .buy-row .buy-button, 374 | .toolbar-row .back-button { 375 | display: block; 376 | border: 0; 377 | border-radius: 2em; 378 | padding: 0 1.75em; 379 | margin: 20px 0; 380 | cursor: pointer; 381 | outline: 0; 382 | line-height: 3em; 383 | height: 3em; 384 | font-size: 1.2em; 385 | font-weight: bold; 386 | text-transform: uppercase; 387 | background-color: #08ffb9; 388 | color: rgba(63, 49, 49, .8); 389 | } 390 | 391 | .buy-row .buy-button { 392 | float: right; 393 | } 394 | 395 | /* Main Footer */ 396 | .main-footer { 397 | text-align: center; 398 | } 399 | 400 | .main-footer .footer-description { 401 | color: #cccccc; 402 | font-size: 12px; 403 | padding: 0 0; 404 | } 405 | 406 | .main-footer .footer-description a { 407 | text-decoration: none; 408 | } 409 | 410 | .main-footer .footer-description a:hover { 411 | text-decoration: underline; 412 | } 413 | 414 | /* overrides for voucherify */ 415 | 416 | .voucherifyContainer, 417 | .voucherifyLogo { 418 | box-sizing: content-box; 419 | } 420 | 421 | .voucherifyContainer { 422 | box-shadow: 0 0 40px 10px rgba(256, 256, 256, 0.8) !important; 423 | -webkit-box-shadow: 0 0 40px 10px rgba(256, 256, 256, 0.8) !important; 424 | } 425 | -------------------------------------------------------------------------------- /public/scripts.js: -------------------------------------------------------------------------------- 1 | (function (window, $, Voucherify, clientConfig, userIdentity) { 2 | 'use strict' 3 | 4 | var sampleProductPrice = 32.40 5 | var sampleShipmentPrice = 12 6 | 7 | var getVouchers = function () { 8 | return $.get('/vouchers.json') 9 | } 10 | 11 | var redeemVoucher = function (voucherCode, trackingId) { 12 | return $.post('/redeem', { 13 | voucher_code: voucherCode, 14 | tracking_id: trackingId 15 | }) 16 | } 17 | 18 | var ProductModel = function (price, countChangeHandler) { 19 | var _self = this 20 | 21 | _self.init = function (price) { 22 | _self.price = price 23 | _self.count = 1 24 | _self.countChangeHandler = countChangeHandler 25 | 26 | _self.render() 27 | 28 | return _self 29 | } 30 | 31 | _self.render = function () { 32 | $('#products-count').val(_self.count) 33 | } 34 | 35 | $('#products-count').on('change', function () { 36 | _self.count = $(this).val() 37 | _self.countChangeHandler(_self.count, _self.price) 38 | }) 39 | 40 | return this.init(price) 41 | } 42 | 43 | var VoucherCode = function (identity, discountChangeHandler) { 44 | this.voucherCode = null 45 | 46 | this.identity = null 47 | 48 | this.trackingId = null 49 | 50 | this.valid = false 51 | 52 | this.res = null 53 | 54 | this.isValid = function () { 55 | return this.valid === true 56 | } 57 | 58 | this.onValidatedHandler = function (res) { 59 | this.res = res 60 | this.voucherCode = res.code 61 | this.trackingId = res.tracking_id 62 | this.valid = res.valid 63 | 64 | this.discountChangeHandler(this.res) 65 | 66 | return this 67 | } 68 | 69 | this.init = function () { 70 | this.identity = identity 71 | this.discountChangeHandler = discountChangeHandler 72 | 73 | $('#voucher-checkout').html('') 74 | 75 | Voucherify.initialize(clientConfig.clientApplicationId, clientConfig.clientPublicKey) 76 | Voucherify.setIdentity(this.identity) 77 | Voucherify.render('#voucher-checkout', { 78 | textValidate: 'Validate voucher', 79 | textPlaceholder: 'Use your voucher code here', 80 | onValidated: this.onValidatedHandler.bind(this) 81 | }) 82 | 83 | return this 84 | } 85 | 86 | return this.init() 87 | } 88 | 89 | var VoucherifySampleShop = function (identity) { 90 | var _self = this 91 | 92 | function setDiscountForCashVouchers () { 93 | if (_self.res.discount.type !== "UNIT" || _self.res.valid !== true) { 94 | _self.setDiscountPrice(Voucherify.utils.calculatePrice(_self.totalPrice, _self.res)) 95 | _self.setDiscount(Voucherify.utils.calculateDiscount(_self.totalPrice, _self.res)) 96 | } 97 | } 98 | 99 | _self.init = function () { 100 | _self.product = new ProductModel(sampleProductPrice, function (count, price) { 101 | _self.setTotalPrice(count * price) 102 | 103 | setDiscountForCashVouchers() 104 | }) 105 | 106 | _self.voucher = new VoucherCode(identity, function (res) { 107 | _self.res = res 108 | 109 | if(!_self.freeShipment && _self.res.discount.type === "UNIT" && _self.res.valid === true) { 110 | _self.setFreeShipment(true) 111 | } 112 | 113 | setDiscountForCashVouchers() 114 | }) 115 | 116 | _self.shipmentPrice = sampleShipmentPrice 117 | 118 | _self.freeShipment = false 119 | 120 | _self.totalPrice = _self.product.count * _self.product.price 121 | 122 | _self.discount = null 123 | 124 | _self.discountPrice = null 125 | 126 | _self.res = null 127 | 128 | getVouchers() 129 | .done(function (vouchersList) { 130 | _self.setVouchersList(vouchersList) 131 | }) 132 | .fail(function (err) { 133 | console.error(err) 134 | }) 135 | 136 | return _self 137 | } 138 | 139 | _self.setDiscountPrice = function (discountPrice) { 140 | _self.discountPrice = discountPrice 141 | _self.render() 142 | } 143 | 144 | _self.setDiscount = function (discount) { 145 | _self.discount = discount 146 | _self.render() 147 | } 148 | 149 | _self.setTotalPrice = function (totalPrice) { 150 | _self.totalPrice = totalPrice 151 | _self.render() 152 | } 153 | 154 | _self.setVouchersList = function (newVouchersList) { 155 | _self.vouchersList = newVouchersList 156 | _self.render() 157 | } 158 | 159 | _self.setShipmentPrice = function (shipmentPrice) { 160 | _self.shipmentPrice = shipmentPrice 161 | _self.render() 162 | } 163 | 164 | _self.setFreeShipment = function (freeShipment) { 165 | _self.freeShipment = freeShipment 166 | _self.render() 167 | } 168 | 169 | _self.render = function () { 170 | if (_self.discount === null) { 171 | $('.old-price-value') 172 | .hide(0) 173 | $('.discount-price') 174 | .hide(0) 175 | } else { 176 | $('.old-price-value') 177 | .show(500) 178 | $('.discount-price') 179 | .show(500) 180 | } 181 | 182 | if (_self.freeShipment) { 183 | $('#free-shipment-label') 184 | .show(500) 185 | $('#shipment-price') 186 | .addClass('free-active') 187 | } else { 188 | $('#free-shipment-label') 189 | .hide(0) 190 | $('#shipment-price') 191 | .removeClass('free-active') 192 | } 193 | 194 | $('#regular-price').text(_self.product.price.toFixed(2)) 195 | $('#discount-price').text((_self.discount * -1).toFixed(2)) 196 | $('#old-total-price').text(_self.totalPrice.toFixed(2)) 197 | $('#shipment-price').text(_self.shipmentPrice.toFixed(2)) 198 | $('#total-price').text(((_self.discount !== null ? _self.discountPrice : _self.totalPrice)).toFixed(2)) 199 | $('#summary-price').text(((_self.discount !== null ? _self.discountPrice : _self.totalPrice) + (_self.freeShipment ? 0 :_self.shipmentPrice)).toFixed(2)) 200 | 201 | $('#vouchers-list').html(_self.vouchersList.map(function (voucher) { 202 | return $('
') 203 | .addClass('voucher-item') 204 | .html(function () { 205 | var result = [] 206 | 207 | result.push($('
') 208 | .addClass('voucher-code') 209 | .append('' + voucher.code + '')) 210 | 211 | switch (voucher.discount.type) { 212 | case 'AMOUNT' : 213 | result.push($('
') 214 | .addClass('voucher-amount') 215 | .append('') 216 | .append('' + (voucher.discount.amount_off / 100).toFixed(2) + '')) 217 | break 218 | case 'PERCENT' : 219 | result.push($('
') 220 | .addClass('voucher-percent') 221 | .append('' + voucher.discount.percent_off + '') 222 | .append('')) 223 | break 224 | case 'UNIT' : 225 | result.push($('
') 226 | .addClass('voucher-unit') 227 | .append('') 228 | .append('Free') 229 | .append('Shipment')) 230 | break 231 | } 232 | 233 | var voucherDiscount = Voucherify.utils.calculateDiscount(_self.totalPrice, voucher) 234 | 235 | if (voucherDiscount) { 236 | result.push($('
') 237 | .addClass('voucher-discount') 238 | .append('Discount: ') 239 | .append('' + voucherDiscount + '')) 240 | } 241 | 242 | if (voucher.redemption.quantity) { 243 | result.push($('
') 244 | .addClass('voucher-redemption') 245 | .append('Used: ') 246 | .append('' + voucher.redemption.redemption_entries.length + '/' + voucher.redemption.quantity + ' times')) 247 | } 248 | 249 | return result 250 | }) 251 | })) 252 | } 253 | 254 | _self.showSummary = function (res) { 255 | var $summaryTab = $('#summary-tab') 256 | var summaryPrice = ((_self.discountPrice || _self.totalPrice) + (_self.freeShipment ? 0 :_self.shipmentPrice)).toFixed(2) 257 | var summaryMessage = 'Congratulations! Your voucher has been redeemed successfully! Final price was ' + summaryPrice + ' EUR' 258 | 259 | $('#summary-message', $summaryTab).html(summaryMessage) 260 | $('#response-code', $summaryTab).text(JSON.stringify(res, null, 2)) 261 | 262 | $('#shop-tab').hide(500, function () { 263 | $summaryTab.show(500) 264 | }) 265 | } 266 | 267 | _self.showShop = function () { 268 | $('#summary-tab').hide(500, function () { 269 | $('#shop-tab').show(500) 270 | }) 271 | 272 | _self.init() 273 | } 274 | 275 | $('#buy-product-button').on('click', function () { 276 | if (_self.voucher.isValid() && confirm('Would you like to redeem this voucher?') || 277 | !_self.voucher.isValid() && confirm('Would you like to buy without discount?')) { 278 | redeemVoucher(_self.voucher.voucherCode, _self.voucher.trackingId) 279 | .done(function (res) { 280 | _self.showSummary(res) 281 | }) 282 | .fail(function (err) { 283 | console.error(err) 284 | }) 285 | } 286 | }) 287 | 288 | $('#back-button').on('click', function () { 289 | _self.showShop() 290 | }) 291 | 292 | return _self.init() 293 | } 294 | 295 | return new VoucherifySampleShop(userIdentity) 296 | 297 | })(window, window.jQuery, window.Voucherify, window.clientConfig, window.userIdentity) 298 | --------------------------------------------------------------------------------