diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38e99b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +.env +*.csv + +yarn.lock \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d380f06 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Commercetools SheerID discount codes + +This is a quick overview of how to integrate [SheerID verification](https://www.sheerid.com/) to your commercetools (https://www.commercetools.com/) site without automatic processes. + +## Requirements +- a commercetools website, access to Merchant Center with a user that has access to the Settings > Developer settings > API keys +- a SheerID account +- computer with nodejs v16 or not much higher (should work from v10 and with recent versions but no guarantees) + +## Installation + +Preparation, this process does not install anything outside of the cloned folder. + +- clone the repository to your local machine +- run `yarn` or `npm install` to install the dependencies + +## The process + +How to use: +- Create a cart discount on your system (e.g. 20% off for students) +- Create a commercetools API client in Merchant Center > Settings > Developer settings > API clients + - Scope can be "Admin client" (not recommended) or "Manage" Cart discounts and Discount codes + - Download the created API client's "Environment variables (.env)" file before closing the popup + - Add the .env file to the project root +- Run `node get-cart-discounts.js`, copy the desired ID (UUID format, like `f9c4718e-0792-4f08-a802-70f81ef9d46d`) +- Run `node generate-discount-codes.js STUD20- f9c4718e-0792-4f08-a802-70f81ef9d46d 20 > newcodes.csv` +- Check `newcodes.csv` for the generated discount codes +- Sanity check the generated discount codes in Merchant Center > Discounts > Discount code list +- Test code behaviours in the cart without using them, or by removing the used up codes from the csv +- On https://my.sheerid.com/ + - Create a SheerID program, e.g. "Student discount" + - Upload `newcodes.csv` to the Codes step "Single-Use Codes" card + - Copy the Web URL from Publish step "New Page" + +- Add the copied Web URL to your website as a banner link or button +- Test linked banner on your website, fill the form, copy the code and test cart and checkout (the code will be invalidated in SheerID system immediately after successful verification) +- Don't forget to switch the program to Live mode on https://my.sheerid.com/ + +## This repository + +This is spike code. Feel free to copy-paste from it, if you like playing with razors blindfolded. diff --git a/generate-discount-codes.js b/generate-discount-codes.js new file mode 100644 index 0000000..d7c6c30 --- /dev/null +++ b/generate-discount-codes.js @@ -0,0 +1,67 @@ +var request = require('request'); +var config = require('dotenv').config().parsed; + +const auth = require('./src/auth'); +const nano = require('./src/nano'); +const getCartDiscount = require('./src/discount'); + +function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + + return (result.substring(0, length / 2) + nano() + result.substring(length / 2, length)).toUpperCase(); +} + +async function main() { + if (process.argv.length < 5) { + console.log('Usage: node generate-discount-codes.js '); + return; + } + const token = await auth(); + if (token.error) { + console.log(token.error); + return; + } + + const prefix = process.argv[2]; + const cartDiscountId = process.argv[3]; + const numberOfCodes = process.argv[4]; + + const discount = await getCartDiscount(cartDiscountId, token.access_token); + const name = discount.name.en; + console.log('Discount Codes'); + + for (let i = 0; i < numberOfCodes; i++) { + request({ + 'method': 'POST', + 'url': `${config.CTP_API_URL}/${config.CTP_PROJECT_KEY}/discount-codes`, + 'headers': { + 'Authorization': 'Bearer ' + token.access_token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + "code": prefix + makeid(6), + "name": discount.name, + "cartDiscounts": [ + { + "typeId": "cart-discount", + "id": cartDiscountId + } + ], + "isActive": true, + "cartPredicate": "1=1" + }) + }, function (error, response) { + if (error) throw new Error(error); + const res = JSON.parse(response.body); + console.log(res.code); + }); + } +} + +main(); + diff --git a/get-cart-discounts.js b/get-cart-discounts.js new file mode 100644 index 0000000..acd6050 --- /dev/null +++ b/get-cart-discounts.js @@ -0,0 +1,29 @@ +var request = require('request'); +var config = require('dotenv').config().parsed; + +const auth = require('./src/auth'); + +async function main() { + const token = await auth(); + if (token.error) { + console.log(token.error); + return; + } + + request({ + 'method': 'GET', + 'url': `${config.CTP_API_URL}/${config.CTP_PROJECT_KEY}/cart-discounts`, + 'headers': { + 'Authorization': 'Bearer '+token.access_token, + } + }, function (error, response) { + if (error) throw new Error(error); + const res = JSON.parse(response.body); + res.results.forEach(element => { + console.log(`${element.key}: ${element.id} - ${element.name.en}`); + }); + }); +} + +main(); + diff --git a/get-discount-codes.js b/get-discount-codes.js new file mode 100644 index 0000000..6d76190 --- /dev/null +++ b/get-discount-codes.js @@ -0,0 +1,30 @@ +var request = require('request'); +var config = require('dotenv').config().parsed; + +const auth = require('./src/auth'); + +async function main() { + const token = await auth(); + if (token.error) { + console.log(token.error); + return; + } + + request({ + 'method': 'GET', + 'url': `${config.CTP_API_URL}/${config.CTP_PROJECT_KEY}/discount-codes`, + 'headers': { + 'Authorization': 'Bearer '+token.access_token, + } + }, function (error, response) { + if (error) throw new Error(error); + const res = JSON.parse(response.body); + res.results.forEach(element => { + // console.log(element) + console.log(`${element.code}: ${element.id} - ${element.name.en} (${element.cartDiscounts[0].id})`); + }); + }); +} + +main(); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..692c3f3 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "commercetools-sheerid", + "version": "0.1.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "dotenv": "^16.0.1", + "request": "^2.88.2" + } +} diff --git a/src/auth.js b/src/auth.js new file mode 100644 index 0000000..ee5f345 --- /dev/null +++ b/src/auth.js @@ -0,0 +1,28 @@ +var config = require('dotenv').config().parsed; +var request = require('request'); + +const auth = async () => { + var auth = 'Basic ' + Buffer.from(config.CTP_CLIENT_ID + ':' + config.CTP_CLIENT_SECRET).toString('base64'); + return new Promise(function (resolve, reject) { + request({ + 'method': 'POST', + 'url': `${config.CTP_AUTH_URL}/oauth/token`, + 'headers': { + 'Authorization': auth, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + form: { + 'grant_type': 'client_credentials', + 'scope': config.CTP_SCOPES + } + }, function (error, res, body) { + if (!error && res.statusCode == 200) { + resolve(JSON.parse(body)); + } else { + reject(error); + } + }); + }); +} + +module.exports = auth; \ No newline at end of file diff --git a/src/discount.js b/src/discount.js new file mode 100644 index 0000000..fc10611 --- /dev/null +++ b/src/discount.js @@ -0,0 +1,22 @@ +var config = require('dotenv').config().parsed; +var request = require('request'); + +const getCartDiscount = async (code, token) => { + return new Promise(function (resolve, reject) { + request({ + 'method': 'GET', + 'url': `${config.CTP_API_URL}/${config.CTP_PROJECT_KEY}/cart-discounts/${code}`, + 'headers': { + 'Authorization': 'Bearer '+token, + }, + }, function (error, res, body) { + if (!error && res.statusCode == 200) { + resolve(JSON.parse(body)); + } else { + reject(error); + } + }); + }); +} + +module.exports = getCartDiscount; \ No newline at end of file diff --git a/src/nano.js b/src/nano.js new file mode 100644 index 0000000..2012aaa --- /dev/null +++ b/src/nano.js @@ -0,0 +1,6 @@ +function nano() { + var hrTime = process.hrtime() + return (hrTime[0] * 1000000000 + hrTime[1]).toString(36) +} + +module.exports = nano;