process payment fields
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
:src="initials.url"
|
||||
>
|
||||
<div
|
||||
v-else-if="field.type === 'file'"
|
||||
v-else-if="field.type === 'file' || field.type === 'payment'"
|
||||
class="px-0.5 flex flex-col justify-center"
|
||||
>
|
||||
<a
|
||||
@@ -158,7 +158,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper } from '@tabler/icons-vue'
|
||||
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldArea',
|
||||
@@ -231,7 +231,8 @@ export default {
|
||||
checkbox: 'Checkbox',
|
||||
radio: 'Radio',
|
||||
multiple: 'Multiple Select',
|
||||
phone: 'Phone'
|
||||
phone: 'Phone',
|
||||
payment: 'Payment'
|
||||
}
|
||||
},
|
||||
fieldIcons () {
|
||||
@@ -247,7 +248,8 @@ export default {
|
||||
radio: IconCircleDot,
|
||||
cells: IconColumns3,
|
||||
multiple: IconChecks,
|
||||
phone: IconPhoneCheck
|
||||
phone: IconPhoneCheck,
|
||||
payment: IconCreditCard
|
||||
}
|
||||
},
|
||||
image () {
|
||||
@@ -281,6 +283,8 @@ export default {
|
||||
attachments () {
|
||||
if (this.field.type === 'file') {
|
||||
return (this.modelValue || []).map((uuid) => this.attachmentsIndex[uuid])
|
||||
} else if (this.field.type === 'payment') {
|
||||
return [this.attachmentsIndex[this.modelValue]].filter(Boolean)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -266,8 +266,22 @@
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
@submit="submitStep"
|
||||
/>
|
||||
<PaymentStep
|
||||
v-else-if="currentField.type === 'payment'"
|
||||
ref="currentStep"
|
||||
:key="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
@submit="submitStep"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 md:mt-8">
|
||||
<div
|
||||
v-if="currentField.type !== 'payment' || submittedValues[currentField.uuid]"
|
||||
class="mt-6 md:mt-8"
|
||||
>
|
||||
<button
|
||||
ref="submitButton"
|
||||
type="submit"
|
||||
@@ -331,6 +345,7 @@ import InitialsStep from './initials_step'
|
||||
import AttachmentStep from './attachment_step'
|
||||
import MultiSelectStep from './multi_select_step'
|
||||
import PhoneStep from './phone_step'
|
||||
import PaymentStep from './payment_step'
|
||||
import TextStep from './text_step'
|
||||
import DateStep from './date_step'
|
||||
import FormCompleted from './completed'
|
||||
@@ -351,6 +366,7 @@ export default {
|
||||
IconArrowsDiagonal,
|
||||
TextStep,
|
||||
PhoneStep,
|
||||
PaymentStep,
|
||||
IconArrowsDiagonalMinimize2,
|
||||
FormCompleted
|
||||
},
|
||||
@@ -402,11 +418,6 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
authenticityToken: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -463,6 +474,12 @@ export default {
|
||||
currentStepFields () {
|
||||
return this.stepFields[this.currentStep]
|
||||
},
|
||||
queryParams () {
|
||||
return new URLSearchParams(window.location.search)
|
||||
},
|
||||
authenticityToken () {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||
},
|
||||
submitterSlug () {
|
||||
return this.submitter.slug
|
||||
},
|
||||
@@ -525,7 +542,13 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
if (this.goToLast) {
|
||||
if (this.queryParams.get('field_uuid')) {
|
||||
const stepIndex = this.stepFields.findIndex((fields) => {
|
||||
return fields.some((f) => f.uuid === this.queryParams.get('field_uuid'))
|
||||
})
|
||||
|
||||
this.currentStep = Math.max(stepIndex, 0)
|
||||
} else if (this.goToLast) {
|
||||
const requiredEmptyStepIndex = this.stepFields.indexOf(this.stepFields.find((fields) => fields.some((f) => f.required && !this.submittedValues[f.uuid])))
|
||||
const lastFilledStepIndex = this.stepFields.indexOf([...this.stepFields].reverse().find((fields) => fields.some((f) => !!this.submittedValues[f.uuid]))) + 1
|
||||
|
||||
@@ -570,7 +593,7 @@ export default {
|
||||
methods: {
|
||||
t,
|
||||
maybeTrackEmailClick () {
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const { queryParams } = this
|
||||
|
||||
if (queryParams.has('t')) {
|
||||
const t = queryParams.get('t')
|
||||
@@ -594,7 +617,7 @@ export default {
|
||||
}
|
||||
},
|
||||
maybeTrackSmsClick () {
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const { queryParams } = this
|
||||
|
||||
if (queryParams.has('c')) {
|
||||
const c = queryParams.get('c')
|
||||
@@ -667,7 +690,7 @@ export default {
|
||||
async submitStep () {
|
||||
this.isSubmitting = true
|
||||
|
||||
const stepPromise = ['signature', 'phone', 'initials'].includes(this.currentField.type)
|
||||
const stepPromise = ['signature', 'phone', 'initials', 'payment'].includes(this.currentField.type)
|
||||
? this.$refs.currentStep.submit
|
||||
: () => Promise.resolve({})
|
||||
|
||||
@@ -684,7 +707,7 @@ export default {
|
||||
}
|
||||
|
||||
await this.saveStep(formData).then(async (response) => {
|
||||
if (response.status === 422) {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
alert(data.error || 'Value is invalid')
|
||||
@@ -714,7 +737,7 @@ export default {
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error submitting form:', error)
|
||||
alert(error)
|
||||
}).finally(() => {
|
||||
this.isSubmitting = false
|
||||
})
|
||||
|
||||
@@ -38,7 +38,9 @@ const en = {
|
||||
type: 'Type',
|
||||
type_text: 'Type text',
|
||||
date: 'Date',
|
||||
email_has_been_sent: 'Email has been sent'
|
||||
email_has_been_sent: 'Email has been sent',
|
||||
processing: 'Processing',
|
||||
pay_with_strip: 'Pay with Stripe'
|
||||
}
|
||||
|
||||
const es = {
|
||||
@@ -81,7 +83,9 @@ const es = {
|
||||
draw: 'Dibujar',
|
||||
type: 'Escribir',
|
||||
type_text: 'Escribir texto',
|
||||
email_has_been_sent: 'El correo electrónico ha sido enviado'
|
||||
email_has_been_sent: 'El correo electrónico ha sido enviado',
|
||||
processing: 'Procesando',
|
||||
pay_with_strip: 'Pagar con Stripe'
|
||||
}
|
||||
|
||||
const it = {
|
||||
@@ -124,7 +128,9 @@ const it = {
|
||||
type: 'Inserisci',
|
||||
type_text: 'Inserisci testo',
|
||||
toggle_multiline_text: 'Attiva Testo Multilinea',
|
||||
email_has_been_sent: "L'email è stata inviata"
|
||||
email_has_been_sent: "L'email è stata inviata",
|
||||
processing: 'Elaborazione',
|
||||
pay_with_strip: 'Paga con Stripe'
|
||||
}
|
||||
|
||||
const de = {
|
||||
@@ -167,7 +173,9 @@ const de = {
|
||||
type: 'Eingeben',
|
||||
type_text: 'Text eingeben',
|
||||
toggle_multiline_text: 'Mehrzeiligen Text umschalten',
|
||||
email_has_been_sent: 'Die E-Mail wurde gesendet'
|
||||
email_has_been_sent: 'Die E-Mail wurde gesendet',
|
||||
processing: 'Verarbeitung',
|
||||
pay_with_strip: 'Mit Stripe bezahlen'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
@@ -210,7 +218,9 @@ const fr = {
|
||||
type: 'Saisir',
|
||||
type_text: 'Saisir du texte',
|
||||
toggle_multiline_text: 'Basculer le Texte Multiligne',
|
||||
email_has_been_sent: "L'email a été envoyé"
|
||||
email_has_been_sent: "L'email a été envoyé",
|
||||
processing: 'Traitement',
|
||||
pay_with_strip: 'Paiement avec Stripe'
|
||||
}
|
||||
|
||||
const pl = {
|
||||
@@ -253,7 +263,9 @@ const pl = {
|
||||
type: 'Wprowadź',
|
||||
type_text: 'Wprowadź tekst',
|
||||
toggle_multiline_text: 'Przełącz Tekst Wielolinijkowy',
|
||||
email_has_been_sent: 'E-mail został wysłany'
|
||||
email_has_been_sent: 'E-mail został wysłany',
|
||||
processing: 'Przetwarzanie',
|
||||
pay_with_strip: 'Płatność za pomocą Stripe'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
@@ -296,7 +308,9 @@ const uk = {
|
||||
type: 'Текст',
|
||||
type_text: 'Введіть текст',
|
||||
toggle_multiline_text: 'Перемкнути Багаторядковий Текст',
|
||||
email_has_been_sent: 'Електронний лист був відправлений'
|
||||
email_has_been_sent: 'Електронний лист був відправлений',
|
||||
processing: 'Обробка',
|
||||
pay_with_strip: 'Сплатити за допомогою Stripe'
|
||||
}
|
||||
|
||||
const cs = {
|
||||
@@ -339,7 +353,9 @@ const cs = {
|
||||
type: 'Zadat',
|
||||
type_text: 'Zadat text',
|
||||
toggle_multiline_text: 'Přepnout Víceřádkový Text',
|
||||
email_has_been_sent: 'E-mail byl odeslán'
|
||||
email_has_been_sent: 'E-mail byl odeslán',
|
||||
processing: 'Zpracování',
|
||||
pay_with_strip: 'Zaplacení přes Stripe'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
@@ -382,7 +398,9 @@ const pt = {
|
||||
type: 'Inserir',
|
||||
type_text: 'Inserir texto',
|
||||
toggle_multiline_text: 'Alternar Texto Multilinha',
|
||||
email_has_been_sent: 'Email enviado'
|
||||
email_has_been_sent: 'Email enviado',
|
||||
processing: 'Processamento',
|
||||
pay_with_strip: 'Pagar com Stripe'
|
||||
}
|
||||
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt }
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<label
|
||||
v-if="!modelValue && !sessionId"
|
||||
:for="field.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ field.name || defaultName }}
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
:value="modelValue"
|
||||
hidden
|
||||
:name="`values[${field.uuid}]`"
|
||||
class="hidden"
|
||||
>
|
||||
<div
|
||||
v-if="modelValue && !sessionId"
|
||||
class=" text-2xl mb-2"
|
||||
>
|
||||
Already paid
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
v-if="sessionId"
|
||||
disabled
|
||||
class="base-button w-full"
|
||||
>
|
||||
<IconLoader
|
||||
width="22"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<span>
|
||||
{{ t('processing') }}...
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:id="field.uuid"
|
||||
class="btn bg-[#7B73FF] text-white hover:bg-[#0A2540] text-lg w-full"
|
||||
:class="{ disabled: isCreatingCheckout }"
|
||||
:disabled="isCreatingCheckout"
|
||||
@click.prevent="startCheckout"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
v-if="isCreatingCheckout"
|
||||
width="22"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<IconBrandStripe
|
||||
v-else
|
||||
width="22"
|
||||
/>
|
||||
<span>
|
||||
{{ t('pay_with_strip') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconBrandStripe, IconInnerShadowTop, IconLoader } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'PaymentStep',
|
||||
components: {
|
||||
IconBrandStripe,
|
||||
IconInnerShadowTop,
|
||||
IconLoader
|
||||
},
|
||||
inject: ['baseUrl', 't'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||
data () {
|
||||
return {
|
||||
isCreatingCheckout: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
queryParams () {
|
||||
return new URLSearchParams(window.location.search)
|
||||
},
|
||||
sessionId () {
|
||||
return this.queryParams.get('stripe_session_id')
|
||||
},
|
||||
defaultName () {
|
||||
const { price, currency } = this.field.preferences || {}
|
||||
|
||||
const formattedPrice = new Intl.NumberFormat([], {
|
||||
style: 'currency',
|
||||
currency
|
||||
}).format(price)
|
||||
|
||||
return `Pay ${formattedPrice}`
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.sessionId) {
|
||||
this.$emit('submit')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submit () {
|
||||
if (this.sessionId) {
|
||||
return fetch((this.baseUrl || '/embed').replace('/embed', '/api/stripe_payments/' + this.sessionId), {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422 || resp.status === 500) {
|
||||
const data = await resp.json()
|
||||
|
||||
alert(data.error || 'Unexpected error')
|
||||
|
||||
return Promise.reject(new Error(data.error))
|
||||
}
|
||||
|
||||
const attachment = await resp.json()
|
||||
|
||||
window.history.replaceState({}, document.title, window.location.pathname)
|
||||
|
||||
this.$emit('update:model-value', attachment.uuid)
|
||||
this.$emit('attached', attachment)
|
||||
|
||||
return resp
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
},
|
||||
startCheckout () {
|
||||
this.isCreatingCheckout = true
|
||||
|
||||
fetch((this.baseUrl || '/embed').replace('/embed', '/api/stripe_payments'), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
submitter_slug: this.submitterSlug,
|
||||
field_uuid: this.field.uuid
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422 || resp.status === 500) {
|
||||
const data = await resp.json()
|
||||
|
||||
alert(data.message || 'Unexpected error')
|
||||
|
||||
return Promise.reject(new Error(data.message))
|
||||
}
|
||||
|
||||
const { url } = await resp.json()
|
||||
|
||||
const link = document.createElement('a')
|
||||
|
||||
link.href = url
|
||||
|
||||
if (url) {
|
||||
link.click()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.isCreatingCheckout = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user