SSR-1063 credit card form on payment-page

This commit is contained in:
Matt Caimi 2024-02-20 09:38:10 -05:00
parent e774babe0e
commit 32af347771
14 changed files with 1631 additions and 11 deletions

13
package-lock.json generated
View file

@ -11,6 +11,7 @@
"axios": "^1.4.0",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
"iframe-resizer": "^4.3.9",
"maska": "^1.5.0",
"pinia": "^2.1.4",
"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": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",

View file

@ -18,6 +18,7 @@
"axios": "^1.4.0",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
"iframe-resizer": "^4.3.9",
"maska": "^1.5.0",
"pinia": "^2.1.4",
"pinia-plugin-persistedstate": "^2.2.0",

13
public/css/README.md Normal file
View 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
View 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;
}

View 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;
}

View 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;
}
}
}

View 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;
}

View file

@ -1,3 +1,4 @@
/* eslint-disable no-restricted-globals */
/**
* @module applicationConfig
* @author T-Wrecks Team
@ -16,7 +17,17 @@ const applicationConfig = Object.freeze({
CLIENTTAG_QUERYSTRING: 'ClientTag',
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
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;

View file

@ -178,6 +178,10 @@ const endpoints = Object.freeze({
FinalDeductible: {
url: '/coverage/api/v1/coverage/final-deductible',
method: 'POST'
},
GetPaymentSignature: {
url: '/order/api/v1/order/sign',
method: 'POST'
}
});

View 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();
});
});
});

View file

@ -8,17 +8,155 @@
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<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
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
:isForwardActionDisabled="true"
isForwardButtonHidden="true"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
@backClicked="backButtonAction" />
</div>
</div>
<div
v-if="shouldBlockInteraction"
class="ui-block"
@click="supressClick"></div>
</div>
</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>
<script>
// 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';
// Supporting files
import applicationConfig from '@/constants/application-config.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
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 {
name: 'payment-page',
@ -40,23 +184,47 @@ export default {
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
directives: {
resize: {
mounted(el) {
el.addEventListener('load', () => {
iframeResize({ checkOrigin: false }, el);
});
},
unmounted(el) {
el.iFrameResizer.removeListeners();
}
}
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const paymentSignaturePromise = await useMainStore().getPaymentSignature();
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}];
},
{
resultKey: 'paymentSignature',
promise: paymentSignaturePromise
}
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$nextTick(() => {
vm.fetchSignatureInfo(resultMap.paymentSignature);
vm.setIFrameListener();
});
});
},
setup() {
@ -64,20 +232,264 @@ export default {
return { mainStore };
},
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:
{
forwardButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
navigateForward() {
{
arePagePrerequisitesValid() {
// Line Items
const packageReqs = !!useMainStore().order.lineItems.supportingItems;
// 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>
<style lang="scss" scoped>
.payment p{
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>

View file

@ -0,0 +1,5 @@
const externalUrls = {
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP
};
export default externalUrls;

View file

@ -2144,6 +2144,13 @@ export const useMainStore = defineStore({
const isPayInAdvance = paymentMethod !== paymentMethods.PAY_AT_TIME_OF_SERVICE;
this.order.payment.isPayInAdvance = isPayInAdvance;
this.order.payment.payInAdvanceType = isPayInAdvance ? paymentMethod : null;
},
getPaymentSignature() {
return globalMethods.callHttpClient({
method: endpoints.GetPaymentSignature.method,
endpoint: endpoints.GetPaymentSignature.url
});
}
},

View file

@ -2,6 +2,7 @@
process.env.VUE_APP_CONSUMER_CF_DISTRO = 'https://digitalapi.dev.safelite.io';
process.env.VUE_APP_CURRENT_ENVIRONMENT = 'Localhost';
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
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.