diff --git a/src/components/dialog/content/setting/CreditsPanel.vue b/src/components/dialog/content/setting/CreditsPanel.vue
index b77605ef4..2a58985c8 100644
--- a/src/components/dialog/content/setting/CreditsPanel.vue
+++ b/src/components/dialog/content/setting/CreditsPanel.vue
@@ -21,7 +21,11 @@
/>
{{ creditBalance }}
-
+
@@ -91,9 +95,17 @@ import TabPanel from 'primevue/tabpanel'
import Tag from 'primevue/tag'
import { ref } from 'vue'
-// Mock data - in a real implementation, this would come from a store or API
+import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
+import { usdToMicros } from '@/utils/formatUtil'
+
+// TODO: Mock data - in a real implementation, this would come from a store or API
const creditBalance = ref(0.05)
+// TODO: Either: (1) Get checkout URL that allows setting price on Stripe side, (2) Add number selection on credits panel
+const selectedCurrencyAmount = usdToMicros(10)
+
+const selectedCurrency = 'usd' // For now, only USD is supported on comfy-api backend
+
interface CreditHistoryItemData {
title: string
timestamp: string
@@ -101,6 +113,22 @@ interface CreditHistoryItemData {
isPositive: boolean
}
+const { initiateCreditPurchase, loading } = useFirebaseAuthStore()
+
+const handlePurchaseCreditsClick = async () => {
+ const response = await initiateCreditPurchase({
+ amount_micros: selectedCurrencyAmount,
+ currency: selectedCurrency
+ })
+ if (!response) return
+
+ const { checkout_url } = response
+ if (checkout_url !== undefined) {
+ // Go to Stripe checkout page
+ window.open(checkout_url, '_blank')
+ }
+}
+
const creditHistory = ref([
{
title: 'Kling Text-to-Video v1-6',
diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts
index 57456e239..c6d3147e5 100644
--- a/src/stores/firebaseAuthStore.ts
+++ b/src/stores/firebaseAuthStore.ts
@@ -16,6 +16,16 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useFirebaseAuth } from 'vuefire'
+import { operations } from '@/types/comfyRegistryTypes'
+
+type CreditPurchaseResponse =
+ operations['InitiateCreditPurchase']['responses']['201']['content']['application/json']
+type CreditPurchasePayload =
+ operations['InitiateCreditPurchase']['requestBody']['content']['application/json']
+
+// TODO: Switch to prod api based on environment (requires prod api to be ready)
+const API_BASE_URL = 'https://stagingapi.comfy.org'
+
export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
// State
const loading = ref(false)
@@ -100,6 +110,39 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
return null
}
+ const addCredits = async (
+ requestBodyContent: CreditPurchasePayload
+ ): Promise => {
+ const token = await getIdToken()
+ if (!token) {
+ error.value = 'Cannot add credits: User not authenticated'
+ return null
+ }
+
+ const response = await fetch(`${API_BASE_URL}/customers/credit`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ },
+ body: JSON.stringify(requestBodyContent)
+ })
+
+ if (!response.ok) {
+ const errorData = await response.json()
+ error.value = `Failed to initiate credit purchase: ${errorData.message}`
+ return null
+ }
+
+ // TODO: start polling /listBalance until balance is updated or n retries fail or report no change
+ return response.json()
+ }
+
+ const initiateCreditPurchase = async (
+ requestBodyContent: CreditPurchasePayload
+ ): Promise =>
+ executeAuthAction((_) => addCredits(requestBodyContent))
+
return {
// State
loading,
@@ -118,6 +161,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
logout,
getIdToken,
loginWithGoogle,
- loginWithGithub
+ loginWithGithub,
+ initiateCreditPurchase
}
})
diff --git a/src/utils/formatUtil.ts b/src/utils/formatUtil.ts
index e454d20db..ce6766787 100644
--- a/src/utils/formatUtil.ts
+++ b/src/utils/formatUtil.ts
@@ -415,3 +415,17 @@ export function compareVersions(
return 0
}
+
+/**
+ * Converts a USD amount to microdollars (1/1,000,000 of a dollar).
+ * This conversion is commonly used in financial systems to avoid floating-point precision issues
+ * by representing monetary values as integers.
+ *
+ * @param usd - The amount in US dollars to convert
+ * @returns The amount in microdollars (multiplied by 1,000,000)
+ * @example
+ * usdToMicros(1.23) // returns 1230000
+ */
+export function usdToMicros(usd: number): number {
+ return Math.round(usd * 1_000_000)
+}