opnform-host-nginx/client/composables/useStripeElements.js

337 lines
9.7 KiB
JavaScript
Raw Normal View History

Stripe Payment (#679) * oAuth for Stripe * Stripe Payment Frontend - WIP * Payment block backend validation and new package for stripe * change stripe scopes * update PaymentBlockConfigurationRule * Set loader on provider modal * stripe oauth * PaymentFieldOptions as seprate component * validate Stripe account * Payment intent * Stripe Payment as composable * confirmCardPayment working * Set payment errors on form.errors * Validate card other fields * Store payment id to database and on submission add link for view payment on stripe * FormPaymentController no need auth middleware * paymentinput error display on field * Make payment block as input change 'nf-payment' to 'payment' * Refactor payment processing and error handling * Multi lang & direction support on payment * reset card on change direction or local * use connected account for loadstripe * validate OAuthProvider before delete it * payment improvements * display payment by stripe * use stripe_currencies.json * Form Payment testcase * Enhance form auto-save behavior for payment forms * Restrict payment block in self-hosted environments * validate form before process payment * Refactor Nuxt Configuration for Improved Development Server Settings - Removed the existing Vite server configuration for hot module replacement (HMR) as it was no longer necessary. - Introduced a new `devServer` configuration to specify the host and port for the development server, allowing for more flexible environment setup based on environment variables. These changes aim to streamline the development process by enhancing server configuration and ensuring better adaptability to different environments. * Enhance Payment Handling and User Experience in Forms - Refactored `FormPaymentController` to improve handling of Stripe account retrieval, including better error messages for both editor preview and public forms. - Updated `OAuthProviderController` to utilize caching for OAuth connection context, enhancing performance and user experience during account connections. - Improved `PaymentInput.client.vue` to display a loading state and a preview message for users, guiding them to save the form for payment activation. - Modified various components to standardize payment-related messages and improve localization support across multiple languages. - Removed the deprecated `connections.vue` page to streamline the codebase. These changes aim to enhance the overall user experience when handling payments and improve the maintainability of the payment-related components. * Refactor Payment Handling and Enhance User Experience in Forms - Updated `FormPaymentController` to utilize a new method for checking if the Stripe provider belongs to any workspace user, improving security and error logging. - Modified `OAuthProviderController` to streamline the OAuth provider creation process by directly using the service object. - Enhanced `Workspace` model with a new method to verify provider ownership, improving code clarity and maintainability. - Improved `PaymentInput.client.vue` to handle loading states and error messages more effectively, enhancing user feedback during payment processing. - Refactored `useFormInput.js` to include an `isAdminPreview` prop for better context handling in form components. These changes aim to improve the overall user experience when handling payments and enhance the maintainability of the payment-related components. * Enhance Payment Validation and User Experience in Forms - Updated `UserFormRequest` to improve workspace handling during form submissions, allowing for better context in validation rules. - Modified `PaymentBlockConfigurationRule` to include workspace validation, ensuring that payment providers are associated with the correct workspace, enhancing security and error logging. - Improved `PaymentInput.client.vue` to dynamically determine the success state of payment processing, providing clearer user feedback. - Updated various localization files to include a payment disclaimer, ensuring users are informed about credit card charges during payment processing. These changes aim to enhance the overall user experience when handling payments and improve the maintainability of payment-related components. * Enhance Payment Features and User Experience in Forms - Added checks in `FormPaymentController` to disable payment features for self-hosted instances, improving clarity for users regarding feature availability. - Updated `PaymentBlockConfigurationRule` to change the minimum amount validation from 0.5 to 1, ensuring stricter payment requirements. - Enhanced `PaymentInput.client.vue` with dark mode support for various UI elements, improving accessibility and user experience in different themes. - Modified `useFormInput.js` to include an `isDark` prop, allowing for better theme handling in form components. - Updated error messages in `useStripeElements.js` to include periods for consistency and improved user feedback. These changes aim to enhance the overall user experience when handling payments and improve the maintainability of payment-related components. * Enhance Payment Input Component with Focus Handling and Theme Support - Updated `PaymentInput.client.vue` to include focus and blur event handlers, improving user interaction by visually indicating when the card input is focused. - Enhanced theme support by adding new properties in `form-themes.js` for `PaymentInput`, allowing for better styling and transitions based on focus state. - Introduced a new `isCardFocused` reactive reference to manage the focus state of the card input, enhancing the overall user experience. These changes aim to improve the usability and visual feedback of the payment input component, aligning with recent enhancements to user experience in payment forms. * Refactor Payment Handling and Improve Code Consistency - Updated various files to enhance code consistency by adding spaces in arrow function definitions, improving readability and adhering to coding standards. - Modified `PaymentBlockConfigurationRule.php`, `FormPaymentController.php`, and `Workspace.php` to ensure uniformity in the use of arrow functions. - Enhanced `PaymentInput.client.vue` and other components by improving the formatting of template elements for better visual structure. - Updated `useStripeElements.js` to streamline state management and improve clarity in the handling of Stripe elements. These changes aim to improve code maintainability and readability across the payment handling components, ensuring a more consistent coding style throughout the codebase. * Enhance Form Model and Logging Configuration - Added a new 'auto_save' boolean property to the Form model, allowing for automatic saving of form data. - Updated the logging configuration to include a 'combined' channel that stacks multiple log channels, improving logging flexibility and error tracking. - Modified the FormFactory to set a default value for 'auto_save' to false, ensuring consistent behavior across form instances. - Improved error message structure in FormPaymentTest to provide clearer feedback when a payment block is missing. These changes aim to enhance the functionality of forms and improve logging capabilities, contributing to better maintainability and user experience. * Update api/config/logging.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Refactor Payment Error Handling and Localization Updates - Updated `FormPaymentTest.php` to improve code consistency by adding spaces in arrow function definitions, enhancing readability. - Modified `useStripeElements.js` to replace hardcoded error messages with localized strings, improving user experience and maintainability. - Enhanced various localization files (e.g., `en.json`, `fr.json`, `de.json`, etc.) to include new error messages related to payment processing, ensuring users receive clear and consistent feedback across different languages. These changes aim to enhance the clarity of error handling in payment processes and improve the overall user experience through better localization support. * Enhance AddFormBlock and FieldOptions with Authentication Checks - Added an icon to indicate authentication requirements for blocks in `AddFormBlock.vue`, improving user awareness of access restrictions. - Implemented alert notifications using `useAlert()` for unauthorized block additions and input cloning, enhancing user feedback. - Updated `FieldOptions.vue` to conditionally render payment field options, ensuring relevant options are displayed based on the field type. - Modified `blocks_types.json` to include an `auth_required` property for specific block types, facilitating authentication checks. These changes aim to improve user experience by providing clear indications of authentication requirements and enhancing form functionality. * Enhance Authentication Checks in AddFormBlock Component - Updated `AddFormBlock.vue` to conditionally render authentication-required icons based on user authentication status, improving user awareness of access restrictions. - Implemented additional authentication checks in the `addBlock` and `handleInputClone` functions, utilizing `useAlert()` for notifying users when login is required to add blocks or clone inputs. - Introduced a computed property to manage the authenticated state, streamlining the authentication logic within the component. These changes aim to enhance user experience by providing clear indications of authentication requirements and improving the functionality of the form component. * Enhance PaymentInput Component with Disabled State Support - Updated `PaymentInput.client.vue` to include a `disabled` prop for the card holder name and email inputs, improving form accessibility and user experience by preventing interaction when necessary. - Modified the card options to respect the `disabled` state, ensuring consistent behavior across the payment input fields. These changes aim to enhance the usability of the payment input component by providing better control over user interactions. * Add Payment Condition Logic and Update Filters - Introduced a new payment condition in `FormLogicConditionChecker.php` to handle 'paid' and 'not_paid' states, enhancing form logic capabilities. - Added corresponding payment comparators in `open_filters.json` for both API and client, ensuring consistent validation and expected types for payment conditions. - Updated the JavaScript logic in `FormLogicConditionChecker.js` to include the new payment condition checks, improving the overall functionality of form conditions. These changes aim to enhance the form logic related to payment states, providing better validation and user experience in payment-related forms. * Refactor Authentication Checks in AddFormBlock and Working Form Store - Removed redundant authentication checks from `AddFormBlock.vue` for adding blocks and cloning inputs, streamlining the logic. - Centralized authentication validation in `working_form.js` to ensure consistent user feedback when authentication is required for specific block types. - Enhanced user experience by utilizing `useAlert()` for notifying users about login requirements, improving clarity and interaction. These changes aim to simplify the authentication logic and improve user notifications regarding access restrictions in form components. * Refactor Feature Flags and Update Payment Input Logic - Updated `FeatureFlagsController.php` to utilize the `Cache` facade directly, improving code clarity and consistency. - Modified `PaymentInput.client.vue` to enhance the display logic for payment previews, ensuring a better user experience by conditionally showing messages based on the state of the Stripe account. - Removed the `STRIPE_PUBLISHABLE_KEY` from `runtimeConfig.js` to streamline the configuration and replaced it with a computed property that retrieves the key from feature flags, improving maintainability. - Adjusted the `.env.example` file to maintain consistency in environment variable definitions. These changes aim to enhance the clarity of feature flag management and improve the user experience in payment interactions by refining the logic and configuration handling. * Update Stripe Configuration in Services - Modified the `services.php` configuration file to enhance the Stripe integration by providing default values for `client_secret` and `redirect` URI. This change ensures that the application can fallback to a predefined secret and a specific callback URL, improving the robustness of the payment service setup. These changes aim to streamline the configuration process for Stripe, ensuring that necessary values are always available for the application to function correctly. --------- Co-authored-by: Chirag Chhatrala <chirag.chhatrala@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-04-10 12:04:25 +02:00
import { computed, provide, inject, reactive } from 'vue'
import { useI18n } from '#imports'
// Symbol for injection key
export const STRIPE_ELEMENTS_KEY = Symbol('stripe-elements')
export const createStripeElements = () => {
// Get the translation function
const { t } = useI18n()
// Use reactive for the state to ensure changes propagate
const state = reactive({
// Loading states
isLoadingAccount: false,
hasAccountLoadingError: false,
isStripeInstanceReady: false,
isCardElementReady: false,
// Core Stripe objects
stripe: null,
elements: null,
card: null,
// Form data
cardHolderName: '',
cardHolderEmail: '',
// Account & payment state
stripeAccountId: null,
intentId: null,
showPreviewMessage: false,
errorMessage: ''
})
// Computed properties
const isReadyForPayment = computed(() => {
return state.isStripeInstanceReady &&
state.isCardElementReady &&
state.stripeAccountId
})
const isCardPopulated = computed(() => {
return state.card && !state.card._empty
})
/**
* Resets the Stripe state to its initial values
*/
const resetStripeState = () => {
state.isLoadingAccount = false
state.hasAccountLoadingError = false
state.isStripeInstanceReady = false
state.isCardElementReady = false
state.stripe = null
state.elements = null
state.card = null
state.intentId = null
state.showPreviewMessage = false
state.stripeAccountId = null
state.errorMessage = ''
}
/**
* Fetches the Stripe account ID required for connecting to the proper account
* @param {string} formSlug - The slug of the form
* @param {string} providerId - The OAuth provider ID
* @param {boolean} isEditorPreview - Whether this is in editor preview mode
* @returns {Promise<Object>} - Object containing success/error information
*/
const prepareStripeState = async (formSlug, providerId, isEditorPreview = false) => {
if (!formSlug || !providerId) {
resetStripeState()
return { success: false, message: t('forms.payment.errors.missingFormOrProvider') }
}
resetStripeState()
state.isLoadingAccount = true
try {
const fetchOptions = {}
if (isEditorPreview) {
fetchOptions.query = { oauth_provider_id: providerId }
}
const response = await opnFetch(`/forms/${formSlug}/stripe-connect/get-account`, fetchOptions)
if (response?.type === 'success' && response?.stripeAccount) {
// Explicitly set the account ID in state
state.stripeAccountId = response.stripeAccount
state.isLoadingAccount = false
// We'll rely on the StripeElements component to create the Stripe instance
// Don't try to create it here
return { success: true, accountId: response.stripeAccount }
} else {
state.hasAccountLoadingError = true
state.isLoadingAccount = false
if (response?.message?.includes('save the form and try again')) {
state.showPreviewMessage = true
}
state.errorMessage = response?.message || t('forms.payment.errors.failedAccountDetails')
return {
success: false,
message: state.errorMessage,
requiresSave: state.showPreviewMessage
}
}
} catch (error) {
state.hasAccountLoadingError = true
state.isLoadingAccount = false
const message = error?.data?.message || t('forms.payment.errors.setupError')
if (message.includes('save the form and try again')) {
state.showPreviewMessage = true
}
state.errorMessage = message
return {
success: false,
message: state.errorMessage,
requiresSave: state.showPreviewMessage
}
}
}
/**
* Sets the Stripe instance in the state
* @param {Object} instance - The Stripe instance from vue-stripe-js
*/
const setStripeInstance = (instance) => {
// Check if the instance is actually a Stripe instance by looking for known methods
const isValidStripeInstance = instance &&
typeof instance === 'object' &&
typeof instance.confirmCardPayment === 'function' &&
typeof instance.createToken === 'function'
if (instance && isValidStripeInstance) {
// Only set if the instance is different to avoid unnecessary updates
if (state.stripe !== instance) {
state.stripe = instance
state.isStripeInstanceReady = true
}
} else {
state.stripe = null
state.isStripeInstanceReady = false
}
}
/**
* Sets the Elements instance in the state
* @param {Object} elementsInstance - The Elements instance from vue-stripe-js
*/
const setElementsInstance = (elementsInstance) => {
if (elementsInstance) {
state.elements = elementsInstance
}
}
/**
* Sets the Card Element in the state
* @param {Object} cardElement - The Card Element instance
*/
const setCardElement = (cardElement) => {
if (cardElement) {
state.card = cardElement
state.isCardElementReady = true
} else {
state.card = null
state.isCardElementReady = false
}
}
/**
* Sets the billing details in the state
* @param {Object} details - The billing details object {name, email}
*/
const setBillingDetails = ({ name, email }) => {
if (name !== undefined) state.cardHolderName = name
if (email !== undefined) state.cardHolderEmail = email
}
/**
* Processes a payment using the Stripe API
* @param {string} formSlug - The slug of the form
* @param {boolean} isRequired - Whether payment is required to proceed
* @returns {Promise<Object>} - Object containing payment result or error
*/
const processPayment = async (formSlug, isRequired = true) => {
// Check if Stripe is fully initialized
if (!isReadyForPayment.value) {
return {
success: false,
error: { message: t('forms.payment.errors.systemNotReady') }
}
}
// Check if the stripe instance exists
if (!state.stripe) {
return {
success: false,
error: { message: t('forms.payment.errors.misconfigured') }
}
}
// Additional validation for card
if (!state.card) {
return {
success: false,
error: { message: t('forms.payment.errors.notFullyReady') }
}
}
// Check if payment is required but card is empty
if (isRequired && state.card._empty) {
return {
success: false,
error: { message: t('forms.payment.errors.paymentRequired') }
}
}
// Only validate billing details if payment is required or card has data
if (isRequired || !state.card._empty) {
// Validate card holder name
if (!state.cardHolderName) {
return {
success: false,
error: { message: t('forms.payment.errors.nameRequired') }
}
}
// Validate billing email
if (!state.cardHolderEmail) {
return {
success: false,
error: { message: t('forms.payment.errors.emailRequired') }
}
}
// Validate email format
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(state.cardHolderEmail)) {
return {
success: false,
error: { message: t('forms.payment.errors.invalidEmail') }
}
}
}
try {
// Get payment intent from server
const responseIntent = await opnFetch('/forms/' + formSlug + '/stripe-connect/payment-intent')
if (responseIntent?.type === 'success') {
const intentSecret = responseIntent?.intent?.secret
// Confirm card payment with Stripe
const result = await state.stripe.confirmCardPayment(intentSecret, {
payment_method: {
card: state.card,
billing_details: {
name: state.cardHolderName,
email: state.cardHolderEmail
},
},
receipt_email: state.cardHolderEmail,
})
// Store payment intent ID on success
if (result?.paymentIntent?.status === 'succeeded') {
state.intentId = result.paymentIntent.id
}
return {
success: result?.paymentIntent?.status === 'succeeded',
...result
}
} else {
return {
success: false,
error: { message: responseIntent?.message || t('forms.payment.errors.failedIntent') }
}
}
} catch (error) {
// Include more details about the error
const errorMessage = error?.message || t('forms.payment.errors.processingFailed')
const errorType = error?.type || 'unknown'
const errorCode = error?.code || 'unknown'
return {
success: false,
error: {
message: errorMessage,
type: errorType,
code: errorCode
}
}
}
}
const stripeElements = {
state,
isReadyForPayment,
isCardPopulated,
processPayment,
resetStripeState,
prepareStripeState,
setStripeInstance,
setElementsInstance,
setCardElement,
setBillingDetails
}
// Return the API
return stripeElements
}
// Use this in the provider component (OpenForm)
export const provideStripeElements = () => {
const stripeElements = createStripeElements()
// Provide the entire stripeElements object to ensure reactivity
provide(STRIPE_ELEMENTS_KEY, stripeElements)
return stripeElements
}
// Use this in consumer components (PaymentInput)
export const useStripeElements = () => {
const stripeElements = inject(STRIPE_ELEMENTS_KEY)
if (!stripeElements) {
console.error('stripeElements was not provided. Make sure to call provideStripeElements in a parent component')
}
return stripeElements
}