@viur/shop-components 0.13.0 → 0.13.2-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.13.0",
3
+ "version": "0.13.2-0",
4
4
  "description": "Frontend Vue components for the shop module of ViUR",
5
5
  "repository": {
6
6
  "type": "git",
@@ -76,16 +76,24 @@
76
76
  </sl-bar>
77
77
 
78
78
  <template v-if="shopStore.state.order?.['payment_provider'] !== null && shopStore.state.order?.['payment_provider'].startsWith('unzer-')">
79
- <sl-dialog v-if="state.paymentPopup" :label="$t('viur.shop.order_pay')" :open="state.paymentPopup" @sl-after-hide="state.paymentPopup=false">
79
+ <sl-dialog v-if="state.paymentPopup" :label="$t('viur.shop.order_pay')" :open="state.paymentPopup" @sl-after-hide="paymentCanceled">
80
80
  <payment-provider-unzer @cancel="paymentCanceled"></payment-provider-unzer>
81
81
  </sl-dialog>
82
82
  </template>
83
83
 
84
+ paypal?
85
+ <template v-if="shopStore.state.order?.['payment_provider'] !== null && shopStore.state.order?.['payment_provider'].startsWith('paypal')">
86
+ <sl-dialog v-if="state.paymentPopup" :label="$t('viur.shop.order_pay')" :open="state.paymentPopup" @sl-after-hide="state.paymentPopup=false">
87
+ <payment-provider-paypal @cancel="paymentCanceled"></payment-provider-paypal>
88
+ </sl-dialog>
89
+ </template>
90
+
84
91
  <slot name="template_confirm">
85
92
  </slot>
86
93
  </template>
87
94
  <script setup>
88
95
  import {computed, onBeforeMount, reactive, watch, useTemplateRef} from 'vue'
96
+ import PaymentProviderPaypal from '../components/PaymentProviderPaypal.vue';
89
97
  import { useViurShopStore } from '../shop';
90
98
  import boneUtils from '@viur/vue-utils/bones/utils'
91
99
  import {Request} from '@viur/vue-utils'
@@ -132,6 +140,7 @@ function getOrderCart(){
132
140
 
133
141
  function paymentCanceled(){
134
142
  state.paymentPopup=false
143
+ shopStore.state.paymentProviderData = null;
135
144
  }
136
145
 
137
146
  //open popup and freeze cart
@@ -143,7 +152,7 @@ async function startCheckout(){
143
152
  }
144
153
  }
145
154
  state.paymentPopup=true
146
- shopStore.checkout()
155
+ shopStore.checkoutStart();
147
156
  }
148
157
 
149
158
  //close popup if payment successfull
@@ -0,0 +1,261 @@
1
+ <template>
2
+ PAYPAL HERE
3
+
4
+ <div class="loading-wrapper" v-if="state.loading">
5
+ <sl-spinner class="loading"></sl-spinner>
6
+ </div>
7
+
8
+ <div class="loading-wrapper" v-if="PaymentCheckIsActive">
9
+ <sl-spinner class="loading"></sl-spinner>
10
+ {{ $t('messages.wait_for_payment') }}
11
+ </div>
12
+ <div class="form-wrapper">
13
+ <sl-alert :open="state.hasError" variant="danger">{{ state.errorMessage }}</sl-alert>
14
+
15
+ <div id="paypal-button-container"></div>
16
+
17
+ <sl-button :disabled="state.loading" variant="danger" @click="cancelPayment">
18
+ {{ $t('actions.cancel') }}
19
+ </sl-button>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import {Request} from '@viur/vue-utils';
25
+ import {HTTPError} from '@viur/vue-utils/utils/request.js';
26
+ import {useIntervalFn} from '@vueuse/core';
27
+ import {onBeforeMount, reactive} from 'vue';
28
+ import {useAddress} from '../composables/address.js';
29
+ import {useOrder} from '../composables/order';
30
+ import {useViurShopStore} from '../shop';
31
+
32
+ const shopStore = useViurShopStore();
33
+ const {fetchOrder} = useOrder();
34
+ const {saveBirthdate} = useAddress();
35
+
36
+ const emits = defineEmits(['cancel']);
37
+
38
+ const {pause: PaymentCheckPause, resume: PaymentCheckResume, isActive: PaymentCheckIsActive} = useIntervalFn(() => {
39
+ console.debug('checking ...');
40
+
41
+ fetchOrder(shopStore.state.orderKey).then(() => {
42
+ if (shopStore.state.order?.['is_paid']) {
43
+ console.debug('Order is paid');
44
+ shopStore.navigateToTab('complete');
45
+ PaymentCheckPause();
46
+ }
47
+ });
48
+
49
+ }, 2000, {immediate: false});
50
+
51
+
52
+ const state = reactive({
53
+ paymentHandler: {},
54
+ loading: false,
55
+ hasError: false,
56
+ errorMessage: null,
57
+ waitPayment: false,
58
+ order_id: null,
59
+ });
60
+
61
+ /**
62
+ * Initialize the payment type creation with Unzer-UI components
63
+ */
64
+ function initPaypalForm() {
65
+ if (shopStore.state.order?.['payment_provider'] !== 'paypal_checkout') {
66
+ console.error(`PayPal Checkout does not work with ${shopStore.state.order?.['payment_provider']}.`);
67
+ return null;
68
+ }
69
+ const client_id = shopStore.state.paymentProviderData.public_key;
70
+
71
+ const scriptConfig = {
72
+ // https://developer.paypal.com/sdk/js/configuration/#configure-and-customize-your-integration
73
+ 'client-id': shopStore.state.paymentProviderData.public_key,
74
+ 'buyer-country': shopStore.state.order.billing_address.dest['country'].toUpperCase() ?? 'DE',
75
+ 'currency': 'EUR',
76
+ 'components': 'buttons,applepay',
77
+ 'enable-funding': 'venmo,paylater,card,sofort,sepa,giropay',
78
+ 'debug': shopStore.state.debug,
79
+ };
80
+
81
+ const paypalScriptPromise = new Promise((resolve, reject) => {
82
+ const paypalScript = document.createElement('script');
83
+ paypalScript.setAttribute('src', `https://www.paypal.com/sdk/js?${new URLSearchParams(scriptConfig).toString()}`);
84
+ paypalScript.onload = () => {
85
+ resolve(paypalScript);
86
+ };
87
+ paypalScript.onerror = reject;
88
+ document.head.appendChild(paypalScript);
89
+ });
90
+
91
+
92
+ paypalScriptPromise.then(() => {
93
+ console.debug('paypalScriptPromise resolved - READY');
94
+ console.debug(shopStore.state.paymentProviderData);
95
+
96
+ const paypalButtons = window.paypal.Buttons({
97
+ style: {
98
+ shape: 'rect',
99
+ layout: 'vertical',
100
+ color: 'gold',
101
+ label: 'paypal',
102
+ },
103
+ message: {
104
+ amount: 100,
105
+ },
106
+
107
+ async createOrder() {
108
+ console.debug('createOrder');
109
+ try {
110
+ let resp = await shopStore.checkoutOrder();//.then((resp) => {
111
+ console.debug(resp);
112
+
113
+ // const orderData = await resp.json();
114
+ const orderData = await resp.payment;
115
+ // const orderData = await response.json();
116
+
117
+ // let orderData = shopStore.state.paymentProviderData;
118
+ console.debug(orderData);
119
+
120
+ if (orderData.id) {
121
+ state.order_id = orderData.id;
122
+ return orderData.id;
123
+ }
124
+
125
+ const errorDetail = orderData?.details?.[0];
126
+ const errorMessage = errorDetail
127
+ ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
128
+ : JSON.stringify(orderData);
129
+
130
+ throw new Error(errorMessage);
131
+ } catch (error) {
132
+ console.error(error);
133
+ // resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
134
+ }
135
+ },
136
+
137
+ async onApprove(data, actions) {
138
+ console.debug('onApprove', data, actions);
139
+ state.loading = true;
140
+
141
+ // buyer approved the payment, send it to the backend to capture it and mark as paid
142
+ const paymenttarget = shopStore.state.order?.['payment_provider'].replace(/-/g, '_');
143
+ Request.post(`${shopStore.state.shopUrl}/pp_${paymenttarget}/capture_order`, {
144
+ dataObj: {
145
+ order_key: shopStore.state.orderKey,
146
+ order_id: state.order_id,
147
+ },
148
+ }).then(async (resp) => {
149
+ console.debug('capture_order', resp);
150
+ const data = await resp.json();
151
+ const orderData = data.payment;
152
+ console.debug(orderData);
153
+
154
+ // Three cases to handle:
155
+ // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
156
+ // (2) Other non-recoverable errors -> Show a failure message
157
+ // (3) Successful transaction -> Show confirmation or thank you message
158
+ const errorDetail = orderData?.details?.[0];
159
+
160
+ if (errorDetail?.issue === 'INSTRUMENT_DECLINED') {
161
+ // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
162
+ // recoverable state, per
163
+ // https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
164
+ return actions.restart();
165
+ } else if (errorDetail) {
166
+ // (2) Other non-recoverable errors -> Show a failure message
167
+ paymentError(`${errorDetail.description} (${orderData.debug_id})`);
168
+ } else if (!orderData.purchase_units) {
169
+ console.error(`No purchase_units`);
170
+ paymentError(`Missing purchase_units`);
171
+ } else {
172
+ // (3) Successful transaction -> Show confirmation or thank you message
173
+ // Or go to another URL: actions.redirect('thank_you.html');
174
+ const transaction =
175
+ orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
176
+ orderData?.purchase_units?.[0]?.payment?.authorizations?.[0];
177
+ console.debug('Capture result', orderData, JSON.stringify(orderData, null, 2));
178
+
179
+ state.loading = false;
180
+ state.hasError = false;
181
+ state.waitPayment = true;
182
+ PaymentCheckResume();
183
+ }
184
+ }).catch(error => {
185
+ paymentError(error);
186
+ });
187
+ },
188
+
189
+ onError: (err) => {
190
+ console.error(err);
191
+ paymentError(err);
192
+ },
193
+
194
+ appSwitchWhenAvailable: true,
195
+ });
196
+ paypalButtons.render('#paypal-button-container');
197
+ });
198
+
199
+ state.loading = false;
200
+ }
201
+
202
+
203
+ /**
204
+ * Handle an error
205
+ *
206
+ * @param error The error. In best cases an object from unzer
207
+ * with a translated (and detailed) description for the customer
208
+ */
209
+ function paymentError(error) {
210
+ console.error(error);
211
+ state.loading = false;
212
+ state.hasError = true;
213
+ if (error && error.constructor.name === HTTPError.name && error.response.headers.get('x-viur-shop-error')) {
214
+ error.response.json().then(res => {
215
+ console.error(res.errors);
216
+ state.errorMessage = res.errors.map(err => err.customer_message || err.message).join(', ');
217
+ });
218
+ } else {
219
+ state.errorMessage = error.customerMessage || error.message || error || 'Error';
220
+ }
221
+ }
222
+
223
+ function cancelPayment() {
224
+ PaymentCheckPause();
225
+ emits('cancel');
226
+ }
227
+
228
+ onBeforeMount(() => {
229
+ console.debug('mounting', shopStore.state.paymentProviderData);
230
+ state.loading = true;
231
+ if (!shopStore.state.paymentProviderData) {
232
+ shopStore.checkoutStart().then(() => {
233
+ initPaypalForm();
234
+ fetchOrder(shopStore.state.orderKey); // refresh order after checkout_start freeze
235
+ }).catch((error) => {
236
+ console.log(error);
237
+ });
238
+ } else {
239
+ initPaypalForm();
240
+ }
241
+ });
242
+
243
+ </script>
244
+
245
+ <style scoped>
246
+ .loading-wrapper {
247
+ display: flex;
248
+ flex-direction: column;
249
+ align-items: center;
250
+ }
251
+
252
+ .form-wrapper {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 20px;
256
+ }
257
+
258
+ .loading {
259
+ font-size: 3rem;
260
+ }
261
+ </style>
@@ -339,7 +339,7 @@ function cancelPayment() {
339
339
  onBeforeMount(() => {
340
340
  state.loading = true;
341
341
  if (!shopStore.state.paymentProviderData) {
342
- shopStore.checkout().then(() => {
342
+ shopStore.checkoutStart().then(() => {
343
343
  initUnzerForm();
344
344
  fetchOrder(shopStore.state.orderKey); // refresh order after checkout_start freeze
345
345
  }).catch((error) => {
@@ -375,4 +375,16 @@ onBeforeMount(() => {
375
375
  .unzerUI.primary.button, .unzerUI.primary.buttons .button {
376
376
  background-color: var(--ignt-color-primary) !important;
377
377
  }
378
+
379
+ .unzerSandboxNotify:not(:first-of-type) {
380
+ display: none;
381
+ }
382
+
383
+ .unzerUI.form {
384
+ margin-top: 1em;
385
+
386
+ .opt-in a.unzerUI {
387
+ line-height: inherit;
388
+ }
389
+ }
378
390
  </style>
package/src/shop.js CHANGED
@@ -212,7 +212,7 @@ export const useViurShopStore = defineStore("viurshopStore", () => {
212
212
  fetchPaymentMeta()
213
213
  }
214
214
 
215
- function checkout(){
215
+ function checkoutStart(){
216
216
  //request Payment
217
217
  return new Promise((resolve,reject)=>{
218
218
  Request.post(`${state.shopUrl}/order/checkout_start`,{dataObj:{
@@ -300,7 +300,7 @@ export const useViurShopStore = defineStore("viurshopStore", () => {
300
300
  navigateToPrevious,
301
301
  tabValidation,
302
302
  fetchMetaData,
303
- checkout,
303
+ checkoutStart,
304
304
  checkoutOrder,
305
305
  addTab,
306
306
  removeTab