@viur/shop-components 0.9.4 → 0.10.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.4",
3
+ "version": "0.10.0",
4
4
  "description": "Frontend Vue components for the shop module of ViUR",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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,
85
- errorMessage:"Bei der Zahlung ist ein Fehler aufgetreten.",
86
- waitPayment:false
87
- })
88
-
95
+ paymentHandler: {},
96
+ loading: false,
97
+ hasError: false,
98
+ errorMessage: null,
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,95 +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
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';
141
222
  }
142
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
+ }
143
239
 
144
- function submitFormToUnzer(){
145
- PaymentCheckPause()
146
- state.loading = true
147
- state.hasError = false
148
- let paymenttarget = shopStore.state.order?.['payment_provider'].split("-")[1]
149
- //send to unzer
150
- state.paymentHandler[shopStore.state.order?.['payment_provider']].createResource().then((result)=>{
151
- 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: {
152
251
  order_key: shopStore.state.orderKey,
153
- type_id: result.id,
154
- }}).then(async (resp)=>{
155
- let data = await resp.json()
156
- shopStore.state.order = data
157
-
158
- shopStore.checkoutOrder().then((resp)=>{
159
- state.loading = false
160
- state.hasError = false
161
- if(shopStore.state.paymentProviderData?.redirectUrl){
162
- state.waitPayment = true
163
- window.open(shopStore.state.paymentProviderData.redirectUrl,"_blank","popup")
164
- PaymentCheckResume()
165
- }
166
- }).catch(async (error)=>{
167
- paymentError(error)
168
- })
169
-
170
- }).catch(error => {
171
- paymentError(error)
172
- })
173
- }).catch((error)=> {
174
- paymentError(error)
175
- })
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
+ });
176
271
  }
177
272
 
178
273
 
179
- function cancelPayment(){
180
- PaymentCheckPause()
181
- emits('cancel')
274
+ function cancelPayment() {
275
+ PaymentCheckPause();
276
+ emits('cancel');
182
277
  }
183
278
 
184
- onBeforeMount(()=>{
185
- state.loading = true
186
- if (!shopStore.state.paymentProviderData){
187
- shopStore.checkout().then(()=>{
188
- initUnzerForm()
189
- }).catch((error)=>{
190
- console.log(error)
191
- })
192
- }else{
193
- 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();
194
289
  }
195
- })
290
+ });
196
291
 
197
292
  </script>
198
293
 
199
294
  <style scoped>
200
- .loading-wrapper{
201
- display:flex;
202
- flex-direction: column;
203
- align-items: center;
295
+ .loading-wrapper {
296
+ display: flex;
297
+ flex-direction: column;
298
+ align-items: center;
204
299
  }
205
300
 
206
- .form-wrapper{
207
- display:flex;
208
- flex-direction: column;
209
- gap:20px;
301
+ .form-wrapper {
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: 20px;
210
305
  }
211
306
 
212
- .loading{
213
- font-size:3rem;
307
+ .loading {
308
+ font-size: 3rem;
214
309
  }
215
310
  </style>
216
311
 
217
312
  <style>
218
313
  /* global style to overwrite UnzerCSS */
219
314
  .unzerUI.primary.button, .unzerUI.primary.buttons .button {
220
- background-color: var(--ignt-color-primary) !important;
315
+ background-color: var(--ignt-color-primary) !important;
221
316
  }
222
317
  </style>
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)),