Skip to content

Commit e7fe204

Browse files
Checkout on credit add (#3555)
1 parent bf4ad38 commit e7fe204

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

src/components/dialog/content/setting/CreditsPanel.vue

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
/>
2222
<div class="text-3xl font-bold">{{ creditBalance }}</div>
2323
</div>
24-
<Button :label="$t('credits.purchaseCredits')" />
24+
<Button
25+
:label="$t('credits.purchaseCredits')"
26+
:loading
27+
@click="handlePurchaseCreditsClick"
28+
/>
2529
</div>
2630
</div>
2731

@@ -91,16 +95,40 @@ import TabPanel from 'primevue/tabpanel'
9195
import Tag from 'primevue/tag'
9296
import { ref } from 'vue'
9397
94-
// Mock data - in a real implementation, this would come from a store or API
98+
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
99+
import { usdToMicros } from '@/utils/formatUtil'
100+
101+
// TODO: Mock data - in a real implementation, this would come from a store or API
95102
const creditBalance = ref(0.05)
96103
104+
// TODO: Either: (1) Get checkout URL that allows setting price on Stripe side, (2) Add number selection on credits panel
105+
const selectedCurrencyAmount = usdToMicros(10)
106+
107+
const selectedCurrency = 'usd' // For now, only USD is supported on comfy-api backend
108+
97109
interface CreditHistoryItemData {
98110
title: string
99111
timestamp: string
100112
amount: number
101113
isPositive: boolean
102114
}
103115
116+
const { initiateCreditPurchase, loading } = useFirebaseAuthStore()
117+
118+
const handlePurchaseCreditsClick = async () => {
119+
const response = await initiateCreditPurchase({
120+
amount_micros: selectedCurrencyAmount,
121+
currency: selectedCurrency
122+
})
123+
if (!response) return
124+
125+
const { checkout_url } = response
126+
if (checkout_url !== undefined) {
127+
// Go to Stripe checkout page
128+
window.open(checkout_url, '_blank')
129+
}
130+
}
131+
104132
const creditHistory = ref<CreditHistoryItemData[]>([
105133
{
106134
title: 'Kling Text-to-Video v1-6',

src/stores/firebaseAuthStore.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ import { defineStore } from 'pinia'
1616
import { computed, ref } from 'vue'
1717
import { useFirebaseAuth } from 'vuefire'
1818

19+
import { operations } from '@/types/comfyRegistryTypes'
20+
21+
type CreditPurchaseResponse =
22+
operations['InitiateCreditPurchase']['responses']['201']['content']['application/json']
23+
type CreditPurchasePayload =
24+
operations['InitiateCreditPurchase']['requestBody']['content']['application/json']
25+
26+
// TODO: Switch to prod api based on environment (requires prod api to be ready)
27+
const API_BASE_URL = 'https://stagingapi.comfy.org'
28+
1929
export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
2030
// State
2131
const loading = ref(false)
@@ -100,6 +110,39 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
100110
return null
101111
}
102112

113+
const addCredits = async (
114+
requestBodyContent: CreditPurchasePayload
115+
): Promise<CreditPurchaseResponse | null> => {
116+
const token = await getIdToken()
117+
if (!token) {
118+
error.value = 'Cannot add credits: User not authenticated'
119+
return null
120+
}
121+
122+
const response = await fetch(`${API_BASE_URL}/customers/credit`, {
123+
method: 'POST',
124+
headers: {
125+
'Content-Type': 'application/json',
126+
Authorization: `Bearer ${token}`
127+
},
128+
body: JSON.stringify(requestBodyContent)
129+
})
130+
131+
if (!response.ok) {
132+
const errorData = await response.json()
133+
error.value = `Failed to initiate credit purchase: ${errorData.message}`
134+
return null
135+
}
136+
137+
// TODO: start polling /listBalance until balance is updated or n retries fail or report no change
138+
return response.json()
139+
}
140+
141+
const initiateCreditPurchase = async (
142+
requestBodyContent: CreditPurchasePayload
143+
): Promise<CreditPurchaseResponse | null> =>
144+
executeAuthAction((_) => addCredits(requestBodyContent))
145+
103146
return {
104147
// State
105148
loading,
@@ -118,6 +161,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
118161
logout,
119162
getIdToken,
120163
loginWithGoogle,
121-
loginWithGithub
164+
loginWithGithub,
165+
initiateCreditPurchase
122166
}
123167
})

src/utils/formatUtil.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,17 @@ export function compareVersions(
415415

416416
return 0
417417
}
418+
419+
/**
420+
* Converts a USD amount to microdollars (1/1,000,000 of a dollar).
421+
* This conversion is commonly used in financial systems to avoid floating-point precision issues
422+
* by representing monetary values as integers.
423+
*
424+
* @param usd - The amount in US dollars to convert
425+
* @returns The amount in microdollars (multiplied by 1,000,000)
426+
* @example
427+
* usdToMicros(1.23) // returns 1230000
428+
*/
429+
export function usdToMicros(usd: number): number {
430+
return Math.round(usd * 1_000_000)
431+
}

0 commit comments

Comments
 (0)