1616 BillingSummary ,
1717 CreditEstimate ,
1818 CreditLotPublic ,
19+ PaymentInvoicePublic ,
1920 PlanPublic ,
2021 ReservationResult ,
2122 TopUpPackPublic ,
23+ UsageSnapshotPublic ,
2224)
2325from src .utils import billing as billing_config
2426
@@ -242,10 +244,15 @@ def grant_pro_subscription(
242244 user_id : str ,
243245 payment_id : str ,
244246 subscription_id : str ,
247+ amount : Optional [int ] = None ,
248+ currency : Optional [str ] = None ,
249+ billing_region : Optional [str ] = None ,
250+ receipt_url : Optional [str ] = None ,
245251 period_end = None ,
246252 ) -> dict [str , Any ]:
247253 account = self .store .ensure_account (owner_id = user_id )
248254 plan = billing_config .PLANS ["pro" ]
255+ price = billing_config .plan_price ("pro" , billing_region )
249256 expires_at = period_end or (utc_now () + timedelta (days = 30 ))
250257 self .store .update_account (
251258 account ["id" ],
@@ -256,14 +263,40 @@ def grant_pro_subscription(
256263 "current_period_end" : expires_at ,
257264 },
258265 )
259- return self .store .grant_credits (
266+ grant = self .store .grant_credits (
260267 account_id = account ["id" ],
261268 amount = int (plan ["monthly_credits" ]),
262269 source = "pro_monthly" ,
263270 expires_at = expires_at ,
264271 idempotency_key = f"pro_grant:{ subscription_id } :{ payment_id } " ,
265- metadata = {"payment_id" : payment_id , "subscription_id" : subscription_id },
272+ metadata = {
273+ "payment_id" : payment_id ,
274+ "subscription_id" : subscription_id ,
275+ "billing_region" : billing_config .normalize_billing_region (
276+ billing_region
277+ ),
278+ },
279+ )
280+ self .store .record_payment_invoice (
281+ payment_id = payment_id ,
282+ payload = {
283+ "billing_account_id" : account ["id" ],
284+ "user_id" : user_id ,
285+ "package_id" : "pro" ,
286+ "package_type" : "plan" ,
287+ "amount_paise" : int (
288+ amount if amount is not None else price ["price_minor_unit" ]
289+ ),
290+ "currency" : str (currency or price .get ("currency" ) or "INR" ),
291+ "credits" : int (plan ["monthly_credits" ]),
292+ "razorpay_subscription_id" : subscription_id ,
293+ "billing_region" : billing_config .normalize_billing_region (
294+ billing_region
295+ ),
296+ "receipt_url" : receipt_url ,
297+ },
266298 )
299+ return grant
267300
268301 def grant_topup (
269302 self ,
@@ -272,10 +305,14 @@ def grant_topup(
272305 pack_id : str ,
273306 payment_id : str ,
274307 order_id : str ,
308+ amount : Optional [int ] = None ,
309+ currency : Optional [str ] = None ,
310+ billing_region : Optional [str ] = None ,
311+ receipt_url : Optional [str ] = None ,
275312 ) -> dict [str , Any ]:
276313 pack = billing_config .TOP_UP_PACKS [pack_id ]
277314 account = self .store .ensure_account (owner_id = user_id )
278- return self .store .grant_credits (
315+ grant = self .store .grant_credits (
279316 account_id = account ["id" ],
280317 amount = int (pack ["credits" ]),
281318 source = pack_id ,
@@ -287,12 +324,87 @@ def grant_topup(
287324 "pack_id" : pack_id ,
288325 },
289326 )
327+ self .store .record_payment_invoice (
328+ payment_id = payment_id ,
329+ payload = {
330+ "billing_account_id" : account ["id" ],
331+ "user_id" : user_id ,
332+ "package_id" : pack_id ,
333+ "package_type" : "topup" ,
334+ "amount_paise" : int (
335+ amount if amount is not None else pack ["price_paise" ]
336+ ),
337+ "currency" : str (currency or pack .get ("currency" ) or "INR" ),
338+ "credits" : int (pack ["credits" ]),
339+ "razorpay_order_id" : order_id ,
340+ "billing_region" : billing_config .normalize_billing_region (
341+ billing_region
342+ ),
343+ "receipt_url" : receipt_url ,
344+ },
345+ )
346+ return grant
290347
291348 def get_billing_summary (self , user : Mapping [str , Any ]) -> BillingSummary :
292349 account = self .ensure_billing_account (user )
293350 wallet = self .store .get_wallet (account ["id" ])
294351 plan_id = str (account .get ("plan_id" ) or "free" )
295352 plan = billing_config .PLANS .get (plan_id , billing_config .PLANS ["free" ])
353+ available_credits = int (wallet .get ("available_credits" ) or 0 )
354+ status = str (account .get ("status" ) or "trialing" )
355+ account_status = "trial" if status == "trialing" else status
356+ invoices = []
357+ for invoice in self .store .list_payment_invoices (account ["id" ]):
358+ raw_amount = (
359+ invoice .get ("amount_minor_units" )
360+ if invoice .get ("amount_minor_units" ) is not None
361+ else (
362+ invoice .get ("amount_paise" )
363+ if invoice .get ("amount_paise" ) is not None
364+ else invoice .get ("amount" )
365+ )
366+ )
367+ amount_minor_units = int (raw_amount or 0 )
368+ invoices .append (
369+ PaymentInvoicePublic (
370+ id = str (invoice .get ("id" ) or invoice .get ("razorpay_payment_id" )),
371+ date = invoice .get ("paid_at" )
372+ or invoice .get ("created_at" )
373+ or utc_now (),
374+ amount_minor_units = amount_minor_units ,
375+ amount_paise = amount_minor_units ,
376+ currency = str (
377+ invoice .get ("currency" ) or plan .get ("currency" ) or "INR"
378+ ),
379+ status = str (invoice .get ("status" ) or "paid" ),
380+ credits = int (invoice .get ("credits" ) or 0 ),
381+ receipt_url = invoice .get ("receipt_url" ),
382+ package_id = invoice .get ("package_id" ),
383+ razorpay_payment_id = invoice .get ("razorpay_payment_id" ),
384+ )
385+ )
386+ last_payment_at = invoices [0 ].date if invoices else None
387+ if plan_id == "free" and plan .get ("trial_credits" ) is not None :
388+ credits_limit = int (plan .get ("trial_credits" ) or 0 )
389+ elif plan .get ("monthly_credits" ) is not None :
390+ credits_limit = int (plan .get ("monthly_credits" ) or 0 )
391+ else :
392+ credits_limit = int (plan .get ("trial_credits" ) or available_credits or 0 )
393+
394+ period_start = account .get ("current_period_start" ) or utc_now ().replace (
395+ day = 1 , hour = 0 , minute = 0 , second = 0 , microsecond = 0
396+ )
397+ ledger_entries = self .store .list_ledger (account ["id" ], limit = 500 )
398+ current_usage = sum (
399+ abs (int (entry .get ("amount" ) or 0 ))
400+ for entry in ledger_entries
401+ if entry .get ("type" ) == "debit"
402+ and (
403+ not period_start
404+ or not entry .get ("created_at" )
405+ or entry ["created_at" ] >= period_start
406+ )
407+ )
296408 lots = [
297409 CreditLotPublic (
298410 id = str (lot ["id" ]),
@@ -308,12 +420,24 @@ def get_billing_summary(self, user: Mapping[str, Any]) -> BillingSummary:
308420 owner_id = str (account .get ("owner_id" )),
309421 plan_id = plan_id ,
310422 plan_name = str (plan .get ("name" ) or plan_id ),
311- status = str (account .get ("status" ) or "trialing" ),
423+ status = status ,
424+ account_status = account_status ,
312425 currency = str (plan .get ("currency" ) or "INR" ),
313- available_credits = int (wallet .get ("available_credits" ) or 0 ),
426+ available_credits = available_credits ,
427+ credit_balance = available_credits ,
314428 reserved_credits = int (wallet .get ("reserved_credits" ) or 0 ),
429+ prepaid_balance_paise = int (
430+ available_credits * billing_config .nominal_paise_per_credit (plan_id )
431+ ),
315432 current_period_start = account .get ("current_period_start" ),
316433 current_period_end = account .get ("current_period_end" ),
434+ current_month = UsageSnapshotPublic (
435+ credits_used = current_usage ,
436+ credits_limit = credits_limit ,
437+ ),
438+ next_invoice_paise = 0 ,
439+ last_payment_at = last_payment_at ,
440+ invoices = invoices ,
317441 credit_lots = lots ,
318442 )
319443
0 commit comments