(params: PurchaseCreditsParams)
| 103 | } |
| 104 | |
| 105 | export async function purchaseCredits(params: PurchaseCreditsParams): Promise<PurchaseResult> { |
| 106 | const { userId, amountDollars, requestId } = params |
| 107 | |
| 108 | if (amountDollars < 10 || amountDollars > 1000) { |
| 109 | return { success: false, error: 'Amount must be between $10 and $1000' } |
| 110 | } |
| 111 | |
| 112 | const canPurchase = await canPurchaseCredits(userId) |
| 113 | if (!canPurchase) { |
| 114 | return { success: false, error: 'Only Pro and Team users can purchase credits' } |
| 115 | } |
| 116 | |
| 117 | const subscription = await getHighestPrioritySubscription(userId) |
| 118 | if (!subscription || !subscription.stripeSubscriptionId) { |
| 119 | return { success: false, error: 'No active subscription found' } |
| 120 | } |
| 121 | |
| 122 | // Enterprise users must contact support |
| 123 | if (isEnterprise(subscription.plan)) { |
| 124 | return { success: false, error: 'Enterprise users must contact support to purchase credits' } |
| 125 | } |
| 126 | |
| 127 | let entityType: 'user' | 'organization' = 'user' |
| 128 | let entityId = userId |
| 129 | |
| 130 | // Org-scoped subs route credit purchases to the organization and must be authorized |
| 131 | // by an org owner/admin. We've already rejected enterprise above. |
| 132 | if (isOrgScopedSubscription(subscription, userId)) { |
| 133 | const isAdmin = await isOrganizationOwnerOrAdmin(userId, subscription.referenceId) |
| 134 | if (!isAdmin) { |
| 135 | return { success: false, error: 'Only organization owners and admins can purchase credits' } |
| 136 | } |
| 137 | entityType = 'organization' |
| 138 | entityId = subscription.referenceId |
| 139 | } |
| 140 | |
| 141 | try { |
| 142 | const stripe = requireStripeClient() |
| 143 | |
| 144 | const stripeSub = await stripe.subscriptions.retrieve(subscription.stripeSubscriptionId) |
| 145 | const customerId = getCustomerId(stripeSub.customer) |
| 146 | if (!customerId) { |
| 147 | return { success: false, error: 'Subscription missing customer' } |
| 148 | } |
| 149 | |
| 150 | const { paymentMethodId: defaultPaymentMethod } = await resolveDefaultPaymentMethod( |
| 151 | stripe, |
| 152 | subscription.stripeSubscriptionId, |
| 153 | customerId |
| 154 | ) |
| 155 | |
| 156 | if (!defaultPaymentMethod) { |
| 157 | return { |
| 158 | success: false, |
| 159 | error: 'No payment method on file. Please update your billing info.', |
| 160 | } |
| 161 | } |
| 162 |
no test coverage detected