diff --git a/app/javascript/dashboard/api/enterprise/account.js b/app/javascript/dashboard/api/enterprise/account.js index 9e6d40a62..24dac96da 100644 --- a/app/javascript/dashboard/api/enterprise/account.js +++ b/app/javascript/dashboard/api/enterprise/account.js @@ -27,6 +27,18 @@ class EnterpriseAccountAPI extends ApiClient { createTopupCheckout(credits) { return axios.post(`${this.url}topup_checkout`, { credits }); } + + getBillingDetails() { + return axios.get(`${this.url}billing_details`); + } + + confirmBillingDetails({ businessName, businessAddress, billingEmail }) { + return axios.post(`${this.url}confirm_billing_details`, { + business_name: businessName, + business_address: businessAddress, + billing_email: billingEmail, + }); + } } export default new EnterpriseAccountAPI(); diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index d885cf8ce..3883d1027 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -483,6 +483,31 @@ "GO_BACK": "Go Back", "CONFIRM_PURCHASE": "Confirm Purchase" } + }, + "CONFIRM_BUSINESS": { + "TITLE": "Confirm Business Details", + "DESCRIPTION": "Please confirm your business information before accessing the billing portal.", + "PREFILLED_BANNER": "We found your existing business details. Review and update if needed, or continue to proceed.", + "BUSINESS_NAME": { + "LABEL": "Business Name", + "PLACEHOLDER": "Enter your business name", + "REQUIRED": "Business name is required" + }, + "BUSINESS_ADDRESS": { + "LABEL": "Business Address", + "PLACEHOLDER": "Enter your business address", + "REQUIRED": "Business address is required" + }, + "BILLING_EMAIL": { + "LABEL": "Billing Email", + "PLACEHOLDER": "Enter your billing email", + "REQUIRED": "Billing email is required", + "INVALID": "Please enter a valid email address", + "HELP_TEXT": "All billing-related emails will be sent to this address" + }, + "CANCEL": "Cancel", + "CONTINUE": "Continue to Billing Portal", + "ERROR": "Failed to update business details. Please try again." } }, "SECURITY_SETTINGS": { diff --git a/app/javascript/dashboard/routes/dashboard/settings/billing/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/billing/Index.vue index bcfa46193..dcf07729c 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/billing/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/billing/Index.vue @@ -12,6 +12,7 @@ import BillingCard from './components/BillingCard.vue'; import BillingHeader from './components/BillingHeader.vue'; import DetailItem from './components/DetailItem.vue'; import PurchaseCreditsModal from './components/PurchaseCreditsModal.vue'; +import ConfirmBusinessDetailsModal from './components/ConfirmBusinessDetailsModal.vue'; import BaseSettingsHeader from '../components/BaseSettingsHeader.vue'; import SettingsLayout from '../SettingsLayout.vue'; import ButtonV4 from 'next/button/Button.vue'; @@ -35,11 +36,16 @@ const BILLING_REFRESH_ATTEMPTED = 'billing_refresh_attempted'; // State for handling refresh attempts and loading const isWaitingForBilling = ref(false); const purchaseCreditsModalRef = ref(null); +const confirmBusinessDetailsModalRef = ref(null); const customAttributes = computed(() => { return currentAccount.value.custom_attributes || {}; }); +const billingDetailsConfirmed = computed(() => { + return customAttributes.value.billing_details_confirmed; +}); + /** * Computed property for plan name * @returns {string|undefined} @@ -119,7 +125,15 @@ const handleBillingPageLogic = async () => { }; const onClickBillingPortal = () => { - store.dispatch('accounts/checkout'); + if (billingDetailsConfirmed.value) { + store.dispatch('accounts/checkout'); + } else { + confirmBusinessDetailsModalRef.value?.open(); + } +}; + +const onBusinessDetailsConfirmed = redirectUrl => { + window.location = redirectUrl; }; const onToggleChatWindow = () => { @@ -263,6 +277,10 @@ onMounted(handleBillingPageLogic); ref="purchaseCreditsModalRef" @success="handleTopupSuccess" /> + diff --git a/app/javascript/dashboard/routes/dashboard/settings/billing/components/ConfirmBusinessDetailsModal.vue b/app/javascript/dashboard/routes/dashboard/settings/billing/components/ConfirmBusinessDetailsModal.vue new file mode 100644 index 000000000..a894ca028 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/billing/components/ConfirmBusinessDetailsModal.vue @@ -0,0 +1,209 @@ + + + + + + + + + {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.PREFILLED_BANNER') }} + + + + + + {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.BUSINESS_NAME.LABEL') }} + + * + + + + + + + {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.BUSINESS_ADDRESS.LABEL') }} + + * + + + + + + + {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.BILLING_EMAIL.LABEL') }} + + * + + + + {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.BILLING_EMAIL.HELP_TEXT') }} + + + + + + + + + + + + diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index bd7b3cefe..b2d660903 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -34,4 +34,12 @@ class AccountPolicy < ApplicationPolicy def topup_checkout? @account_user.administrator? end + + def billing_details? + @account_user.administrator? + end + + def confirm_billing_details? + @account_user.administrator? + end end diff --git a/config/routes.rb b/config/routes.rb index 9d442600e..9f386a7f3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -475,6 +475,8 @@ Rails.application.routes.draw do post :checkout post :subscription get :limits + get :billing_details + post :confirm_billing_details post :toggle_deletion post :topup_checkout end diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb index d176db597..e0bcaa35e 100644 --- a/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb +++ b/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb @@ -4,6 +4,32 @@ class Enterprise::Api::V1::AccountsController < Api::BaseController before_action :check_authorization before_action :check_cloud_env, only: [:limits, :toggle_deletion] + def billing_details + return render json: { billing_details_confirmed: false }, status: :ok if stripe_customer_id.blank? + + customer = Stripe::Customer.retrieve(stripe_customer_id) + render json: { + billing_details_confirmed: @account.custom_attributes['billing_details_confirmed'] || false, + business_name: customer.name, + billing_email: customer.email, + business_address: format_stripe_address(customer.address) + }, status: :ok + end + + def confirm_billing_details + return render_invalid_billing_details if stripe_customer_id.blank? + + Stripe::Customer.update(stripe_customer_id, { + name: params[:business_name], + email: params[:billing_email], + address: { line1: params[:business_address] } + }) + + @account.update!(custom_attributes: @account.custom_attributes.merge('billing_details_confirmed' => true)) + + create_stripe_billing_session(stripe_customer_id) + end + def subscription if stripe_customer_id.blank? && @account.custom_attributes['is_creating_customer'].blank? @account.update(custom_attributes: { is_creating_customer: true }) @@ -118,6 +144,12 @@ class Enterprise::Api::V1::AccountsController < Api::BaseController end end + def format_stripe_address(address) + return '' if address.blank? + + [address.line1, address.line2, address.city, address.state, address.postal_code, address.country].compact_blank.join(', ') + end + def render_invalid_billing_details render_could_not_create_error('Please subscribe to a plan before viewing the billing details') end
+ {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.PREFILLED_BANNER') }} +
+ {{ $t('BILLING_SETTINGS.CONFIRM_BUSINESS.BILLING_EMAIL.HELP_TEXT') }} +