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