paypal configs

Co-authored-by: Pete Matsyburka <pete@docuseal.com>
master^2
Alex Turchyn 6 days ago committed by Pete Matsyburka
parent 972f38242e
commit b8c29324b8

@ -37,7 +37,7 @@ class AccountConfigsController < ApplicationController
end end
def destroy def destroy
raise InvalidKey unless allowed_keys.include?(@account_config.key) raise InvalidKey unless allowed_destroy_keys.include?(@account_config.key)
@account_config.destroy! @account_config.destroy!
@ -50,6 +50,10 @@ class AccountConfigsController < ApplicationController
ALLOWED_KEYS ALLOWED_KEYS
end end
def allowed_destroy_keys
ALLOWED_KEYS
end
def load_account_config def load_account_config
raise InvalidKey unless allowed_keys.include?(account_config_params[:key]) raise InvalidKey unless allowed_keys.include?(account_config_params[:key])

@ -18,6 +18,6 @@ class TemplatesFormPreviewController < ApplicationController
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments) @attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid) .preload(:blob).index_by(&:uuid)
@form_configs = Submitters::FormConfigs.call(@submitter) @form_configs = Submitters::FormConfigs.call(@submitter, SubmitFormController::CONFIG_KEYS)
end end
end end

@ -181,7 +181,10 @@ safeRegisterElement('template-builder', class extends HTMLElement {
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content, authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
withCustomFields: true, withCustomFields: true,
withPayment: this.dataset.withPayment === 'true', withPayment: this.dataset.withPayment === 'true',
isPaymentConnected: this.dataset.isPaymentConnected === 'true', isStripeConnected: this.dataset.isPaymentConnected === 'true' || this.dataset.isStripeConnected === 'true',
withStripe: this.dataset.withStripe !== 'false',
withPaypal: this.dataset.withPaypal === 'true',
isPaypalConnected: this.dataset.isPaypalConnected === 'true',
withFormula: this.dataset.withFormula === 'true', withFormula: this.dataset.withFormula === 'true',
withSendButton: this.dataset.withSendButton !== 'false', withSendButton: this.dataset.withSendButton !== 'false',
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false', withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',

@ -35,6 +35,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
scrollPadding: this.dataset.scrollPadding || '-80px', scrollPadding: this.dataset.scrollPadding || '-80px',
signatureText: this.dataset.signatureText, signatureText: this.dataset.signatureText,
language: this.dataset.language, language: this.dataset.language,
paymentProvider: this.dataset.paymentProvider,
dryRun: this.dataset.dryRun === 'true', dryRun: this.dataset.dryRun === 'true',
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
withSignatureId: this.dataset.withSignatureId === 'true', withSignatureId: this.dataset.withSignatureId === 'true',

@ -560,6 +560,7 @@
:fields="formulaFields" :fields="formulaFields"
:values="values" :values="values"
:readonly-values="readonlyFieldValues" :readonly-values="readonlyFieldValues"
:provider="paymentProvider"
@attached="attachments.push($event)" @attached="attachments.push($event)"
@focus="scrollIntoField(currentField)" @focus="scrollIntoField(currentField)"
@submit="!isSubmitting && submitStep()" @submit="!isSubmitting && submitStep()"
@ -1000,6 +1001,11 @@ export default {
required: false, required: false,
default: '' default: ''
}, },
paymentProvider: {
type: String,
required: false,
default: ''
},
values: { values: {
type: Object, type: Object,
required: false, required: false,

@ -107,6 +107,7 @@ const en = {
email_has_been_sent: 'Email has been sent', email_has_been_sent: 'Email has been sent',
processing: 'Processing', processing: 'Processing',
pay_with_stripe: 'Pay with Stripe', pay_with_stripe: 'Pay with Stripe',
pay_with_paypal: 'Pay with PayPal',
reupload: 'Reupload', reupload: 'Reupload',
upload: 'Upload', upload: 'Upload',
files: 'Files', files: 'Files',
@ -225,6 +226,7 @@ const es = {
email_has_been_sent: 'El correo electrónico ha sido enviado', email_has_been_sent: 'El correo electrónico ha sido enviado',
processing: 'Procesando', processing: 'Procesando',
pay_with_stripe: 'Pagar con Stripe', pay_with_stripe: 'Pagar con Stripe',
pay_with_paypal: 'Pagar con PayPal',
reupload: 'Volver a subir', reupload: 'Volver a subir',
upload: 'Subir', upload: 'Subir',
files: 'Archivos', files: 'Archivos',
@ -343,6 +345,7 @@ const it = {
email_has_been_sent: "L'email è stata inviata", email_has_been_sent: "L'email è stata inviata",
processing: 'Elaborazione', processing: 'Elaborazione',
pay_with_stripe: 'Paga con Stripe', pay_with_stripe: 'Paga con Stripe',
pay_with_paypal: 'Paga con PayPal',
reupload: 'Ricarica', reupload: 'Ricarica',
upload: 'Carica', upload: 'Carica',
files: 'File', files: 'File',
@ -461,6 +464,7 @@ const de = {
email_has_been_sent: 'E-Mail wurde gesendet', email_has_been_sent: 'E-Mail wurde gesendet',
processing: 'Verarbeitung', processing: 'Verarbeitung',
pay_with_stripe: 'Mit Stripe bezahlen', pay_with_stripe: 'Mit Stripe bezahlen',
pay_with_paypal: 'Mit PayPal bezahlen',
reupload: 'Erneut hochladen', reupload: 'Erneut hochladen',
upload: 'Hochladen', upload: 'Hochladen',
files: 'Dateien', files: 'Dateien',
@ -579,6 +583,7 @@ const fr = {
email_has_been_sent: "L'e-mail a été envoyé", email_has_been_sent: "L'e-mail a été envoyé",
processing: 'Traitement en cours', processing: 'Traitement en cours',
pay_with_stripe: 'Payer avec Stripe', pay_with_stripe: 'Payer avec Stripe',
pay_with_paypal: 'Payer avec PayPal',
reupload: 'Retéléverser', reupload: 'Retéléverser',
upload: 'Téléverser', upload: 'Téléverser',
files: 'Fichiers', files: 'Fichiers',
@ -697,6 +702,7 @@ const pl = {
email_has_been_sent: 'E-mail został wysłany', email_has_been_sent: 'E-mail został wysłany',
processing: 'Przetwarzanie', processing: 'Przetwarzanie',
pay_with_stripe: 'Zapłać za pomocą Stripe', pay_with_stripe: 'Zapłać za pomocą Stripe',
pay_with_paypal: 'Zapłać za pomocą PayPal',
reupload: 'Ponowne przesłanie', reupload: 'Ponowne przesłanie',
upload: 'Przesyłanie', upload: 'Przesyłanie',
files: 'Pliki', files: 'Pliki',
@ -815,6 +821,7 @@ const uk = {
email_has_been_sent: 'Електронний лист був відправлений', email_has_been_sent: 'Електронний лист був відправлений',
processing: 'Обробка', processing: 'Обробка',
pay_with_stripe: 'Сплатити за допомогою Stripe', pay_with_stripe: 'Сплатити за допомогою Stripe',
pay_with_paypal: 'Сплатити за допомогою PayPal',
reupload: 'Перезавантажити', reupload: 'Перезавантажити',
upload: 'Завантажити', upload: 'Завантажити',
files: 'Файли', files: 'Файли',
@ -933,6 +940,7 @@ const cs = {
email_has_been_sent: 'E-mail byl odeslán', email_has_been_sent: 'E-mail byl odeslán',
processing: 'Zpracování', processing: 'Zpracování',
pay_with_stripe: 'Zaplatit přes Stripe', pay_with_stripe: 'Zaplatit přes Stripe',
pay_with_paypal: 'Zaplatit přes PayPal',
reupload: 'Znovu nahrát', reupload: 'Znovu nahrát',
upload: 'Nahrát', upload: 'Nahrát',
files: 'Soubory', files: 'Soubory',
@ -1051,6 +1059,7 @@ const pt = {
email_has_been_sent: 'Email enviado', email_has_been_sent: 'Email enviado',
processing: 'Processamento', processing: 'Processamento',
pay_with_stripe: 'Pagar com Stripe', pay_with_stripe: 'Pagar com Stripe',
pay_with_paypal: 'Pagar com PayPal',
reupload: 'Recarregar', reupload: 'Recarregar',
upload: 'Carregar', upload: 'Carregar',
files: 'Arquivos', files: 'Arquivos',
@ -1169,6 +1178,7 @@ const he = {
email_has_been_sent: 'האימייל נשלח', email_has_been_sent: 'האימייל נשלח',
processing: 'מעבד', processing: 'מעבד',
pay_with_stripe: 'שלם עם סטרייפ', pay_with_stripe: 'שלם עם סטרייפ',
pay_with_paypal: 'שלם עם פייפאל',
reupload: 'העלה שוב', reupload: 'העלה שוב',
upload: 'העלאה', upload: 'העלאה',
files: 'קבצים', files: 'קבצים',
@ -1287,6 +1297,7 @@ const nl = {
email_has_been_sent: 'E-mail is verzonden', email_has_been_sent: 'E-mail is verzonden',
processing: 'Verwerken', processing: 'Verwerken',
pay_with_stripe: 'Betalen met Stripe', pay_with_stripe: 'Betalen met Stripe',
pay_with_paypal: 'Betalen met PayPal',
reupload: 'Opnieuw uploaden', reupload: 'Opnieuw uploaden',
upload: 'Uploaden', upload: 'Uploaden',
files: 'Bestanden', files: 'Bestanden',
@ -1405,6 +1416,7 @@ const ar = {
email_has_been_sent: 'تم إرسال البريد الإلكتروني', email_has_been_sent: 'تم إرسال البريد الإلكتروني',
processing: 'جارٍ المعالجة', processing: 'جارٍ المعالجة',
pay_with_stripe: 'الدفع بواسطة Stripe', pay_with_stripe: 'الدفع بواسطة Stripe',
pay_with_paypal: 'الدفع بواسطة PayPal',
reupload: 'إعادة التحميل', reupload: 'إعادة التحميل',
upload: 'تحميل', upload: 'تحميل',
files: 'الملفات', files: 'الملفات',
@ -1523,6 +1535,7 @@ const ko = {
email_has_been_sent: '이메일이 전송되었습니다', email_has_been_sent: '이메일이 전송되었습니다',
processing: '처리 중', processing: '처리 중',
pay_with_stripe: '스트라이프로 결제', pay_with_stripe: '스트라이프로 결제',
pay_with_paypal: 'PayPal로 결제',
reupload: '다시 업로드', reupload: '다시 업로드',
upload: '업로드', upload: '업로드',
files: '파일', files: '파일',
@ -1641,6 +1654,7 @@ const ja = {
email_has_been_sent: 'メールが送信されました', email_has_been_sent: 'メールが送信されました',
processing: '処理中', processing: '処理中',
pay_with_stripe: 'Stripeで支払う', pay_with_stripe: 'Stripeで支払う',
pay_with_paypal: 'PayPalで支払う',
reupload: '再アップロード', reupload: '再アップロード',
upload: 'アップロード', upload: 'アップロード',
files: 'ファイル', files: 'ファイル',

@ -1,6 +1,6 @@
<template> <template>
<label <label
v-if="!modelValue && !sessionId" v-if="!modelValue && !isProcessing"
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label" class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
> >
<MarkdownContent <MarkdownContent
@ -25,14 +25,14 @@
class="hidden" class="hidden"
> >
<div <div
v-if="modelValue && !sessionId" v-if="modelValue && !isProcessing"
class=" text-2xl mb-2" class=" text-2xl mb-2"
> >
{{ t('already_paid') }} {{ t('already_paid') }}
</div> </div>
<div v-else> <div v-else>
<button <button
v-if="sessionId" v-if="isProcessing"
type="button" type="button"
disabled disabled
class="base-button w-full modal-save-button" class="base-button w-full modal-save-button"
@ -49,8 +49,8 @@
v-else v-else
:id="field.uuid" :id="field.uuid"
type="button" type="button"
class="btn bg-[#7B73FF] text-white hover:bg-[#0A2540] text-lg w-full" class="btn text-white text-lg w-full"
:class="{ disabled: isCreatingCheckout }" :class="[isPaypal ? 'bg-[#003087] hover:bg-[#012169]' : 'bg-[#7B73FF] hover:bg-[#0A2540]', { disabled: isCreatingCheckout }]"
:disabled="isCreatingCheckout" :disabled="isCreatingCheckout"
@click.prevent="postCheckout" @click.prevent="postCheckout"
> >
@ -59,12 +59,16 @@
width="22" width="22"
class="animate-spin" class="animate-spin"
/> />
<IconBrandPaypal
v-else-if="isPaypal"
width="22"
/>
<IconBrandStripe <IconBrandStripe
v-else v-else
width="22" width="22"
/> />
<span> <span>
{{ t('pay_with_stripe') }} {{ isPaypal ? t('pay_with_paypal') : t('pay_with_stripe') }}
</span> </span>
</button> </button>
</div> </div>
@ -72,13 +76,14 @@
</template> </template>
<script> <script>
import { IconBrandStripe, IconInnerShadowTop, IconLoader } from '@tabler/icons-vue' import { IconBrandStripe, IconBrandPaypal, IconInnerShadowTop, IconLoader } from '@tabler/icons-vue'
import MarkdownContent from './markdown_content' import MarkdownContent from './markdown_content'
export default { export default {
name: 'PaymentStep', name: 'PaymentStep',
components: { components: {
IconBrandStripe, IconBrandStripe,
IconBrandPaypal,
MarkdownContent, MarkdownContent,
IconInnerShadowTop, IconInnerShadowTop,
IconLoader IconLoader
@ -111,12 +116,18 @@ export default {
submitterSlug: { submitterSlug: {
type: String, type: String,
required: true required: true
},
provider: {
type: String,
required: false,
default: ''
} }
}, },
emits: ['focus', 'submit', 'update:model-value', 'attached'], emits: ['focus', 'submit', 'update:model-value', 'attached'],
data () { data () {
return { return {
isCreatingCheckout: false, isCreatingCheckout: false,
isProcessing: false,
isMathLoaded: false isMathLoaded: false
} }
}, },
@ -131,8 +142,14 @@ export default {
queryParams () { queryParams () {
return new URLSearchParams(window.location.search) return new URLSearchParams(window.location.search)
}, },
isPaypal () {
return this.provider === 'paypal'
},
paymentEndpoint () {
return this.isPaypal ? '/api/paypal_payments' : '/api/stripe_payments'
},
sessionId () { sessionId () {
return this.queryParams.get('stripe_session_id') return this.queryParams.get('stripe_session_id') || this.queryParams.get('paypal_order_id') || (this.isPaypal && this.queryParams.get('token'))
}, },
defaultName () { defaultName () {
if (this.field.preferences?.price_id || this.field.preferences?.payment_link_id) { if (this.field.preferences?.price_id || this.field.preferences?.payment_link_id) {
@ -159,6 +176,8 @@ export default {
}, },
async mounted () { async mounted () {
if (this.sessionId) { if (this.sessionId) {
this.isProcessing = true
this.$emit('submit') this.$emit('submit')
} }
@ -175,6 +194,21 @@ export default {
} }
}, },
methods: { methods: {
normalizedPaymentUrl () {
const url = new URL(window.location.href)
const keys = ['stripe_session_id', 'paypal_order_id', 'field_uuid']
if (this.isPaypal) {
keys.push('token', 'PayerID')
}
keys.forEach((key) => url.searchParams.delete(key))
return url.toString()
},
normalizedPaymentParams () {
window.history.replaceState({}, document.title, this.normalizedPaymentUrl())
},
calculateFormula () { calculateFormula () {
const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => { const transformedFormula = this.normalizeFormula(this.field.preferences.formula).replace(/{{(.*?)}}/g, (match, uuid) => {
return this.readonlyValues[uuid] || this.values[uuid] || 0.0 return this.readonlyValues[uuid] || this.values[uuid] || 0.0
@ -195,7 +229,7 @@ export default {
}, },
async submit () { async submit () {
if (this.sessionId) { if (this.sessionId) {
return fetch(this.baseUrl + '/api/stripe_payments/' + this.sessionId, { return fetch(this.baseUrl + this.paymentEndpoint + '/' + this.sessionId, {
method: 'PUT', method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
submitter_slug: this.submitterSlug submitter_slug: this.submitterSlug
@ -203,16 +237,22 @@ export default {
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}).then(async (resp) => { }).then(async (resp) => {
if (resp.status === 422 || resp.status === 500) { if (resp.status === 422 || resp.status === 500) {
this.isProcessing = false
const data = await resp.json() const data = await resp.json()
alert(data.error || 'Unexpected error') if (data.error) {
alert(data.error)
}
return Promise.reject(new Error(data.error)) return Promise.reject(new Error(data.error))
} }
const attachment = await resp.json() const attachment = await resp.json()
window.history.replaceState({}, document.title, window.location.pathname) this.isProcessing = false
this.normalizedPaymentParams()
this.$emit('update:model-value', attachment.uuid) this.$emit('update:model-value', attachment.uuid)
this.$emit('attached', attachment) this.$emit('attached', attachment)
@ -226,13 +266,13 @@ export default {
postCheckout ({ checkStatus } = {}) { postCheckout ({ checkStatus } = {}) {
this.isCreatingCheckout = true this.isCreatingCheckout = true
fetch(this.baseUrl + '/api/stripe_payments', { fetch(this.baseUrl + this.paymentEndpoint, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
submitter_slug: this.submitterSlug, submitter_slug: this.submitterSlug,
field_uuid: this.field.uuid, field_uuid: this.field.uuid,
check_status: checkStatus, check_status: checkStatus,
referer: document.location.href referer: this.normalizedPaymentUrl()
}), }),
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}).then(async (resp) => { }).then(async (resp) => {

@ -779,7 +779,10 @@ export default {
withVerification: this.withVerification, withVerification: this.withVerification,
withKba: this.withKba, withKba: this.withKba,
withPayment: this.withPayment, withPayment: this.withPayment,
isPaymentConnected: this.isPaymentConnected, isStripeConnected: this.isStripeConnected,
withStripe: this.withStripe,
withPaypal: this.withPaypal,
isPaypalConnected: this.isPaypalConnected,
withFormula: this.withFormula, withFormula: this.withFormula,
withConditions: this.withConditions, withConditions: this.withConditions,
withCustomFields: this.withCustomFields, withCustomFields: this.withCustomFields,
@ -1049,7 +1052,22 @@ export default {
required: false, required: false,
default: false default: false
}, },
isPaymentConnected: { isStripeConnected: {
type: Boolean,
required: false,
default: false
},
withStripe: {
type: Boolean,
required: false,
default: true
},
withPaypal: {
type: Boolean,
required: false,
default: false
},
isPaypalConnected: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
@ -1363,6 +1381,10 @@ export default {
if (document.location.search?.includes('stripe_connect_success')) { if (document.location.search?.includes('stripe_connect_success')) {
document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus() document.querySelector('form[action="/auth/stripe_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
} }
if (document.location.search?.includes('paypal_connect_success')) {
document.querySelector('a[href^="/auth/paypal_connect"]')?.closest('.dropdown')?.querySelector('label')?.focus()
}
}) })
this.template.schema.forEach((item) => { this.template.schema.forEach((item) => {

@ -494,6 +494,7 @@ import FieldType from './field_type.vue'
import FieldSettings from './field_settings.vue' import FieldSettings from './field_settings.vue'
import ContextSubmenu from './field_context_submenu.vue' import ContextSubmenu from './field_context_submenu.vue'
import ContextModal from './field_context_modal.vue' import ContextModal from './field_context_modal.vue'
import { stripeConnectedRef } from './payment_settings'
export default { export default {
name: 'FieldContextMenu', name: 'FieldContextMenu',
@ -716,12 +717,18 @@ export default {
currentCurrency () { currentCurrency () {
return this.field.preferences?.currency || 'USD' return this.field.preferences?.currency || 'USD'
}, },
isStripeConnected: () => stripeConnectedRef.value,
priceTypeOptions () { priceTypeOptions () {
return [ const options = [
{ value: 'one_off', label: this.t('fixed') }, { value: 'one_off', label: this.t('fixed') },
{ value: 'formula', label: this.t('formula') }, { value: 'formula', label: this.t('formula') }
{ value: 'payment_link', label: this.t('payment_link') }
] ]
if (this.isStripeConnected) {
options.push({ value: 'payment_link', label: this.t('payment_link') })
}
return options
}, },
currentPriceType () { currentPriceType () {
if (this.field.preferences?.formula) { if (this.field.preferences?.formula) {

@ -1,7 +1,7 @@
<template> <template>
<span <span
class="dropdown dropdown-end field-settings-dropdown" class="dropdown dropdown-end field-settings-dropdown"
:class="{ 'dropdown-open': withForceOpen && ((!field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id) || !isConnected) && !isLoading }" :class="{ 'dropdown-open': withForceOpen && !field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id && !isLoading }"
> >
<label <label
tabindex="0" tabindex="0"
@ -21,7 +21,7 @@
@click="closeDropdown" @click="closeDropdown"
> >
<div <div
v-if="!('price_id' in field.preferences) && !('payment_link_id' in field.preferences)" v-if="isPaypal || (!('price_id' in field.preferences) && !('payment_link_id' in field.preferences))"
class="field-settings-currency py-1.5 px-1 relative" class="field-settings-currency py-1.5 px-1 relative"
@click.stop @click.stop
> >
@ -52,14 +52,14 @@
@click.stop @click.stop
> >
<input <input
v-if="'payment_link_id' in field.preferences" v-if="isStripe && 'payment_link_id' in field.preferences"
v-model="field.preferences.payment_link_id" v-model="field.preferences.payment_link_id"
placeholder="plink_XXXXX" placeholder="plink_XXXXX"
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0" class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@blur="save" @blur="save"
> >
<input <input
v-else-if="'price_id' in field.preferences" v-else-if="isStripe && 'price_id' in field.preferences"
v-model="field.preferences.price_id" v-model="field.preferences.price_id"
placeholder="Price ID: price_XXXXX" placeholder="Price ID: price_XXXXX"
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0" class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
@ -89,7 +89,10 @@
> >
{{ 'payment_link_id' in field.preferences ? t('payment_link') : t('price') }} {{ 'payment_link_id' in field.preferences ? t('payment_link') : t('price') }}
</label> </label>
<div class="flex items-center justify-center"> <div
v-if="isStripe && stripeConnected"
class="flex items-center justify-center"
>
<a <a
href="#" href="#"
class="hover:underline" class="hover:underline"
@ -120,21 +123,22 @@
</div> </div>
</div> </div>
<div <div
v-if="!isConnected || isOauthSuccess" v-if="!stripeConnected && !paypalConnected"
class="field-settings-stripe-connect py-1.5 px-1 relative" class="flex flex-col gap-1 py-1.5 px-1"
@click.stop @click.stop
> >
<div <a
v-if="isConnected && isOauthSuccess" v-if="withPaypal"
class="text-sm text-center" :href="paypalConnectUrl"
target="_blank"
data-turbo="false"
class="btn bg-[#003087] hover:bg-[#012169] btn-sm text-white w-full"
> >
<IconCircleCheck <IconBrandPaypal class="w-4 h-4 inline" />
class="inline text-green-600 w-4 h-4" <span>Connect PayPal</span>
/> </a>
Stripe Connected
</div>
<form <form
v-if="!isConnected" v-if="withStripe"
data-turbo="false" data-turbo="false"
action="/auth/stripe_connect" action="/auth/stripe_connect"
accept-charset="UTF-8" accept-charset="UTF-8"
@ -170,33 +174,25 @@
:disabled="isLoading" :disabled="isLoading"
class="btn bg-[#7B73FF] hover:bg-[#0A2540] btn-sm text-white w-full" class="btn bg-[#7B73FF] hover:bg-[#0A2540] btn-sm text-white w-full"
> >
<span <IconInnerShadowTop
v-if="isLoading" v-if="isLoading"
class="flex items-center space-x-1" class="w-4 h-4 animate-spin inline"
> />
<IconInnerShadowTop <IconBrandStripe
class="w-4 h-4 animate-spin inline"
/>
<span>
Connect Stripe
</span>
</span>
<span
v-else v-else
class="flex items-center space-x-1" class="w-4 h-4 inline"
> />
<IconBrandStripe <span>Connect Stripe</span>
class="w-4 h-4 inline"
/>
<span>
Connect Stripe
</span>
</span>
</button> </button>
</form> </form>
</div>
<div
v-if="isStripe && !stripeConnected"
class="px-1 pb-1.5"
@click.stop
>
<a <a
v-if="!isConnected" class="block link text-center text-xs"
class="block link text-center mt-1"
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease" href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
target="_blank" target="_blank"
data-turbo="false" data-turbo="false"
@ -272,24 +268,25 @@
</template> </template>
<script> <script>
import { IconMathFunction, IconSettings, IconCircleCheck, IconInfoCircle, IconBrandStripe, IconInnerShadowTop, IconRouteAltLeft, IconForms } from '@tabler/icons-vue' import { IconMathFunction, IconSettings, IconInfoCircle, IconBrandStripe, IconBrandPaypal, IconInnerShadowTop, IconRouteAltLeft, IconForms } from '@tabler/icons-vue'
import { ref } from 'vue' import { ref } from 'vue'
const isConnected = ref(false) export const stripeConnectedRef = ref(false)
export const paypalConnectedRef = ref(false)
export default { export default {
name: 'PaymentSettings', name: 'PaymentSettings',
components: { components: {
IconSettings, IconSettings,
IconCircleCheck,
IconRouteAltLeft, IconRouteAltLeft,
IconInfoCircle, IconInfoCircle,
IconForms, IconForms,
IconMathFunction, IconMathFunction,
IconInnerShadowTop, IconInnerShadowTop,
IconBrandStripe IconBrandStripe,
IconBrandPaypal
}, },
inject: ['backgroundColor', 'save', 'currencies', 't', 'isPaymentConnected'], inject: ['backgroundColor', 'save', 'currencies', 't', 'isStripeConnected', 'isPaypalConnected', 'withStripe', 'withPaypal'],
props: { props: {
field: { field: {
type: Object, type: Object,
@ -318,13 +315,27 @@ export default {
} }
}, },
computed: { computed: {
isConnected: () => isConnected.value, stripeConnected: () => stripeConnectedRef.value,
isOauthSuccess () { paypalConnected: () => paypalConnectedRef.value,
return document.location.search?.includes('stripe_connect_success') provider () {
if (this.paypalConnected && !this.stripeConnected) return 'paypal'
if (this.stripeConnected && !this.paypalConnected) return 'stripe'
if (this.withPaypal && !this.withStripe) return 'paypal'
return 'stripe'
},
isStripe () {
return this.provider === 'stripe'
},
isPaypal () {
return this.provider === 'paypal'
}, },
redirectUri () { redirectUri () {
return document.location.origin + '/auth/stripe_connect/callback' return document.location.origin + '/auth/stripe_connect/callback'
}, },
paypalConnectUrl () {
return `/auth/paypal_connect?redir=${encodeURIComponent(document.location.href)}`
},
defaultCurrencies () { defaultCurrencies () {
return ['USD', 'EUR', 'GBP', 'CAD', 'AUD'] return ['USD', 'EUR', 'GBP', 'CAD', 'AUD']
}, },
@ -363,26 +374,53 @@ export default {
mounted () { mounted () {
this.field.preferences.currency ||= this.defaultCurrency this.field.preferences.currency ||= this.defaultCurrency
isConnected.value ||= this.isPaymentConnected stripeConnectedRef.value ||= this.isStripeConnected
paypalConnectedRef.value ||= this.isPaypalConnected
if (this.withStripe && !this.stripeConnected && !this.paypalConnected) {
this.checkStripeStatus()
}
if (!this.isConnected) { if (this.withPaypal && !this.paypalConnected && !this.stripeConnected) {
this.checkStatus() this.checkPaypalStatus()
window.addEventListener('focus', this.onWindowFocus)
} }
}, },
beforeUnmount () {
window.removeEventListener('focus', this.onWindowFocus)
},
methods: { methods: {
checkStatus () { onWindowFocus () {
if (this.withPaypal && !this.paypalConnected) {
this.checkPaypalStatus()
}
},
checkStripeStatus () {
this.isLoading = true this.isLoading = true
fetch('/api/stripe_connect').then(async (resp) => { fetch('/api/stripe_connect').then(async (resp) => {
const { status } = await resp.json() const { status } = await resp.json()
if (status === 'connected') { if (status === 'connected') {
isConnected.value = true stripeConnectedRef.value = true
window.removeEventListener('focus', this.onWindowFocus)
} }
}).finally(() => { }).finally(() => {
this.isLoading = false this.isLoading = false
}) })
}, },
checkPaypalStatus () {
fetch('/api/paypal_connect').then(async (resp) => {
const { status } = await resp.json()
if (status === 'connected') {
paypalConnectedRef.value = true
window.removeEventListener('focus', this.onWindowFocus)
}
})
},
closeDropdown () { closeDropdown () {
document.activeElement.blur() document.activeElement.blur()
} }

@ -53,6 +53,25 @@ en: &en
require_all_recipients: Require all recipients require_all_recipients: Require all recipients
stripe_account_has_been_connected: Stripe account has been connected. stripe_account_has_been_connected: Stripe account has been connected.
re_connect_stripe: Re-connect Stripe re_connect_stripe: Re-connect Stripe
paypal_integration: PayPal Integration
paypal_account_has_been_connected: PayPal account has been connected.
re_connect_paypal: Re-connect PayPal
disconnect: Disconnect
failed_to_connect_paypal: Failed to connect PayPal. Please try again.
paypal_connect_not_completed: PayPal connection was not completed.
payment_receipt: Payment Receipt
amount_paid: Amount paid
transaction: Transaction
transaction_id: Transaction ID
order_id: Order ID
payment_date: Date
paid_by: Paid by
paid_to: Paid to
business: Business
country: Country
payer_id: Payer ID
merchant_id: Merchant ID
breakdown: Breakdown
bcc_recipients: BCC recipients bcc_recipients: BCC recipients
resend_pending: Re-send pending resend_pending: Re-send pending
always_enforce_signing_order: Always enforce the signing order always_enforce_signing_order: Always enforce the signing order
@ -1124,6 +1143,24 @@ es: &es
stripe_integration: Integración con Stripe stripe_integration: Integración con Stripe
stripe_account_has_been_connected: La cuenta de Stripe ha sido conectada. stripe_account_has_been_connected: La cuenta de Stripe ha sido conectada.
re_connect_stripe: Volver a conectar Stripe re_connect_stripe: Volver a conectar Stripe
paypal_integration: Integración con PayPal
paypal_account_has_been_connected: La cuenta de PayPal ha sido conectada.
re_connect_paypal: Volver a conectar PayPal
failed_to_connect_paypal: No se pudo conectar PayPal. Por favor, inténtalo de nuevo.
paypal_connect_not_completed: La conexión con PayPal no se completó.
payment_receipt: Recibo de pago
amount_paid: Importe pagado
transaction: Transacción
transaction_id: ID de transacción
order_id: ID de pedido
payment_date: Fecha
paid_by: Pagado por
paid_to: Pagado a
business: Empresa
country: País
payer_id: ID de pagador
merchant_id: ID de comerciante
breakdown: Desglose
private: Privado private: Privado
_variables: Variables _variables: Variables
your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Su dirección de correo electrónico ha sido liberada inmediatamente. Si se une a un equipo, ahora puede ser invitado al equipo de su empresa sin esperar a que finalice el período de eliminación. your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Su dirección de correo electrónico ha sido liberada inmediatamente. Si se une a un equipo, ahora puede ser invitado al equipo de su empresa sin esperar a que finalice el período de eliminación.
@ -2202,6 +2239,24 @@ it: &it
stripe_integration: Integrazione Stripe stripe_integration: Integrazione Stripe
stripe_account_has_been_connected: L'account Stripe è stato collegato. stripe_account_has_been_connected: L'account Stripe è stato collegato.
re_connect_stripe: Ricollega Stripe re_connect_stripe: Ricollega Stripe
paypal_integration: Integrazione PayPal
paypal_account_has_been_connected: L'account PayPal è stato collegato.
re_connect_paypal: Ricollega PayPal
failed_to_connect_paypal: Impossibile collegare PayPal. Riprova.
paypal_connect_not_completed: La connessione con PayPal non è stata completata.
payment_receipt: Ricevuta di pagamento
amount_paid: Importo pagato
transaction: Transazione
transaction_id: ID transazione
order_id: ID ordine
payment_date: Data
paid_by: Pagato da
paid_to: Pagato a
business: Azienda
country: Paese
payer_id: ID pagatore
merchant_id: ID esercente
breakdown: Dettaglio
private: Privato private: Privato
_variables: Variabili _variables: Variabili
your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Il tuo indirizzo email è stato rilasciato immediatamente. Se stai per unirti a un team, ora puoi essere invitato nel team della tua azienda senza aspettare la fine del periodo di eliminazione. your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Il tuo indirizzo email è stato rilasciato immediatamente. Se stai per unirti a un team, ora puoi essere invitato nel team della tua azienda senza aspettare la fine del periodo di eliminazione.
@ -3280,6 +3335,24 @@ fr: &fr
stripe_integration: Intégration Stripe stripe_integration: Intégration Stripe
stripe_account_has_been_connected: Le compte Stripe a été connecté. stripe_account_has_been_connected: Le compte Stripe a été connecté.
re_connect_stripe: Reconnecter Stripe re_connect_stripe: Reconnecter Stripe
paypal_integration: Intégration PayPal
paypal_account_has_been_connected: Le compte PayPal a été connecté.
re_connect_paypal: Reconnecter PayPal
failed_to_connect_paypal: Échec de la connexion à PayPal. Veuillez réessayer.
paypal_connect_not_completed: La connexion à PayPal n'a pas été complétée.
payment_receipt: Reçu de paiement
amount_paid: Montant payé
transaction: Transaction
transaction_id: ID de transaction
order_id: ID de commande
payment_date: Date
paid_by: Payé par
paid_to: Payé à
business: Entreprise
country: Pays
payer_id: ID payeur
merchant_id: ID marchand
breakdown: Détail
private: Privé private: Privé
_variables: Variables _variables: Variables
your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Votre adresse e-mail a été libérée immédiatement. Si vous rejoignez une équipe, vous pouvez maintenant être invité dans l'équipe de votre entreprise sans attendre la fin de la période de suppression. your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Votre adresse e-mail a été libérée immédiatement. Si vous rejoignez une équipe, vous pouvez maintenant être invité dans l'équipe de votre entreprise sans attendre la fin de la période de suppression.
@ -4355,6 +4428,24 @@ pt: &pt
stripe_integration: Integração com Stripe stripe_integration: Integração com Stripe
stripe_account_has_been_connected: Conta Stripe foi conectada. stripe_account_has_been_connected: Conta Stripe foi conectada.
re_connect_stripe: Reconectar Stripe re_connect_stripe: Reconectar Stripe
paypal_integration: Integração com PayPal
paypal_account_has_been_connected: Conta PayPal foi conectada.
re_connect_paypal: Reconectar PayPal
failed_to_connect_paypal: Falha ao conectar PayPal. Tente novamente.
paypal_connect_not_completed: A conexão com PayPal não foi concluída.
payment_receipt: Recibo de pagamento
amount_paid: Valor pago
transaction: Transação
transaction_id: ID da transação
order_id: ID do pedido
payment_date: Data
paid_by: Pago por
paid_to: Pago a
business: Empresa
country: País
payer_id: ID do pagador
merchant_id: ID do comerciante
breakdown: Detalhamento
private: Privado private: Privado
_variables: Variáveis _variables: Variáveis
your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Seu endereço de e-mail foi liberado imediatamente. Se você estiver entrando em uma equipe, agora pode ser convidado para a equipe da sua empresa sem esperar o fim do período de exclusão. your_email_address_has_been_released_immediately_you_can_now_be_added_to_your_company_team_without_waiting_for_the_deletion_period_to_end: Seu endereço de e-mail foi liberado imediatamente. Se você estiver entrando em uma equipe, agora pode ser convidado para a equipe da sua empresa sem esperar o fim do período de exclusão.
@ -5443,6 +5534,24 @@ de: &de
require_all_recipients: Alle Empfänger verpflichten require_all_recipients: Alle Empfänger verpflichten
stripe_account_has_been_connected: Stripe-Konto wurde verbunden. stripe_account_has_been_connected: Stripe-Konto wurde verbunden.
re_connect_stripe: Stripe erneut verbinden re_connect_stripe: Stripe erneut verbinden
paypal_integration: PayPal-Integration
paypal_account_has_been_connected: PayPal-Konto wurde verbunden.
re_connect_paypal: PayPal erneut verbinden
failed_to_connect_paypal: PayPal konnte nicht verbunden werden. Bitte erneut versuchen.
paypal_connect_not_completed: Die PayPal-Verbindung wurde nicht abgeschlossen.
payment_receipt: Zahlungsbeleg
amount_paid: Gezahlter Betrag
transaction: Transaktion
transaction_id: Transaktions-ID
order_id: Bestell-ID
payment_date: Datum
paid_by: Gezahlt von
paid_to: Gezahlt an
business: Unternehmen
country: Land
payer_id: Zahler-ID
merchant_id: Händler-ID
breakdown: Aufschlüsselung
bcc_recipients: BCC-Empfänger bcc_recipients: BCC-Empfänger
resend_pending: Ausstehende erneut versenden resend_pending: Ausstehende erneut versenden
always_enforce_signing_order: Reihenfolge der Unterschriften immer erzwingen always_enforce_signing_order: Reihenfolge der Unterschriften immer erzwingen
@ -6925,6 +7034,24 @@ nl: &nl
require_all_recipients: Alle ontvangers verplichten require_all_recipients: Alle ontvangers verplichten
stripe_account_has_been_connected: Stripe-account is verbonden. stripe_account_has_been_connected: Stripe-account is verbonden.
re_connect_stripe: Stripe opnieuw verbinden re_connect_stripe: Stripe opnieuw verbinden
paypal_integration: PayPal-integratie
paypal_account_has_been_connected: PayPal-account is verbonden.
re_connect_paypal: PayPal opnieuw verbinden
failed_to_connect_paypal: PayPal kon niet worden verbonden. Probeer het opnieuw.
paypal_connect_not_completed: De verbinding met PayPal is niet voltooid.
payment_receipt: Betalingsbewijs
amount_paid: Betaald bedrag
transaction: Transactie
transaction_id: Transactie-ID
order_id: Order-ID
payment_date: Datum
paid_by: Betaald door
paid_to: Betaald aan
business: Bedrijf
country: Land
payer_id: Betaler-ID
merchant_id: Verkoper-ID
breakdown: Specificatie
bcc_recipients: BCC-ontvangers bcc_recipients: BCC-ontvangers
resend_pending: Opnieuw verzenden in behandeling resend_pending: Opnieuw verzenden in behandeling
always_enforce_signing_order: Ondertekenvolgorde altijd afdwingen always_enforce_signing_order: Ondertekenvolgorde altijd afdwingen

Loading…
Cancel
Save