-
Notifications
You must be signed in to change notification settings - Fork 440
feat: add Stripe pricing table integration for subscription dialog (conditional on feature flag) #7288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Stripe pricing table integration for subscription dialog (conditional on feature flag) #7288
Changes from 1 commit
19a3a9f
8ba0a6a
ebe29b1
7e1bd02
355414f
926e649
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { remoteConfig } from '@/platform/remoteConfig/remoteConfig' | ||
|
|
||
| export const STRIPE_PRICING_TABLE_SCRIPT_SRC = | ||
| 'https://js.stripe.com/v3/pricing-table.js' | ||
|
|
||
| function getEnvValue( | ||
| key: 'VITE_STRIPE_PUBLISHABLE_KEY' | 'VITE_STRIPE_PRICING_TABLE_ID' | ||
| ) { | ||
| return import.meta.env?.[key] | ||
| } | ||
|
|
||
| export function getStripePricingTableConfig() { | ||
| const publishableKey = | ||
| remoteConfig.value.stripe_publishable_key || | ||
| window.__CONFIG__?.stripe_publishable_key || | ||
| getEnvValue('VITE_STRIPE_PUBLISHABLE_KEY') || | ||
| '' | ||
|
|
||
| const pricingTableId = | ||
| remoteConfig.value.stripe_pricing_table_id || | ||
| window.__CONFIG__?.stripe_pricing_table_id || | ||
| getEnvValue('VITE_STRIPE_PRICING_TABLE_ID') || | ||
| '' | ||
|
|
||
| return { | ||
| publishableKey, | ||
| pricingTableId | ||
| } | ||
| } | ||
christian-byrne marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| export function hasStripePricingTableConfig() { | ||
| const { publishableKey, pricingTableId } = getStripePricingTableConfig() | ||
| return Boolean(publishableKey && pricingTableId) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| <template> | ||
| <div | ||
| ref="tableContainer" | ||
| class="relative w-full rounded-[20px] border border-interface-stroke bg-interface-panel-background" | ||
| > | ||
| <div | ||
| v-if="!hasValidConfig" | ||
| class="absolute inset-0 flex items-center justify-center px-6 text-center text-sm text-text-secondary" | ||
| data-testid="stripe-table-missing-config" | ||
| > | ||
| {{ $t('subscription.pricingTable.missingConfig') }} | ||
| </div> | ||
| <div | ||
| v-else-if="loadError" | ||
| class="absolute inset-0 flex items-center justify-center px-6 text-center text-sm text-text-secondary" | ||
| data-testid="stripe-table-error" | ||
| > | ||
| {{ $t('subscription.pricingTable.loadError') }} | ||
| </div> | ||
| <div | ||
| v-else-if="!isReady" | ||
| class="absolute inset-0 flex items-center justify-center px-6 text-center text-sm text-text-secondary" | ||
| data-testid="stripe-table-loading" | ||
| > | ||
| {{ $t('subscription.pricingTable.loading') }} | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { computed, onBeforeUnmount, ref, watch } from 'vue' | ||
| import { | ||
| getStripePricingTableConfig, | ||
| hasStripePricingTableConfig | ||
| } from '@/config/stripePricingTableConfig' | ||
| import { useStripePricingTableLoader } from '@/platform/cloud/subscription/composables/useStripePricingTableLoader' | ||
| const props = defineProps<{ | ||
| pricingTableId?: string | ||
| publishableKey?: string | ||
| }>() | ||
| const tableContainer = ref<HTMLDivElement | null>(null) | ||
| const isReady = ref(false) | ||
| const loadError = ref<string | null>(null) | ||
| const lastRenderedKey = ref('') | ||
| const stripeElement = ref<HTMLElement | null>(null) | ||
| const resolvedConfig = computed(() => { | ||
| const fallback = getStripePricingTableConfig() | ||
| return { | ||
| publishableKey: props.publishableKey || fallback.publishableKey, | ||
| pricingTableId: props.pricingTableId || fallback.pricingTableId | ||
| } | ||
| }) | ||
| const hasValidConfig = computed(() => { | ||
| if (props.publishableKey && props.pricingTableId) return true | ||
| return hasStripePricingTableConfig() | ||
| }) | ||
christian-byrne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const { loadScript } = useStripePricingTableLoader() | ||
| const renderPricingTable = async () => { | ||
| if (!tableContainer.value) return | ||
| const { publishableKey, pricingTableId } = resolvedConfig.value | ||
| if (!publishableKey || !pricingTableId) { | ||
| return | ||
| } | ||
| const renderKey = `${publishableKey}:${pricingTableId}` | ||
| if (renderKey === lastRenderedKey.value && isReady.value) { | ||
| return | ||
| } | ||
| try { | ||
| await loadScript() | ||
| loadError.value = null | ||
| if (!tableContainer.value) { | ||
| return | ||
| } | ||
| if (stripeElement.value) { | ||
| stripeElement.value.remove() | ||
| stripeElement.value = null | ||
|
Comment on lines
+82
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM - Missing null check after async operation Category: bug Description: Suggestion: Confidence: 70% |
||
| } | ||
| const stripeTable = document.createElement('stripe-pricing-table') | ||
| stripeTable.setAttribute('publishable-key', publishableKey) | ||
| stripeTable.setAttribute('pricing-table-id', pricingTableId) | ||
| stripeTable.style.display = 'block' | ||
| stripeTable.style.width = '100%' | ||
| stripeTable.style.minHeight = '420px' | ||
| tableContainer.value.appendChild(stripeTable) | ||
| stripeElement.value = stripeTable | ||
| lastRenderedKey.value = renderKey | ||
| isReady.value = true | ||
| } catch (error) { | ||
| console.error('[StripePricingTable] Failed to load pricing table', error) | ||
| loadError.value = (error as Error).message | ||
| isReady.value = false | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 HIGH - Unsafe type assertion without validation Category: bug Description: Suggestion: Confidence: 85% |
||
| watch( | ||
| [resolvedConfig, () => tableContainer.value], | ||
|
Comment on lines
+66
to
+104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 LOW - Long method - renderPricingTable Category: quality Description: Suggestion: Confidence: 60% |
||
| () => { | ||
| if (!hasValidConfig.value) return | ||
| if (!tableContainer.value) return | ||
| void renderPricingTable() | ||
| }, | ||
| { immediate: true } | ||
| ) | ||
| onBeforeUnmount(() => { | ||
| stripeElement.value?.remove() | ||
| stripeElement.value = null | ||
| }) | ||
| </script> | ||
Uh oh!
There was an error while loading. Please reload this page.