Initial commit, port PayPalCheckoutServerSideV2_nodejs.zip to jst_server
authorNick Downing <nick@ndcode.org>
Sun, 19 Dec 2021 02:16:07 +0000 (13:16 +1100)
committerNick Downing <nick@ndcode.org>
Sun, 19 Dec 2021 02:16:07 +0000 (13:16 +1100)
28 files changed:
.gitignore [new file with mode: 0644]
_config/config.json [new file with mode: 0644]
_config/server.jst [new file with mode: 0644]
_config/site.jst [new file with mode: 0644]
_library/basic_functions.js [new file with mode: 0644]
_library/random_number.js [new file with mode: 0644]
_orig/PayPalCheckoutServerSideV2_nodejs.zip [new file with mode: 0644]
_ssl/ca.conf [new file with mode: 0644]
_ssl/ca_cert.pem [new file with mode: 0644]
_ssl/ca_cert.srl [new file with mode: 0644]
_ssl/ca_key.pem [new file with mode: 0644]
_ssl/localhost.conf [new file with mode: 0644]
_ssl/localhost_cert.pem [new file with mode: 0644]
_ssl/localhost_cert_bundle.pem [new file with mode: 0644]
_ssl/localhost_csr.pem [new file with mode: 0644]
_ssl/localhost_ext.conf [new file with mode: 0644]
_ssl/localhost_key.pem [new file with mode: 0644]
_ssl/n.sh [new file with mode: 0755]
api/captureOrder.json.jst [new file with mode: 0644]
api/createOrder.json.jst [new file with mode: 0644]
api/getOrderDetails.json.jst [new file with mode: 0644]
api/patchOrder.json.jst [new file with mode: 0644]
img/camera.jpg [new file with mode: 0644]
index.html [new file with mode: 0644]
js/config.js [new file with mode: 0644]
package.json [new file with mode: 0644]
pages/shipping.html [new file with mode: 0644]
pages/success.html [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e0ecbc3
--- /dev/null
@@ -0,0 +1,9 @@
+.*.deps
+.*.html
+.*.json
+.*.jst
+.*.less
+.*.min
+.*.svg
+/node_modules
+/pnpm-lock.yaml
diff --git a/_config/config.json b/_config/config.json
new file mode 100644 (file)
index 0000000..4d54786
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "environment": "sandbox",
+    "sandbox_client_id": "Aa2IfcoEvHnfJRnVQLSFrSs3SmTTkv5N1weMEL66ysqYIeHfAqXpDVkjOv3vLhkhbP4eKB6MpRlQIcJw",
+    "sandbox_secret": "EF6l6PDQJEZbdKTeg35pbBSft6WRdALQC3Xrl5vvG0VNgBUehQyTCQ09QdIauxoccvJOf5Aoy-OGsH5G",
+    "production_client_id": "",
+    "production_secret": ""
+}
\ No newline at end of file
diff --git a/_config/server.jst b/_config/server.jst
new file mode 100644 (file)
index 0000000..e6ebffc
--- /dev/null
@@ -0,0 +1,16 @@
+return async (resources, prev_server) => new _jst_server.Server(
+  resources,
+  {
+    hosts: {
+      'localhost': {
+        type: 'site',
+        root: '.'
+      },
+      'localhost.localdomain': {
+        type: 'redirect',
+        host: 'localhost'
+      }
+    }
+  },
+  prev_server
+)
diff --git a/_config/site.jst b/_config/site.jst
new file mode 100644 (file)
index 0000000..dd80b4d
--- /dev/null
@@ -0,0 +1,26 @@
+let CustomSite = function(resources, root, options, prev_site) {
+  if (!this instanceof CustomSite)
+    throw Error('CustomSite is a constructor')
+  _jst_server.Site.call(this, resources, root, options, prev_site)
+}
+
+CustomSite.prototype = Object.create(_jst_server.Site.prototype)
+
+CustomSite.prototype.respond = async function(env) {
+  if (
+    env.parsed_url.pathname === '/node_modules' ||
+    env.parsed_url.pathname.slice(0, 14) === '/node_modules/' ||
+    env.parsed_url.pathname === '/package.json'
+  ) {
+    this.die(env, `banned file ${env.parsed_url.pathname}`)
+    return
+  }
+  return /*await*/ _jst_server.Site.prototype.respond.call(this, env)
+}
+
+return async (resources, root, prev_site) => new CustomSite(
+  resources,
+  root,
+  {},
+  prev_site
+)
diff --git a/_library/basic_functions.js b/_library/basic_functions.js
new file mode 100644 (file)
index 0000000..b6ac0a9
--- /dev/null
@@ -0,0 +1,176 @@
+const https = require('https');
+const config = require('../_config/config.json');
+const get_oauth = () => {
+  return new Promise(resolve => {
+      
+      const data = 'grant_type=client_credentials';
+
+      const options = {
+        hostname: 'api.' + (config.environment === 'sandbox' ? 'sandbox.' : '') + 'paypal.com',
+        port: 443,
+        path: '/v1/oauth2/token',
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+          'Content-Length': data.length,
+          'Authorization': 'Basic ' + Buffer.from((config.environment === 'sandbox' ? config.sandbox_client_id : config.production_client_id) + ':' + (config.environment === 'sandbox' ? config.sandbox_secret : config.production_secret)).toString('base64')
+        }
+      }
+      const my_callback = function(res){
+
+        let str='';
+
+        res.on('data',function(chunk){
+            str+=chunk;
+        });
+
+        res.on('end',function(){
+            obj=JSON.parse(str);
+            resolve(obj);
+        });
+      }
+      let request = https.request(options, my_callback);
+      request.write(data);
+      request.end();
+
+    });
+};
+const get_access_token = () => {
+  return new Promise(resolve => {
+    get_oauth().then((response) => {
+      resolve(response.access_token);
+    });
+  });
+};
+const create_order = (item_obj) => {
+  return new Promise(resolve => {
+    get_access_token().then((access_token) => {
+  
+    const options = {
+      hostname: 'api.' + (config.environment === 'sandbox' ? 'sandbox.' : '') + 'paypal.com',
+      port: 443,
+      path: '/v2/checkout/orders',
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + access_token
+      }
+    }
+    const my_callback = function(res){
+
+      let str='';
+
+      res.on('data',function(chunk){
+          str+=chunk;
+      });
+
+      res.on('end',function(){
+          obj=JSON.parse(str);
+          resolve(obj);
+      });
+    }
+    let request = https.request(options,my_callback);
+    request.write(JSON.stringify(item_obj));
+    request.end();
+
+  });
+
+  });
+};
+const get_order_details = (order_id) => {
+  return new Promise(resolve => {
+    get_access_token().then((access_token) => {
+  
+    const options = {
+      hostname: 'api.' + (config.environment === 'sandbox' ? 'sandbox.' : '') + 'paypal.com',
+      port: 443,
+      path: '/v2/checkout/orders/' + order_id,
+      method: 'GET',
+      headers: {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + access_token
+      }
+    }
+    const my_callback = function(res){
+
+      let str='';
+
+      res.on('data',function(chunk){
+          str+=chunk;
+      });
+
+      res.on('end',function(){
+          obj=JSON.parse(str);
+          resolve(obj);
+      });
+    }
+    let request = https.request(options,my_callback);
+    request.end();
+
+  });
+
+  });
+};
+const patch_order_details = (new_order_details) => {
+  return new Promise((resolve, reject) => {
+    get_access_token().then((access_token) => {
+      patch_details = new_order_details.patch_details;
+      order_id = new_order_details.order_id;
+      const options = {
+        hostname: 'api.' + (config.environment === 'sandbox' ? 'sandbox.' : '') + 'paypal.com',
+        port: 443,
+        path: '/v2/checkout/orders/' + order_id,
+        method: 'PATCH',
+        headers: {
+          'Content-Type': 'application/json',
+          'Authorization': 'Bearer ' + access_token
+        }
+      }
+      let request = https.request(options, function (response) {
+        resolve(response.statusCode);
+      });
+      request.write(JSON.stringify(patch_details));
+      request.end();
+  });
+});
+
+};
+const capture_order = (order_id) => {
+  return new Promise(resolve => {
+    get_access_token().then((access_token) => {
+    const options = {
+      hostname: 'api.' + (config.environment === 'sandbox' ? 'sandbox.' : '') + 'paypal.com',
+      port: 443,
+      path: '/v2/checkout/orders/' + order_id + '/capture',
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + access_token,
+        'return': 'representation',
+        'PayPal-Partner-Attribution-Id': 'PP-DemoPortal-Checkout-NodeJS-SDK'
+      }
+    }
+    const my_callback = function(res){
+      let str='';
+      res.on('data',function(chunk){
+          str+=chunk;
+      });
+      res.on('end',function(){
+          obj=JSON.parse(str);
+          resolve(obj);
+      });
+    }
+    let request = https.request(options,my_callback);
+    request.end();
+    });
+  });
+};
+
+module.exports = {
+  get_access_token: get_access_token,
+  get_oauth: get_oauth,
+  create_order: create_order,
+  get_order_details: get_order_details,
+  patch_order_details: patch_order_details,
+  capture_order: capture_order
+};
diff --git a/_library/random_number.js b/_library/random_number.js
new file mode 100644 (file)
index 0000000..a446935
--- /dev/null
@@ -0,0 +1 @@
+module.exports = () => Math.floor(100000 + Math.random() * 900000)
diff --git a/_orig/PayPalCheckoutServerSideV2_nodejs.zip b/_orig/PayPalCheckoutServerSideV2_nodejs.zip
new file mode 100644 (file)
index 0000000..5e2c706
Binary files /dev/null and b/_orig/PayPalCheckoutServerSideV2_nodejs.zip differ
diff --git a/_ssl/ca.conf b/_ssl/ca.conf
new file mode 100644 (file)
index 0000000..d0d9a5f
--- /dev/null
@@ -0,0 +1,25 @@
+[ ca ]
+default_ca      = CA_default            # The default ca section
+
+[ CA_default ]
+
+#dir            = ./demoCA              # top dir
+#database       = $dir/index.txt        # index file.
+#new_certs_dir  = $dir/newcerts         # new certs dir
+#
+#certificate    = $dir/cacert.pem       # The CA cert
+#serial         = $dir/serial           # serial no file
+#private_key    = $dir/private/cakey.pem# CA private key
+#RANDFILE       = $dir/private/.rand    # random number file
+#
+default_days   = 365                   # how long to certify for
+#default_crl_days= 30                   # how long before next CRL
+#default_md     = md5                   # md to use
+#
+#policy         = policy_any            # default policy
+#email_in_dn    = no                    # Don't add the email into cert DN
+#
+#name_opt       = ca_default            # Subject name display option
+#cert_opt       = ca_default            # Certificate display option
+#copy_extensions = none
+copy_extensions = copyall
diff --git a/_ssl/ca_cert.pem b/_ssl/ca_cert.pem
new file mode 100644 (file)
index 0000000..74cb479
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUFREyB8FfpEHUio8HY7b0d7FaKdcwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDgwNzU0MTNaFw0yMTEy
+MDgwNzU0MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDLY+eBF29tRNWwNb4uDdWUAs+7jDybeos94E8tgPKO
+JEc2flBwolka9GU357x9J5v2KCnOuCkKArhL2tmkms5f2F4Eezb/l9DnryUdckqC
+MI2bwAz/a/T+3HLjqI4l5mawE21JXNggqAccixRUY/gK3+V7M+zOdpwRc4v7Ca0R
+sBwwfve8Ws8xqKryVFvzJM0QXiAjZvxMpLygdbnmjCKKVCY00Mfh20KqHYBMIMHa
+M0RhdRrvi8/2oPfRcMByjZdjn8BQ+yUwQXbzP2MC+iq87lLuxUxEYGaQU4nzIMH1
+7Ga05t6zhF41jqDnrbjKgbgxPGNNxvau/rrpCnr++NQ/AgMBAAGjUzBRMB0GA1Ud
+DgQWBBRBtnDR6bDQjBctACDXe2FTsASBEzAfBgNVHSMEGDAWgBRBtnDR6bDQjBct
+ACDXe2FTsASBEzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCM
+H2UzfYIphO/16z8SO0thdjexjfad0bMIZC39kD92ILHvxk68D0uel4q5Rp3+F66j
+J7OupHIzEGSB23suw6n/cDrIJOa5VU9geaUZBVIH8GSy6nQTnkukiX92OTS11ecW
+qABoACcptQz4oC3E68K6BYre4/QamJDAqdP4aNVbka040XTiWzvfRlOVeWz+gZTa
+zObQiEzj1SwsYgcNSkrquZ/7vcEBaCncxXD8oRxqfEgNA6BRqUqnKCUSUmk/ed3b
+hRSpHURjJGntHAzzFzaCC5dfMBEnAszfoOdLua/BV/JZcB7t4YIcIYMKdSYxpvoO
+Nr3opPQhzBXur01KZuUA
+-----END CERTIFICATE-----
diff --git a/_ssl/ca_cert.srl b/_ssl/ca_cert.srl
new file mode 100644 (file)
index 0000000..b882cea
--- /dev/null
@@ -0,0 +1 @@
+D29D1C6A226490AD
diff --git a/_ssl/ca_key.pem b/_ssl/ca_key.pem
new file mode 100644 (file)
index 0000000..e65d8ce
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAy2PngRdvbUTVsDW+Lg3VlALPu4w8m3qLPeBPLYDyjiRHNn5Q
+cKJZGvRlN+e8fSeb9igpzrgpCgK4S9rZpJrOX9heBHs2/5fQ568lHXJKgjCNm8AM
+/2v0/txy46iOJeZmsBNtSVzYIKgHHIsUVGP4Ct/lezPsznacEXOL+wmtEbAcMH73
+vFrPMaiq8lRb8yTNEF4gI2b8TKS8oHW55owiilQmNNDH4dtCqh2ATCDB2jNEYXUa
+74vP9qD30XDAco2XY5/AUPslMEF28z9jAvoqvO5S7sVMRGBmkFOJ8yDB9exmtObe
+s4ReNY6g5624yoG4MTxjTcb2rv666Qp6/vjUPwIDAQABAoIBAA3vOEsl2eJ4ltNN
+u0vYcsuDLcxBnV1hleyVU5dggD2wypg3KzesR8KK/+xGmilQ72R79/FLuLQQ36OC
+yOp4GK+EWVyhPHFia1OUMkzEKcqlnO4QyFMviEd1vwFN6P87u8lQa2pdTAlguawA
+81Gcz7+e+0/njM/QXHztl6eJUCwn7zEbG8+we0g8GkLILOBA3Tuy5Vu+ox1LQZQp
+WPqIMhm0GXPOVRKBr0mPVWtJvyAYvTX13GqNM1YFbElkbnkbsTaJhg/L3J9UJNsm
+BbOg0oUdq1yWoy7cjOXiq3aG9rNws9PBXbbDB/G67aae/1SJ7eBTV/kCEuYh0YDx
+MSn5xYECgYEA/uZueW4dFTKMBebNFvJreQei1ecaGwEFckUYD5u02VugtYc9inQ2
+sK7ENq/CsA10WKBxV84lIdiG5yXjG1RwCLvTV6YBP5uL9dVMln25x6ZDUxOlYzuZ
+OZUcabR3Y3Ar349vDV+vFle47J0YW8H89O5YlVxvLfZ+CFFFSBSMRCECgYEAzESS
+5IvNH1RUCR0tqRkHA0SXMiNxF85lFbUrbS2yMI60LCaTE2q/uK5008f5laD3LYKU
+Gq7SVP1U4z0+g9jGa0rvYd1h3o6fJMrFBiBN1ZRRzMFaiQxxNbyjCV7LR86m6aSo
+pz4f7k8EmzrsmZw7MF/l9Dy3AWHpg1cwYdR6DF8CgYB17kS0d6aK9Rzlahf/At+I
+WmkTD937Gmjbqm3sYry0R3k+IzjswsG+0szDBGRNsZvfmTN3TU/OrfAUJ2pAbbt7
+vvKTvaEcPanubeYGRlrarOi/GfrNw3grtPo1SaJm5jHWN/VIObm225UaG8B2S3Tu
+GQvw5pglqsI6tOcZ5y/SIQKBgBVJuD1VTIVNVoy0m8OZth9jEJbLFsgyXFqMzP/N
+2VoyJRjM1FsbrutiUw6XMq2jXt9BUooNWiI9XJFqGo/HEbaw0o3ScpatKmy9LRdc
+WoA9uuCp7fOGdm3xQNSDKpBLOx3yaRk04kMFvScoVuwTWh/Kfr6bbT8Zoypq9cHc
+UPPlAoGBAIEtVPhH6R51/HREPMWXWgwZujkeD0VQX+hNP8VOgSlts9L4bDn7res5
+DXHDXU3Q+vY+86qDvJgJaOdmt7IoNXgRlU9tUZP4MD8YG32Xjn3dnB5e/3SmBJwD
+NV4mVx+7X6me5rb4NdpMcSzulVXy3SfnQTaDsNghpcTqOXLFI0ti
+-----END RSA PRIVATE KEY-----
diff --git a/_ssl/localhost.conf b/_ssl/localhost.conf
new file mode 100644 (file)
index 0000000..fa3ef54
--- /dev/null
@@ -0,0 +1,66 @@
+# The main section is named req because the command we are using is req
+# (openssl req ...)
+[ req ]
+## This specifies the default key size in bits. If not specified then 512 is
+## used. It is used if the -new option is used. It can be overridden by using
+## the -newkey option. 
+#default_bits = 2048
+#
+## This is the default filename to write a private key to. If not specified the
+## key is written to standard output. This can be overridden by the -keyout
+## option.
+#default_keyfile = oats.key
+#
+## If this is set to no then if a private key is generated it is not encrypted.
+## This is equivalent to the -nodes command line option. For compatibility
+## encrypt_rsa_key is an equivalent option. 
+#encrypt_key = no
+
+# This option specifies the digest algorithm to use. Possible values include
+# md5 sha1 mdc2. If not present then MD5 is used. This option can be overridden
+# on the command line.
+default_md = sha1
+
+# if set to the value no this disables prompting of certificate fields and just
+# takes values from the config file directly. It also changes the expected
+# format of the distinguished_name and attributes sections.
+prompt = no
+
+# if set to the value yes then field values to be interpreted as UTF8 strings,
+# by default they are interpreted as ASCII. This means that the field values,
+# whether prompted from a terminal or obtained from a configuration file, must
+# be valid UTF8 strings.
+utf8 = yes
+
+# This specifies the section containing the distinguished name fields to
+# prompt for when generating a certificate or certificate request.
+distinguished_name = my_req_distinguished_name
+
+# this specifies the configuration file section containing a list of extensions
+# to add to the certificate request. It can be overridden by the -reqexts
+# command line switch. See the x509v3_config(5) manual page for details of the
+# extension section format.
+req_extensions = my_extensions
+
+[ my_req_distinguished_name ]
+C = PT
+ST = Lisboa
+L = Lisboa
+O  = Oats In The Water
+#CN = *.oats.org
+CN = localhost
+
+[ my_extensions ]
+basicConstraints=CA:FALSE
+subjectAltName=@my_subject_alt_names
+subjectKeyIdentifier = hash
+
+[ my_subject_alt_names ]
+#DNS.1 = *.oats.org
+#DNS.2 = *.oats.net
+#DNS.3 = *.oats.in
+#DNS.4 = oats.org
+#DNS.5 = oats.net
+#DNS.6 = oats.in
+DNS.1 = localhost
+DNS.2 = localhost.localdomain
diff --git a/_ssl/localhost_cert.pem b/_ssl/localhost_cert.pem
new file mode 100644 (file)
index 0000000..13a969f
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgDCCAmigAwIBAgIJANKdHGoiZJCtMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMjExMTA4MDc1NDEzWhcNMjExMjA4MDc1NDEzWjBf
+MQswCQYDVQQGEwJQVDEPMA0GA1UECAwGTGlzYm9hMQ8wDQYDVQQHDAZMaXNib2Ex
+GjAYBgNVBAoMEU9hdHMgSW4gVGhlIFdhdGVyMRIwEAYDVQQDDAlsb2NhbGhvc3Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDJLav5I1Dxx9Lzv7DyHig
+ApIkm9U8tcRKuM3Q4q3xj8P2VmSHVHZ0FfjKs/NZ8hLi3go+EzGolmiRKPA7V9UQ
+Zgm5MmC6tewiol76lsjehUKuG+DmT3n79xSHsj5PC6wkE/IkifcQIeGVJWgQbRR4
+JSGaX4tmmpxNrDDgpVnaXEO8I5KR/jvniAn2DoTKlY5XSIUXlbZQPHdlLdLUU7in
+5M19b9qnIdJRxa4mcOWNtsUHU16SJZAmQBmg4vAhMc4XyJ23YE8cNxUxusKrhoUB
+sWmvjGgbbaYgtwBmCFQNSFiJhyzgg6sdUot7wVXDkXBTJPQRQO82CJyw6YoDnTCL
+AgMBAAGjWTBXMAkGA1UdEwQCMAAwKwYDVR0RBCQwIoIJbG9jYWxob3N0ghVsb2Nh
+bGhvc3QubG9jYWxkb21haW4wHQYDVR0OBBYEFPKfGhTkosDI90l245Si/Ce/CLr/
+MA0GCSqGSIb3DQEBCwUAA4IBAQCMjFo4qbgRHefLMc5yc53qn9OVa693lR9RQb+n
+HgVHUuhPRQfNr6WgbfiWjpK5r2RU7Tj7GsueNZlK+tdJxXIvo29yjrdFZ4uUfCMB
+Rr9VrjdBhv5/MLuLLnNGuC4LxNaZaZZEPF5DtVh5FZajisgB4jYFOEJNvltWgBca
+pR469W8qte2NYprjni1lzMlr0cQBMFt00jt5ZU8u7oeFt7/lzpiStA9W5FIv7DZV
+FoEzq51uwGK6FvRhil8x9pr1GzSrZdLfsEELNRZNqLG8rRhcKAUsMER+lS6LtRdJ
+ORQ3LnfodE2bCNO8MMx0A5ZA1CGlMOOE5twSlYLgS/hvkK6y
+-----END CERTIFICATE-----
diff --git a/_ssl/localhost_cert_bundle.pem b/_ssl/localhost_cert_bundle.pem
new file mode 100644 (file)
index 0000000..a1ed9bc
--- /dev/null
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIDgDCCAmigAwIBAgIJANKdHGoiZJCtMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMjExMTA4MDc1NDEzWhcNMjExMjA4MDc1NDEzWjBf
+MQswCQYDVQQGEwJQVDEPMA0GA1UECAwGTGlzYm9hMQ8wDQYDVQQHDAZMaXNib2Ex
+GjAYBgNVBAoMEU9hdHMgSW4gVGhlIFdhdGVyMRIwEAYDVQQDDAlsb2NhbGhvc3Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDJLav5I1Dxx9Lzv7DyHig
+ApIkm9U8tcRKuM3Q4q3xj8P2VmSHVHZ0FfjKs/NZ8hLi3go+EzGolmiRKPA7V9UQ
+Zgm5MmC6tewiol76lsjehUKuG+DmT3n79xSHsj5PC6wkE/IkifcQIeGVJWgQbRR4
+JSGaX4tmmpxNrDDgpVnaXEO8I5KR/jvniAn2DoTKlY5XSIUXlbZQPHdlLdLUU7in
+5M19b9qnIdJRxa4mcOWNtsUHU16SJZAmQBmg4vAhMc4XyJ23YE8cNxUxusKrhoUB
+sWmvjGgbbaYgtwBmCFQNSFiJhyzgg6sdUot7wVXDkXBTJPQRQO82CJyw6YoDnTCL
+AgMBAAGjWTBXMAkGA1UdEwQCMAAwKwYDVR0RBCQwIoIJbG9jYWxob3N0ghVsb2Nh
+bGhvc3QubG9jYWxkb21haW4wHQYDVR0OBBYEFPKfGhTkosDI90l245Si/Ce/CLr/
+MA0GCSqGSIb3DQEBCwUAA4IBAQCMjFo4qbgRHefLMc5yc53qn9OVa693lR9RQb+n
+HgVHUuhPRQfNr6WgbfiWjpK5r2RU7Tj7GsueNZlK+tdJxXIvo29yjrdFZ4uUfCMB
+Rr9VrjdBhv5/MLuLLnNGuC4LxNaZaZZEPF5DtVh5FZajisgB4jYFOEJNvltWgBca
+pR469W8qte2NYprjni1lzMlr0cQBMFt00jt5ZU8u7oeFt7/lzpiStA9W5FIv7DZV
+FoEzq51uwGK6FvRhil8x9pr1GzSrZdLfsEELNRZNqLG8rRhcKAUsMER+lS6LtRdJ
+ORQ3LnfodE2bCNO8MMx0A5ZA1CGlMOOE5twSlYLgS/hvkK6y
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUFREyB8FfpEHUio8HY7b0d7FaKdcwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDgwNzU0MTNaFw0yMTEy
+MDgwNzU0MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDLY+eBF29tRNWwNb4uDdWUAs+7jDybeos94E8tgPKO
+JEc2flBwolka9GU357x9J5v2KCnOuCkKArhL2tmkms5f2F4Eezb/l9DnryUdckqC
+MI2bwAz/a/T+3HLjqI4l5mawE21JXNggqAccixRUY/gK3+V7M+zOdpwRc4v7Ca0R
+sBwwfve8Ws8xqKryVFvzJM0QXiAjZvxMpLygdbnmjCKKVCY00Mfh20KqHYBMIMHa
+M0RhdRrvi8/2oPfRcMByjZdjn8BQ+yUwQXbzP2MC+iq87lLuxUxEYGaQU4nzIMH1
+7Ga05t6zhF41jqDnrbjKgbgxPGNNxvau/rrpCnr++NQ/AgMBAAGjUzBRMB0GA1Ud
+DgQWBBRBtnDR6bDQjBctACDXe2FTsASBEzAfBgNVHSMEGDAWgBRBtnDR6bDQjBct
+ACDXe2FTsASBEzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCM
+H2UzfYIphO/16z8SO0thdjexjfad0bMIZC39kD92ILHvxk68D0uel4q5Rp3+F66j
+J7OupHIzEGSB23suw6n/cDrIJOa5VU9geaUZBVIH8GSy6nQTnkukiX92OTS11ecW
+qABoACcptQz4oC3E68K6BYre4/QamJDAqdP4aNVbka040XTiWzvfRlOVeWz+gZTa
+zObQiEzj1SwsYgcNSkrquZ/7vcEBaCncxXD8oRxqfEgNA6BRqUqnKCUSUmk/ed3b
+hRSpHURjJGntHAzzFzaCC5dfMBEnAszfoOdLua/BV/JZcB7t4YIcIYMKdSYxpvoO
+Nr3opPQhzBXur01KZuUA
+-----END CERTIFICATE-----
diff --git a/_ssl/localhost_csr.pem b/_ssl/localhost_csr.pem
new file mode 100644 (file)
index 0000000..b28c691
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIDDDCCAfQCAQAwXzELMAkGA1UEBhMCUFQxDzANBgNVBAgMBkxpc2JvYTEPMA0G
+A1UEBwwGTGlzYm9hMRowGAYDVQQKDBFPYXRzIEluIFRoZSBXYXRlcjESMBAGA1UE
+AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyS2
+r+SNQ8cfS87+w8h4oAKSJJvVPLXESrjN0OKt8Y/D9lZkh1R2dBX4yrPzWfIS4t4K
+PhMxqJZokSjwO1fVEGYJuTJgurXsIqJe+pbI3oVCrhvg5k95+/cUh7I+TwusJBPy
+JIn3ECHhlSVoEG0UeCUhml+LZpqcTaww4KVZ2lxDvCOSkf4754gJ9g6EypWOV0iF
+F5W2UDx3ZS3S1FO4p+TNfW/apyHSUcWuJnDljbbFB1NekiWQJkAZoOLwITHOF8id
+t2BPHDcVMbrCq4aFAbFpr4xoG22mILcAZghUDUhYiYcs4IOrHVKLe8FVw5FwUyT0
+EUDvNgicsOmKA50wiwIDAQABoGgwZgYJKoZIhvcNAQkOMVkwVzAJBgNVHRMEAjAA
+MCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluMB0G
+A1UdDgQWBBTynxoU5KLAyPdJduOUovwnvwi6/zANBgkqhkiG9w0BAQUFAAOCAQEA
+L4XofNRjzahIDCj/pHVnsaXtioidv5Hp0vE+9LPhzZz/bH/e7s8lJnTalEkPNUxD
+FPwiRar6MILQwn5N1b+kToSoiYDsCL77Y5WSlhcwe7gLwgDTlNwu2H030BZEr0ve
+AEAems45TJk8o3kC5s3dt0KaGbRikub1HMTpFrlQUBLbO848t6qXcZCVjoAWYKlp
+jaYn7r3bWVyZ2W2oIlQ19Tbxz+kG81Vxrg1FtAo7aBHTaOvEskgnEQ2Emc0mbLoe
+YoT0Gg7Vrurl+T5gazmV1WMKszSjP8NKhTUcMESiRIUsmQczWiDBONiH9PGmGKHI
+7aEBzQIK7m3goyS3I4q6nw==
+-----END CERTIFICATE REQUEST-----
diff --git a/_ssl/localhost_ext.conf b/_ssl/localhost_ext.conf
new file mode 100644 (file)
index 0000000..d5005dc
--- /dev/null
@@ -0,0 +1,13 @@
+basicConstraints=CA:FALSE
+subjectAltName=@my_subject_alt_names
+subjectKeyIdentifier = hash
+
+[ my_subject_alt_names ]
+#DNS.1 = *.oats.org
+#DNS.2 = *.oats.net
+#DNS.3 = *.oats.in
+#DNS.4 = oats.org
+#DNS.5 = oats.net
+#DNS.6 = oats.in
+DNS.1 = localhost
+DNS.2 = localhost.localdomain
diff --git a/_ssl/localhost_key.pem b/_ssl/localhost_key.pem
new file mode 100644 (file)
index 0000000..6c94353
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwyS2r+SNQ8cfS87+w8h4oAKSJJvVPLXESrjN0OKt8Y/D9lZk
+h1R2dBX4yrPzWfIS4t4KPhMxqJZokSjwO1fVEGYJuTJgurXsIqJe+pbI3oVCrhvg
+5k95+/cUh7I+TwusJBPyJIn3ECHhlSVoEG0UeCUhml+LZpqcTaww4KVZ2lxDvCOS
+kf4754gJ9g6EypWOV0iFF5W2UDx3ZS3S1FO4p+TNfW/apyHSUcWuJnDljbbFB1Ne
+kiWQJkAZoOLwITHOF8idt2BPHDcVMbrCq4aFAbFpr4xoG22mILcAZghUDUhYiYcs
+4IOrHVKLe8FVw5FwUyT0EUDvNgicsOmKA50wiwIDAQABAoIBAEuOiImcJbIrhAuX
+Lv9hPIs/05QHHk4uVr1TxqTtT8orDwXvN2dKpb6Wz0i02jFmUDe1HyQfzGdpLT2f
+Kzze6ik6SOODBP7l93MFiV7fSREXadT+CFtERIfxh+pucj+q1lD1xBivrpB5fd2A
+qUVK5tUKE3OxMMlebcyJMjeY7ixkRVgjROFeXs0t50xitzn/wPN0dVPZ6JFCPgRD
+fc72E1K+DmfrzFZ2+rzqoVzAE7OSYQns3r2jNXlCEVw2jXsmnIRIi9wejbRthjbx
+gA1IdenZcf4FkOMTTFuWGynT5tfF3cVzy5QtYemGZWSK4LNuWaTSzZCk3/sCxWk5
+Vbz/AdkCgYEA8gc2WEfbk5kVhU7UV5SRDRFtGuS8fmZ0RnqFdx6qy2EWYnwYXgzT
+cZc6TiEuJ5getmEVsqzxFm3gWMyD0bEhY0jqoMVKMM70QQOQOZOB4lykXmpRDKny
+/DU7/0bp7sBzFCKCWISCjGKWc98KWy24ZdKl8ZAr3zjDxE5UYvquKIcCgYEAzmie
+r+1Y7DUtniogqpDLr6NnuvH52d1s4DEsHZQ6KRWE82d/X+2YU2R7/0QwfLBvcbT7
+OxlQP5rRoL6pX+ViZBmky0CSc/HYZZ+8uGwFc1X46zGfuvTSWaAStdIFPOX2O0JF
+LLyF5/0/CK56agL3YxGREx2iQdauqTsOyr2LLN0CgYEAzYEgPN9u6ymd415m4KKO
+c1krmh1Ei9M0wa9A6j9I6H3cgu05x4n+c8HjyPlVdlstINDYmqlL4C6VwvCMsR1E
+60e7qZ07fKwNK7L54FmGfI0LJ/wAK8+WOV5+PiiZc5dHX35ZzQ1eyBLiCysEYR02
+KIcvTiiLh/NsDqAv+Qc/n7sCgYAup0z+3LnVoeturXz2sIWpbFi804ayrK64OcUL
+5n4C3T9QuNr8drqQVs0EFIiVFlgKLmr+n6kYx0iMeavU5gcIMxehbTXtCQPtbF5+
+nMPantsFZhEBc+a7pUe1WwQeEKhIGqGCDBaEKiR+NNmsboE2HjlZRcBv+zM9QwED
+6DW17QKBgQCRZfGUWasWXW6s9aZ1FgLPAXz/M9BLZjnx627KvbF4C+ZwfszntJxn
+1A74mhHWJyKbt8YzfOTsTj40lp/npSpgjRQmUoqThx8d2cA41ALo0ktk6E9Zpp1j
+phi0woa+TdEpqE2Jb8hJcyRKGCOVOKL/bnZL/QVgMebhg02n//wa9w==
+-----END RSA PRIVATE KEY-----
diff --git a/_ssl/n.sh b/_ssl/n.sh
new file mode 100755 (executable)
index 0000000..9fbf379
--- /dev/null
+++ b/_ssl/n.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# see https://gist.github.com/Soarez/9688998
+
+# note that the CSR contains X509 extensions, particularly the SAN which
+# is mandatory for getting Google Chrome to accept the certificate, but
+# unfortunately "openssl x509" will strip extensions -- this can be fixed
+# by using the "openssl ca" command with "extensions = copyall" (or in
+# the normal case filters would be specified on what extensions to copy),
+# but the "openssl ca" command is very complicated and requires a directory
+# structure to be set up, so we fake it with the "localhost_ext.conf" file
+
+# generate a key for the subject
+#openssl genrsa -out localhost_key.pem 2048
+
+# generate a CSR
+openssl req -new -key localhost_key.pem -out localhost_csr.pem -config localhost.conf
+
+# generate a key for the CA
+#openssl genrsa -out ca_key.pem 2048
+
+# generate a self signed certificate for the CA
+openssl req -new -x509 -key ca_key.pem -out ca_cert.pem
+
+# sign the certificate
+#openssl x509 -req -in localhost_csr.pem -extfile localhost_ext.conf -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out localhost_cert.pem
+openssl x509 -req -in localhost_csr.pem -extfile localhost_ext.conf -CA ca_cert.pem -CAkey ca_key.pem -CAserial ca_cert.srl -out localhost_cert.pem
+
+# resolve problems with not including the signing chain (can improve this?)
+cat localhost_cert.pem ca_cert.pem > localhost_cert_bundle.pem
diff --git a/api/captureOrder.json.jst b/api/captureOrder.json.jst
new file mode 100644 (file)
index 0000000..3186882
--- /dev/null
@@ -0,0 +1,15 @@
+let basic_functions = require('../_library/basic_functions.js')
+let cookie = require('cookie')
+
+return async env => {
+  let cookies = cookie.parse(env.request.headers.cookie || '')
+  let response = await basic_functions.capture_order(cookies.order_id)
+  console.log('capture_order()', response)
+
+  env.site.serve(
+    env,
+    200,
+    Buffer.from(JSON.stringify(response)),
+    'getOrderDetails.json.jst'
+  )
+}
diff --git a/api/createOrder.json.jst b/api/createOrder.json.jst
new file mode 100644 (file)
index 0000000..4cdb0fb
--- /dev/null
@@ -0,0 +1,117 @@
+let basic_functions = require('../_library/basic_functions.js')
+let querystring = require('querystring')
+let random_number = require('../_library/random_number.js')
+let stream_buffers = require('stream-buffers')
+let XDate = require('xdate')
+
+return async env => {
+  let response = null;
+  if (env.request.method === 'POST') {
+    let write_stream = new stream_buffers.WritableStreamBuffer()
+    let data = new Promise(
+      (resolve, reject) => {
+        write_stream.
+        on('finish', () => {resolve(write_stream.getContents())}).
+        on('error', () => {reject()})
+      }
+    )
+    env.request.pipe(write_stream)
+    let query = querystring.parse((await data).toString())
+    console.log('received createOrder form:', JSON.stringify(query))
+
+    //Build the PayPal object to create order and populate with POST params from website
+    let item_obj = {
+      "intent" : "CAPTURE",
+      "application_context" : {
+        "return_url" : query.return_url,
+        "cancel_url" : query.cancel_url
+      },
+      "purchase_units" : [ 
+        {
+          "reference_id" : "PU1",
+          "description" : "Camera Shop",
+          "invoice_id" : "INV-CameraShop-" + random_number(),
+          "custom_id" : "CUST-CameraShop",
+          "amount" : {
+            "currency_code" : query.currency,
+            "value" : query.total_amt,
+            "breakdown" : {
+              "item_total" : {
+                "currency_code" : query.currency,
+                "value" : query.item_amt
+              },
+              "shipping" : {
+                "currency_code" : query.currency,
+                "value" : query.shipping_amt
+              },
+              "tax_total" : {
+                "currency_code" : query.currency,
+                "value" : query.tax_amt
+              },
+              "handling" : {
+                "currency_code" : query.currency,
+                "value" : query.handling_fee
+              },
+              "shipping_discount" : {
+                "currency_code" : query.currency,
+                "value" : query.shipping_discount
+              },
+              "insurance" : {
+                "currency_code" : query.currency,
+                "value" : query.insurance_fee
+              }
+            }
+          },
+          "items" : [{
+            "name" : "DSLR Camera",
+            "description" : "Black Camera - Digital SLR",
+            "sku" : "sku01",
+            "unit_amount" : {
+              "currency_code" : query.currency,
+              "value" : query.item_amt
+            },
+            "quantity" : "1",
+            "category" : "PHYSICAL_GOODS"
+          }]
+        }
+      ]
+    };
+    //If order details contain shipping, append shipping and preferences
+    if (query.line1 !== undefined) {
+      item_obj.application_context.shipping_preference = "SET_PROVIDED_ADDRESS";
+      item_obj.application_context.user_action = "PAY_NOW";
+      item_obj.purchase_units[0].shipping = {
+        "address" : {
+        "address_line_1": query.line1,
+        "address_line_2": query.line2,
+        "admin_area_2": query.city,
+        "admin_area_1": query.state,
+        "postal_code": query.zip,
+        "country_code": query.countrySelect
+        }
+      };
+    }
+    console.log('item_obj', item_obj)
+    response = await basic_functions.create_order(item_obj)
+    console.log('create_order()', response)
+
+    //sess.order_id = response.id;
+    let expires = new XDate()
+    expires.addDays(1)
+    env.response.setHeader(
+      'Set-Cookie',
+      'order_id=' +
+      response.id +
+      '; expires=' +
+      expires +
+      '; path=/;'
+    )
+  }
+
+  env.site.serve(
+    env,
+    200,
+    Buffer.from(JSON.stringify(response)),
+    'createOrder.json.jst'
+  )
+}
diff --git a/api/getOrderDetails.json.jst b/api/getOrderDetails.json.jst
new file mode 100644 (file)
index 0000000..b8cfaa9
--- /dev/null
@@ -0,0 +1,15 @@
+let basic_functions = require('../_library/basic_functions.js')
+let cookie = require('cookie')
+
+return async env => {
+  let cookies = cookie.parse(env.request.headers.cookie || '')
+  let response = await basic_functions.get_order_details(cookies.order_id)
+  console.log('get_order_details()', response)
+
+  env.site.serve(
+    env,
+    200,
+    Buffer.from(JSON.stringify(response)),
+    'getOrderDetails.json.jst'
+  )
+}
diff --git a/api/patchOrder.json.jst b/api/patchOrder.json.jst
new file mode 100644 (file)
index 0000000..0094534
--- /dev/null
@@ -0,0 +1,73 @@
+let basic_functions = require('../_library/basic_functions.js')
+let cookie = require('cookie')
+let querystring = require('querystring')
+let random_number = require('../_library/random_number.js')
+let stream_buffers = require('stream-buffers')
+let XDate = require('xdate')
+
+return async env => {
+  let response = null;
+  if (env.request.method === 'POST') {
+    let write_stream = new stream_buffers.WritableStreamBuffer()
+    let data = new Promise(
+      (resolve, reject) => {
+        write_stream.
+        on('finish', () => {resolve(write_stream.getContents())}).
+        on('error', () => {reject()})
+      }
+    )
+    env.request.pipe(write_stream)
+    let query = querystring.parse((await data).toString())
+    console.log('received patchOrder form:', JSON.stringify(query))
+
+    let cookies = cookie.parse(env.request.headers.cookie || '')
+    let new_order_details = {
+      "patch_details": [{
+        "op" : "replace",
+        "path" : "/purchase_units/@reference_id==\'PU1\'/amount",
+        "value" : {
+          "currency_code" : query.currency,
+          "value" : parseInt(query.total_amt) + parseInt(query.updated_shipping) - parseInt(query.current_shipping),
+          "breakdown" : {
+            "item_total" : {
+              "currency_code" : query.currency,
+              "value" : query.item_amt,
+            },
+            "shipping" : {
+              "currency_code" : query.currency,
+              "value" : query.updated_shipping
+            },
+            "tax_total" : {
+              "currency_code" : query.currency,
+              "value" : query.tax_amt
+            },
+            "shipping_discount" : {
+              "currency_code" : query.currency,
+              "value" : query.shipping_discount
+            },
+            "handling" : {
+              "currency_code" : query.currency,
+              "value" : query.handling_fee
+            },
+            "insurance" : {
+              "currency_code" : query.currency,
+              "value" : query.insurance_fee
+            }
+          }
+        }       
+      }],
+      "order_id": cookies.order_id
+    };
+    response = {
+      http_code: await basic_functions.patch_order_details(new_order_details)
+    }
+    console.log('patch_order_details()', response)
+  }
+
+  env.site.serve(
+    env,
+    200,
+    Buffer.from(JSON.stringify(response)),
+    'patchOrder.json.jst'
+  )
+}
diff --git a/img/camera.jpg b/img/camera.jpg
new file mode 100644 (file)
index 0000000..acfbbf1
Binary files /dev/null and b/img/camera.jpg differ
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..d8e5cce
--- /dev/null
@@ -0,0 +1,263 @@
+<!DOCTYPE html>\r
+<html lang="en">\r
+\r
+<head>\r
+       <meta charset="utf-8" />\r
+       <meta http-equiv="x-ua-compatible" content="ie=edge" />\r
+       <meta name="viewport" content="width=device-width, initial-scale=1" />\r
+       <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">\r
+       <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>\r
+       <title>Checkout With PayPal NodeJs Demo</title>\r
+       <style>\r
+       /* http://angrytools.com/gradient/ */\r
+       \r
+       .header-bg-color {\r
+               color: white;\r
+               background: -moz-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* ff3.6+ */\r
+               background: -webkit-gradient(linear, left top, right top, color-stop(0%, #004094), color-stop(50%, #0096D9), color-stop(100%, #004094));\r
+               /* safari4+,chrome */\r
+               background: -webkit-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* safari5.1+,chrome10+ */\r
+               background: -o-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* opera 11.10+ */\r
+               background: -ms-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* ie10+ */\r
+               background: linear-gradient(90deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* w3c */\r
+               filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#004094', endColorstr='#004094', GradientType=1);\r
+               /* ie6-9 */\r
+       }\r
+       </style>\r
+</head>\r
+\r
+<body>\r
+       <div class="container">\r
+               <div class="row header-bg-color mb-5 p-3">\r
+                       <h2 class="text-center">Checkout with PayPal Demo</h2>\r
+                       <h4 class="text-center">Using Orders v2 REST API with PayPal JavaScript SDK</h4>\r
+                       <h5 class="text-center">Server-side Integration</h5> </div>\r
+               <div class="row">\r
+                       <div class="col-sm">\r
+                               <div class="card"> <img class="card-img-top img-responsive" src="https://www.paypalobjects.com/web/res/b0b/e9fea36098a7a8192723a8d3b86b2/img/platform/common/merchantStore/cart/camera-lg.jpg">\r
+                                       <div class="card-body">\r
+                                               <h4 class="text-center">Sample Sandbox Buyer Credentials</h4>\r
+                                               <table class="table table-striped">\r
+                                                       <thead>\r
+                                                               <tr>\r
+                                                                       <th scope="col">Buyer Email</th>\r
+                                                                       <th scope="col">Password</th>\r
+                                                               </tr>\r
+                                                       </thead>\r
+                                                       <tbody>\r
+                                                               <tr>\r
+                                                                       <td>emily_doe@buyer.com</td>\r
+                                                                       <td>qwer1234</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>bill_bong@buyer.com</td>\r
+                                                                       <td>qwer1234</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>jack_potter@buyer.com</td>\r
+                                                                       <td>123456789</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>harry_doe@buyer.com</td>\r
+                                                                       <td>123456789</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>ron_brown@buyer.com</td>\r
+                                                                       <td>qwer1234</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>bella_brown@buyer.com</td>\r
+                                                                       <td>qwer1234</td>\r
+                                                               </tr>\r
+                                                       </tbody>\r
+                                               </table>\r
+                                       </div>\r
+                               </div>\r
+                       </div>\r
+                       <div class="col-sm">\r
+                               <h3 class="text-center">Pricing Details</h3>\r
+                               <hr>\r
+                               <form id="camera_form">\r
+                                       <!-- Cart Details -->\r
+                                       <input name="return_url" type="hidden" value="http://localhost/pages/success.html">\r
+                                       <input name="cancel_url" type="hidden" value="http://localhost/pages/success.html">\r
+                                       <div class="form-group">\r
+                                               <label for="camera_amount" class="col-sm-5 control-label">Camera</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="camera_amount" name="item_amt" value="300" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="tax_amt" class="col-sm-5 control-label">Tax</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="tax_amt" name="tax_amt" value="5" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="insurance_fee" class="col-sm-5 control-label">Insurance</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="insurance_fee" name="insurance_fee" value="10" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="handling_fee" class="col-sm-5 control-label">Handling Fee</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="handling_fee" name="handling_fee" value="5" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="shipping_amt" class="col-sm-5 control-label">Estimated Shipping</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="shipping_amt" name="shipping_amt" value="2" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="shipping_discount" class="col-sm-5 control-label">Shipping Discount</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="shipping_discount" name="shipping_discount" value="2" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="total_amt" class="col-sm-5 control-label">Total Amount</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="total_amt" name="total_amt" value="320" readonly=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group">\r
+                                               <label for="currency_Code" class="col-sm-5 control-label">Currency</label>\r
+                                               <div class="col-sm">\r
+                                                       <input class="form-control" type="text" id="currency_Code" name="currency" value="USD" readonly=""> </div>\r
+                                       </div>\r
+                                       <hr>\r
+                                       <!-- Checkout Options -->\r
+                                       <div class="form-group">\r
+                                               <div class="col-sm-offset-5 col-sm">\r
+                                                       <!-- Container for PayPal Shortcut Checkout -->\r
+                                                       <div id="paypalCheckoutContainer"></div>\r
+                                                       <!-- Container for PayPal Mark Redirect -->\r
+                                                       <div id="paypalMarkRedirect">\r
+                                                               <h4 class="text-center">OR</h4>\r
+                                                               <a class="w-100 btn btn-success btn-block" href="pages/shipping.html" role="button">\r
+                                                                       <h4>Proceed to Checkout</h4> </a>\r
+                                                       </div>\r
+                                               </div>\r
+                                       </div>\r
+                               </form>\r
+                       </div>\r
+                       <div class="col-sm-4">\r
+                               <hr class="m-5">\r
+                               <h3 class="text-center">Readme</h3>\r
+                               <ol>\r
+                                       <li> Enter REST API credentials in common/config/config.json. You can get your own REST app credentials by creating a REST app with the steps outlined <i>\r
+                        <a href="https://developer.paypal.com/docs/api-basics/manage-apps/#create-or-edit-sandbox-and-live-apps" target="_blank">here</a>\r
+                    </i>. </li>\r
+                                       <li> Click on 'PayPal Checkout’ button and see the experience. </li>\r
+                                       <li> Checkout with PayPal using a buyer sandbox account provided on this page. And you're done! </li>\r
+                                       <li>In the guest checkout experience, the buyer country can be switched. When switched to one of Germany, Poland, Austria, Netherlands, Italy and Spain, you will be able to choose the alternative payment methods offered in those countries. </li>\r
+                                       <li>For example: Selecting 'Germany' in the country drop down will pre-fill the shipping address on the Shipping Information page. For all other countries not mentioned in step 5, the address has to be manually entered. </li>\r
+                               </ol>\r
+                               <hr>\r
+                               <h3 class="text-center">In-Context Checkout integration steps with PayPal JavaScript SDK</h3>\r
+                               <ol>\r
+                                       <li> Copy the files and folders in the package to the same location where you have your shopping cart page. </li>\r
+                                       <li> In order to view Alternative Payment Methods as part of the guest checkout flow, you must add query parameters intent=capture, commit=true, vault=false and buyer-country= and you must provide a supported buyer country </li>\r
+                                       <li> Include the following script on your shopping cart page: (For APMs, the layout must be <code>vertical</code> and setting up the payment in the alternative payment method <a href="https://developer.paypal.com/docs/checkout/integration-features/alternative-payment-methods/#availability" target="_blank">supported currency</a> is required for the alternative payment method to render.) <pre><code>\r
+        paypal.Buttons({\r
+        env: 'sandbox', // sandbox | production\r
+    \r
+            // Set style of buttons\r
+            style: {\r
+                layout: 'vertical',   // horizontal | vertical &lt;-Must be vertical for APMs\r
+                size:   'responsive',   // medium | large | responsive\r
+                shape:  'pill',         // pill | rect\r
+                color:  'gold',         // gold | blue | silver | black,\r
+                fundingicons: false,    // true | false,\r
+                tagline: false          // true | false,\r
+            },\r
+    \r
+        // payment() is called when the button is clicked\r
+        createOrder: function() {\r
+    \r
+            return fetch('/my-server/create-paypal-transaction')\r
+                .then(function(res) {\r
+                    return res.json();\r
+                }).then(function(data) {\r
+                    return data.orderID;\r
+                });\r
+        },\r
+    \r
+        // onAuthorize() is called when the buyer approves the payment\r
+        onApprove: function(data, actions) {\r
+    \r
+            return fetch('/my-server/capture-paypal-transaction', {\r
+                    body: JSON.stringify({\r
+                    orderID: data.orderID\r
+                    })\r
+                }).then(function(res) {\r
+                    return res.json();\r
+                }).then(function(details) {\r
+                    alert('Transaction funds captured from ' + details.payer_given_name);\r
+                });\r
+        }\r
+    }).render('#paypal-button-container');</code>\r
+                </pre> </li>\r
+                                       <li> Open your browser and navigate to your Shopping cart page. Click on 'Checkout with PayPal' button and complete the flow. </li>\r
+                                       <li> You can use the sample Buyer Sandbox credentials provided on index/home page. </li>\r
+                                       <li>Refer to <a href="https://developer.paypal.com/docs/checkout/" target="_blank">PayPal Developer</a> site for detailed guidelines.</li>\r
+                                       <li>Click <a href="https://developer.paypal.com/docs/api/orders/v2/" target="_blank">here</a> for the API reference. </li>\r
+                               </ol>\r
+                       </div>\r
+               </div>\r
+       </div>\r
+       <!-- Javascript Import -->\r
+       <script src="../js/config.js"></script>\r
+       <!-- PayPal In-Context Checkout script -->\r
+       <script type="text/javascript">\r
+       init_smart_buttons = () => {\r
+               paypal.Buttons({\r
+                       // Set your environment\r
+                       env: 'sandbox',\r
+                       // Set style of buttons\r
+                       style: {\r
+                               layout: 'vertical', // horizontal | vertical\r
+                               size: 'responsive', // medium | large | responsive\r
+                               shape: 'pill', // pill | rect\r
+                               color: 'gold', // gold | blue | silver | black,\r
+                               fundingicons: false, // true | false,\r
+                               tagline: false // true | false,\r
+                       },\r
+                       // Wait for the PayPal button to be clicked\r
+                       createOrder: function() {\r
+                               return new Promise((resolve, reject) => {\r
+                                       $.ajax({\r
+                                               type: 'POST',\r
+                                               url: 'api/createOrder.json',\r
+                                               data: serialize(document.getElementById("camera_form")),\r
+                                               success: function(response) {\r
+                                                       return(response)\r
+                                               },\r
+                                               error: function(error) {\r
+                                                       reject(error)\r
+                                               },\r
+                                       }).then(function(response) {\r
+                                               console.log('Order ID: ' + response.id);\r
+                                               resolve(response.id);\r
+                                       });\r
+                               })\r
+                       },\r
+                       // Wait for the payment to be authorized by the customer\r
+                       onApprove: function(data, actions) {\r
+                               return fetch('api/getOrderDetails.json', {\r
+                                       method: 'GET'\r
+                               }).then(function(res) {\r
+                                       return res.json();\r
+                               }).then(function(res) {\r
+                                       window.location.href = 'pages/success.html';\r
+                               });\r
+                       }\r
+               }).render('#paypalCheckoutContainer');\r
+       }\r
+       </script>\r
+       <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->\r
+       <script src="https://code.jquery.com/jquery.js"></script>\r
+</body>\r
+\r
+</html>\r
diff --git a/js/config.js b/js/config.js
new file mode 100644 (file)
index 0000000..a5e380b
--- /dev/null
@@ -0,0 +1,258 @@
+//Helper Functions\r
+function showDom(id) {\r
+    let arr;\r
+    if (!Array.isArray(id)) {\r
+        arr = [id];\r
+    } else {\r
+        arr = id;\r
+    }\r
+    arr.forEach(function (domId) {\r
+        document.getElementById(domId).style.display = "block";\r
+    });\r
+}\r
+\r
+function hideDom(id) {\r
+    let arr;\r
+    if (!Array.isArray(id)) {\r
+        arr = [id];\r
+    } else {\r
+        arr = id;\r
+    }\r
+    arr.forEach(function (domId) {\r
+        document.getElementById(domId).style.display = "none";\r
+    });\r
+}\r
+\r
+function getUrlParams(prop) {\r
+    let params = {},\r
+        search = decodeURIComponent(window.location.href.slice(window.location.href.indexOf("?") + 1)),\r
+        definitions = search.split("&");\r
+\r
+    definitions.forEach(function (val) {\r
+        let parts = val.split("=", 2);\r
+        params[parts[0]] = parts[1];\r
+    });\r
+\r
+    return (prop && prop in params) ? params[prop] : params;\r
+}\r
+\r
+document.addEventListener("input", function (event) {\r
+    if ((event.target.id === "countrySelect") && event.target.id !== null) {\r
+        window.location.href =\r
+            window.location.pathname +\r
+            "?buyer-country=" +\r
+            event.target.options[event.target.selectedIndex].value;\r
+    }\r
+});\r
+\r
+ready = function (fn) {\r
+    if (document.readyState != "loading") {\r
+        fn();\r
+    } else {\r
+        document.addEventListener("DOMContentLoaded", fn);\r
+    }\r
+}\r
+\r
+select_country_dropdown = function () {\r
+    const queryString = window.location.search;\r
+    const urlParams = new URLSearchParams(queryString);\r
+    const buyer_country = urlParams.get("buyer-country")\r
+    //If this param exists in URL\r
+    if (buyer_country) {\r
+        //Build script tag for head based on buyer country\r
+        let head = document.getElementsByTagName("head")[0];\r
+        let paypal_sdk_script = document.createElement("script");\r
+        paypal_sdk_script.type = "text/javascript"; \r
+        paypal_sdk_script.setAttribute("async", "");\r
+        paypal_sdk_script.src = "https://www.paypal.com/sdk/js?intent=capture&vault=false&client-id=Aa2IfcoEvHnfJRnVQLSFrSs3SmTTkv5N1weMEL66ysqYIeHfAqXpDVkjOv3vLhkhbP4eKB6MpRlQIcJw&buyer-country=" + buyer_country;\r
+        paypal_sdk_script.onload = init_smart_buttons; \r
+        head.appendChild(paypal_sdk_script);\r
+        //Pre-populate fields\r
+        for (let i = 0; i < document.getElementById("countrySelect").options.length; i++) {\r
+            if (document.getElementById("countrySelect").options[i].value == buyer_country) {\r
+                document.getElementById("countrySelect").options[i].selected = true;\r
+            }\r
+        }\r
+        apm_country_list = [\r
+            {\r
+                country: "DE", address: {\r
+                    address_line_1: "Bayreuther Straße 42",\r
+                    address_line_2: "",\r
+                    city: "Gmindersdorf",\r
+                    state: "Reutlingen",\r
+                    postal_code: "72760",\r
+                }, recipient_name: "Jane Doe"\r
+            },\r
+            {\r
+                country: "US", address: {\r
+                    address_line_1: "2211 North Street",\r
+                    address_line_2: "",\r
+                    city: "San Jose",\r
+                    state: "CA",\r
+                    postal_code: "95123"\r
+                }, recipient_name: "Jane Doe"\r
+            },\r
+            {\r
+                country: "BE", address: {\r
+                    address_line_1: "Putstraat 478",\r
+                    address_line_2: "",\r
+                    city: "Sint-Pauwels",\r
+                    state: "",\r
+                    postal_code: "9170"\r
+                }, recipient_name: "Marina Beich"\r
+            },\r
+            {\r
+                country: "PL", address: {\r
+                    address_line_1: "ul. Królewska 78",\r
+                    address_line_2: "",\r
+                    city: "Kraków",\r
+                    state: "",\r
+                    postal_code: "30-081"\r
+                }, recipient_name: "August Pedersen"\r
+            },\r
+            {\r
+                country: "AT", address: {\r
+                    address_line_1: "Hauptstrasse 85",\r
+                    address_line_2: "",\r
+                    city: "PULGARN",\r
+                    state: "",\r
+                    postal_code: "4221"\r
+                }, recipient_name: "Berrie Hulstein"\r
+            },\r
+            {\r
+                country: "NL", address: {\r
+                    address_line_1: "Asterstraat 135",\r
+                    address_line_2: "",\r
+                    city: "Almelo",\r
+                    state: "",\r
+                    postal_code: "7601 AK"\r
+                }, recipient_name: "Joos Voorham"\r
+            },\r
+            {\r
+                country: "IT", address: {\r
+                    address_line_1: "Via Longhena, 132",\r
+                    address_line_2: "",\r
+                    city: "Galloro RM",\r
+                    state: "",\r
+                    postal_code: "00040"\r
+                }, recipient_name: "Colette Jalbert"\r
+            },\r
+            {\r
+                country: "ES", address: {\r
+                    address_line_1: "Calle Alcalá 22",\r
+                    address_line_2: "",\r
+                    city: "Madrid",\r
+                    state: "",\r
+                    postal_code: "28055"\r
+                }, recipient_name: "Ana García López"\r
+            },\r
+            {\r
+                country: "AU", address: {\r
+                    address_line_1: "15 Mary Street",\r
+                    address_line_2: "",\r
+                    city: "North Sydney",\r
+                    state: "NSW",\r
+                    postal_code: "2001"\r
+                }, recipient_name: "Jane Doe"\r
+            },\r
+            {\r
+                country: "MX", address: {\r
+                    address_line_1: "Av. Caudillo del Sur 1234, Edificio B-5",\r
+                    address_line_2: "11560, Col. Municipio Libre, D.F.",\r
+                    city: "Mexico City",\r
+                    state: "Morelos",\r
+                    postal_code: "11560"\r
+                }, recipient_name: "Raúl Uriarte, Jr."\r
+            },\r
+            {\r
+                country: "BR", address: {\r
+                    address_line_1: "Rua da Matriz 123",\r
+                    address_line_2: "apto 25 Centro",\r
+                    city: "Rio de Janeiro",\r
+                    state: "Paraná",\r
+                    postal_code: "01000-001"\r
+                }, recipient_name: "João da Silva"\r
+            },\r
+            {\r
+                country: "JP", address: {\r
+                    address_line_1: "123-4567 æ±äº¬éƒ½æ¸¯åŒº",\r
+                    address_line_2: "é’å±± 1-1-1 ãƒšã‚¤ãƒ‘ルビル 1037",\r
+                    city: "港区",\r
+                    state: "æ±äº¬éƒ½",\r
+                    postal_code: "104-0051"\r
+                }, recipient_name: "山田 èŠ±å­"\r
+            },\r
+            {\r
+                country: "GB", address: {\r
+                    address_line_1: "1 Main Terrace",\r
+                    address_line_2: "",\r
+                    city: "Wolverhampton",\r
+                    state: "West Midlands",\r
+                    postal_code: "W12 4LQ"\r
+                }, recipient_name: "Jane Doe"\r
+            }\r
+        ];\r
+        apm_country_check = false;\r
+        apm_country_list.forEach(function (country_object) {\r
+            if (country_object.country === buyer_country) {\r
+                apm_country_check = true;\r
+                document.getElementById("recipient_name").value = country_object.recipient_name;\r
+                document.getElementById("line1").value = country_object.address.address_line_1;\r
+                document.getElementById("line2").value = country_object.address.address_line_2;\r
+                document.getElementById("city").value = country_object.address.city;\r
+                document.getElementById("state").value = country_object.address.state;\r
+                document.getElementById("zip").value = country_object.address.postal_code;\r
+            }\r
+        });\r
+        if (apm_country_check === false && buyer_country !== null) {\r
+            document.getElementById("recipient_name").value = "Jane Doe";\r
+            document.getElementById("line1").value =\r
+                document.getElementById("line2").value =\r
+                document.getElementById("city").value =\r
+                document.getElementById("state").value =\r
+                document.getElementById("zip").value = "";\r
+        }\r
+    } else {\r
+        //Build script tag with no particular buyer country\r
+        let head = document.getElementsByTagName("head")[0];\r
+        let paypal_sdk_script = document.createElement("script");\r
+        paypal_sdk_script.type = "text/javascript"; \r
+        paypal_sdk_script.setAttribute("async", "");\r
+        paypal_sdk_script.src = "https://www.paypal.com/sdk/js?intent=capture&vault=false&client-id=Aa2IfcoEvHnfJRnVQLSFrSs3SmTTkv5N1weMEL66ysqYIeHfAqXpDVkjOv3vLhkhbP4eKB6MpRlQIcJw";\r
+        paypal_sdk_script.onload = init_smart_buttons; \r
+        head.appendChild(paypal_sdk_script);\r
+    }\r
+}\r
+\r
+ready(select_country_dropdown);\r
+\r
+let serialize = function (form) {\r
+\r
+       // Setup our serialized data\r
+       let serialized = [];\r
+\r
+       // Loop through each field in the form\r
+       for (let i = 0; i < form.elements.length; i++) {\r
+\r
+               let field = form.elements[i];\r
+\r
+               // Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields\r
+               if (!field.name || field.disabled || field.type === "file" || field.type === "reset" || field.type === "submit" || field.type === "button") continue;\r
+\r
+               // If a multi-select, get all selections\r
+               if (field.type === "select-multiple") {\r
+                       for (let n = 0; n < field.options.length; n++) {\r
+                               if (!field.options[n].selected) continue;\r
+                               serialized.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.options[n].value));\r
+                       }\r
+               }\r
+\r
+               // Convert field data to a query string\r
+               else if ((field.type !== "checkbox" && field.type !== "radio") || field.checked) {\r
+                       serialized.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));\r
+               }\r
+       }\r
+\r
+       return serialized.join("&");\r
+\r
+};
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..9a95157
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "name": "@ndcode/paypal_example_site",
+  "version": "0.0.1",
+  "description": "Example website using JavaScript Template system",
+  "directories": {},
+  "dependencies": {
+    "cookie": "^0.3.1",
+    "querystring": "^0.2.0",
+    "stream-buffers": "^3.0.2",
+    "xdate": "^0.8.2"
+  },
+  "devDependencies": {},
+  "scripts": {},
+  "author": "Nick Downing",
+  "license": "CC-BY-SA-3.0"
+}
diff --git a/pages/shipping.html b/pages/shipping.html
new file mode 100644 (file)
index 0000000..f47efb1
--- /dev/null
@@ -0,0 +1,435 @@
+<!DOCTYPE html>\r
+<html lang="en">\r
+\r
+<head>\r
+       <meta charset="utf-8" />\r
+       <meta http-equiv="x-ua-compatible" content="ie=edge" />\r
+       <meta name="viewport" content="width=device-width, initial-scale=1" />\r
+       <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">\r
+       <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>\r
+       <title>Checkout With PayPal NodeJs Demo</title>\r
+       <style>\r
+       /* http://angrytools.com/gradient/ */\r
+       \r
+       .header-bg-color {\r
+               color: white;\r
+               background: -moz-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* ff3.6+ */\r
+               background: -webkit-gradient(linear, left top, right top, color-stop(0%, #004094), color-stop(50%, #0096D9), color-stop(100%, #004094));\r
+               /* safari4+,chrome */\r
+               background: -webkit-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* safari5.1+,chrome10+ */\r
+               background: -o-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* opera 11.10+ */\r
+               background: -ms-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* ie10+ */\r
+               background: linear-gradient(90deg, #004094 0%, #0096D9 50%, #004094 100%);\r
+               /* w3c */\r
+               filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#004094', endColorstr='#004094', GradientType=1);\r
+               /* ie6-9 */\r
+       }\r
+       </style>\r
+</head>\r
+\r
+<body>\r
+       <div class="container">\r
+               <div class="row header-bg-color mb-5 p-3">\r
+                       <h2 class="text-center">Checkout with PayPal Demo</h2>\r
+                       <h4 class="text-center">Using Orders v2 REST API with PayPal JavaScript SDK</h4>\r
+                       <h5 class="text-center">Server-side Integration</h5> </div>\r
+               <div class="row">\r
+                       <div class="col-lg-3"></div>\r
+                       <div class="col-lg-6">\r
+                               <h3 class="text-center">Shipping Information</h3>\r
+                               <hr>\r
+                               <form id="camera_form">\r
+                                       <input name="return_url" type="hidden" value="http://localhost/pages/success.html">\r
+                                       <input name="cancel_url" type="hidden" value="http://localhost/pages/success.html">\r
+                                       <input id="item_amt" name="item_amt" type="hidden" value="300">\r
+                                       <input id="insurance_fee" name="insurance_fee" type="hidden" value="10">\r
+                                       <input id="handling_fee" name="handling_fee" type="hidden" value="5">\r
+                                       <input id="tax_amt" name="tax_amt" type="hidden" value="5">\r
+                                       <input id="shipping_discount" name="shipping_discount" type="hidden" value="2">\r
+                                       <input id="currency" name="currency" type="hidden" value="USD">\r
+                                       <input id="total_amt" name="total_amt" type="hidden" value="300">\r
+                                       <!-- Shipping Information -->\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="recipient_name" class="col-sm-4 col-form-label">Recipient Name</label>\r
+                                                       <div class="col-sm-8">\r
+                                                       <input type="text" name="recipient_name" id="recipient_name" class="form-control" aria-describedby="passwordHelpInline" value="Jane Doe"> \r
+                                               </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="line1" class="col-sm-4 col-form-label">Address Line 1</label>\r
+                                                       <div class="col-sm-8">\r
+                                                               <input type="text" name="line1" id="line1" class="form-control" aria-describedby="passwordHelpInline" value="2211 North Street"> </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="line2" class="col-sm-4 col-form-label">Address Line 2</label>\r
+                                                       <div class="col-sm-8">\r
+                                                               <input type="text" name="line2" id="line2" class="form-control" aria-describedby="passwordHelpInline" value=""> </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="city" class="col-sm-4 col-form-label">City</label>\r
+                                                       <div class="col-sm-8">\r
+                                                               <input type="text" name="city" id="city" class="form-control" aria-describedby="passwordHelpInline" value="San Jose"> </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="state" class="col-sm-4 col-form-label">State</label>\r
+                                                       <div class="col-sm-8">\r
+                                                               <input type="text" name="state" id="state" class="form-control" aria-describedby="passwordHelpInline" value="CA"> </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="zip" class="col-sm-4 col-form-label">Postal Code</label>\r
+                                               <div class="col-sm-8">\r
+                                                       <input type="text" name="zip" id="zip" class="form-control" aria-describedby="passwordHelpInline" value="95123"> </div>\r
+                                       </div>\r
+                                       <div class="form-group row mb-2">\r
+                                                       <label for="countrySelect" class="col-sm-4 col-form-label">Country</label>\r
+                                               <div class="col-sm-8">\r
+                                                       <select class="form-select mb-2" name="countrySelect" id="countrySelect" aria-label="Country">\r
+                                                       <option value="AF">Afghanistan</option>\r
+                                                       <option value="AX">Aland Islands</option>\r
+                                                       <option value="AL">Albania</option>\r
+                                                       <option value="DZ">Algeria</option>\r
+                                                       <option value="AS">American Samoa</option>\r
+                                                       <option value="AD">Andorra</option>\r
+                                                       <option value="AO">Angola</option>\r
+                                                       <option value="AI">Anguilla</option>\r
+                                                       <option value="AQ">Antarctica</option>\r
+                                                       <option value="AG">Antigua and Barbuda</option>\r
+                                                       <option value="AR">Argentina</option>\r
+                                                       <option value="AM">Armenia</option>\r
+                                                       <option value="AW">Aruba</option>\r
+                                                       <option value="AU">Australia</option>\r
+                                                       <option value="AT">Austria</option>\r
+                                                       <option value="AZ">Azerbaijan</option>\r
+                                                       <option value="BS">Bahamas</option>\r
+                                                       <option value="BH">Bahrain</option>\r
+                                                       <option value="BD">Bangladesh</option>\r
+                                                       <option value="BB">Barbados</option>\r
+                                                       <option value="BY">Belarus</option>\r
+                                                       <option value="BE">Belgium</option>\r
+                                                       <option value="BZ">Belize</option>\r
+                                                       <option value="BJ">Benin</option>\r
+                                                       <option value="BM">Bermuda</option>\r
+                                                       <option value="BT">Bhutan</option>\r
+                                                       <option value="BO">Bolivia</option>\r
+                                                       <option value="BA">Bosnia and Herzegovina</option>\r
+                                                       <option value="BW">Botswana</option>\r
+                                                       <option value="BV">Bouvet Island</option>\r
+                                                       <option value="BR">Brazil</option>\r
+                                                       <option value="IO">British Indian Ocean Territory</option>\r
+                                                       <option value="BN">Brunei Darussalam</option>\r
+                                                       <option value="BG">Bulgaria</option>\r
+                                                       <option value="BF">Burkina Faso</option>\r
+                                                       <option value="BI">Burundi</option>\r
+                                                       <option value="KH">Cambodia</option>\r
+                                                       <option value="CM">Cameroon</option>\r
+                                                       <option value="CA">Canada</option>\r
+                                                       <option value="CV">Cape Verde</option>\r
+                                                       <option value="KY">Cayman Islands</option>\r
+                                                       <option value="CF">Central African Republic</option>\r
+                                                       <option value="TD">Chad</option>\r
+                                                       <option value="CL">Chile</option>\r
+                                                       <option value="CN">China</option>\r
+                                                       <option value="CX">Christmas Island</option>\r
+                                                       <option value="CC">Cocos (Keeling) Islands</option>\r
+                                                       <option value="CO">Colombia</option>\r
+                                                       <option value="KM">Comoros</option>\r
+                                                       <option value="CG">Congo</option>\r
+                                                       <option value="CD">Congo, The Democratic Republic of The</option>\r
+                                                       <option value="CK">Cook Islands</option>\r
+                                                       <option value="CR">Costa Rica</option>\r
+                                                       <option value="CI">Cote D'ivoire</option>\r
+                                                       <option value="HR">Croatia</option>\r
+                                                       <option value="CU">Cuba</option>\r
+                                                       <option value="CY">Cyprus</option>\r
+                                                       <option value="CZ">Czech Republic</option>\r
+                                                       <option value="DK">Denmark</option>\r
+                                                       <option value="DJ">Djibouti</option>\r
+                                                       <option value="DM">Dominica</option>\r
+                                                       <option value="DO">Dominican Republic</option>\r
+                                                       <option value="EC">Ecuador</option>\r
+                                                       <option value="EG">Egypt</option>\r
+                                                       <option value="SV">El Salvador</option>\r
+                                                       <option value="GQ">Equatorial Guinea</option>\r
+                                                       <option value="ER">Eritrea</option>\r
+                                                       <option value="EE">Estonia</option>\r
+                                                       <option value="ET">Ethiopia</option>\r
+                                                       <option value="FK">Falkland Islands (Malvinas)</option>\r
+                                                       <option value="FO">Faroe Islands</option>\r
+                                                       <option value="FJ">Fiji</option>\r
+                                                       <option value="FI">Finland</option>\r
+                                                       <option value="FR">France</option>\r
+                                                       <option value="GF">French Guiana</option>\r
+                                                       <option value="PF">French Polynesia</option>\r
+                                                       <option value="TF">French Southern Territories</option>\r
+                                                       <option value="GA">Gabon</option>\r
+                                                       <option value="GM">Gambia</option>\r
+                                                       <option value="GE">Georgia</option>\r
+                                                       <option value="DE">Germany</option>\r
+                                                       <option value="GH">Ghana</option>\r
+                                                       <option value="GI">Gibraltar</option>\r
+                                                       <option value="GR">Greece</option>\r
+                                                       <option value="GL">Greenland</option>\r
+                                                       <option value="GD">Grenada</option>\r
+                                                       <option value="GP">Guadeloupe</option>\r
+                                                       <option value="GU">Guam</option>\r
+                                                       <option value="GT">Guatemala</option>\r
+                                                       <option value="GG">Guernsey</option>\r
+                                                       <option value="GN">Guinea</option>\r
+                                                       <option value="GW">Guinea-bissau</option>\r
+                                                       <option value="GY">Guyana</option>\r
+                                                       <option value="HT">Haiti</option>\r
+                                                       <option value="HM">Heard Island and Mcdonald Islands</option>\r
+                                                       <option value="VA">Holy See (Vatican City State)</option>\r
+                                                       <option value="HN">Honduras</option>\r
+                                                       <option value="HK">Hong Kong</option>\r
+                                                       <option value="HU">Hungary</option>\r
+                                                       <option value="IS">Iceland</option>\r
+                                                       <option value="IN">India</option>\r
+                                                       <option value="ID">Indonesia</option>\r
+                                                       <option value="IR">Iran, Islamic Republic of</option>\r
+                                                       <option value="IQ">Iraq</option>\r
+                                                       <option value="IE">Ireland</option>\r
+                                                       <option value="IM">Isle of Man</option>\r
+                                                       <option value="IL">Israel</option>\r
+                                                       <option value="IT">Italy</option>\r
+                                                       <option value="JM">Jamaica</option>\r
+                                                       <option value="JP">Japan</option>\r
+                                                       <option value="JE">Jersey</option>\r
+                                                       <option value="JO">Jordan</option>\r
+                                                       <option value="KZ">Kazakhstan</option>\r
+                                                       <option value="KE">Kenya</option>\r
+                                                       <option value="KI">Kiribati</option>\r
+                                                       <option value="KP">Korea, Democratic People's Republic of</option>\r
+                                                       <option value="KR">Korea, Republic of</option>\r
+                                                       <option value="KW">Kuwait</option>\r
+                                                       <option value="KG">Kyrgyzstan</option>\r
+                                                       <option value="LA">Lao People's Democratic Republic</option>\r
+                                                       <option value="LV">Latvia</option>\r
+                                                       <option value="LB">Lebanon</option>\r
+                                                       <option value="LS">Lesotho</option>\r
+                                                       <option value="LR">Liberia</option>\r
+                                                       <option value="LY">Libyan Arab Jamahiriya</option>\r
+                                                       <option value="LI">Liechtenstein</option>\r
+                                                       <option value="LT">Lithuania</option>\r
+                                                       <option value="LU">Luxembourg</option>\r
+                                                       <option value="MO">Macao</option>\r
+                                                       <option value="MK">Macedonia, The Former Yugoslav Republic of</option>\r
+                                                       <option value="MG">Madagascar</option>\r
+                                                       <option value="MW">Malawi</option>\r
+                                                       <option value="MY">Malaysia</option>\r
+                                                       <option value="MV">Maldives</option>\r
+                                                       <option value="ML">Mali</option>\r
+                                                       <option value="MT">Malta</option>\r
+                                                       <option value="MH">Marshall Islands</option>\r
+                                                       <option value="MQ">Martinique</option>\r
+                                                       <option value="MR">Mauritania</option>\r
+                                                       <option value="MU">Mauritius</option>\r
+                                                       <option value="YT">Mayotte</option>\r
+                                                       <option value="MX">Mexico</option>\r
+                                                       <option value="FM">Micronesia, Federated States of</option>\r
+                                                       <option value="MD">Moldova, Republic of</option>\r
+                                                       <option value="MC">Monaco</option>\r
+                                                       <option value="MN">Mongolia</option>\r
+                                                       <option value="ME">Montenegro</option>\r
+                                                       <option value="MS">Montserrat</option>\r
+                                                       <option value="MA">Morocco</option>\r
+                                                       <option value="MZ">Mozambique</option>\r
+                                                       <option value="MM">Myanmar</option>\r
+                                                       <option value="NA">Namibia</option>\r
+                                                       <option value="NR">Nauru</option>\r
+                                                       <option value="NP">Nepal</option>\r
+                                                       <option value="NL">Netherlands</option>\r
+                                                       <option value="AN">Netherlands Antilles</option>\r
+                                                       <option value="NC">New Caledonia</option>\r
+                                                       <option value="NZ">New Zealand</option>\r
+                                                       <option value="NI">Nicaragua</option>\r
+                                                       <option value="NE">Niger</option>\r
+                                                       <option value="NG">Nigeria</option>\r
+                                                       <option value="NU">Niue</option>\r
+                                                       <option value="NF">Norfolk Island</option>\r
+                                                       <option value="MP">Northern Mariana Islands</option>\r
+                                                       <option value="NO">Norway</option>\r
+                                                       <option value="OM">Oman</option>\r
+                                                       <option value="PK">Pakistan</option>\r
+                                                       <option value="PW">Palau</option>\r
+                                                       <option value="PS">Palestinian Territory, Occupied</option>\r
+                                                       <option value="PA">Panama</option>\r
+                                                       <option value="PG">Papua New Guinea</option>\r
+                                                       <option value="PY">Paraguay</option>\r
+                                                       <option value="PE">Peru</option>\r
+                                                       <option value="PH">Philippines</option>\r
+                                                       <option value="PN">Pitcairn</option>\r
+                                                       <option value="PL">Poland</option>\r
+                                                       <option value="PT">Portugal</option>\r
+                                                       <option value="PR">Puerto Rico</option>\r
+                                                       <option value="QA">Qatar</option>\r
+                                                       <option value="RE">Reunion</option>\r
+                                                       <option value="RO">Romania</option>\r
+                                                       <option value="RU">Russian Federation</option>\r
+                                                       <option value="RW">Rwanda</option>\r
+                                                       <option value="SH">Saint Helena</option>\r
+                                                       <option value="KN">Saint Kitts and Nevis</option>\r
+                                                       <option value="LC">Saint Lucia</option>\r
+                                                       <option value="PM">Saint Pierre and Miquelon</option>\r
+                                                       <option value="VC">Saint Vincent and The Grenadines</option>\r
+                                                       <option value="WS">Samoa</option>\r
+                                                       <option value="SM">San Marino</option>\r
+                                                       <option value="ST">Sao Tome and Principe</option>\r
+                                                       <option value="SA">Saudi Arabia</option>\r
+                                                       <option value="SN">Senegal</option>\r
+                                                       <option value="RS">Serbia</option>\r
+                                                       <option value="SC">Seychelles</option>\r
+                                                       <option value="SL">Sierra Leone</option>\r
+                                                       <option value="SG">Singapore</option>\r
+                                                       <option value="SK">Slovakia</option>\r
+                                                       <option value="SI">Slovenia</option>\r
+                                                       <option value="SB">Solomon Islands</option>\r
+                                                       <option value="SO">Somalia</option>\r
+                                                       <option value="ZA">South Africa</option>\r
+                                                       <option value="GS">South Georgia and The South Sandwich Islands</option>\r
+                                                       <option value="ES">Spain</option>\r
+                                                       <option value="LK">Sri Lanka</option>\r
+                                                       <option value="SD">Sudan</option>\r
+                                                       <option value="SR">Suriname</option>\r
+                                                       <option value="SJ">Svalbard and Jan Mayen</option>\r
+                                                       <option value="SZ">Swaziland</option>\r
+                                                       <option value="SE">Sweden</option>\r
+                                                       <option value="CH">Switzerland</option>\r
+                                                       <option value="SY">Syrian Arab Republic</option>\r
+                                                       <option value="TW">Taiwan, Province of China</option>\r
+                                                       <option value="TJ">Tajikistan</option>\r
+                                                       <option value="TZ">Tanzania, United Republic of</option>\r
+                                                       <option value="TH">Thailand</option>\r
+                                                       <option value="TL">Timor-leste</option>\r
+                                                       <option value="TG">Togo</option>\r
+                                                       <option value="TK">Tokelau</option>\r
+                                                       <option value="TO">Tonga</option>\r
+                                                       <option value="TT">Trinidad and Tobago</option>\r
+                                                       <option value="TN">Tunisia</option>\r
+                                                       <option value="TR">Turkey</option>\r
+                                                       <option value="TM">Turkmenistan</option>\r
+                                                       <option value="TC">Turks and Caicos Islands</option>\r
+                                                       <option value="TV">Tuvalu</option>\r
+                                                       <option value="UG">Uganda</option>\r
+                                                       <option value="UA">Ukraine</option>\r
+                                                       <option value="AE">United Arab Emirates</option>\r
+                                                       <option value="GB">United Kingdom</option>\r
+                                                       <option value="US" selected="">United States</option>\r
+                                                       <option value="UM">United States Minor Outlying Islands</option>\r
+                                                       <option value="UY">Uruguay</option>\r
+                                                       <option value="UZ">Uzbekistan</option>\r
+                                                       <option value="VU">Vanuatu</option>\r
+                                                       <option value="VE">Venezuela</option>\r
+                                                       <option value="VN">Vietnam</option>\r
+                                                       <option value="VG">Virgin Islands, British</option>\r
+                                                       <option value="VI">Virgin Islands, U.S.</option>\r
+                                                       <option value="WF">Wallis and Futuna</option>\r
+                                                       <option value="EH">Western Sahara</option>\r
+                                                       <option value="YE">Yemen</option>\r
+                                                       <option value="ZM">Zambia</option>\r
+                                                       <option value="ZW">Zimbabwe</option>\r
+                                                       </select>\r
+                                               </div>\r
+                                       </div>\r
+                               <div class="row g-3 align-items-center mb-2">\r
+                                                       <label for="shipping_amt" class="col-sm-5 control-label">Shipping Type</label>\r
+                                               <div class="col-sm-7">\r
+                                                       <select class="form-control" name="shipping_amt" id="shipping_amt">\r
+                                                               <optgroup label="United Parcel Service" style="font-style:normal;">\r
+                                                                       <option value="8.00"> Worldwide Expedited - $8.00</option>\r
+                                                                       <option value="4.00"> Worldwide Express Saver - $4.00</option>\r
+                                                               </optgroup>\r
+                                                               <optgroup label="Flat Rate" style="font-style:normal;">\r
+                                                                       <option value="2.00" selected=""> Fixed - $2.00</option>\r
+                                                               </optgroup>\r
+                                                       </select>\r
+                                               </div>\r
+                                       </div>\r
+                                       <!-- Checkout Options -->\r
+                                       <div class="form-group mb-2">\r
+                                               <div class="col-sm-offset-5 col-sm-7">\r
+                                                       <div class="radio">\r
+                                                               <label>\r
+                                                                       <input type="radio" name="paymentMethod" id="paypalRadio" value="paypal" checked=""> <img src="https://www.paypalobjects.com/webstatic/mktg/logo/pp_cc_mark_37x23.jpg" alt="Acceptance Mark" class="v-middle"> <a href="https://www.paypal.com/us/cgi-bin/webscr?cmd=xpt/Marketing/popup/OLCWhatIsPayPal-outside" onclick="javascript:window.open('https://www.paypal.com/us/cgi-bin/webscr?cmd=xpt/Marketing/popup/OLCWhatIsPayPal-outside','olcwhatispaypal','toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, ,left=0, top=0, width=400, height=350'); return false;">What is PayPal?</a> </label>\r
+                                                       </div>\r
+                                                       <div class="radio disabled mb-2">\r
+                                                               <label>\r
+                                                                       <input type="radio" name="paymentMethod" id="cardRadio" value="card" disabled=""> Card </label>\r
+                                                       </div>\r
+                                               </div>\r
+                                       </div>\r
+                                       <div class="row">\r
+                                                       <!-- Container for PayPal Mark Checkout -->\r
+                                                       <div id="paypalCheckoutContainer"></div>\r
+                                       </div>\r
+                       </form>\r
+                       </div>\r
+                       <div class="col-lg-3"></div>\r
+               </div>\r
+       </div>\r
+       <!-- Javascript Import -->\r
+       <script src="../js/config.js"></script>\r
+       <!-- PayPal In-Context Checkout script -->\r
+       <script type="text/javascript">\r
+       init_smart_buttons = () => {\r
+               paypal.Buttons({\r
+                       // Set your environment\r
+                       env: 'sandbox',\r
+                       // Set style of button\r
+                       style: {\r
+                               layout: 'vertical', // horizontal | vertical\r
+                               size: 'responsive', // medium | large | responsive\r
+                               shape: 'pill', // pill | rect\r
+                               color: 'gold' // gold | blue | silver | black\r
+                       },\r
+                       // Execute payment on authorize\r
+                       commit: true,\r
+                       // Wait for the PayPal button to be clicked\r
+                       createOrder: function() {\r
+                               let shipping_amtSelect = document.getElementById("shipping_amt"),\r
+                                       updatedShipping = shipping_amtSelect.options[shipping_amtSelect.selectedIndex].value,\r
+                                       countrySelect = document.getElementById("countrySelect"),\r
+                                       total_amt = parseFloat(document.getElementById("item_amt").value) + parseFloat(document.getElementById("tax_amt").value) + parseFloat(document.getElementById("handling_fee").value) + parseFloat(document.getElementById("insurance_fee").value) + parseFloat(updatedShipping) - parseFloat(document.getElementById("shipping_discount").value);\r
+                                       document.getElementById("total_amt").value = total_amt;\r
+                               return new Promise((resolve, reject) => {\r
+                                       $.ajax({\r
+                                               type: 'POST',\r
+                                               url: '/api/createOrder.json',\r
+                                               data: serialize(document.getElementById("camera_form")),\r
+                                               success: function(response) {\r
+                                                       return(response)\r
+                                               },\r
+                                               error: function(error) {\r
+                                                       reject(error)\r
+                                               },\r
+                                       }).then(function(response) {\r
+                                               console.log('Order ID: ' + response.id);\r
+                                               resolve(response.id);\r
+                                       });\r
+                               })\r
+                       },\r
+                       // Wait for the payment to be authorized by the customer\r
+                       onApprove: function(data, actions) {\r
+                               // Capture Order\r
+                               let postData = new FormData();\r
+                               return fetch('../api/captureOrder.json', {\r
+                                       method: 'POST',\r
+                                       body: postData\r
+                               }).then(function(res) {\r
+                                       return res.json();\r
+                               }).then(function() {\r
+                                       window.location.href = 'success.html?commit=true';\r
+                               });\r
+                       }\r
+               }).render('#paypalCheckoutContainer');\r
+       }\r
+       </script>\r
+       <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->\r
+       <script src="https://code.jquery.com/jquery.js"></script>\r
+</body>\r
+\r
+</html>\r
diff --git a/pages/success.html b/pages/success.html
new file mode 100644 (file)
index 0000000..437d54b
--- /dev/null
@@ -0,0 +1,255 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+       <meta charset="utf-8" />
+       <meta http-equiv="x-ua-compatible" content="ie=edge" />
+       <meta name="viewport" content="width=device-width, initial-scale=1" />
+       <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
+       <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
+       <title>Checkout With PayPal NodeJs Demo</title>
+       <style>
+       /* http://angrytools.com/gradient/ */
+       
+       .header-bg-color {
+               color: white;
+               background: -moz-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);
+               /* ff3.6+ */
+               background: -webkit-gradient(linear, left top, right top, color-stop(0%, #004094), color-stop(50%, #0096D9), color-stop(100%, #004094));
+               /* safari4+,chrome */
+               background: -webkit-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);
+               /* safari5.1+,chrome10+ */
+               background: -o-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);
+               /* opera 11.10+ */
+               background: -ms-linear-gradient(0deg, #004094 0%, #0096D9 50%, #004094 100%);
+               /* ie10+ */
+               background: linear-gradient(90deg, #004094 0%, #0096D9 50%, #004094 100%);
+               /* w3c */
+               filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#004094', endColorstr='#004094', GradientType=1);
+               /* ie6-9 */
+       }
+       #orderView p, #orderConfirm p {
+               margin:0;
+       }
+       </style>
+</head>
+
+<body>
+       <div class="container">
+               <div class="row header-bg-color mb-5 p-3">
+                       <h2 class="text-center">Checkout with PayPal Demo</h2>
+                       <h4 class="text-center">Using Orders v2 REST API with PayPal JavaScript SDK</h4>
+                       <h5 class="text-center">Server-side Integration</h5> </div>
+               <div class="row">
+                       <div class="col-sm"> </div>
+                       <div class="col-sm">
+                               <div id="loadingAlert" class="card" style="display: none;">
+                                       <div class="card-body">
+                                               <div class="alert alert-info block" role="alert"> Loading.... </div>
+                                       </div>
+                               </div>
+                               <form id="orderConfirm" class="form-horizontal" style="display: none;">
+                                       <h3>Your payment is authorized.</h3>
+                                       <h4>Confirm the order to execute</h4>
+                                       <hr>
+                                       <div class="form-group">
+                                               <label style="font-weight:bold" class="col-sm-5 control-label">Shipping Information</label>
+                                               <div class="col-sm">
+                                                       <p id="confirmRecipient"></p>
+                                                       <p id="confirmAddressLine1"></p>
+                                                       <p id="confirmAddressLine2"></p>
+                                                       <p> <span id="confirmCity"></span>, <span id="confirmState"></span> - <span id="confirmZip"></span> </p>
+                                                       <p id="confirmCountry"></p>
+                                               </div>
+                                       </div>
+                                       <div class="form-group">
+                                               <label style="font-weight:bold" for="shippingMethod" class="col-sm-5 control-label">Shipping Type</label>
+                                               <div class="col-sm">
+                                                       <select class="form-control" name="shippingMethod" id="shippingMethod">
+                                                               <optgroup label="United Parcel Service" style="font-style:normal;">
+                                                                       <option value="8.00"> Worldwide Expedited - $8.00</option>
+                                                                       <option value="4.00"> Worldwide Express Saver - $4.00</option>
+                                                               </optgroup>
+                                                               <optgroup label="Flat Rate" style="font-style:normal;">
+                                                                       <option value="2.00" selected> Fixed - $2.00</option>
+                                                               </optgroup>
+                                                       </select>
+                                               </div>
+                                       </div>
+                                       <hr>
+                                       <div class="form-group">
+                                               <div class="col-sm-offset-5 col-sm">
+                                                       <label class="btn btn-primary" id="confirmButton">Complete Payment</label>
+                                               </div>
+                                       </div>
+                               </form>
+                               <form id="orderView" class="form-horizontal" style="display: none;">
+                                       <h3>Your payment is complete</h3>
+                                       <h4>
+                    <span id="viewFirstName"></span>
+                    <span id="viewLastName"></span>,
+                    Thank you for your Order
+                </h4>
+                                       <hr>
+                                       <div class="form-group">
+                                               <div class="form-group">
+                                                       <label style="font-weight:bold" class="col-sm-5 control-label">Shipping Details</label>
+                                                       <div class="col-sm">
+                                                               <p id="viewRecipientName"></p>
+                                                               <p id="viewAddressLine1"></p>
+                                                               <p id="viewAddressLine2"></p>
+                                                               <p> <span id="viewCity"></span>, <span id="viewState"></span> - <span id="viewPostalCode"></span> </p>
+                                                               <p id="confirmCountry"></p>
+                                                       </div>
+                                               </div>
+                                               <div class="form-group">
+                                                       <label style="font-weight:bold" class="col-sm-5 control-label">Transaction Details</label>
+                                                       <div class="col-sm">
+                                                               <p>Transaction ID: <span id="viewTransactionID"></span></p>
+                                                               <p>Payment Total Amount: <span id="viewFinalAmount"></span> </p>
+                                                               <p>Currency Code: <span id="viewCurrency"></span></p>
+                                                               <p>Payment Status: <span id="viewPaymentState"></span></p>
+                                                               <p id="transactionType">Payment Type: <span id="viewTransactionType"></span> </p>
+                                                       </div>
+                                               </div>
+                                       </div>
+                                       <hr>
+                                       <h4> Click <a href='/'>here </a> to return to Home Page</h4> </form>
+                       </div>
+                       <div class="col-sm"> </div>
+               </div>
+       </div>
+       <!-- Javascript Import -->
+       <script src="../js/config.js"></script>
+       <script type="text/javascript">
+       showDom('loadingAlert');
+       document.onreadystatechange = function() {
+               if(document.readyState === 'complete') {
+                       $.ajax({
+                               type: 'POST',
+                               url: '/api/getOrderDetails.json',
+                               success: function(response, textStatus, xhr) {
+                                       hideDom('loadingAlert');
+                                       if(textStatus === "success") {
+                                               console.log("Success Response:\n" + response);
+                                               if(getUrlParams('commit') === 'true') {
+                                                       showPaymentExecute(response);
+                                               } else {
+                                                       showPaymentGet(response);
+                                               }
+                                       } else {
+                                               alert('Something went wrong');
+                                       }
+                               }
+                       });
+               }
+       }
+
+       function showPaymentGet(res) {
+               let shipping = res.purchase_units[0].shipping;
+               let shipping_address = shipping.address;
+               console.log('Get Order result ' + JSON.stringify(res));
+               console.log('shipping add ' + JSON.stringify(shipping));
+               document.getElementById('confirmRecipient').innerText = shipping.name.full_name;
+               document.getElementById('confirmAddressLine1').innerText = shipping_address.address_line_1;
+               if(shipping_address.address_line_2) document.getElementById('confirmAddressLine2').innerText = shipping_address.address_line_1;
+               else document.getElementById('confirmAddressLine2').innerText = "";
+               document.getElementById('confirmCity').innerText = shipping_address.admin_area_2;
+               document.getElementById('confirmState').innerText = shipping_address.admin_area_1;
+               document.getElementById('confirmZip').innerText = shipping_address.postal_code;
+               document.getElementById('confirmCountry').innerText = shipping_address.country_code;
+               showDom('orderConfirm');
+               // Listen for click on confirm button
+               document.querySelector('#confirmButton').addEventListener('click', function() {
+                       let shippingMethodSelect = document.getElementById("shippingMethod"),
+                               updatedShipping = shippingMethodSelect.options[shippingMethodSelect.selectedIndex].value,
+                               currentShipping = res.purchase_units[0].amount.breakdown.shipping.value;
+                       let postPatchOrderData = {
+                               "order_id": res.id,
+                               "item_amt": res.purchase_units[0].amount.breakdown.item_total.value,
+                               "tax_amt": res.purchase_units[0].amount.breakdown.tax_total.value,
+                               "handling_fee": res.purchase_units[0].amount.breakdown.handling.value,
+                               "insurance_fee": res.purchase_units[0].amount.breakdown.insurance.value,
+                               "shipping_discount": res.purchase_units[0].amount.breakdown.shipping_discount.value,
+                               "total_amt": res.purchase_units[0].amount.value,
+                               "currency": res.purchase_units[0].amount.currency_code,
+                               "updated_shipping": updatedShipping,
+                               "current_shipping": currentShipping
+                       };
+                       console.log('patch data: ' + JSON.stringify(postPatchOrderData));
+                       // Execute the payment
+                       hideDom('orderConfirm');
+                       hideDom('confirmButton');
+                       showDom('loadingAlert');
+                       console.log('Current shipping ' + currentShipping + ' and updated shipping is ' + updatedShipping);
+                       if(currentShipping == updatedShipping) {
+                               return callPaymentCapture();
+                       } else {
+                               console.log("Trying patch first");
+                               $.ajax({
+                                       type: 'POST',
+                                       url: '/api/patchOrder.json',
+                                       data: postPatchOrderData,
+                                       success: function(response) {
+                                               console.log('Patch Order Response has no content in V2: ' + JSON.stringify(response));
+                                               //The PayPal Orders V2 Patch call only returns 204 http status code
+                                               if(response.http_code === 204) return callPaymentCapture();
+                                       }
+                               });
+                       }
+               });
+       }
+
+       function callPaymentCapture() {
+               $.ajax({
+                       type: 'POST',
+                       url: '/api/captureOrder.json',
+                       success: function(response) {
+                               hideDom('orderConfirm');
+                               hideDom('loadingAlert');
+                               console.log('Capture Response : ' + JSON.stringify(response));
+                               showPaymentExecute(response);
+                       },
+                       error: function(err) {
+                               alert("Something went wrong");
+                       }
+               });
+       }
+
+       function showPaymentExecute(result) {
+               console.log("Final Result:\n" + result);
+               document.getElementById('viewFirstName').textContent = result.payer.name.given_name;
+               document.getElementById('viewLastName').textContent = result.payer.name.surname;
+               document.getElementById('viewRecipientName').textContent = result.purchase_units[0].shipping.name.full_name;
+               document.getElementById('viewAddressLine1').textContent = result.purchase_units[0].shipping.address.address_line_1;
+               if (result.purchase_units[0].shipping.address.address_line_2) {
+               document.getElementById('viewAddressLine2').textContent = result.purchase_units[0].shipping.address.address_line_2;} else {
+               document.getElementById('viewAddressLine2').textContent = ""; }
+               document.getElementById('viewCity').textContent = result.purchase_units[0].shipping.address.admin_area_2;
+               document.getElementById('viewState').textContent = result.purchase_units[0].shipping.address.admin_area_1;
+               document.getElementById('viewPostalCode').innerHTML = result.purchase_units[0].shipping.address.postal_code;
+               document.getElementById('viewTransactionID').textContent = result.id;
+               if(result.purchase_units[0].payments && result.purchase_units[0].payments.captures) {
+                       document.getElementById('viewFinalAmount').textContent = result.purchase_units[0].payments.captures[0].amount.value;
+                       document.getElementById('viewCurrency').textContent = result.purchase_units[0].payments.captures[0].amount.currency_code;
+               } else {
+                       document.getElementById('viewFinalAmount').textContent = result.purchase_units[0].amount.value;
+                       document.getElementById('viewCurrency').textContent = result.purchase_units[0].amount.currency_code;
+               }
+               document.getElementById('viewPaymentState').textContent = result.status;
+               if(result.intent) {
+                       document.getElementById('viewTransactionType').textContent = result.intent;
+                       document.getElementById('transactionType').style.display = 'block';
+               } else {
+                       document.getElementById('transactionType').style.display = 'none';
+               }
+               hideDom('orderConfirm');
+               hideDom('loadingAlert');
+               showDom('orderView');
+       }
+       </script>
+       <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
+       <script src="https://code.jquery.com/jquery.js"></script>
+</body>
+
+</html>