Fix issues when purchasing credits
This commit is contained in:
parent
051d8ef401
commit
dbf5a2edc5
|
|
@ -5,6 +5,7 @@ import { UserPlan } from './Interface'
|
|||
import { GeneralErrorMessage, LICENSE_QUOTAS } from './utils/constants'
|
||||
import { InternalFlowiseError } from './errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import logger from './utils/logger'
|
||||
|
||||
export class StripeManager {
|
||||
private static instance: StripeManager
|
||||
|
|
@ -826,7 +827,10 @@ export class StripeManager {
|
|||
const creditBalance = await this.stripe!.billing.creditBalanceSummary.retrieve({
|
||||
customer: customerId,
|
||||
filter: {
|
||||
type: 'monetary' as any
|
||||
type: 'applicability_scope',
|
||||
applicability_scope: {
|
||||
price_type: 'metered'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -849,6 +853,10 @@ export class StripeManager {
|
|||
}
|
||||
|
||||
public async purchaseCredits(subscriptionId: string, packageType: string): Promise<Record<string, any>> {
|
||||
if (!this.stripe) {
|
||||
throw new Error('Stripe is not initialized')
|
||||
}
|
||||
|
||||
try {
|
||||
if (!process.env.CREDIT_PRODUCT_ID) {
|
||||
throw new Error('CREDIT_PRODUCT_ID environment variable is required')
|
||||
|
|
@ -860,7 +868,7 @@ export class StripeManager {
|
|||
throw new Error('Subscription ID and package type are required')
|
||||
}
|
||||
|
||||
const subscription = await this.stripe!.subscriptions.retrieve(subscriptionId)
|
||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
|
||||
const customerId = subscription.customer as string
|
||||
|
||||
if (!customerId) {
|
||||
|
|
@ -869,28 +877,40 @@ export class StripeManager {
|
|||
|
||||
// Get credit packages
|
||||
const packages = await this.getCreditsPackages()
|
||||
logger.info(`Retrieved credit packages: ${JSON.stringify(packages)}`)
|
||||
const selectedPackage = packages.find((pkg: any) => pkg.id === packageType)
|
||||
logger.info(`Selected credit packages: ${JSON.stringify(selectedPackage)}`)
|
||||
|
||||
if (!selectedPackage) {
|
||||
throw new Error(`No active price found for ${packageType} package`)
|
||||
}
|
||||
|
||||
// Create invoice for credit purchase
|
||||
const invoiceItem = await this.stripe!.invoiceItems.create({
|
||||
customer: customerId,
|
||||
amount: selectedPackage.price,
|
||||
currency: 'usd',
|
||||
description: `${selectedPackage.credits} Credits Package`
|
||||
})
|
||||
|
||||
const invoice = await this.stripe!.invoices.create({
|
||||
const invoice = await this.stripe.invoices.create({
|
||||
customer: customerId,
|
||||
auto_advance: true,
|
||||
collection_method: 'charge_automatically'
|
||||
})
|
||||
|
||||
const finalizedInvoice = await this.stripe!.invoices.finalizeInvoice(invoice.id!)
|
||||
const paidInvoice = await this.stripe!.invoices.pay(finalizedInvoice.id!)
|
||||
if (!invoice.id) {
|
||||
throw new Error('Invoice creation failed')
|
||||
}
|
||||
|
||||
await this.stripe.invoiceItems.create({
|
||||
customer: customerId,
|
||||
amount: selectedPackage.price,
|
||||
invoice: invoice.id,
|
||||
currency: 'usd',
|
||||
description: `${selectedPackage.credits} Credits Package`
|
||||
})
|
||||
|
||||
const finalizedInvoice = await this.stripe.invoices.finalizeInvoice(invoice.id)
|
||||
|
||||
if (!finalizedInvoice.id) {
|
||||
throw new Error('Failed to finalize invoice')
|
||||
}
|
||||
|
||||
const paidInvoice = await this.stripe.invoices.pay(finalizedInvoice.id)
|
||||
|
||||
if (paidInvoice.status !== 'paid') {
|
||||
throw new Error('Payment failed')
|
||||
|
|
@ -899,8 +919,11 @@ export class StripeManager {
|
|||
// Add metered subscription item if it doesn't exist
|
||||
const meteredItemResult = await this.addMeteredSubscriptionItem(subscriptionId)
|
||||
|
||||
// Get price
|
||||
const price = await this.stripe.prices.retrieve(process.env.METERED_PRICE_ID!)
|
||||
|
||||
// Create credit grant
|
||||
const creditGrant = await this.stripe!.billing.creditGrants.create({
|
||||
const creditGrant = await this.stripe.billing.creditGrants.create({
|
||||
customer: customerId,
|
||||
amount: {
|
||||
type: 'monetary' as any,
|
||||
|
|
@ -911,8 +934,7 @@ export class StripeManager {
|
|||
},
|
||||
applicability_config: {
|
||||
scope: {
|
||||
price_type: 'metered',
|
||||
prices: [process.env.METERED_PRICE_ID!]
|
||||
prices: [{ id: price.id }]
|
||||
}
|
||||
} as any,
|
||||
category: 'paid',
|
||||
|
|
@ -951,7 +973,10 @@ export class StripeManager {
|
|||
const creditBalance = await this.stripe!.billing.creditBalanceSummary.retrieve({
|
||||
customer: customerId,
|
||||
filter: {
|
||||
type: 'monetary' as any
|
||||
type: 'applicability_scope',
|
||||
applicability_scope: {
|
||||
price_type: 'metered'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -1063,6 +1088,10 @@ export class StripeManager {
|
|||
}
|
||||
|
||||
public async addMeteredSubscriptionItem(subscriptionId: string): Promise<{ added: boolean; message: string }> {
|
||||
if (!this.stripe) {
|
||||
throw new Error('Stripe is not initialized')
|
||||
}
|
||||
|
||||
try {
|
||||
if (!process.env.METERED_PRICE_ID) {
|
||||
throw new Error('METERED_PRICE_ID environment variable is required')
|
||||
|
|
@ -1071,7 +1100,7 @@ export class StripeManager {
|
|||
throw new Error('Subscription ID is required')
|
||||
}
|
||||
|
||||
const subscription = await this.stripe!.subscriptions.retrieve(subscriptionId)
|
||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
|
||||
|
||||
// Check if metered item already exists
|
||||
const existingMeteredItem = subscription.items.data.find((item) => item.price.id === process.env.METERED_PRICE_ID)
|
||||
|
|
@ -1084,10 +1113,9 @@ export class StripeManager {
|
|||
}
|
||||
|
||||
// Add metered subscription item
|
||||
await this.stripe!.subscriptionItems.create({
|
||||
await this.stripe.subscriptionItems.create({
|
||||
subscription: subscriptionId,
|
||||
price: process.env.METERED_PRICE_ID!,
|
||||
quantity: 0
|
||||
price: process.env.METERED_PRICE_ID!
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ import { MODE } from './Interface'
|
|||
import { LICENSE_QUOTAS } from './utils/constants'
|
||||
import { StripeManager } from './StripeManager'
|
||||
|
||||
const DISABLED_QUOTAS = {
|
||||
const getDisabledQuotas = () => ({
|
||||
[LICENSE_QUOTAS.PREDICTIONS_LIMIT]: 0,
|
||||
[LICENSE_QUOTAS.STORAGE_LIMIT]: 0, // in MB
|
||||
[LICENSE_QUOTAS.FLOWS_LIMIT]: 0,
|
||||
[LICENSE_QUOTAS.USERS_LIMIT]: 0,
|
||||
[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT]: 0
|
||||
}
|
||||
})
|
||||
|
||||
const UNLIMITED_QUOTAS = {
|
||||
const getUnlimitedQuotas = () => ({
|
||||
[LICENSE_QUOTAS.PREDICTIONS_LIMIT]: -1,
|
||||
[LICENSE_QUOTAS.STORAGE_LIMIT]: -1,
|
||||
[LICENSE_QUOTAS.FLOWS_LIMIT]: -1,
|
||||
[LICENSE_QUOTAS.USERS_LIMIT]: -1,
|
||||
[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT]: -1
|
||||
}
|
||||
})
|
||||
|
||||
export class UsageCacheManager {
|
||||
private cache: Cache
|
||||
|
|
@ -67,7 +67,7 @@ export class UsageCacheManager {
|
|||
public async getSubscriptionDetails(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, any>> {
|
||||
const stripeManager = await StripeManager.getInstance()
|
||||
if (!stripeManager || !subscriptionId) {
|
||||
return UNLIMITED_QUOTAS
|
||||
return getUnlimitedQuotas()
|
||||
}
|
||||
|
||||
// Skip cache if withoutCache is true
|
||||
|
|
@ -90,7 +90,7 @@ export class UsageCacheManager {
|
|||
public async getQuotas(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, number>> {
|
||||
const stripeManager = await StripeManager.getInstance()
|
||||
if (!stripeManager || !subscriptionId) {
|
||||
return UNLIMITED_QUOTAS
|
||||
return getUnlimitedQuotas()
|
||||
}
|
||||
|
||||
// Skip cache if withoutCache is true
|
||||
|
|
@ -105,7 +105,7 @@ export class UsageCacheManager {
|
|||
const subscription = await stripeManager.getStripe().subscriptions.retrieve(subscriptionId)
|
||||
const items = subscription.items.data
|
||||
if (items.length === 0) {
|
||||
return DISABLED_QUOTAS
|
||||
return getDisabledQuotas()
|
||||
}
|
||||
|
||||
const productId = items[0].price.product as string
|
||||
|
|
@ -113,7 +113,7 @@ export class UsageCacheManager {
|
|||
const productMetadata = product.metadata
|
||||
|
||||
if (!productMetadata || Object.keys(productMetadata).length === 0) {
|
||||
return DISABLED_QUOTAS
|
||||
return getDisabledQuotas()
|
||||
}
|
||||
|
||||
const quotas: Record<string, number> = {}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ const calculatePercentage = (count, total) => {
|
|||
return Math.min((count / total) * 100, 100)
|
||||
}
|
||||
|
||||
const calculateTotalCredits = (grants) => {
|
||||
if (!grants || !Array.isArray(grants)) return 0
|
||||
return grants.reduce((total, grant) => {
|
||||
const grantValue = grant?.amount?.monetary?.value || 0
|
||||
return total + grantValue
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const AccountSettings = () => {
|
||||
const theme = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
|
|
@ -89,6 +97,10 @@ const AccountSettings = () => {
|
|||
const [occupiedSeats, setOccupiedSeats] = useState(0)
|
||||
const [totalSeats, setTotalSeats] = useState(0)
|
||||
const [creditsBalance, setCreditsBalance] = useState(null)
|
||||
|
||||
const totalCredits = useMemo(() => {
|
||||
return creditsBalance ? calculateTotalCredits(creditsBalance.grants) : 0
|
||||
}, [creditsBalance])
|
||||
const [creditsPackages, setCreditsPackages] = useState([])
|
||||
const [usageWithCredits, setUsageWithCredits] = useState(null)
|
||||
const [openCreditsDialog, setOpenCreditsDialog] = useState(false)
|
||||
|
|
@ -837,11 +849,7 @@ const AccountSettings = () => {
|
|||
<Stack sx={{ alignItems: 'center' }} flexDirection='row'>
|
||||
<Typography variant='body2'>Available Credits:</Typography>
|
||||
<Typography sx={{ ml: 1, color: theme.palette.success.dark }} variant='h3'>
|
||||
{getCreditsBalanceApi.loading ? (
|
||||
<CircularProgress size={16} />
|
||||
) : (
|
||||
creditsBalance?.balance || 0
|
||||
)}
|
||||
{getCreditsBalanceApi.loading ? <CircularProgress size={16} /> : totalCredits || 0}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{usageWithCredits && (
|
||||
|
|
@ -1633,7 +1641,7 @@ const AccountSettings = () => {
|
|||
Current Balance
|
||||
</Typography>
|
||||
<Typography variant='h4' color='success.main'>
|
||||
{creditsBalance?.balance || 0} Credits
|
||||
{totalCredits || 0} Credits
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue