PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / StepForm / PaymentStep / PaymentStep.vue
ameliabooking / v3 / src / views / public / StepForm / PaymentStep Last commit date
parts 6 days ago PaymentStep.vue 6 days ago
PaymentStep.vue
729 lines
1 <template>
2 <div ref="paymentStepRef" :class="props.globalClass" :style="cssVars">
3 <div v-if="paymentError" class="am-fs__payments-error">
4 <AmAlert type="error" :title="paymentError" :show-icon="true" :closable="false"> </AmAlert>
5 </div>
6 <div v-show="ready && !loading" class="am-fs__payments">
7 <div class="am-fs__payments-heading">
8 <span class="am-fs__payments-heading-main">
9 {{ amLabels.summary }}
10 </span>
11 </div>
12
13 <div class="am-fs__payments-price">
14 <component
15 :is="componentTypes[formType]"
16 @set-on-site-payment="setOnSitePayment"
17 ></component>
18 </div>
19
20 <AmCheckbox
21 v-if="hasDeposit && fullAmount && paymentGateway !== 'onSite'"
22 v-model="paymentDeposit"
23 class="am-fs__payments-full"
24 :label="amLabels.full_amount_consent"
25 :class="{ 'am-fs__payments-full-checked': paymentDeposit }"
26 @keydown.enter="paymentDeposit = !paymentDeposit"
27 >
28 </AmCheckbox>
29
30 <p
31 v-show="Object.keys(availablePayments).filter((item) => availablePayments[item]).length > 1"
32 class="am-fs__payments-method"
33 >
34 {{ amLabels.payment_method }}
35 </p>
36 <div class="am-fs__payments-main">
37 <div class="am-fs__payments-main-cards">
38 <template v-for="(available, gateway) in availablePayments">
39 <div
40 v-if="
41 available &&
42 Object.keys(availablePayments).filter((item) => availablePayments[item]).length > 1
43 "
44 :key="gateway"
45 :class="
46 'am-fs__payments-main-button' +
47 (paymentGateway === gateway ? ' am-fs__payments-main-button_selected' : '') +
48 ' am-fs__payments-main-button-' +
49 gateway
50 "
51 tabindex="0"
52 @click="setPaymentGateway(gateway)"
53 @keydown.enter="setPaymentGateway(gateway)"
54 >
55 <img
56 v-if="available"
57 :src="
58 baseUrls.wpAmeliaPluginURL +
59 '/v3/src/assets/img/icons/' +
60 paymentsBtn.find((item) => item.key === gateway).value.name
61 "
62 :alt="gateway"
63 />
64 <div>
65 <p>{{ paymentsBtn.find((item) => item.key === gateway).value.text }}</p>
66 </div>
67 </div>
68 </template>
69 </div>
70 </div>
71 <div class="am-fs__payments-sentence">
72 <p>{{ getPaymentSentence() }}</p>
73 </div>
74 <PaymentOnSite
75 ref="refOnSitePayment"
76 :instant-booking="true"
77 :show-recaptcha="false"
78 @payment-error="callPaymentError"
79 />
80 <div v-if="paymentGateway" class="am-fs__payments-pm">
81 <component
82 :is="paymentTypes[paymentGateway]"
83 @payment-error="callPaymentError"
84 @payment-abandoned="callPaymentAbandoned"
85 />
86 </div>
87 </div>
88
89 <BookingSkeleton />
90 </div>
91 </template>
92
93 <script setup>
94 import { useStore } from 'vuex'
95 import { ref, inject, provide, markRaw, watch, computed, onMounted, reactive } from 'vue'
96 import AmCheckbox from '../../../_components/checkbox/AmCheckbox.vue'
97 import AmAlert from '../../../_components/alert/AmAlert.vue'
98 import AppointmentInfo from './parts/AppointmentInfo'
99 import CartInfo from './parts/CartInfo'
100 import PaymentStripe from '../../Parts/Payment/PaymentStripe.vue'
101 import PaymentPayPal from '../../Parts/Payment/PaymentPayPal.vue'
102 import PaymentOnSite from '../../Parts/Payment/PaymentOnSite.vue'
103 import PaymentWc from '../../Parts/Payment/PaymentWc.vue'
104 import PaymentCommon from '../../Parts/Payment/PaymentCommon.vue'
105 import PaymentSquare from '../../Parts/Payment/PaymentSquare.vue'
106 import BookingSkeleton from '../../Parts/BookingSkeleton.vue'
107 import PackageInfo from './parts/PackageInfo'
108 import { settings } from '../../../../plugins/settings'
109 import { useColorTransparency } from '../../../../assets/js/common/colorManipulation'
110 import { usePrepaidPrice, usePaymentError } from '../../../../assets/js/common/appointments'
111 import useAction from '../../../../assets/js/public/actions'
112 import { useCart, useCartItem, useCartHasItems } from '../../../../assets/js/public/cart'
113
114 let props = defineProps({
115 globalClass: {
116 type: String,
117 default: '',
118 },
119 })
120
121 // * Store
122 const store = useStore()
123 const baseUrls = inject('baseUrls')
124
125 // * Labels
126 const globalLabels = inject('labels')
127
128 // * local language short code
129 const localLanguage = inject('localLanguage')
130
131 // * if local lang is in settings lang
132 let langDetection = computed(() => settings.general.usedLanguages.includes(localLanguage.value))
133
134 // * Computed labels
135 let amLabels = computed(() => {
136 let computedLabels = reactive({ ...globalLabels })
137
138 if (settings.customizedData?.sbsNew?.paymentStep?.translations) {
139 let customizedLabels = settings.customizedData.sbsNew.paymentStep.translations
140 Object.keys(customizedLabels).forEach((labelKey) => {
141 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
142 computedLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
143 } else if (customizedLabels[labelKey].default) {
144 computedLabels[labelKey] = customizedLabels[labelKey].default
145 }
146 })
147 }
148 return computedLabels
149 })
150
151 provide('amLabels', amLabels)
152
153 // * Payment Part
154 const componentTypes = {
155 appointment: markRaw(AppointmentInfo),
156 package: markRaw(PackageInfo),
157 cart: markRaw(CartInfo),
158 }
159
160 let paymentError = computed(() => store.getters['booking/getError'])
161
162 // * Step Functions
163 const { footerBtnDisabledUpdater, headerButtonPreviousClicked, footerButtonClick } = inject(
164 'changingStepsFunctions',
165 {
166 footerBtnDisabledUpdater: () => {},
167 headerButtonPreviousClicked: {
168 value: false,
169 },
170 footerButtonClick: () => {},
171 },
172 )
173
174 watch(headerButtonPreviousClicked, () => {
175 store.commit('booking/setPaymentGateway', '')
176 })
177
178 let cart = computed(() => useCart(store))
179
180 const paymentTypes = {
181 onSite: markRaw(PaymentOnSite),
182 stripe: markRaw(PaymentStripe),
183 payPal: markRaw(PaymentPayPal),
184 razorpay: markRaw(PaymentCommon),
185 mollie: markRaw(PaymentCommon),
186 wc: markRaw(PaymentWc),
187 square: markRaw(PaymentSquare),
188 barion: markRaw(PaymentCommon),
189 }
190
191 let ready = computed(() => store.getters['entities/getReady'])
192
193 let loading = computed(() => store.getters['booking/getLoading'])
194
195 let formType = computed(() =>
196 cart.value.length > 1 ? 'cart' : store.getters['booking/getBookableType'],
197 )
198
199 let paymentGateway = computed(() => {
200 if (!store.getters['booking/getPaymentGateway']) {
201 store.commit('booking/setPaymentGateway', settings.defaultPaymentMethod)
202 }
203 return store.getters['booking/getPaymentGateway']
204 })
205
206 let hasDeposit = computed(() => {
207 switch (store.getters['booking/getBookableType']) {
208 case 'appointment': {
209 let depositPayment = false
210
211 useCart(store)
212 .filter((i) => 'services' in i && Object.keys(i.services).length)
213 .forEach((item) => {
214 let service = store.getters['entities/getCategories']
215 .find((i) => i.serviceList.filter((j) => j.id === item.serviceId).length)
216 .serviceList.find((i) => i.id === item.serviceId)
217
218 if (service.depositPayment !== 'disabled') {
219 depositPayment = true
220 }
221 })
222
223 return ready.value ? depositPayment : false
224 }
225
226 case 'package': {
227 let item = useCartItem(store)
228
229 let pack = store.getters['entities/getPackage'](item.packageId)
230
231 return ready.value ? pack.depositPayment !== 'disabled' : false
232 }
233 }
234
235 return false
236 })
237
238 provide('hasDeposit', hasDeposit)
239
240 let fullAmount = computed(() => {
241 switch (store.getters['booking/getBookableType']) {
242 case 'appointment': {
243 let fullAmountPayment = false
244
245 useCart(store)
246 .filter((i) => 'services' in i && Object.keys(i.services).length)
247 .forEach((item) => {
248 let service = store.getters['entities/getCategories']
249 .find((i) => i.serviceList.filter((j) => j.id === item.serviceId).length)
250 .serviceList.find((i) => i.id === item.serviceId)
251
252 if (service.fullPayment) {
253 fullAmountPayment = true
254 }
255 })
256
257 return ready.value ? fullAmountPayment : false
258 }
259
260 case 'package': {
261 let item = useCartItem(store)
262
263 let pack = store.getters['entities/getPackage'](item.packageId)
264
265 return ready.value ? pack.fullPayment : false
266 }
267 }
268
269 return false
270 })
271
272 // true = pay full amount
273 let paymentDeposit = computed({
274 get: () => store.getters['booking/getPaymentDeposit'],
275 set: (value) => store.commit('booking/setPaymentDeposit', value),
276 })
277
278 let paymentStepRef = ref(null)
279 provide('paymentStepRef', paymentStepRef)
280
281 let mandatoryOnSitePayment = ref(false)
282
283 let refOnSitePayment = ref(null)
284
285 function callPaymentError(msg) {
286 if (store.getters['payment/getOnSitePayment']) {
287 store.commit('payment/setOnSitePayment', false)
288 store.commit('booking/setPaymentGateway', 'onSite')
289 mandatoryOnSitePayment.value = true
290 refOnSitePayment.value.continueWithBooking()
291 }
292 usePaymentError(store, msg)
293
294 if (paymentStepRef.value) {
295 paymentStepRef.value.focus()
296 }
297 }
298
299 function callPaymentAbandoned() {
300 store.commit('booking/setLoading', false)
301 }
302
303 let availablePayments = computed(() => {
304 if (ready.value) {
305 if (mandatoryOnSitePayment.value) {
306 return {
307 onSite: true,
308 stripe: false,
309 payPal: false,
310 wc: false,
311 mollie: false,
312 square: false,
313 razorpay: false,
314 barion: false,
315 }
316 }
317
318 let entity = null
319
320 switch (store.getters['booking/getBookableType']) {
321 case 'appointment':
322 entity =
323 useCartHasItems(store) === 1
324 ? store.getters['entities/getBookableFromBookableEntities'](
325 store.getters['booking/getSelection'],
326 )
327 : null
328
329 break
330
331 case 'package':
332 entity = store.getters['entities/getPackage'](store.getters['booking/getPackageId'])
333
334 break
335 }
336
337 let entityPayments = entity && entity.settings ? JSON.parse(entity.settings)['payments'] : null
338
339 let payments = !entityPayments
340 ? {
341 onSite: settings.payments.onSite,
342 stripe: settings.payments.stripe.enabled,
343 payPal: settings.payments.payPal.enabled,
344 wc: settings.payments.wc.enabled,
345 mollie: settings.payments.mollie.enabled,
346 square: settings.payments.square.enabled,
347 razorpay: settings.payments.razorpay.enabled,
348 barion: settings.payments.barion.enabled,
349 }
350 : {
351 onSite:
352 'onSite' in entityPayments
353 ? entityPayments.onSite && settings.payments.onSite
354 : settings.payments.onSite,
355 stripe:
356 'stripe' in entityPayments
357 ? entityPayments.stripe.enabled && settings.payments.stripe.enabled
358 : settings.payments.stripe.enabled,
359 payPal:
360 'payPal' in entityPayments
361 ? entityPayments.payPal.enabled && settings.payments.payPal.enabled
362 : settings.payments.payPal.enabled,
363 wc:
364 'wc' in entityPayments
365 ? (!('enabled' in entityPayments.wc) || entityPayments.wc.enabled) &&
366 settings.payments.wc.enabled
367 : settings.payments.wc.enabled,
368 mollie:
369 'mollie' in entityPayments
370 ? entityPayments.mollie.enabled && settings.payments.mollie.enabled
371 : settings.payments.mollie.enabled,
372 razorpay:
373 'razorpay' in entityPayments
374 ? entityPayments.razorpay.enabled && settings.payments.razorpay.enabled
375 : settings.payments.razorpay.enabled,
376 square:
377 'square' in entityPayments
378 ? entityPayments.square.enabled && settings.payments.square.enabled
379 : settings.payments.square.enabled,
380 barion:
381 'barion' in entityPayments
382 ? entityPayments.barion.enabled &&
383 settings.payments.barion.enabled &&
384 ['USD', 'EUR', 'HUF', 'CZK'].includes(settings.payments.currencyCode)
385 : settings.payments.barion.enabled &&
386 ['USD', 'EUR', 'HUF', 'CZK'].includes(settings.payments.currencyCode),
387 }
388
389 if (
390 !payments.onSite &&
391 !payments.stripe &&
392 !payments.payPal &&
393 !payments.wc &&
394 !payments.mollie &&
395 !payments.square &&
396 !payments.razorpay &&
397 !payments.barion
398 ) {
399 payments = {
400 onSite: settings.payments.onSite,
401 stripe: settings.payments.stripe.enabled,
402 payPal: settings.payments.payPal.enabled,
403 wc: settings.payments.wc.enabled,
404 mollie: settings.payments.mollie.enabled,
405 square: settings.payments.square.enabled,
406 razorpay: settings.payments.razorpay.enabled,
407 barion: settings.payments.barion.enabled,
408 }
409 }
410
411 if (
412 settings.payments.defaultPaymentMethod &&
413 payments[settings.payments.defaultPaymentMethod]
414 ) {
415 store.commit('booking/setPaymentGateway', settings.payments.defaultPaymentMethod)
416
417 footerBtnDisabledUpdater(false)
418 } else {
419 if (Object.keys(payments).filter((item) => payments[item]).length === 1) {
420 for (let key in payments) {
421 if (payments[key]) {
422 store.commit('booking/setPaymentGateway', key)
423
424 footerBtnDisabledUpdater(false)
425 }
426 }
427 }
428 }
429
430 return payments
431 }
432
433 return {}
434 })
435
436 function setPaymentGateway(gateway) {
437 store.commit('booking/setPaymentGateway', gateway)
438
439 footerBtnDisabledUpdater(false)
440 }
441
442 function setOnSitePayment(isMandatory) {
443 if (settings.payments.wc.enabled ? settings.payments.wc.onSiteIfFree : true) {
444 if (isMandatory) {
445 store.commit('booking/setPaymentGateway', 'onSite')
446 }
447
448 mandatoryOnSitePayment.value = isMandatory
449 }
450 }
451
452 onMounted(() => {
453 if (paymentStepRef.value) {
454 paymentStepRef.value.focus()
455 }
456
457 const isPaymentStatusError =
458 paymentError.value &&
459 (paymentError.value === globalLabels.payment_canceled ||
460 paymentError.value === globalLabels.payment_error ||
461 paymentError.value === globalLabels.payment_error_default)
462
463 // If it's a payment status error (canceled/failed) delay clear for 3s
464 if (!isPaymentStatusError) {
465 usePaymentError(store, '')
466 } else {
467 setTimeout(() => {
468 usePaymentError(store, '')
469 }, 3000)
470 }
471
472 if (usePrepaidPrice(store) === 0) {
473 if (settings.payments.wc.enabled === true && !settings.payments.wc.onSiteIfFree) {
474 store.commit('booking/setPaymentGateway', 'wc')
475 } else {
476 store.commit('booking/setPaymentGateway', 'onSite')
477 }
478
479 footerButtonClick()
480 }
481
482 useAction(store, {}, 'InitiateCheckout', formType.value, null, null)
483 })
484
485 let amColors = inject('amColors')
486
487 const cssVars = computed(() => {
488 return {
489 '--am-c-ps-price-bgr': useColorTransparency(amColors.value.colorBtnPrim, 0.05),
490 '--am-c-ps-total-price': amColors.value.colorBtnPrim,
491 '--am-c-ps-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
492 '--am-c-ps-text-op50': useColorTransparency(amColors.value.colorMainText, 0.5),
493 '--am-c-ps-text-op25': useColorTransparency(amColors.value.colorMainText, 0.25),
494 '--am-c-ps-text-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
495 '--am-c-ps-text-op06': useColorTransparency(amColors.value.colorMainText, 0.06),
496 '--am-c-ps-primary': amColors.value.colorPrimary,
497 '--am-c-ps-primary-op10': useColorTransparency(amColors.value.colorPrimary, 0.1),
498 '--am-c-ps-primary-op06': useColorTransparency(amColors.value.colorPrimary, 0.06),
499 }
500 })
501
502 let paymentsBtn = []
503
504 Object.keys(availablePayments.value).forEach((key) => {
505 paymentsBtn.push({ key, value: getPaymentBtn(key) })
506 })
507
508 function getPaymentBtn(key) {
509 switch (key) {
510 case 'onSite':
511 return { text: amLabels.value['on_site'], name: 'onSite.svg' }
512 case 'payPal':
513 return { text: amLabels.value['pay_pal'], name: 'payPal.svg' }
514 case 'stripe':
515 return { text: amLabels.value['stripe'], name: 'stripe.svg' }
516 case 'razorpay':
517 return { text: amLabels.value['razorpay'], name: 'razorpay.svg' }
518 case 'mollie':
519 return { text: amLabels.value['mollie'], name: 'mollie.svg' }
520 case 'wc':
521 return { text: amLabels.value['on_line'], name: 'online.svg' }
522 case 'square':
523 return { text: amLabels.value['square'], name: 'square.svg' }
524 case 'barion':
525 return { text: amLabels.value['barion'], name: 'barion.svg' }
526 default:
527 return ''
528 }
529 }
530
531 function getPaymentSentence() {
532 return paymentGateway.value === 'onSite' && !mandatoryOnSitePayment.value
533 ? amLabels.value.payment_onsite_sentence
534 : paymentGateway.value === 'mollie' ||
535 paymentGateway.value === 'wc' ||
536 paymentGateway.value === 'barion'
537 ? amLabels.value.payment_wc_mollie_sentence
538 : ''
539 }
540 </script>
541
542 <script>
543 export default {
544 name: 'PaymentStep',
545 key: 'paymentStep',
546 sidebarData: {
547 label: 'payment_step',
548 icon: 'payment',
549 stepSelectedData: [],
550 finished: false,
551 selected: false,
552 },
553 }
554 </script>
555
556 <style lang="scss">
557 .amelia-v2-booking #amelia-container {
558 .am-fs__main-content.am-fs__payments {
559 padding-top: 0;
560 }
561
562 .am-fs__payments {
563 --am-c-ps-text: var(--am-c-main-text);
564 --am-c-ps-border: var(--am-c-inp-border);
565 & > * {
566 $count: 6;
567 @for $i from 0 through $count {
568 &:nth-child(#{$i + 1}) {
569 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{$i * 100}ms am-animation-slide-up;
570 animation-fill-mode: both;
571 }
572 }
573 }
574
575 &-heading {
576 font-size: 15px;
577 font-style: normal;
578 font-weight: 500;
579 line-height: 1.6;
580 color: var(--am-c-ps-text);
581 margin-bottom: 4px;
582 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{100}ms am-animation-slide-up;
583 animation-fill-mode: both;
584 }
585
586 &-price {
587 border: 1px solid var(--am-c-ps-text-op20);
588 border-radius: 8px;
589 padding: 16px;
590 margin-bottom: 16px;
591 }
592
593 &-sentence {
594 display: flex;
595 justify-content: center;
596 margin-top: 2px;
597 p {
598 display: inline-flex;
599 font-size: 14px;
600 font-weight: 500;
601 line-height: 1.42857;
602 color: var(--am-c-ps-text-op60);
603 }
604 }
605
606 &-full {
607 color: var(--am-c-ps-text);
608 border: 1px solid var(--am-c-ps-text-op25);
609 border-radius: 8px;
610 box-shadow: 0 1px 1px var(--am-c-ps-text-op06);
611 box-sizing: border-box;
612 padding: 12px;
613 margin-bottom: 16px;
614
615 &-checked {
616 border: 1px solid var(--am-c-ps-primary);
617 background: var(--am-c-ps-primary-op10);
618
619 &:after {
620 transform: rotate(45deg) scaleY(1);
621 }
622 }
623
624 .el-checkbox {
625 &__input {
626 height: 32px;
627 line-height: 32px;
628 align-items: center;
629 }
630
631 &__label {
632 line-height: 32px;
633 align-items: center;
634 }
635 }
636 }
637
638 &-method {
639 font-size: 18px;
640 font-weight: 500;
641 line-height: 1.55555;
642 color: var(--am-c-ps-text);
643 margin-bottom: 8px;
644 }
645
646 &-error {
647 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{100}ms am-animation-slide-up;
648 animation-fill-mode: both;
649 margin-bottom: 10px;
650 }
651
652 &-main {
653 &-cards {
654 display: flex;
655 gap: 6px;
656 justify-items: center;
657 flex-wrap: wrap;
658 }
659
660 &-button {
661 display: flex;
662 align-items: center;
663 gap: 2px;
664 min-width: 108px;
665 border: 1px solid var(--am-c-ps-text-op25);
666 border-radius: 8px;
667 box-shadow: 0 1px 1px var(--am-c-ps-text-op06);
668 box-sizing: border-box;
669 padding: 12px;
670 cursor: pointer;
671 flex-direction: column;
672 justify-items: center;
673 height: fit-content;
674
675 &:focus {
676 border: 1px solid var(--am-c-ps-primary);
677 box-shadow: 0 1px 1px var(--am-c-ps-primary-op06);
678 }
679
680 img {
681 height: 24px;
682 width: 24px;
683 }
684
685 &-barion {
686 img {
687 height: 24px;
688 width: 50px;
689 }
690 }
691
692 div {
693 p {
694 font-weight: 500;
695 font-size: 15px;
696 line-height: 24px;
697 color: var(--am-c-ps-text);
698 margin: 0;
699 text-align: center;
700 word-break: break-all;
701 }
702 span {
703 font-size: 12px;
704 font-weight: 400;
705 line-height: 1.66666;
706 color: var(--am-c-ps-text-op50);
707 }
708 }
709
710 &_selected {
711 background: var(--am-c-ps-primary-op10);
712 border: 1px solid var(--am-c-ps-primary);
713 box-sizing: border-box;
714 box-shadow: 0 1px 1px var(--am-c-ps-primary-op06);
715 }
716 }
717 }
718 // pm - payment method
719 &-pm {
720 margin-top: 24px;
721 }
722
723 .el-checkbox__label {
724 font-size: 15px;
725 }
726 }
727 }
728 </style>
729