SSR-1063 credit card form on payment-page
This commit is contained in:
parent
e774babe0e
commit
32af347771
14 changed files with 1631 additions and 11 deletions
13
package-lock.json
generated
13
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"axios-retry": "^3.5.0",
|
"axios-retry": "^3.5.0",
|
||||||
"bootstrap": "^5.3",
|
"bootstrap": "^5.3",
|
||||||
|
"iframe-resizer": "^4.3.9",
|
||||||
"maska": "^1.5.0",
|
"maska": "^1.5.0",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"pinia-plugin-persistedstate": "^2.2.0",
|
"pinia-plugin-persistedstate": "^2.2.0",
|
||||||
|
|
@ -9734,6 +9735,18 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/iframe-resizer": {
|
||||||
|
"version": "4.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/iframe-resizer/-/iframe-resizer-4.3.9.tgz",
|
||||||
|
"integrity": "sha512-MCt+V/THB4a9OcAdrWo5NsI2CRpeMM4ijhTfiLtsdgDJXWYXf62Ve8yO8rKGmYNs991zty/EolYOxActlkfU+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/davidjbradshaw/iframe-resizer/blob/master/FUNDING.md"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"axios-retry": "^3.5.0",
|
"axios-retry": "^3.5.0",
|
||||||
"bootstrap": "^5.3",
|
"bootstrap": "^5.3",
|
||||||
|
"iframe-resizer": "^4.3.9",
|
||||||
"maska": "^1.5.0",
|
"maska": "^1.5.0",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"pinia-plugin-persistedstate": "^2.2.0",
|
"pinia-plugin-persistedstate": "^2.2.0",
|
||||||
|
|
|
||||||
13
public/css/README.md
Normal file
13
public/css/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Using Sass in this folder
|
||||||
|
|
||||||
|
This public/css folder uses a standalone .scss > .css setup.
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
From a Terminal window, run the Sass Watch command from the public folder only:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sass --no-source-map --watch scss:css
|
||||||
|
```
|
||||||
|
This will compile and output .css files (into the public/css folder) that can be consumed by the Payment page.
|
||||||
|
|
||||||
|
# Do not use the .css files on their own. You must use the .scss files and have Sass compile them to .css files.
|
||||||
205
public/css/hop-styling.css
Normal file
205
public/css/hop-styling.css
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
body {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
background-color: white;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
body .validation-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
body .italicSmall {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
body span {
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
body a {
|
||||||
|
color: #1574A1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
body .form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
body .headerlinecontainer .headerline1 {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 24px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
body input,
|
||||||
|
body select {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
body input,
|
||||||
|
body select {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #8E9292;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
body input:focus, body input:focus-visible,
|
||||||
|
body select:focus,
|
||||||
|
body select:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2.5px #1574a1;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
body input:disabled, body input.disabled,
|
||||||
|
body select:disabled,
|
||||||
|
body select.disabled {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
body input:disabled:hover, body input.disabled:hover,
|
||||||
|
body select:disabled:hover,
|
||||||
|
body select.disabled:hover {
|
||||||
|
box-shadow: 0 0 0 4px transparent;
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
}
|
||||||
|
body input:hover,
|
||||||
|
body select:hover {
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
box-shadow: 0 0 0 4px #9fcee6;
|
||||||
|
}
|
||||||
|
body select {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #8E9292;
|
||||||
|
padding: 16px;
|
||||||
|
width: 100%;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 95% 50%;
|
||||||
|
background-size: 15px 9px;
|
||||||
|
appearance: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
body .creditCardSpecific div {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
body .creditCardSpecific div .headerlinecontainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
body .creditCardSpecific div label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
body .creditCardSpecific #infoRow2 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
body #cardExpirationContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
@media (min-width: 400px) {
|
||||||
|
body #cardExpirationContainer {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
body #cardExpirationContainer .card_expirationYear,
|
||||||
|
body #cardExpirationContainer .card_expirationMonth {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body #cardExpirationContainer .card_expirationYear {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body .optional-label span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
body .optional-label:after {
|
||||||
|
content: " (optional)";
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
body .buttonContainer {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
body .buttonContainer button {
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
@media (hover: hover) {
|
||||||
|
body .buttonContainer button {
|
||||||
|
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body .buttonContainer button:focus {
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
}
|
||||||
|
body .buttonContainer button:focus, body .buttonContainer button:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(270deg, rgb(6, 87, 124) 0%, rgb(6, 87, 124) 100%);
|
||||||
|
}
|
||||||
|
body .buttonContainer button:disabled {
|
||||||
|
background: #e3e4e4 !important;
|
||||||
|
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
|
||||||
|
color: #4d5151 !important;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 48px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
body .buttonContainer button.has-loader {
|
||||||
|
color: #fff;
|
||||||
|
background: #06577c;
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
body .buttonContainer button.delay {
|
||||||
|
transition: background 0s 0s ease-in-out;
|
||||||
|
}
|
||||||
|
body .has-error input,
|
||||||
|
body .has-error select {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
}
|
||||||
|
body .has-error input:focus,
|
||||||
|
body .has-error select:focus {
|
||||||
|
box-shadow: 0 0 0 2.5px #fcbfbb;
|
||||||
|
}
|
||||||
|
body .has-error input:hover,
|
||||||
|
body .has-error select:hover {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
box-shadow: 0 0 0 4px #fcbfbb;
|
||||||
|
}
|
||||||
|
body .has-error select {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 95% 50%;
|
||||||
|
background-size: 15px 9px;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
body .has-error small {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #d4281c;
|
||||||
|
}
|
||||||
|
body .cc-error-container #alert-message {
|
||||||
|
background-color: #fcbfbb;
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
}
|
||||||
114
public/css/site-2020Styling.css
Normal file
114
public/css/site-2020Styling.css
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* Style the modal when PayPal button is selected */
|
||||||
|
#pageLoadingModal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-content {
|
||||||
|
margin: auto auto;
|
||||||
|
}
|
||||||
|
#pageLoadingModal p {
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal {
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-content {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-clip: padding-box;
|
||||||
|
outline: 0;
|
||||||
|
width: 176px;
|
||||||
|
height: 136px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 96px;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-content:after {
|
||||||
|
content: "";
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
width: 5.5rem;
|
||||||
|
height: 5.5rem;
|
||||||
|
border: 0.45em solid #d4281c;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: spinnerrotate 0.75s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spinnerrotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-content p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
#pageLoadingModal .modal-backdrop.fade {
|
||||||
|
opacity: 0;
|
||||||
|
filter: alpha(opacity=0);
|
||||||
|
}
|
||||||
|
#pageLoadingModal.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-backdrop {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
.buy-backdrop #afterpay__iframe-checkout-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.buy-backdrop #afterpay__iframe-checkout-container .style_closeWrapper__lIHJe {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-container-closer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.creditCardSpecific {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
224
public/scss/hop-styling.scss
Normal file
224
public/scss/hop-styling.scss
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
body {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
background-color: white;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.validation-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italicSmall {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #1574A1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerlinecontainer {
|
||||||
|
.headerline1 {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 24px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #8E9292;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
&:focus,
|
||||||
|
&:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2.5px #1574a1;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
&:disabled,
|
||||||
|
&.disabled {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 0 4px transparent;
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
box-shadow: 0 0 0 4px #9fcee6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #8E9292;
|
||||||
|
padding: 16px;
|
||||||
|
width: 100%;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 95% 50%;
|
||||||
|
background-size: 15px 9px;
|
||||||
|
appearance: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creditCardSpecific {
|
||||||
|
div {
|
||||||
|
padding: 8px 0;
|
||||||
|
.headerlinecontainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#infoRow2 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#cardExpirationContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
@media (min-width: 400px) {
|
||||||
|
flex-direction: row;
|
||||||
|
.card_expirationYear,
|
||||||
|
.card_expirationMonth {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.card_expirationYear {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.optional-label {
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: " (optional)";
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
height: 48px;
|
||||||
|
button {
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 48px;
|
||||||
|
@media (hover: hover) {
|
||||||
|
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
}
|
||||||
|
&:focus, // Mouse, touch, stylus focus
|
||||||
|
&:focus-visible {
|
||||||
|
// Keyboard focus for accessibility
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
|
||||||
|
}
|
||||||
|
&:disabled {
|
||||||
|
background: #e3e4e4 !important;
|
||||||
|
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
|
||||||
|
color: #4d5151 !important;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 48px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
&.has-loader {
|
||||||
|
color: #fff;
|
||||||
|
background: #06577c;
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
&.delay {
|
||||||
|
// fixes flicker while transitioning between states
|
||||||
|
transition: background 0s 0s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Error Styling
|
||||||
|
.has-error {
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 2.5px #fcbfbb;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
box-shadow: 0 0 0 4px #fcbfbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 95% 50%;
|
||||||
|
background-size: 15px 9px;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
small {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #d4281c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-error-container {
|
||||||
|
#alert-message {
|
||||||
|
background-color: #fcbfbb;
|
||||||
|
border: 1px solid #d4281c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
118
public/scss/site-2020Styling.scss
Normal file
118
public/scss/site-2020Styling.scss
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||||
|
|
||||||
|
/* Style the modal when PayPal button is selected */
|
||||||
|
#pageLoadingModal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.40);
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
margin: auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-clip: padding-box;
|
||||||
|
outline: 0;
|
||||||
|
width: 176px;
|
||||||
|
height: 136px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 96px;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
width: 5.5rem;
|
||||||
|
height: 5.5rem;
|
||||||
|
border: 0.45em solid #d4281c;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: spinnerrotate 0.75s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spinnerrotate {
|
||||||
|
0% { transform: rotate(0)}
|
||||||
|
100% { transform: rotate(360deg)}
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #000000;
|
||||||
|
&.fade {
|
||||||
|
opacity: 0;
|
||||||
|
filter: alpha(opacity=0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterPay Styles
|
||||||
|
|
||||||
|
.buy-backdrop {
|
||||||
|
background-color: rgba(0,0,0,.3)!important;
|
||||||
|
#afterpay__iframe-checkout-container {
|
||||||
|
width: 100%;
|
||||||
|
.style_closeWrapper__lIHJe {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-container-closer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal Spinner
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% { transform: rotate(0)}
|
||||||
|
100% { transform: rotate(360deg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
.creditCardSpecific {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
/**
|
/**
|
||||||
* @module applicationConfig
|
* @module applicationConfig
|
||||||
* @author T-Wrecks Team
|
* @author T-Wrecks Team
|
||||||
|
|
@ -16,7 +17,17 @@ const applicationConfig = Object.freeze({
|
||||||
CLIENTTAG_QUERYSTRING: 'ClientTag',
|
CLIENTTAG_QUERYSTRING: 'ClientTag',
|
||||||
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
|
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
|
||||||
ISS_DEV_CMS_DOMAIN: 'https://digitalisscms.dev.safelite.io',
|
ISS_DEV_CMS_DOMAIN: 'https://digitalisscms.dev.safelite.io',
|
||||||
CASH_PARENT_ACCOUNT_NUMBER: 167132
|
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||||
|
PAY_IN_ADVANCE_RESPONSE_URL:
|
||||||
|
`${location.protocol
|
||||||
|
}//${
|
||||||
|
location.host
|
||||||
|
}/?issPage=payment-return&src=iss-nextgen`,
|
||||||
|
PAY_IN_ADVANCE_CANCEL_URL:
|
||||||
|
`${location.protocol
|
||||||
|
}//${
|
||||||
|
location.host
|
||||||
|
}/?issPage=payment-method&src=iss-nextgen`
|
||||||
});
|
});
|
||||||
|
|
||||||
export default applicationConfig;
|
export default applicationConfig;
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,10 @@ const endpoints = Object.freeze({
|
||||||
FinalDeductible: {
|
FinalDeductible: {
|
||||||
url: '/coverage/api/v1/coverage/final-deductible',
|
url: '/coverage/api/v1/coverage/final-deductible',
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
},
|
||||||
|
GetPaymentSignature: {
|
||||||
|
url: '/order/api/v1/order/sign',
|
||||||
|
method: 'POST'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
492
src/layouts/payment-page/payment-page.spec.js
Normal file
492
src/layouts/payment-page/payment-page.spec.js
Normal file
|
|
@ -0,0 +1,492 @@
|
||||||
|
// Components
|
||||||
|
import payment from '@/layouts/payment-page/payment-page.vue';
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { getMountOptions } from '@/helpers/unit-test-helper';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
|
import paymentMethods from '@/constants/payment-method-constants'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const parts = {
|
||||||
|
windshield: {
|
||||||
|
name: 'windshield',
|
||||||
|
canSafeliteRecalibrate: true,
|
||||||
|
childParts: [],
|
||||||
|
color: 'Green Tint',
|
||||||
|
description:
|
||||||
|
'solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control',
|
||||||
|
id: 'db22fd44-10dd-456f-979b-ff88cf68cca6',
|
||||||
|
partNumber: 'FW04896GTYN',
|
||||||
|
partType: 'WINDSHIELD',
|
||||||
|
recalibrationType: 'STATIC',
|
||||||
|
requiresCapabilityQuestions: false,
|
||||||
|
requiresRecalibration: true,
|
||||||
|
salesTax: 63.86,
|
||||||
|
sellingPrice: 791.46
|
||||||
|
},
|
||||||
|
frontWipers: {
|
||||||
|
name: 'front wipers',
|
||||||
|
partNumber: 'SBB16',
|
||||||
|
description: 'SAFELITE BEAM BLADE 16',
|
||||||
|
partType: 'FRONT WIPER',
|
||||||
|
price: 32.64
|
||||||
|
},
|
||||||
|
rearWipers: {
|
||||||
|
name: 'rear wipers',
|
||||||
|
partNumber: 'SBBR12A',
|
||||||
|
description: 'SAFELITE REAR BLADE 12A',
|
||||||
|
partType: 'REAR WIPER',
|
||||||
|
price: 24.48
|
||||||
|
},
|
||||||
|
rainDefense: {
|
||||||
|
name: 'rain defense',
|
||||||
|
partNumber: 'RAIN DEFENSE',
|
||||||
|
description: null,
|
||||||
|
partType: 'RAIN DEFENSE',
|
||||||
|
price: 35.5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pricedParts = {
|
||||||
|
windshield: {
|
||||||
|
...parts.windshield,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 60,
|
||||||
|
salesTax: 0,
|
||||||
|
sellingPrice: 791.46
|
||||||
|
},
|
||||||
|
frontWipers: {
|
||||||
|
...parts.frontWipers,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 20,
|
||||||
|
salesTax: 0,
|
||||||
|
sellingPrice: 32.64
|
||||||
|
},
|
||||||
|
rearWipers: {
|
||||||
|
...parts.rearWipers,
|
||||||
|
kitPrice: 10,
|
||||||
|
laborAmount: 10,
|
||||||
|
salesTax: 0,
|
||||||
|
sellingPrice: 24.48
|
||||||
|
},
|
||||||
|
rainDefense: {
|
||||||
|
...parts.rainDefense,
|
||||||
|
kitPrice: 20,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 0,
|
||||||
|
sellingPrice: 35.5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxedParts = {
|
||||||
|
windshield: {
|
||||||
|
...pricedParts.windshield,
|
||||||
|
salesTax: 63.86,
|
||||||
|
subTotal: 100
|
||||||
|
},
|
||||||
|
frontWipers: {
|
||||||
|
...pricedParts.frontWipers,
|
||||||
|
salesTax: 2.36,
|
||||||
|
subTotal: 100
|
||||||
|
},
|
||||||
|
rearWipers: {
|
||||||
|
...pricedParts.rearWipers,
|
||||||
|
salesTax: 6.0,
|
||||||
|
subTotal: 100
|
||||||
|
},
|
||||||
|
rainDefense: {
|
||||||
|
...pricedParts.rainDefense,
|
||||||
|
salesTax: 1.0,
|
||||||
|
subTotal: 100
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup global mocks
|
||||||
|
let mockCmsContent = {};
|
||||||
|
|
||||||
|
jest.mock('@/mixins/base-mixin.js', () => ({
|
||||||
|
methods: {
|
||||||
|
getAmountDue: jest.fn().mockImplementation(() => 5),
|
||||||
|
getDisplayAmountDue: jest.fn().mockImplementation(() => '$5.00')
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/helpers/cms-content-helper', () => ({
|
||||||
|
fetchCmsContentForPage: () => Promise.resolve('content')
|
||||||
|
}));
|
||||||
|
|
||||||
|
function setupMocks({ customMountOptions }) {
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
...customMountOptions,
|
||||||
|
route: { query: { issPage: 'page-name' }, params: {} }
|
||||||
|
});
|
||||||
|
|
||||||
|
// set all mock stuff
|
||||||
|
mountOptions.mixins = [
|
||||||
|
{
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
|
||||||
|
if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName]) {
|
||||||
|
return mockCmsContent[widgetName][fieldName];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
setCmsContent: jest.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const wrapper = shallowMount(payment, mountOptions);
|
||||||
|
|
||||||
|
// act on vm
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('payment-page.vue', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useMainStore().order =
|
||||||
|
{
|
||||||
|
vehicle: {
|
||||||
|
year: '2020',
|
||||||
|
make: 'acura',
|
||||||
|
model: 'mdx',
|
||||||
|
style: '4-door sedan',
|
||||||
|
carId: 'dummyCarId',
|
||||||
|
category: 'dummyCategory',
|
||||||
|
vin: 'dummyVin'
|
||||||
|
},
|
||||||
|
serviceLocation: {
|
||||||
|
address: 'add1',
|
||||||
|
address2: 'add2',
|
||||||
|
city: 'city',
|
||||||
|
state: 'state',
|
||||||
|
zipCode: 'zip',
|
||||||
|
zipCodeCtu: 'zipCtu',
|
||||||
|
appointmentType: 'IN_SHOP',
|
||||||
|
isVehicleProtected: true,
|
||||||
|
provider: {
|
||||||
|
providerNumber: 2,
|
||||||
|
address: {
|
||||||
|
streetAddress: 'add3',
|
||||||
|
city: 'city2',
|
||||||
|
state: 'state2',
|
||||||
|
zipCode: 'zip2',
|
||||||
|
zipCodeCtu: 'zipCtu2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
techNotes: ''
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
firstName: 'first',
|
||||||
|
lastName: 'last',
|
||||||
|
emailAddress: 'builddigitaltest@safelite.com',
|
||||||
|
phoneNumber: '555-555-5555',
|
||||||
|
isSmsOptIn: false
|
||||||
|
},
|
||||||
|
damage: {
|
||||||
|
isRepair: false,
|
||||||
|
numberOfChips: null,
|
||||||
|
glassToReplace: [{ location: 'windshield' }]
|
||||||
|
},
|
||||||
|
lineItems: {
|
||||||
|
glassParts: [parts.windshield],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [parts.frontWipers],
|
||||||
|
promos: []
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
isInsurance: false,
|
||||||
|
insuranceCoverage: {
|
||||||
|
isVerified: null,
|
||||||
|
coverageStatus: null,
|
||||||
|
coverageVerificationType: null
|
||||||
|
},
|
||||||
|
isPayInAdvance: true,
|
||||||
|
payInAdvanceType: 'Afterpay',
|
||||||
|
inactivePromos: []
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
date: 'date',
|
||||||
|
startTime: 'start',
|
||||||
|
endTime: 'end',
|
||||||
|
jobMinMinutes: '30',
|
||||||
|
jobMaxMinutes: '45'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCmsContent = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('arePagePrerequisitesValid', () => {
|
||||||
|
test('Returns true in nominal conditions', () => {
|
||||||
|
// Arrange
|
||||||
|
// Store defaults are valid
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Payment method cases', () => {
|
||||||
|
test('Returns false if pia options are not valid', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.isPayInAdvance = null;
|
||||||
|
useMainStore().order.payment.payInAdvanceType = null;
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns false if pay at time of service', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.isPayInAdvance = false;
|
||||||
|
useMainStore().order.payment.payInAdvanceType = null;
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('beforeRouteEnter', () => {
|
||||||
|
test('Properly initializes fields and kicks off hop', async () => {
|
||||||
|
// Arrange
|
||||||
|
const vmMock = {
|
||||||
|
setCmsContent: jest.fn(),
|
||||||
|
$refs: {
|
||||||
|
cart: {
|
||||||
|
cartItems: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getPiaLineItems: jest.fn(),
|
||||||
|
fetchSignatureInfo: jest.fn(),
|
||||||
|
setIFrameListener: jest.fn(),
|
||||||
|
|
||||||
|
$nextTick: (f) => {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextF = (f) => {
|
||||||
|
f(vmMock);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await payment.beforeRouteEnter.call(
|
||||||
|
vmMock,
|
||||||
|
{ query: { issPage: 'payment-page' } },
|
||||||
|
undefined,
|
||||||
|
nextF
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(vmMock.setCmsContent).toBeCalled();
|
||||||
|
expect(vmMock.fetchSignatureInfo).toBeCalled();
|
||||||
|
expect(vmMock.setIFrameListener).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('payment type mapping', () => {
|
||||||
|
describe('getPaymentType', () => {
|
||||||
|
test('Maps AFTERPAY -> ap', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.payInAdvanceType = paymentMethods.AFTERPAY;
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const mapped = wrapper.vm.getPaymentType();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mapped).toBe('ap');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Maps CREDIT_CARD -> cc', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.payInAdvanceType = paymentMethods.CREDIT_CARD;
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const mapped = wrapper.vm.getPaymentType();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mapped).toBe('cc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Maps PAYPAL -> pp', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.payInAdvanceType = paymentMethods.PAYPAL;
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const mapped = wrapper.vm.getPaymentType();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mapped).toBe('pp');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Maps other values to self', () => {
|
||||||
|
// Arrange
|
||||||
|
useMainStore().order.payment.payInAdvanceType = 'SomeOtherText';
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const mapped = wrapper.vm.getPaymentType();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mapped).toBe('SomeOtherText');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isPaypal', () => {
|
||||||
|
test('Is responsive if initial data changes', async () => {
|
||||||
|
// Arrange
|
||||||
|
// note: begins with payment-type = afterpay
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const preVal = wrapper.vm.isPaypal;
|
||||||
|
wrapper.vm.paymentType = 'pp';
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const postVal = wrapper.vm.isPaypal;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(preVal).toBe(false);
|
||||||
|
expect(postVal).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hop form', () => {
|
||||||
|
describe('fetchSignatureInfo', () => {
|
||||||
|
test('Sets auth fields', async () => {
|
||||||
|
// Arrange
|
||||||
|
const signature = {
|
||||||
|
token: 'TOKEN',
|
||||||
|
signature: 'SIGNATURE',
|
||||||
|
startDate: 'STARTDATE'
|
||||||
|
};
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.submitHopForm = jest.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.fetchSignatureInfo(signature);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.authToken).toBe(signature.token);
|
||||||
|
expect(wrapper.vm.authSignature).toBe(signature.signature);
|
||||||
|
expect(wrapper.vm.authSignatureStart).toBe(signature.startDate);
|
||||||
|
|
||||||
|
expect(wrapper.vm.submitHopForm).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('submitHopForm', () => {
|
||||||
|
test('Submits form', async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.hopForm.submit = jest.fn();
|
||||||
|
wrapper.vm.$refs.paymentFrame = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.submitHopForm();
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$refs.hopForm.submit).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleIFrameContentWindowMessage', () => {
|
||||||
|
test('Navigates back if afterpay is closed', () => {
|
||||||
|
// Arrange
|
||||||
|
const event = {
|
||||||
|
data: 'afterpayClosed'
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.backButtonAction = jest.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.handleIFrameContentWindowMessage(event);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.backButtonAction).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Blocks UI interaction if credit card is submitted', () => {
|
||||||
|
// Arrange
|
||||||
|
const event = {
|
||||||
|
data: 'creditCardSubmit'
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.handleIFrameContentWindowMessage(event);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.shouldBlockInteraction).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UI Blocking', () => {
|
||||||
|
test('UI block appears when toggled', async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.setUIBlock(true);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const uiBlockElements = wrapper.findAll('.ui-block');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(uiBlockElements.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Don't propogate clicks from UI block", async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
const outerDiv = wrapper.find('.container-fluid');
|
||||||
|
const clickFn = jest.fn();
|
||||||
|
outerDiv.element.addEventListener('click', clickFn);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.setUIBlock(true);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const uiBlockElement = wrapper.find('.ui-block');
|
||||||
|
|
||||||
|
await uiBlockElement.trigger('click');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(clickFn).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -8,17 +8,155 @@
|
||||||
<div class="fade-on-route-transition position-relative">
|
<div class="fade-on-route-transition position-relative">
|
||||||
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||||
<div class="container-fluid pb-2 payment">
|
<div class="container-fluid pb-2 payment">
|
||||||
<p>Placeholder for payment page</p>
|
<div id="card-container">
|
||||||
|
<iframe
|
||||||
|
id="card-frame"
|
||||||
|
ref="paymentFrame"
|
||||||
|
v-resize
|
||||||
|
class="hop-iframe payment-iframe"
|
||||||
|
name="card-frame"
|
||||||
|
seamless
|
||||||
|
scrolling="no">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
<siteFooter
|
<siteFooter
|
||||||
ref="siteFooter"
|
ref="siteFooter"
|
||||||
cmsWidgetName="SiteFooterWidget"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardActionDisabled="true"
|
||||||
|
isForwardButtonHidden="true"
|
||||||
@ForwardClicked="forwardButtonAction"
|
@ForwardClicked="forwardButtonAction"
|
||||||
@backClicked="navigateBack" />
|
@backClicked="backButtonAction" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="shouldBlockInteraction"
|
||||||
|
class="ui-block"
|
||||||
|
@click="supressClick"></div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
ref="hopForm"
|
||||||
|
id="card-data"
|
||||||
|
:target="isPaypal ? '_top' : 'card-frame'"
|
||||||
|
method="POST"
|
||||||
|
:action="checkoutUrl">
|
||||||
|
<input type="hidden" name="paymentType" :value="paymentType" />
|
||||||
|
<input type="hidden" name="sgSessionId" :value="authToken" />
|
||||||
|
<input type="hidden" name="sgAuthToken" :value="authToken" />
|
||||||
|
<input type="hidden" name="sgSignaturePublic" :value="authSignature" />
|
||||||
|
<input type="hidden" name="sgSignatureStartDate" :value="authSignatureStart" />
|
||||||
|
<input type="hidden" name="referralSeqNum" :value="referralSequenceNumber" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sge_commerce_indicator_isinternet" value="true" />
|
||||||
|
<input type="hidden" name="sghopsource" value="Safelite.com" />
|
||||||
|
<input type="hidden" name="sgHtmlStyle" value="ResourceSafeliteHtml" />
|
||||||
|
<input type="hidden" name="sgErrorMessagesEmbedded" value="true" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgNoKeystrokeProcessing" value="false" />
|
||||||
|
|
||||||
|
<input type="hidden" name="maskCharacter" value="*" />
|
||||||
|
<input type="hidden" name="styleSheetCode" :value="dynamicCSSUrl" />
|
||||||
|
<input type="hidden" name="styleSheetCode2" :value="dynamicHopCSSUrl" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgReceiptResponseURL" :value="payInAdvanceResponseUrl" />
|
||||||
|
<input type="hidden" name="sgDeclineResponseURL" :value="payInAdvanceResponseUrl" />
|
||||||
|
<input type="hidden" name="sgErrorResponseURL" :value="payInAdvanceResponseUrl" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgheaderline1" :value="getHeaderLine1" />
|
||||||
|
<input type="hidden" name="sgHeader1subtitle" value="" />
|
||||||
|
<input type="hidden" name="sgheaderline2" :value="getHeaderLine2" />
|
||||||
|
<input type="hidden" name="sgheaderline3" value="" />
|
||||||
|
<input type="hidden" name="sgheaderline4" value="" />
|
||||||
|
<input type="hidden" name="sgheaderline5" value="*Required information" />
|
||||||
|
|
||||||
|
<input type="hidden" name="billTo_firstName" :value="firstName" />
|
||||||
|
<input type="hidden" name="billTo_firstNameShow" value="true" />
|
||||||
|
<input type="hidden" name="sghopmiddleInitialShow" value="false" />
|
||||||
|
<input type="hidden" name="billTo_lastName" :value="lastName" />
|
||||||
|
<input type="hidden" name="billTo_lastNameShow" value="true" />
|
||||||
|
|
||||||
|
<input type="hidden" name="billTo_street1" value="" />
|
||||||
|
<input type="hidden" name="billTo_street2" value="" />
|
||||||
|
<input type="hidden" name="billTo_city" value="" />
|
||||||
|
<input type="hidden" name="billTo_state" value="" />
|
||||||
|
|
||||||
|
<input type="hidden" name="billTo_postalCode" value="" />
|
||||||
|
<input type="hidden" name="billTo_postalCodeShow" value="true" />
|
||||||
|
<input type="hidden" name="billTo_postalCodeEnable" value="true" />
|
||||||
|
<input type="hidden" name="sgLabelPostalCode" value="Billing ZIP" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgtotalamount" :value="displayAmount" />
|
||||||
|
<input type="hidden" name="totalAmountDecimal" :value="totalAmount" />
|
||||||
|
<input type="hidden" name="lineItems" :value="payInAdvanceLineItems" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgdiscountstrikethruamount" value="" />
|
||||||
|
|
||||||
|
<input type="hidden" name="callerDisplayText" value="" />
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="sgtermsofusedisplaytext"
|
||||||
|
value="By selecting submit, I agree to Safelite's" />
|
||||||
|
<input type="hidden" name="sgtermsofuseurl" value="http://www.safelite.com/terms-of-use/" />
|
||||||
|
<input type="hidden" name="sgtermsofuselinkdisplaytext" value="terms of use" />
|
||||||
|
<input type="hidden" name="sgtermsofcancelrefunddisplaytext" value="and" />
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="sgtermsofcancelrefundlinkdisplaytext"
|
||||||
|
value="cancellation and refund policy" />
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="sgtermsofcancelrefundurl"
|
||||||
|
value="http://www.safelite.com/cancellation-refund-policy" />
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="sgErrorMessageCardVerification"
|
||||||
|
value="Credit card could not be processed" />
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="sgErrorMessageTimeOut"
|
||||||
|
value="For your security, this transaction has been timed out." />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgLabelCardNumber" value="" />
|
||||||
|
<input type="hidden" name="sgLabelAddressLine1" :value="address1" />
|
||||||
|
<input type="hidden" name="sgLabelAddressLine2" :value="address2" />
|
||||||
|
<input type="hidden" name="sgLabelCity" :value="city" />
|
||||||
|
<input type="hidden" name="sgLabelState" :value="state" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgCtu" :value="ctu" />
|
||||||
|
<input type="hidden" name="sgWorkOrder" :value="workOrderNumber" />
|
||||||
|
<input type="hidden" name="sgEmailAddress" :value="emailAddress" />
|
||||||
|
<input type="hidden" name="paypalInvoiceNumber" :value="invoiceNumber" />
|
||||||
|
<input type="hidden" name="paypalSuccessUrl" :value="payInAdvanceResponseUrl" />
|
||||||
|
<input type="hidden" name="paypalCancelUrl" :value="payInAdvanceCancelUrl" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgCCDeclineURL" :value="payInAdvanceCancelUrl" />
|
||||||
|
<input type="hidden" name="sgCCTimeoutURL" :value="payInAdvanceCancelUrl" />
|
||||||
|
|
||||||
|
<input type="hidden" name="sgTransactionType" value="authorization" />
|
||||||
|
<input type="hidden" name="amount" :value="totalAmount" />
|
||||||
|
<input type="hidden" name="ctu" :value="ctu" />
|
||||||
|
<input type="hidden" name="orderNumber" :value="workOrderNumber" />
|
||||||
|
|
||||||
|
<input type="hidden" name="billTo_email" :value="emailAddress" />
|
||||||
|
<input type="hidden" name="useDecisionManager" value="True" />
|
||||||
|
<input type="hidden" name="correlationId" :value="referralCorrelationId" />
|
||||||
|
<input type="hidden" name="sgErrorMessageCard_CVN" value="Please enter a valid CVV" />
|
||||||
|
|
||||||
|
<input type="hidden" name="ship_to_address_line1" :value="address1" />
|
||||||
|
<input type="hidden" name="ship_to_address_line2" :value="address2" />
|
||||||
|
<input type="hidden" name="ship_to_address_city" :value="city" />
|
||||||
|
<input type="hidden" name="ship_to_address_state" :value="state" />
|
||||||
|
<input type="hidden" name="ship_to_address_country" value="US" />
|
||||||
|
<input type="hidden" name="ship_to_address_postal_code" :value="zipCode" />
|
||||||
|
<input type="hidden" name="ship_to_phone" :value="phoneNumber" />
|
||||||
|
|
||||||
|
<input type="hidden" name="calling_application" value="NextGen" />
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
// Components
|
// Components
|
||||||
|
|
@ -26,11 +164,17 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
|
import applicationConfig from '@/constants/application-config.js';
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||||
import settleAllPromises from '@/helpers/layout-helper';
|
import settleAllPromises from '@/helpers/layout-helper';
|
||||||
import { Form } from 'vee-validate';
|
import { Form } from 'vee-validate';
|
||||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
|
import iframeResize from '../../../node_modules/iframe-resizer/js/iframeResizer.js';
|
||||||
|
import externalUrls from '@/router/router-constants/externalUrl-values';
|
||||||
|
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||||
|
import paymentMethods from '@/constants/payment-method-constants';
|
||||||
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'payment-page',
|
name: 'payment-page',
|
||||||
|
|
@ -40,23 +184,47 @@ export default {
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form
|
Form
|
||||||
},
|
},
|
||||||
|
directives: {
|
||||||
|
resize: {
|
||||||
|
mounted(el) {
|
||||||
|
el.addEventListener('load', () => {
|
||||||
|
iframeResize({ checkOrigin: false }, el);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
el.iFrameResizer.removeListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
mixins: [BaseFormMixin],
|
mixins: [BaseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||||
|
|
||||||
|
const paymentSignaturePromise = await useMainStore().getPaymentSignature();
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: 'cmsContent',
|
resultKey: 'cmsContent',
|
||||||
promise: cmsContentPromise
|
promise: cmsContentPromise
|
||||||
}];
|
},
|
||||||
|
{
|
||||||
|
resultKey: 'paymentSignature',
|
||||||
|
promise: paymentSignaturePromise
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// use resultMap to populate layout content.
|
// use resultMap to populate layout content.
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
|
||||||
|
vm.$nextTick(() => {
|
||||||
|
vm.fetchSignatureInfo(resultMap.paymentSignature);
|
||||||
|
vm.setIFrameListener();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -64,20 +232,264 @@ export default {
|
||||||
return { mainStore };
|
return { mainStore };
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
return {
|
||||||
|
checkoutUrl: externalUrls.SAFELITE_HOP,
|
||||||
|
payInAdvanceResponseUrl: applicationConfig.PAY_IN_ADVANCE_RESPONSE_URL,
|
||||||
|
payInAdvanceCancelUrl: applicationConfig.PAY_IN_ADVANCE_CANCEL_URL,
|
||||||
|
paymentType: this.getPaymentType(),
|
||||||
|
authToken: '',
|
||||||
|
authSignature: '',
|
||||||
|
authSignatureStart: '',
|
||||||
|
referralSequenceNumber: useMainStore().order.referralSequenceNumber,
|
||||||
|
emailAddress: useMainStore().order.customer.emailAddress,
|
||||||
|
address1: this.getAddress1(),
|
||||||
|
address2: this.getAddress2(),
|
||||||
|
city: this.getCity(),
|
||||||
|
state: this.getState(),
|
||||||
|
zipCode: this.getZipCode(),
|
||||||
|
firstName: useMainStore().order.customer.firstName,
|
||||||
|
lastName: useMainStore().order.customer.lastName,
|
||||||
|
phoneNumber: useMainStore().order.customer.phoneNumber,
|
||||||
|
ctu: useMainStore().order.serviceLocation.zipCodeCtu,
|
||||||
|
referralCorrelationId: useMainStore().order.referralCorrelationId,
|
||||||
|
workOrderNumber: this.getWorkOrderNumber(),
|
||||||
|
invoiceNumber: this.getInvoiceNumber(),
|
||||||
|
totalAmount: this.getAmountDue(),
|
||||||
|
displayAmount: this.getDisplayAmountDue(),
|
||||||
|
payInAdvanceLineItems: '',
|
||||||
|
shouldBlockInteraction: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dynamicCSSUrl() {
|
||||||
|
const { protocol, hostname, port } = window.location;
|
||||||
|
return `${protocol}//${hostname}:${port}/css/site-2020Styling.css`;
|
||||||
|
},
|
||||||
|
dynamicHopCSSUrl() {
|
||||||
|
const { protocol, hostname, port } = window.location;
|
||||||
|
return `${protocol}//${hostname}:${port}/css/hop-styling.css`;
|
||||||
|
},
|
||||||
|
getHeaderLine1() {
|
||||||
|
if (!this.isPaypal) {
|
||||||
|
return this.getCmsContent('PayInAdvanceHeaderLine1', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getHeaderLine2() {
|
||||||
|
if (!this.isPaypal) {
|
||||||
|
return this.getCmsContent('PayInAdvanceHeaderLine2', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
isPaypal() {
|
||||||
|
if (this.paymentType === 'pp') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods:
|
methods:
|
||||||
{
|
{
|
||||||
forwardButtonAction() {
|
arePagePrerequisitesValid() {
|
||||||
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
// Line Items
|
||||||
},
|
const packageReqs = !!useMainStore().order.lineItems.supportingItems;
|
||||||
navigateForward() {
|
|
||||||
|
// Service Location
|
||||||
|
const { serviceLocation } = useMainStore().order;
|
||||||
|
const mobileReqs = !!(
|
||||||
|
serviceLocation.address
|
||||||
|
&& serviceLocation.city
|
||||||
|
&& serviceLocation.state
|
||||||
|
&& serviceLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
|
const providerLocation = serviceLocation.provider.address;
|
||||||
|
const dropOffInshopReqs = !!(
|
||||||
|
providerLocation.streetAddress
|
||||||
|
&& providerLocation.city
|
||||||
|
&& providerLocation.state
|
||||||
|
&& providerLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||||
|
|
||||||
|
const serviceLocationReqs =
|
||||||
|
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||||
|
|
||||||
|
// Insurance
|
||||||
|
const isInsuranceSet = useMainStore().order.payment.isInsurance !== null;
|
||||||
|
|
||||||
|
// Schedule
|
||||||
|
const { schedule } = useMainStore().order;
|
||||||
|
const scheduleReqs = !!(
|
||||||
|
schedule.date
|
||||||
|
&& schedule.startTime
|
||||||
|
&& schedule.endTime
|
||||||
|
&& schedule.jobMaxMinutes
|
||||||
|
&& schedule.jobMinMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Customer
|
||||||
|
const { customer } = useMainStore().order;
|
||||||
|
const customerReqs = !!(
|
||||||
|
customer.firstName
|
||||||
|
&& customer.lastName
|
||||||
|
&& customer.phoneNumber
|
||||||
|
&& customer.emailAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
const paymentMethodReqs =
|
||||||
|
useMainStore().order.payment.isPayInAdvance !== null
|
||||||
|
&& (useMainStore().order.payment.isPayInAdvance || !!useMainStore().order.payment.payInAdvanceType);
|
||||||
|
|
||||||
|
return (
|
||||||
|
packageReqs
|
||||||
|
&& serviceLocationReqs
|
||||||
|
&& isInsuranceSet
|
||||||
|
&& scheduleReqs
|
||||||
|
&& customerReqs
|
||||||
|
&& paymentMethodReqs
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getWorkOrderNumber() {
|
||||||
|
const { workOrderNumber } = useMainStore().order;
|
||||||
|
if (workOrderNumber) {
|
||||||
|
const items = workOrderNumber.split('-');
|
||||||
|
if (items.length > 1) {
|
||||||
|
return items[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//this.backButtonAction();
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
getInvoiceNumber() {
|
||||||
|
const { workOrderNumber } = useMainStore().order;
|
||||||
|
if (workOrderNumber) {
|
||||||
|
return workOrderNumber.replace('-', '');
|
||||||
|
}
|
||||||
|
//this.backButtonAction();
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
getAddress1() {
|
||||||
|
return useMainStore().order.serviceLocation.address
|
||||||
|
? useMainStore().order.serviceLocation.address
|
||||||
|
: useMainStore().order.serviceLocation.provider.address.streetAddress;
|
||||||
|
},
|
||||||
|
getAddress2() {
|
||||||
|
return useMainStore().order.serviceLocation.address2
|
||||||
|
? useMainStore().order.serviceLocation.address2
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
getCity() {
|
||||||
|
return useMainStore().order.serviceLocation.city
|
||||||
|
? useMainStore().order.serviceLocation.city
|
||||||
|
: useMainStore().order.serviceLocation.provider.address.city;
|
||||||
|
},
|
||||||
|
getState() {
|
||||||
|
return useMainStore().order.serviceLocation.state
|
||||||
|
? useMainStore().order.serviceLocation.state
|
||||||
|
: useMainStore().order.serviceLocation.provider.address.state;
|
||||||
|
},
|
||||||
|
getZipCode() {
|
||||||
|
return useMainStore().order.serviceLocation.zipCode
|
||||||
|
? useMainStore().order.serviceLocation.zipCode
|
||||||
|
: useMainStore().order.serviceLocation.provider.address.zipCode;
|
||||||
|
},
|
||||||
|
getPaymentType() {
|
||||||
|
switch (useMainStore().order.payment.payInAdvanceType) {
|
||||||
|
case paymentMethods.AFTERPAY:
|
||||||
|
return 'ap';
|
||||||
|
case paymentMethods.CREDIT_CARD:
|
||||||
|
return 'cc';
|
||||||
|
case paymentMethods.PAYPAL:
|
||||||
|
return 'pp';
|
||||||
|
default:
|
||||||
|
return useMainStore().payment.payInAdvanceType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAmountDue() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
getDisplayAmountDue() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
fetchSignatureInfo(signatureInfo) {
|
||||||
|
this.authToken = signatureInfo.token;
|
||||||
|
this.authSignature = signatureInfo.signature;
|
||||||
|
this.authSignatureStart = signatureInfo.startDate;
|
||||||
|
|
||||||
|
this.submitHopForm();
|
||||||
|
},
|
||||||
|
submitHopForm() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.hopForm.submit();
|
||||||
|
|
||||||
|
const iframe = this.$refs.paymentFrame;
|
||||||
|
if (iframe) {
|
||||||
|
iframe.onload = () => {
|
||||||
|
if (iframe.contentWindow) {
|
||||||
|
if (this.paymentType === 'cc') {
|
||||||
|
iframe.contentWindow.postMessage('CreditCardChosen', '*');
|
||||||
|
} else if (this.paymentType === 'ap') {
|
||||||
|
iframe.contentWindow.postMessage('afterpay', '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleIFrameContentWindowMessage(event) {
|
||||||
|
if (typeof event.data === 'string') {
|
||||||
|
if (event.data.indexOf('afterpayClosed') > -1) {
|
||||||
|
this.backButtonAction();
|
||||||
|
}
|
||||||
|
if (event.data.indexOf('creditCardSubmit') > -1) {
|
||||||
|
this.setUIBlock(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setIFrameListener() {
|
||||||
|
window.addEventListener('message', (event) =>
|
||||||
|
this.handleIFrameContentWindowMessage(event));
|
||||||
|
},
|
||||||
|
setUIBlock(val) {
|
||||||
|
this.shouldBlockInteraction = val;
|
||||||
|
},
|
||||||
|
supressClick(event) {
|
||||||
|
if (this.shouldBlockInteraction) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backButtonAction() {
|
||||||
|
// page will not render with normal navigation due to navigating from the iframe
|
||||||
|
window.location = this.payInAdvanceCancelUrl;
|
||||||
|
},
|
||||||
|
forwardButtonAction() {
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||||
|
},
|
||||||
|
navigateForward() {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.payment p{
|
.payment p{
|
||||||
margin-bottom:0.75rem;
|
margin-bottom:0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hop-iframe {
|
||||||
|
position: relative;
|
||||||
|
min-height: 40.625rem;
|
||||||
|
width: 100%;
|
||||||
|
border: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-block {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
5
src/router/router-constants/externalUrl-values.js
Normal file
5
src/router/router-constants/externalUrl-values.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const externalUrls = {
|
||||||
|
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP
|
||||||
|
};
|
||||||
|
|
||||||
|
export default externalUrls;
|
||||||
|
|
@ -2144,6 +2144,13 @@ export const useMainStore = defineStore({
|
||||||
const isPayInAdvance = paymentMethod !== paymentMethods.PAY_AT_TIME_OF_SERVICE;
|
const isPayInAdvance = paymentMethod !== paymentMethods.PAY_AT_TIME_OF_SERVICE;
|
||||||
this.order.payment.isPayInAdvance = isPayInAdvance;
|
this.order.payment.isPayInAdvance = isPayInAdvance;
|
||||||
this.order.payment.payInAdvanceType = isPayInAdvance ? paymentMethod : null;
|
this.order.payment.payInAdvanceType = isPayInAdvance ? paymentMethod : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPaymentSignature() {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetPaymentSignature.method,
|
||||||
|
endpoint: endpoints.GetPaymentSignature.url
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
process.env.VUE_APP_CONSUMER_CF_DISTRO = 'https://digitalapi.dev.safelite.io';
|
process.env.VUE_APP_CONSUMER_CF_DISTRO = 'https://digitalapi.dev.safelite.io';
|
||||||
process.env.VUE_APP_CURRENT_ENVIRONMENT = 'Localhost';
|
process.env.VUE_APP_CURRENT_ENVIRONMENT = 'Localhost';
|
||||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = 'AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0';
|
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = 'AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0';
|
||||||
|
process.env.VUE_APP_SAFELITE_HOP = 'https://sv2-safelitehop-sys.safelite.com/fmgCheckoutEmbedded.aspx';
|
||||||
|
|
||||||
// GA & GTM
|
// GA & GTM
|
||||||
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.
|
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue