@viur/shop-components 0.9.5 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viur/shop-components",
3
- "version": "0.9.5",
3
+ "version": "0.11.0",
4
4
  "description": "Frontend Vue components for the shop module of ViUR",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,7 +5,6 @@
5
5
  :nextfunction="nextStep"
6
6
  >
7
7
  </slot>
8
-
9
8
  <loading-handler :isLoading="cartState.isLoading" :isUpdating="cartState.isUpdating">
10
9
  <div class="viur-shop-item-wrapper" v-if="state.items.length>0">
11
10
  <cart-item v-for="item in state.items" :item="item"
@@ -55,7 +54,7 @@ const props = defineProps({
55
54
  })
56
55
 
57
56
  const state = reactive({
58
- items: computed(()=>shopStore.state.cartList.filter(i=>i['skel_type']==='leaf'))
57
+ items: computed(()=>shopStore.state.cartList)
59
58
  })
60
59
 
61
60
  onBeforeMount(()=>{
@@ -36,9 +36,10 @@
36
36
  import { reactive } from "vue";
37
37
  import { useViurShopStore } from "../shop";
38
38
  import { useCart } from '../composables/cart';
39
+ import {useI18n} from 'vue-i18n';
39
40
  const shopStore = useViurShopStore();
40
41
  const {addDiscount, removeDiscount} = useCart()
41
-
42
+ const i18n = useI18n();
42
43
  import ShopAlert from "./ShopAlert.vue";
43
44
 
44
45
  const state = reactive({
@@ -56,7 +57,7 @@ function addDiscountAction() {
56
57
  .catch((error) => {
57
58
  state.loading=false
58
59
  console.log("error bei rabatt", error);
59
- state.alert.msg = "not found";
60
+ state.alert.msg = i18n.t('viur.shop.discount_not_found');
60
61
  state.alert.show = true;
61
62
  state.alert.variant = "danger";
62
63
  });
@@ -74,7 +75,7 @@ function removeDiscountAction(){
74
75
  .catch((error) => {
75
76
  state.loading=false
76
77
  console.log("error bei rabatt", error);
77
- state.alert.msg = "not found";
78
+ state.alert.msg = $t('viur.shop.discount_not_found');
78
79
  state.alert.show = true;
79
80
  state.alert.variant = "danger";
80
81
  });
@@ -1,93 +1,108 @@
1
1
  <template>
2
- <div class="loading-wrapper" v-if="state.loading">
3
- <sl-spinner class="loading"></sl-spinner>
4
- </div>
5
-
6
- <div class="loading-wrapper" v-if="PaymentCheckIsActive">
7
- <sl-spinner class="loading"></sl-spinner>
8
- {{ $t('messages.wait_for_payment') }}
9
- </div>
10
- <div class="form-wrapper">
11
- <sl-alert :open="state.hasError" variant="danger">{{state.errorMessage}}</sl-alert>
12
- <form class="unzerUI form" novalidate>
13
- <template v-if="shopStore.state.order?.['payment_provider'] === 'unzer-card'">
14
- <div class="field">
15
- <div id="card-element-id-number" class="unzerInput">
16
- <!-- Card number UI Element is inserted here. -->
17
- </div>
18
- </div>
19
- <div class="two fields">
20
- <div class="field ten wide">
21
- <div id="card-element-id-expiry" class="unzerInput">
22
- <!-- Card expiry date UI Element is inserted here. -->
23
- </div>
24
- </div>
25
- <div class="field six wide">
26
- <div id="card-element-id-cvc" class="unzerInput">
27
- <!-- Card CVC UI Element is inserted here. -->
28
- </div>
29
- </div>
30
- </div>
31
- </template>
32
-
33
- <template v-else-if="shopStore.state.order?.['payment_provider'] === 'unzer-paypal'">
34
- <sl-alert open variant="danger">{{$t('viur.shop.paypal_client_popup_info')}}</sl-alert>
35
- <div id="paypal-element" class="field"></div>
36
- </template>
37
-
38
- <template v-else-if="shopStore.state.order?.['payment_provider'] === 'unzer-ideal'">
39
- <div id="ideal-element" class="field"></div>
40
- </template>
41
- <p
42
- v-if="!!shopStore.state?.paymentProviderData?.redirectUrl"
43
- v-html="$t('viur.shop.payment_link', {url: shopStore.state.paymentProviderData.redirectUrl})"
44
- />
45
- </form>
46
-
47
- <button :disabled="state.loading" class="unzerUI primary button fluid" @click="submitFormToUnzer">{{ $t('viur.shop.pay') }}</button>
48
- <sl-button :disabled="state.loading" variant="danger" @click="cancelPayment">{{ $t('actions.cancel') }}</sl-button>
49
- </div>
2
+ <div class="loading-wrapper" v-if="state.loading">
3
+ <sl-spinner class="loading"></sl-spinner>
4
+ </div>
5
+
6
+ <div class="loading-wrapper" v-if="PaymentCheckIsActive">
7
+ <sl-spinner class="loading"></sl-spinner>
8
+ {{ $t('messages.wait_for_payment') }}
9
+ </div>
10
+ <div class="form-wrapper">
11
+ <sl-alert :open="state.hasError" variant="danger">{{ state.errorMessage }}</sl-alert>
12
+ <form class="unzerUI form" novalidate>
13
+ <template v-if="shopStore.state.order?.['payment_provider'] === 'unzer-card'">
14
+ <div class="field">
15
+ <div id="card-element-id-number" class="unzerInput">
16
+ <!-- Card number UI Element is inserted here. -->
17
+ </div>
18
+ </div>
19
+ <div class="two fields">
20
+ <div class="field ten wide">
21
+ <div id="card-element-id-expiry" class="unzerInput">
22
+ <!-- Card expiry date UI Element is inserted here. -->
23
+ </div>
24
+ </div>
25
+ <div class="field six wide">
26
+ <div id="card-element-id-cvc" class="unzerInput">
27
+ <!-- Card CVC UI Element is inserted here. -->
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <template v-else-if="shopStore.state.order?.['payment_provider'] === 'unzer-paypal'">
34
+ <sl-alert open variant="danger">{{ $t('viur.shop.paypal_client_popup_info') }}</sl-alert>
35
+ <div id="paypal-element" class="field"></div>
36
+ </template>
37
+
38
+ <template v-else-if="shopStore.state.order?.['payment_provider'] === 'unzer-ideal'">
39
+ <div id="ideal-element" class="field"></div>
40
+ </template>
41
+
42
+ <template v-else-if="shopStore.state.order?.['payment_provider'] === 'unzer-googlepay'">
43
+ <div id="googlepay-element" class="field"></div>
44
+ </template>
45
+ <p
46
+ v-if="!!shopStore.state?.paymentProviderData?.redirectUrl"
47
+ v-html="$t('viur.shop.payment_link', {url: shopStore.state.paymentProviderData.redirectUrl})"
48
+ />
49
+ </form>
50
+
51
+ <button
52
+ v-if="shopStore.state.order?.['payment_provider'] !== 'unzer-googlepay'"
53
+ :disabled="state.loading"
54
+ class="unzerUI primary button fluid"
55
+ @click="submitFormToUnzer"
56
+ >{{ $t('viur.shop.pay') }}
57
+ </button>
58
+ <sl-button :disabled="state.loading" variant="danger" @click="cancelPayment">
59
+ {{ $t('actions.cancel') }}
60
+ </sl-button>
61
+ </div>
50
62
  </template>
51
63
 
52
64
  <script setup>
53
- import {computed, onBeforeMount, onMounted, reactive} from 'vue'
54
- import { useViurShopStore } from '../shop';
55
- import { useOrder } from '../composables/order';
56
- import {Request} from '@viur/vue-utils'
57
- import { useIntervalFn } from '@vueuse/core'
58
- const shopStore = useViurShopStore()
59
- const {fetchOrder} = useOrder()
60
-
61
- const emits = defineEmits(['cancel'])
62
-
63
- const { pause:PaymentCheckPause, resume:PaymentCheckResume, isActive:PaymentCheckIsActive } = useIntervalFn(() => {
64
- console.debug('checking ...')
65
-
66
- fetchOrder(shopStore.state.orderKey).then(()=>{
67
- if (shopStore.state.order?.['is_paid']){
68
- console.debug('Order is paid')
69
- shopStore.navigateToTab('complete');
70
- PaymentCheckPause();
71
- }
72
- })
65
+ import {Request} from '@viur/vue-utils';
66
+ import {useIntervalFn} from '@vueuse/core';
67
+ import {computed, onBeforeMount, reactive} from 'vue';
68
+ import {useOrder} from '../composables/order';
69
+ import {useViurShopStore} from '../shop';
73
70
 
74
- }, 2000,{immediate:false})
71
+ const shopStore = useViurShopStore();
72
+ const {fetchOrder} = useOrder();
73
+
74
+ const emits = defineEmits(['cancel']);
75
+
76
+ const {pause: PaymentCheckPause, resume: PaymentCheckResume, isActive: PaymentCheckIsActive} = useIntervalFn(() => {
77
+ console.debug('checking ...');
78
+
79
+ fetchOrder(shopStore.state.orderKey).then(() => {
80
+ if (shopStore.state.order?.['is_paid']) {
81
+ console.debug('Order is paid');
82
+ shopStore.navigateToTab('complete');
83
+ PaymentCheckPause();
84
+ }
85
+ });
86
+
87
+ }, 2000, {immediate: false});
75
88
 
76
89
 
77
90
  const state = reactive({
78
- unzer:computed(()=>{
79
- if (!shopStore.state.paymentProviderData) return null
80
- return new unzer(shopStore.state.paymentProviderData["public_key"], {locale: shopStore.state.locale})
91
+ unzer: computed(() => {
92
+ if (!shopStore.state.paymentProviderData) return null;
93
+ return new unzer(shopStore.state.paymentProviderData['public_key'], {locale: shopStore.state.locale});
81
94
  }),
82
- paymentHandler:{},
83
- loading:false,
84
- hasError:false,
95
+ paymentHandler: {},
96
+ loading: false,
97
+ hasError: false,
85
98
  errorMessage: null,
86
- waitPayment:false
87
- })
88
-
99
+ waitPayment: false,
100
+ });
89
101
 
90
- function initUnzerForm(){
102
+ /**
103
+ * Initialize the payment type creation with Unzer-UI components
104
+ */
105
+ function initUnzerForm() {
91
106
  //Unzer field definition
92
107
  if (shopStore.state.order?.['payment_provider'] === 'unzer-card') {
93
108
  const card = state.unzer.Card();
@@ -106,21 +121,20 @@ function initUnzerForm(){
106
121
  containerId: 'card-element-id-cvc',
107
122
  onlyIframe: false,
108
123
  });
109
-
110
- state.paymentHandler['unzer-card'] = card
124
+ state.paymentHandler['unzer-card'] = card;
111
125
  } else if (shopStore.state.order?.['payment_provider'] === 'unzer-paypal') {
112
126
  // Creating a PayPal instance
113
- const paypal = state.unzer.Paypal()
127
+ const paypal = state.unzer.Paypal();
114
128
  // Rendering input field
115
129
  //paypal.create('email', {
116
130
  // containerId: 'paypal-element',
117
131
  //});
118
- state.paymentHandler['unzer-paypal']= paypal;
132
+ state.paymentHandler['unzer-paypal'] = paypal;
119
133
  } else if (shopStore.state.order?.['payment_provider'] === 'unzer-sofort') {
120
- const sofort = state.unzer.Sofort()
134
+ const sofort = state.unzer.Sofort();
121
135
  state.paymentHandler['unzer-sofort'] = sofort;
122
136
  } else if (shopStore.state.order?.['payment_provider'] === 'unzer-ideal') {
123
- const ideal = state.unzer.Ideal()
137
+ const ideal = state.unzer.Ideal();
124
138
  ideal.create('ideal', {
125
139
  containerId: 'ideal-element',
126
140
  });
@@ -128,96 +142,176 @@ function initUnzerForm(){
128
142
  } else if (shopStore.state.order?.['payment_provider'] === 'unzer-bancontact') {
129
143
  const bancontact = state.unzer.Bancontact();
130
144
  state.paymentHandler['unzer-bancontact'] = bancontact;
145
+ } else if (shopStore.state.order?.['payment_provider'] === 'unzer-googlepay') {
146
+ const googlepayScriptPromise = new Promise((resolve, reject) => {
147
+ const googlepayScript = document.createElement('script');
148
+ googlepayScript.setAttribute('src', 'https://pay.google.com/gp/p/js/pay.js');
149
+ googlepayScript.onload = () => {
150
+ resolve(googlepayScript);
151
+ };
152
+ googlepayScript.onerror = reject;
153
+ document.head.appendChild(googlepayScript);
154
+ });
155
+ const googlepay = state.unzer.Googlepay();
156
+ state.paymentHandler['unzer-googlepay'] = googlepay;
157
+ const paymentDataRequestObject = googlepay.initPaymentDataRequestObject({
158
+ gatewayMerchantId: shopStore.state.paymentProviderData.channel,
159
+ merchantInfo: {
160
+ merchantId: shopStore.state.paymentProviderData.merchant_id,
161
+ merchantName: shopStore.state.paymentProviderData.merchant_name,
162
+ },
163
+ transactionInfo: {
164
+ countryCode: shopStore.state.order.billing_address.dest['country'].toUpperCase(),
165
+ currencyCode: 'EUR',
166
+ totalPrice: shopStore.state.order.total.toString(),
167
+ totalPriceStatus: 'ESTIMATED', // backend should have the last words
168
+ // totalPriceStatus: 'FINAL',
169
+ // checkoutOption: 'COMPLETE_IMMEDIATE_PURCHASE',
170
+ },
171
+ // buttonColor: 'white',
172
+ allowedCardNetworks: shopStore.state.paymentProviderData.brands,
173
+ allowCreditCards: shopStore.state.paymentProviderData.allow_credit_cards,
174
+ allowPrepaidCards: shopStore.state.paymentProviderData.allow_prepaid_cards,
175
+
176
+ onPaymentAuthorizedCallback: (paymentData) => {
177
+ PaymentCheckPause();
178
+ state.loading = true;
179
+ state.hasError = false;
180
+
181
+ return googlepay.createResource(paymentData)
182
+ .then(result => {
183
+ console.debug(result);
184
+ saveType(result.id);
185
+ return {status: 'success'}; // Tell Google Pay we could handle it
186
+ })
187
+ .catch(function (error) {
188
+ paymentError(error);
189
+ return { // Tell Google Pay we could NOT handle it
190
+ status: 'error',
191
+ message: error.customerMessage || error.message || error || 'Unexpected error',
192
+ };
193
+ });
194
+ },
195
+ });
196
+ console.debug('unzer-googlepay', googlepay, paymentDataRequestObject);
197
+ // TODO: How can we catch >Uncaught (in promise) AbortError: User closed the Payment Request UI.< ?
198
+ googlepayScriptPromise.then(() => {
199
+ // gpay script has been loaded, we can start the payment
200
+ googlepay.create(
201
+ {containerId: 'googlepay-element'},
202
+ paymentDataRequestObject,
203
+ );
204
+ });
131
205
  } else {
132
206
  console.warn(`Unknown payment provider: ${shopStore.state.order?.['payment_provider']}`);
133
207
  }
134
- state.loading = false
208
+ state.loading = false;
135
209
  }
136
210
 
137
- function paymentError(error){
138
- console.error(error)
139
- state.loading = false
140
- state.hasError = true
141
- state.errorMessage = error.customerMessage || error.message || error || 'Error';
211
+ /**
212
+ * Handle an error
213
+ *
214
+ * @param error The error. In best cases an object from unzer
215
+ * with a translated (and detailed) description for the customer
216
+ */
217
+ function paymentError(error) {
218
+ console.error(error);
219
+ state.loading = false;
220
+ state.hasError = true;
221
+ state.errorMessage = error.customerMessage || error.message || error || 'Error';
142
222
  }
143
223
 
224
+ /**
225
+ * Create the payment Type (ressource) on behalf of the Unzer-UI components.
226
+ *
227
+ * This function is used by all unzer pyment types, except Google Pay.
228
+ */
229
+ function submitFormToUnzer() {
230
+ PaymentCheckPause();
231
+ state.loading = true;
232
+ state.hasError = false;
233
+ state.paymentHandler[shopStore.state.order?.['payment_provider']].createResource().then((result) => {
234
+ saveType(result.id);
235
+ }).catch((error) => {
236
+ paymentError(error);
237
+ });
238
+ }
144
239
 
145
- function submitFormToUnzer(){
146
- PaymentCheckPause()
147
- state.loading = true
148
- state.hasError = false
149
- let paymenttarget = shopStore.state.order?.['payment_provider'].split("-")[1]
150
- //send to unzer
151
- state.paymentHandler[shopStore.state.order?.['payment_provider']].createResource().then((result)=>{
152
- Request.post(`${shopStore.state.shopUrl}/pp_unzer_${paymenttarget}/save_type`, {dataObj:{
240
+ /**
241
+ * Save type-id on the order
242
+ *
243
+ * After the Unzer-UI components have collected the customer data and created the payment Type this function
244
+ * has to be called to send the type-id to the viur-shop backend and store it.
245
+ * @param typeId The type-id of the created Type, e.g. ``s-crd-abc123def456``
246
+ */
247
+ function saveType(typeId) {
248
+ const paymenttarget = shopStore.state.order?.['payment_provider'].split('-')[1];
249
+ Request.post(`${shopStore.state.shopUrl}/pp_unzer_${paymenttarget}/save_type`, {
250
+ dataObj: {
153
251
  order_key: shopStore.state.orderKey,
154
- type_id: result.id,
155
- }}).then(async (resp)=>{
156
- let data = await resp.json()
157
- shopStore.state.order = data
158
-
159
- shopStore.checkoutOrder().then((resp)=>{
160
- state.loading = false
161
- state.hasError = false
162
- if(shopStore.state.paymentProviderData?.redirectUrl){
163
- state.waitPayment = true
164
- window.open(shopStore.state.paymentProviderData.redirectUrl,"_blank","popup")
165
- PaymentCheckResume()
166
- }
167
- }).catch(async (error)=>{
168
- paymentError(error)
169
- })
170
-
171
- }).catch(error => {
172
- paymentError(error)
173
- })
174
- }).catch((error)=> {
175
- paymentError(error)
176
- })
252
+ type_id: typeId,
253
+ },
254
+ }).then(async (resp) => {
255
+ shopStore.state.order = await resp.json();
256
+ shopStore.checkoutOrder().then((resp) => {
257
+ state.loading = false;
258
+ state.hasError = false;
259
+ if (shopStore.state.paymentProviderData?.redirectUrl) {
260
+ state.waitPayment = true;
261
+ window.open(shopStore.state.paymentProviderData.redirectUrl, '_blank', 'popup');
262
+ PaymentCheckResume();
263
+ }
264
+ }).catch(async (error) => {
265
+ paymentError(error);
266
+ });
267
+
268
+ }).catch(error => {
269
+ paymentError(error);
270
+ });
177
271
  }
178
272
 
179
273
 
180
- function cancelPayment(){
181
- PaymentCheckPause()
182
- emits('cancel')
274
+ function cancelPayment() {
275
+ PaymentCheckPause();
276
+ emits('cancel');
183
277
  }
184
278
 
185
- onBeforeMount(()=>{
186
- state.loading = true
187
- if (!shopStore.state.paymentProviderData){
188
- shopStore.checkout().then(()=>{
189
- initUnzerForm()
190
- }).catch((error)=>{
191
- console.log(error)
192
- })
193
- }else{
194
- initUnzerForm()
279
+ onBeforeMount(() => {
280
+ state.loading = true;
281
+ if (!shopStore.state.paymentProviderData) {
282
+ shopStore.checkout().then(() => {
283
+ initUnzerForm();
284
+ }).catch((error) => {
285
+ console.log(error);
286
+ });
287
+ } else {
288
+ initUnzerForm();
195
289
  }
196
- })
290
+ });
197
291
 
198
292
  </script>
199
293
 
200
294
  <style scoped>
201
- .loading-wrapper{
202
- display:flex;
203
- flex-direction: column;
204
- align-items: center;
295
+ .loading-wrapper {
296
+ display: flex;
297
+ flex-direction: column;
298
+ align-items: center;
205
299
  }
206
300
 
207
- .form-wrapper{
208
- display:flex;
209
- flex-direction: column;
210
- gap:20px;
301
+ .form-wrapper {
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: 20px;
211
305
  }
212
306
 
213
- .loading{
214
- font-size:3rem;
307
+ .loading {
308
+ font-size: 3rem;
215
309
  }
216
310
  </style>
217
311
 
218
312
  <style>
219
313
  /* global style to overwrite UnzerCSS */
220
314
  .unzerUI.primary.button, .unzerUI.primary.buttons .button {
221
- background-color: var(--ignt-color-primary) !important;
315
+ background-color: var(--ignt-color-primary) !important;
222
316
  }
223
317
  </style>
@@ -69,11 +69,24 @@ export function useCart() {
69
69
 
70
70
  function fetchCartItems(key, parentKey=null){
71
71
  //fetch cart items
72
+ if (key === shopStore.state.cartRoot["key"]){ // initial
73
+ shopStore.state.cartList = []
74
+ }
72
75
  return Request.get(`${shopStore.state.shopApiUrl}/cart_list`,{dataObj:{
73
76
  cart_key:key
74
77
  }}).then(async( resp) =>{
75
78
  let data = await resp.clone().json()
76
- shopStore.state.cartList=data
79
+
80
+ let currentLeafs = []
81
+ for (const item of data){
82
+ if (item["skel_type"]==="leaf"){
83
+ currentLeafs.push(item)
84
+ }else{
85
+ await fetchCartItems(item['key'])
86
+ }
87
+ }
88
+
89
+ shopStore.state.cartList=shopStore.state.cartList.concat(currentLeafs)
77
90
  return resp
78
91
  })
79
92
  }
package/src/shop.js CHANGED
@@ -11,6 +11,7 @@ export const useViurShopStore = defineStore("viurshopStore", () => {
11
11
  const state = reactive({
12
12
  //shop module name
13
13
  showNodes:false,
14
+ locale: 'de-DE',
14
15
  language:"de",
15
16
  moduleName:'shop',
16
17
  hostUrl: computed(()=>(import.meta.env.VITE_API_URL ? import.meta.env.VITE_API_URL : window.location.origin)),
@@ -37,7 +37,7 @@ export default {
37
37
  'summary_headline': 'Zusammenfassung',
38
38
  'summary_subtotal': 'Zwischensumme',
39
39
  'summary_shipping_total': 'Versand- und Bearbeitungspauschale',
40
- 'summary_delivery_time': 'Lieferzeit',
40
+ 'summary_delivery_time': 'Voraussichtliche Lieferzeit',
41
41
  'summary_discount': 'Sie sparen bei Ihrem Einkauf im Aktionszeitraum',
42
42
  'summary_checkout': 'Zur Kasse gehen',
43
43
  'summary_total': 'Gesamtbetrag',
@@ -40,7 +40,7 @@ export default {
40
40
  'summary_delivery_time': 'Délai de livraison estimé',
41
41
  'summary_discount': 'Vous économisez sur votre achat',
42
42
  'summary_checkout': 'Acheter cet article',
43
- 'summary_total': 'Montant total',
43
+ 'summary_total': 'Montant TTC',
44
44
  'summary_vat': 'TVA incluse ({percentage})',
45
45
  'discount_not_found': 'Ce code promo n’existe pas.',
46
46
  'discount_not_active': 'Ce code promo n’est pas actif.',