spree_stripe 1.0.2
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.
- checksums.yaml +7 -0
- data/LICENSE.md +14 -0
- data/README.md +132 -0
- data/Rakefile +21 -0
- data/app/assets/config/spree_stripe_manifest.js +3 -0
- data/app/controllers/spree/api/v2/storefront/stripe/base_controller.rb +21 -0
- data/app/controllers/spree/api/v2/storefront/stripe/payment_intents_controller.rb +61 -0
- data/app/controllers/spree/api/v2/storefront/stripe/setup_intents_controller.rb +19 -0
- data/app/controllers/spree_stripe/apple_pay_domain_verification_controller.rb +11 -0
- data/app/controllers/spree_stripe/payment_intents_controller.rb +61 -0
- data/app/controllers/spree_stripe/store_controller_decorator.rb +9 -0
- data/app/controllers/stripe_event/webhook_controller_decorator.rb +17 -0
- data/app/helpers/spree_stripe/base_helper.rb +13 -0
- data/app/javascript/spree_stripe/application.js +18 -0
- data/app/javascript/spree_stripe/controllers/stripe_button_controller.js +452 -0
- data/app/javascript/spree_stripe/controllers/stripe_controller.js +311 -0
- data/app/jobs/spree_stripe/base_job.rb +5 -0
- data/app/jobs/spree_stripe/complete_order_job.rb +8 -0
- data/app/jobs/spree_stripe/create_webhook_endpoint_job.rb +7 -0
- data/app/jobs/spree_stripe/register_domain_job.rb +20 -0
- data/app/models/spree_stripe/base.rb +6 -0
- data/app/models/spree_stripe/credit_card_decorator.rb +13 -0
- data/app/models/spree_stripe/custom_domain_decorator.rb +18 -0
- data/app/models/spree_stripe/gateway.rb +366 -0
- data/app/models/spree_stripe/order_decorator.rb +19 -0
- data/app/models/spree_stripe/payment_decorator.rb +46 -0
- data/app/models/spree_stripe/payment_intent.rb +66 -0
- data/app/models/spree_stripe/payment_method_decorator.rb +18 -0
- data/app/models/spree_stripe/payment_methods_webhook_key.rb +8 -0
- data/app/models/spree_stripe/payment_source_decorator.rb +9 -0
- data/app/models/spree_stripe/payment_sources/affirm.rb +9 -0
- data/app/models/spree_stripe/payment_sources/after_pay.rb +13 -0
- data/app/models/spree_stripe/payment_sources/alipay.rb +9 -0
- data/app/models/spree_stripe/payment_sources/ideal.rb +15 -0
- data/app/models/spree_stripe/payment_sources/klarna.rb +9 -0
- data/app/models/spree_stripe/payment_sources/link.rb +13 -0
- data/app/models/spree_stripe/payment_sources/przelewy24.rb +11 -0
- data/app/models/spree_stripe/payment_sources/sepa_debit.rb +13 -0
- data/app/models/spree_stripe/store_decorator.rb +24 -0
- data/app/models/spree_stripe/webhook_key.rb +14 -0
- data/app/presenters/spree_stripe/customer_presenter.rb +35 -0
- data/app/presenters/spree_stripe/payment_intent_presenter.rb +103 -0
- data/app/serializers/spree/api/v2/platform/affirm_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/after_pay_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/alipay_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/ideal_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/klarna_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/link_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/przelewy24_serializer.rb +9 -0
- data/app/serializers/spree/api/v2/platform/sepa_debit_serializer.rb +9 -0
- data/app/serializers/spree/v2/storefront/affirm_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/after_pay_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/alipay_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/ideal_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/klarna_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/link_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/payment_intent_serializer.rb +11 -0
- data/app/serializers/spree/v2/storefront/przelewy24_serializer.rb +7 -0
- data/app/serializers/spree/v2/storefront/sepa_debit_serializer.rb +7 -0
- data/app/services/spree_stripe/complete_order.rb +99 -0
- data/app/services/spree_stripe/create_gateway_webhooks.rb +55 -0
- data/app/services/spree_stripe/create_payment.rb +43 -0
- data/app/services/spree_stripe/create_payment_intent.rb +40 -0
- data/app/services/spree_stripe/create_setup_intent.rb +20 -0
- data/app/services/spree_stripe/create_source.rb +87 -0
- data/app/services/spree_stripe/register_domain.rb +22 -0
- data/app/services/spree_stripe/webhook_handlers/payment_intent_payment_failed.rb +18 -0
- data/app/services/spree_stripe/webhook_handlers/payment_intent_succeeded.rb +13 -0
- data/app/services/spree_stripe/webhook_handlers/setup_intent_succeeded.rb +34 -0
- data/app/views/spree/admin/payment_methods/configuration_guides/_spree_stripe.html.erb +0 -0
- data/app/views/spree/admin/payment_methods/custom_form_fields/_spree_stripe.html.erb +47 -0
- data/app/views/spree/admin/payment_methods/descriptions/_spree_stripe.html.erb +15 -0
- data/app/views/spree/checkout/payment/_spree_stripe.html.erb +63 -0
- data/app/views/spree_stripe/_head.html.erb +2 -0
- data/app/views/spree_stripe/_quick_checkout.html.erb +34 -0
- data/config/importmap.rb +8 -0
- data/config/initializers/spree.rb +8 -0
- data/config/initializers/stripe.rb +14 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20250310152812_setup_spree_stripe_models.rb +41 -0
- data/lib/generators/spree_stripe/install/install_generator.rb +20 -0
- data/lib/spree_stripe/configuration.rb +5 -0
- data/lib/spree_stripe/engine.rb +36 -0
- data/lib/spree_stripe/factories.rb +3 -0
- data/lib/spree_stripe/testing_support/factories/after_pay_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/alipay_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/gateway_factory.rb +23 -0
- data/lib/spree_stripe/testing_support/factories/ideal_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/klarna_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/link_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/payment_intent_factory.rb +24 -0
- data/lib/spree_stripe/testing_support/factories/payment_source_factory.rb +8 -0
- data/lib/spree_stripe/testing_support/factories/przelewy24_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/sepa_debit_payment_source_factory.rb +6 -0
- data/lib/spree_stripe/testing_support/factories/webhook_key_factory.rb +23 -0
- data/lib/spree_stripe/version.rb +7 -0
- data/lib/spree_stripe.rb +14 -0
- data/vendor/javascript/@stripe--stripe-js--dist--pure.esm.js.js +4 -0
- metadata +295 -0
@@ -0,0 +1,452 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
import { loadStripe } from '@stripe/stripe-js/pure'
|
3
|
+
import showFlashMessage from 'spree/storefront/helpers/show_flash_message'
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static values = {
|
7
|
+
apiKey: String,
|
8
|
+
orderToken: String,
|
9
|
+
clientSecret: String,
|
10
|
+
currency: String,
|
11
|
+
amount: Number,
|
12
|
+
availableCountries: Array,
|
13
|
+
giftCardCode: String,
|
14
|
+
giftCardAmount: Number,
|
15
|
+
borderRadius: Number,
|
16
|
+
height: Number,
|
17
|
+
theme: String,
|
18
|
+
maxRows: Number,
|
19
|
+
maxColumns: Number,
|
20
|
+
buttonWidth: Number,
|
21
|
+
storeUrl: String,
|
22
|
+
returnUrl: String,
|
23
|
+
}
|
24
|
+
|
25
|
+
static targets = ['container']
|
26
|
+
|
27
|
+
connect() {
|
28
|
+
this.paymentMethod = null
|
29
|
+
this.initStripe()
|
30
|
+
this.shippingRates = []
|
31
|
+
this.currentShippingOptionId = null
|
32
|
+
}
|
33
|
+
|
34
|
+
initStripe() {
|
35
|
+
if (typeof Stripe === 'undefined') {
|
36
|
+
loadStripe(this.apiKeyValue).then((stripe) => {
|
37
|
+
this.stripe = stripe
|
38
|
+
this.prepareExpressCheckoutElement()
|
39
|
+
})
|
40
|
+
} else if (typeof this.stripe !== 'function') {
|
41
|
+
this.stripe = Stripe(this.apiKeyValue)
|
42
|
+
this.prepareExpressCheckoutElement()
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
prepareExpressCheckoutElement() {
|
47
|
+
this.elements = this.stripe.elements({
|
48
|
+
mode: 'payment',
|
49
|
+
currency: this.currencyValue,
|
50
|
+
amount: parseInt(this.amountValue),
|
51
|
+
appearance: {
|
52
|
+
theme: 'stripe',
|
53
|
+
variables: {
|
54
|
+
borderRadius: this.hasBorderRadiusValue ? `${this.borderRadiusValue}px` : undefined
|
55
|
+
}
|
56
|
+
},
|
57
|
+
paymentMethodCreation: 'manual'
|
58
|
+
})
|
59
|
+
|
60
|
+
const prButton = this.elements.create('expressCheckout', {
|
61
|
+
wallets: { applePay: 'always', googlePay: 'always' },
|
62
|
+
buttonHeight: this.heightValue > 0 ? this.heightValue : undefined,
|
63
|
+
buttonTheme: {
|
64
|
+
applePay: this.themeValue.length ? this.themeValue : undefined,
|
65
|
+
googlePay: this.themeValue.length ? this.themeValue : undefined
|
66
|
+
},
|
67
|
+
layout: {
|
68
|
+
overflow: this.maxRowsValue > 0 ? 'auto' : 'never',
|
69
|
+
maxColumns: this.maxColumnsValue > 0 ? this.maxColumnsValue : undefined,
|
70
|
+
maxRows: this.maxRowsValue > 0 ? this.maxRowsValue : undefined
|
71
|
+
},
|
72
|
+
buttonType: {
|
73
|
+
applePay: 'check-out',
|
74
|
+
googlePay: 'checkout'
|
75
|
+
},
|
76
|
+
paymentMethodOrder: ['applePay', 'googlePay', 'link']
|
77
|
+
})
|
78
|
+
prButton.mount('#payment-request-button')
|
79
|
+
|
80
|
+
prButton.on('ready', ({ availablePaymentMethods }) => {
|
81
|
+
if (!availablePaymentMethods) {
|
82
|
+
this.containerTarget.style.display = 'hidden'
|
83
|
+
return
|
84
|
+
}
|
85
|
+
const availableMethodsCount = Object.keys(availablePaymentMethods).filter(
|
86
|
+
(key) => availablePaymentMethods[key]
|
87
|
+
).length
|
88
|
+
if (this.buttonWidthValue > 0) {
|
89
|
+
this.containerTarget.style.setProperty(
|
90
|
+
'--desktop-max-width',
|
91
|
+
this.buttonWidthValue * availableMethodsCount + 'px'
|
92
|
+
)
|
93
|
+
this.containerTarget.classList.add('desktop-max-width')
|
94
|
+
}
|
95
|
+
window.parent?.postMessage({ enabledPaymentMethodsCount: availableMethodsCount }, '*')
|
96
|
+
})
|
97
|
+
prButton.on('click', (event) => {
|
98
|
+
this.paymentMethod = event.expressPaymentType
|
99
|
+
|
100
|
+
event.resolve({
|
101
|
+
emailRequired: true,
|
102
|
+
shippingAddressRequired: true,
|
103
|
+
allowedShippingCountries: this.availableCountriesValue,
|
104
|
+
// If we want to collect shipping address then we need to provide at least one shipping option, it will be updated to the real ones in the `shippingaddresschange` event
|
105
|
+
shippingRates: [{ id: 'loading', displayName: 'Loading...', amount: 0 }],
|
106
|
+
lineItems: [
|
107
|
+
{ name: 'Subtotal', amount: 0 },
|
108
|
+
{ name: 'Shipping', amount: 0 },
|
109
|
+
{ name: 'Store credit', amount: 0 },
|
110
|
+
{ name: 'Discount', amount: 0 },
|
111
|
+
{ name: 'Tax', amount: 0 }
|
112
|
+
]
|
113
|
+
})
|
114
|
+
})
|
115
|
+
prButton.on('shippingaddresschange', this.handleAddressChange.bind(this))
|
116
|
+
prButton.on('shippingratechange', this.handleShippingOptionChange.bind(this))
|
117
|
+
prButton.on('confirm', this.handleFinalizePayment.bind(this))
|
118
|
+
prButton.on('cancel', this.handleCancelPayment.bind(this))
|
119
|
+
}
|
120
|
+
|
121
|
+
async handleAddressChange(ev) {
|
122
|
+
// Perform server-side request to fetch shipping options
|
123
|
+
// https://stripe.com/docs/js/payment_request/events/on_shipping_address_change#payment_request_on_shipping_address_change-handler-shippingAddress
|
124
|
+
const orderUpdatePayload = {
|
125
|
+
order: {
|
126
|
+
ship_address_attributes: {
|
127
|
+
// we need to use quick checkout option to skip first/last/street address validation
|
128
|
+
// as at this point we don't receive this information as browsers do not share it with us
|
129
|
+
quick_checkout: true,
|
130
|
+
firstname: ev.name.split(' ')[0],
|
131
|
+
lastname: ev.name.split(' ')[1],
|
132
|
+
city: ev.address.city,
|
133
|
+
zipcode: ev.address.postal_code,
|
134
|
+
country_iso: ev.address.country,
|
135
|
+
state_name: ev.address.state
|
136
|
+
},
|
137
|
+
bill_address_id: 'CLEAR' // we need to clear out the bill address to avoid order being pushed to confirm/complete state
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
// 1st we need to persist the address to the order
|
142
|
+
const saveAddressResponse = await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout`, {
|
143
|
+
method: 'PATCH',
|
144
|
+
headers: {
|
145
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
146
|
+
'Content-Type': 'application/json'
|
147
|
+
},
|
148
|
+
body: JSON.stringify(orderUpdatePayload)
|
149
|
+
})
|
150
|
+
|
151
|
+
// 2nd we need to push the order to delivery state to generate shipping rates
|
152
|
+
if (saveAddressResponse.status === 200) {
|
153
|
+
// In case of any error here we have to allow user try again
|
154
|
+
try {
|
155
|
+
const response = await fetch(
|
156
|
+
`${this.storeUrlValue}/api/v2/storefront/checkout/advance?state=delivery&include=shipments.shipping_rates,line_items.vendor`,
|
157
|
+
{
|
158
|
+
method: 'PATCH',
|
159
|
+
headers: {
|
160
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
161
|
+
'Content-Type': 'application/json'
|
162
|
+
},
|
163
|
+
body: JSON.stringify({ quick_checkout: true, shipping_method_id: this.currentShippingOptionId })
|
164
|
+
}
|
165
|
+
)
|
166
|
+
const newOrderResponse = await response.json()
|
167
|
+
this.shippingRates = newOrderResponse.included.filter((item) => item.type === 'shipping_rate')
|
168
|
+
|
169
|
+
if (this.shippingRates.length > 0) {
|
170
|
+
this.elements.update({ amount: newOrderResponse.data.attributes.total_minus_store_credits_cents })
|
171
|
+
const shippingRates = this.shippingOptions(this.shippingRates)
|
172
|
+
// We need to select first shipping rate as default, because Apple Pay sometimes doesn't trigger `shippingratechange` event when the modal is opened
|
173
|
+
this.currentShippingOptionId = String(shippingRates[0].id)
|
174
|
+
|
175
|
+
ev.resolve({
|
176
|
+
shippingRates: shippingRates,
|
177
|
+
lineItems: this.buildLineItems(newOrderResponse)
|
178
|
+
})
|
179
|
+
return
|
180
|
+
}
|
181
|
+
} catch (error) {
|
182
|
+
ev.reject()
|
183
|
+
return
|
184
|
+
}
|
185
|
+
}
|
186
|
+
ev.reject()
|
187
|
+
}
|
188
|
+
|
189
|
+
async handleShippingOptionChange(ev) {
|
190
|
+
const { resolve, shippingRate, reject } = ev
|
191
|
+
|
192
|
+
if (shippingRate) {
|
193
|
+
const shippingRateId = String(shippingRate.id).replace(/_google_pay_\d+/, '')
|
194
|
+
|
195
|
+
if (shippingRateId === 'standard') return resolve()
|
196
|
+
if (shippingRateId === 'loading') return reject()
|
197
|
+
|
198
|
+
this.currentShippingOptionId = shippingRateId
|
199
|
+
|
200
|
+
const response = await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout/select_shipping_method`, {
|
201
|
+
method: 'PATCH',
|
202
|
+
headers: {
|
203
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
204
|
+
'Content-Type': 'application/json'
|
205
|
+
},
|
206
|
+
body: JSON.stringify({ shipping_method_id: shippingRateId })
|
207
|
+
})
|
208
|
+
|
209
|
+
if (response.status === 200) {
|
210
|
+
const newOrderResponse = await response.json()
|
211
|
+
|
212
|
+
this.elements.update({ amount: newOrderResponse.data.attributes.total_minus_store_credits_cents })
|
213
|
+
resolve({ lineItems: this.buildLineItems(newOrderResponse) })
|
214
|
+
} else {
|
215
|
+
reject()
|
216
|
+
}
|
217
|
+
} else {
|
218
|
+
reject()
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
async handleFinalizePayment(ev) {
|
223
|
+
let shippingRateId = (ev.shippingRate?.id || this.currentShippingOptionId)
|
224
|
+
if (shippingRateId) {
|
225
|
+
shippingRateId = String(shippingRateId).replace(/_google_pay_\d+/, '')
|
226
|
+
}
|
227
|
+
|
228
|
+
if (!shippingRateId || shippingRateId === 'loading') {
|
229
|
+
ev.paymentFailed({ reason: 'invalid_shipping_address' })
|
230
|
+
return
|
231
|
+
}
|
232
|
+
|
233
|
+
if (this.giftCardCodeValue && this.giftCardAmountValue) {
|
234
|
+
const giftCardValidationResponse = await fetch(
|
235
|
+
`${this.storeUrlValue}/api/v2/storefront/checkout/validate_gift_card_data`,
|
236
|
+
{
|
237
|
+
method: 'POST',
|
238
|
+
headers: {
|
239
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
240
|
+
'Content-Type': 'application/json'
|
241
|
+
},
|
242
|
+
body: JSON.stringify({
|
243
|
+
gift_card_code: this.giftCardCodeValue,
|
244
|
+
gift_card_amount: this.giftCardAmountValue
|
245
|
+
})
|
246
|
+
}
|
247
|
+
)
|
248
|
+
if (giftCardValidationResponse.status === 422) {
|
249
|
+
ev.paymentFailed()
|
250
|
+
return
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
const validationResponse = await fetch(
|
255
|
+
`${this.storeUrlValue}/api/v2/storefront/checkout/validate_order_for_payment?skip_state=true`,
|
256
|
+
{
|
257
|
+
method: 'POST',
|
258
|
+
headers: {
|
259
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
260
|
+
'Content-Type': 'application/json'
|
261
|
+
}
|
262
|
+
}
|
263
|
+
)
|
264
|
+
|
265
|
+
if (validationResponse.status === 422) {
|
266
|
+
ev.paymentFailed()
|
267
|
+
return
|
268
|
+
}
|
269
|
+
|
270
|
+
// Confirm the PaymentIntent without handling potential next actions (yet).
|
271
|
+
const { error: confirmError } = await this.elements.submit()
|
272
|
+
|
273
|
+
if (confirmError) {
|
274
|
+
if (confirmError.length > 0) {
|
275
|
+
showFlashMessage(confirmError, 'error')
|
276
|
+
}
|
277
|
+
return
|
278
|
+
}
|
279
|
+
|
280
|
+
// we need to persist some information about the customer to move the order to the next state
|
281
|
+
const orderUpdatePayload = {
|
282
|
+
order: {
|
283
|
+
email: ev.billingDetails.email,
|
284
|
+
ship_address_attributes: {
|
285
|
+
quick_checkout: true,
|
286
|
+
firstname: ev.shippingAddress.name.split(' ')[0],
|
287
|
+
lastname: ev.shippingAddress.name.split(' ')[1],
|
288
|
+
address1: ev.shippingAddress.address.line1,
|
289
|
+
address2: ev.shippingAddress.address.line2,
|
290
|
+
city: ev.shippingAddress.address.city,
|
291
|
+
zipcode: ev.shippingAddress.address.postal_code,
|
292
|
+
country_iso: ev.shippingAddress.address.country,
|
293
|
+
state_name: ev.shippingAddress.address.state,
|
294
|
+
phone: ev.billingDetails.phone
|
295
|
+
}
|
296
|
+
},
|
297
|
+
do_not_change_state: true
|
298
|
+
}
|
299
|
+
|
300
|
+
const updateResponse = await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout`, {
|
301
|
+
method: 'PATCH',
|
302
|
+
headers: {
|
303
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
304
|
+
'Content-Type': 'application/json'
|
305
|
+
},
|
306
|
+
body: JSON.stringify(orderUpdatePayload)
|
307
|
+
})
|
308
|
+
|
309
|
+
if (updateResponse.status === 200) {
|
310
|
+
const advanceResponse = await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout/advance?state=payment`, {
|
311
|
+
method: 'PATCH',
|
312
|
+
headers: {
|
313
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
314
|
+
'Content-Type': 'application/json'
|
315
|
+
},
|
316
|
+
body: JSON.stringify({ shipping_method_id: shippingRateId })
|
317
|
+
})
|
318
|
+
|
319
|
+
if (advanceResponse.status === 200) {
|
320
|
+
try {
|
321
|
+
const { error: paymentMethodError, paymentMethod } = await this.stripe.createPaymentMethod({
|
322
|
+
elements: this.elements
|
323
|
+
})
|
324
|
+
|
325
|
+
if (paymentMethodError) {
|
326
|
+
showFlashMessage(error, 'error')
|
327
|
+
return
|
328
|
+
}
|
329
|
+
|
330
|
+
const { error } = await this.stripe.confirmPayment({
|
331
|
+
clientSecret: this.clientSecretValue,
|
332
|
+
confirmParams: {
|
333
|
+
payment_method: paymentMethod.id,
|
334
|
+
// Stripe will automatically add `payment_intent` and `payment_intent_client_secret` params
|
335
|
+
return_url: this.returnUrlValue
|
336
|
+
}
|
337
|
+
})
|
338
|
+
if (error) {
|
339
|
+
if (error.length > 0) {
|
340
|
+
showFlashMessage(error, 'error')
|
341
|
+
}
|
342
|
+
return
|
343
|
+
}
|
344
|
+
} catch (e) {
|
345
|
+
console.log(e)
|
346
|
+
}
|
347
|
+
} else {
|
348
|
+
ev.paymentFailed()
|
349
|
+
}
|
350
|
+
} else {
|
351
|
+
ev.paymentFailed()
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
async handleCancelPayment(ev) {
|
356
|
+
const orderUpdatePayload = {
|
357
|
+
order: {
|
358
|
+
ship_address_id: 'CLEAR',
|
359
|
+
bill_address_id: 'CLEAR'
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
// reset shipping method for some cases when user select paid shipping method then reload page
|
364
|
+
// do not reset shipping method if we have only one or is multivendor order
|
365
|
+
let defaultShippingMethodId = this.shippingRates[0]?.attributes?.shipping_method_id
|
366
|
+
if (defaultShippingMethodId) {
|
367
|
+
defaultShippingMethodId = String(defaultShippingMethodId)
|
368
|
+
}
|
369
|
+
|
370
|
+
if (this.shippingRates?.length > 1 && this.currentShippingOptionId !== defaultShippingMethodId) {
|
371
|
+
// reset shipping choice
|
372
|
+
await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout/select_shipping_method`, {
|
373
|
+
method: 'PATCH',
|
374
|
+
headers: {
|
375
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
376
|
+
'Content-Type': 'application/json'
|
377
|
+
},
|
378
|
+
body: JSON.stringify({ shipping_method_id: defaultShippingMethodId })
|
379
|
+
})
|
380
|
+
}
|
381
|
+
|
382
|
+
// reset addresses
|
383
|
+
await fetch(`${this.storeUrlValue}/api/v2/storefront/checkout`, {
|
384
|
+
method: 'PATCH',
|
385
|
+
headers: {
|
386
|
+
'X-Spree-Order-Token': this.orderTokenValue,
|
387
|
+
'Content-Type': 'application/json'
|
388
|
+
},
|
389
|
+
body: JSON.stringify(orderUpdatePayload)
|
390
|
+
})
|
391
|
+
|
392
|
+
this.currentShippingOptionId = null
|
393
|
+
}
|
394
|
+
|
395
|
+
shippingOptions(shippingRates) {
|
396
|
+
return shippingRates.map((rate) => {
|
397
|
+
let id = String(rate.attributes.shipping_method_id)
|
398
|
+
if (this.paymentMethod === 'google_pay') {
|
399
|
+
// We need to add some random data to the shipping rate to avoid weird issue with Google Pay, in which it clears the shipping rates when a new address is added
|
400
|
+
id += `_google_pay_${Math.floor(Math.random() * 100)}`
|
401
|
+
}
|
402
|
+
return {
|
403
|
+
id: id, // shipping rates can be refreshed and removed, shipping methods are more reliable
|
404
|
+
displayName: rate.attributes.name,
|
405
|
+
deliveryEstimate: rate.attributes.display_delivery_range || '',
|
406
|
+
amount: parseInt(rate.attributes.final_price_cents)
|
407
|
+
}
|
408
|
+
})
|
409
|
+
}
|
410
|
+
|
411
|
+
multiVendorOrder(newOrderResponse) {
|
412
|
+
const vendorIds = newOrderResponse.included.filter((item) => item.type === 'vendor').map((vendor) => vendor.id)
|
413
|
+
|
414
|
+
return [...new Set(vendorIds)].length > 1
|
415
|
+
}
|
416
|
+
|
417
|
+
buildLineItems(newOrderResponse) {
|
418
|
+
return [
|
419
|
+
{ name: 'Subtotal', amount: newOrderResponse.data.attributes.subtotal_cents },
|
420
|
+
{ name: 'Shipping', amount: newOrderResponse.data.attributes.ship_total_cents },
|
421
|
+
this.buildStoreCreditLine(newOrderResponse),
|
422
|
+
this.buildDiscountLine(newOrderResponse),
|
423
|
+
this.buildTaxLine(newOrderResponse)
|
424
|
+
].filter((i) => i)
|
425
|
+
}
|
426
|
+
|
427
|
+
buildStoreCreditLine(newOrderResponse) {
|
428
|
+
const amount = newOrderResponse.data.attributes.store_credit_total_cents
|
429
|
+
|
430
|
+
if (amount > 0) {
|
431
|
+
return { name: 'Store credit', amount: -amount }
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
buildDiscountLine(newOrderResponse) {
|
436
|
+
const amount = newOrderResponse.data.attributes.promo_total_cents
|
437
|
+
|
438
|
+
if (amount > 0) {
|
439
|
+
return { name: 'Discount', amount: -amount }
|
440
|
+
}
|
441
|
+
}
|
442
|
+
|
443
|
+
buildTaxLine(newOrderResponse) {
|
444
|
+
const attributes = newOrderResponse.data.attributes
|
445
|
+
const isTaxIncluded = parseInt(attributes.included_tax_total) > 0
|
446
|
+
if (isTaxIncluded) return null
|
447
|
+
|
448
|
+
const amount = attributes.tax_total_cents
|
449
|
+
|
450
|
+
return { name: 'Tax', amount: amount }
|
451
|
+
}
|
452
|
+
}
|