@burdenoff/microfe-billing 2026.821.1 → 2026.825.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  2. package/dist/billing/BillingRoot.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js.map +1 -1
  4. package/dist/billing/hooks/useBillingAccountSelection.js.map +1 -1
  5. package/dist/billing/hooks/useBillingEventEmitter.js.map +1 -1
  6. package/dist/billing/hooks/useBillingNavigate.js.map +1 -1
  7. package/dist/billing/hooks/useBillingPermissions.js.map +1 -1
  8. package/dist/billing/hooks/useDefaultBillingCurrency.js.map +1 -1
  9. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  10. package/dist/billing/modules/addons/hooks/useAddons.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  12. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  16. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  17. package/dist/billing/modules/addons/store/addonCartStore.js.map +1 -1
  18. package/dist/billing/modules/affiliate/api.js.map +1 -1
  19. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +107 -107
  20. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  21. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +80 -81
  22. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/hooks/useInvoices.js.map +1 -1
  24. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  25. package/dist/billing/modules/billing/hooks/useTransactions.js.map +1 -1
  26. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  28. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  29. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  30. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  31. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  32. package/dist/billing/modules/billing/pages/UserRefundsPage.js +127 -127
  33. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  34. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  36. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
  38. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  40. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  42. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  44. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  46. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  47. package/dist/billing/modules/checkout/hooks/useCheckout.js +0 -1
  48. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  49. package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
  50. package/dist/billing/modules/checkout/hooks/usePaymentProviders.js.map +1 -1
  51. package/dist/billing/modules/checkout/hooks/usePaymentStatusPolling.js.map +1 -1
  52. package/dist/billing/modules/checkout/hooks/useStoreShipping.js.map +1 -1
  53. package/dist/billing/modules/checkout/pages/CheckoutPage.js +412 -395
  54. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  55. package/dist/billing/modules/checkout/utils/storeOrder.js.map +1 -1
  56. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  57. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  58. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  59. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  60. package/dist/billing/modules/credits/hooks/useCredits.js.map +1 -1
  61. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  62. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  64. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  65. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  66. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  67. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  68. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  69. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  70. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  71. package/dist/billing/modules/dashboard/hooks/useBillingOverviewData.js.map +1 -1
  72. package/dist/billing/modules/dashboard/hooks/useDashboard.js.map +1 -1
  73. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  74. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  75. package/dist/billing/modules/earnings/api.js.map +1 -1
  76. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +145 -145
  77. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
  78. package/dist/billing/modules/earnings/pages/EarningsPage.js +133 -133
  79. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  80. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +7 -9
  81. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  82. package/dist/billing/modules/earnings/pages/PayoutsPage.js +95 -95
  83. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  84. package/dist/billing/modules/plans/constants/featuredQuotas.js.map +1 -1
  85. package/dist/billing/modules/plans/hooks/usePlans.js.map +1 -1
  86. package/dist/billing/modules/plans/hooks/useQuotas.js.map +1 -1
  87. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  88. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  89. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  90. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  91. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  92. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  94. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  95. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  96. package/dist/billing/modules/quota/hooks/useQuotaTemplates.js.map +1 -1
  97. package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js.map +1 -1
  98. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  99. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  100. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  101. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  102. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  103. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  104. package/dist/billing/modules/store/pages/StoreCheckoutPage.js +5 -6
  105. package/dist/billing/modules/store/pages/StoreCheckoutPage.js.map +1 -1
  106. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  107. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
  108. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  109. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  110. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  111. package/dist/billing/modules/usage/hooks/useUsage.js +14 -9
  112. package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
  113. package/dist/billing/modules/usage/pages/UsagePage.js +481 -453
  114. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  115. package/dist/billing/providers/BillingProvider.js.map +1 -1
  116. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  117. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  118. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  119. package/dist/billing/shared/components/EmptyState.js.map +1 -1
  120. package/dist/billing/shared/components/PageHeader.js.map +1 -1
  121. package/dist/billing/shared/components/ServerError.js.map +1 -1
  122. package/dist/billing/shared/components/StatCard.js.map +1 -1
  123. package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -1
  124. package/dist/billing/shared/types/graphql.js.map +1 -1
  125. package/dist/billing/shared/ui/Card.js.map +1 -1
  126. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  127. package/dist/billing/shared/ui/ProgressBar.js.map +1 -1
  128. package/dist/billing/shared/ui/StatusPill.js.map +1 -1
  129. package/dist/billing/shared/utils/directBillingGraphql.js.map +1 -1
  130. package/dist/billing/shared/utils/error.js.map +1 -1
  131. package/dist/billing/shared/utils/format.js.map +1 -1
  132. package/dist/billing/shared/utils/gatewayGraphql.js.map +1 -1
  133. package/dist/billing/shared/utils/navigation.js.map +1 -1
  134. package/dist/billing/shared/utils/paymentTheme.js.map +1 -1
  135. package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -1
  136. package/dist/billing/shared/utils/status.js.map +1 -1
  137. package/dist/billing/shared/utils/tokens.js.map +1 -1
  138. package/dist/generated/global-operations.js +5 -1
  139. package/dist/generated/global-operations.js.map +1 -1
  140. package/dist/generated/global-types.js.map +1 -1
  141. package/dist/generated/wspace-operations.js +1 -1
  142. package/dist/generated/wspace-operations.js.map +1 -1
  143. package/dist/node_modules/@capacitor/core/dist/index.js +1 -1
  144. package/dist/node_modules/@capacitor/core/dist/index.js.map +1 -1
  145. package/package.json +13 -10
@@ -1 +1 @@
1
- {"version":3,"file":"UnifiedPaymentMethodSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/UnifiedPaymentMethodSelector.tsx"],"sourcesContent":["/**\n * Unified Payment Method Selector\n * Combines payment providers and saved payment methods into one clear interface\n */\n\nimport { type FC, useState } from 'react';\nimport { Coins, CreditCard, Wallet, ChevronDown } from 'lucide-react';\nimport type { SavedPaymentMethod } from '../types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface UnifiedPaymentMethodSelectorProps {\n // Saved cards\n savedPaymentMethods: SavedPaymentMethod[];\n selectedPaymentMethodId: string | null;\n onSelectSavedCard: (id: string | null) => void;\n\n // Provider selection (for new cards)\n selectedProvider: 'CREDITS' | 'STRIPE' | 'RAZORPAY' | 'AUTO';\n onSelectProvider: (provider: 'CREDITS' | 'STRIPE' | 'RAZORPAY') => void;\n\n // Provider availability\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n canPayWithCredits: boolean;\n availableCredits: number;\n totalAmount: number;\n billingCountry?: string;\n\n // Save card option\n saveCard: boolean;\n onSaveCardChange: (save: boolean) => void;\n\n // Auto-renewal (when enabled, card must be saved - checkbox hidden)\n autoRenewEnabled?: boolean;\n\n isLoading?: boolean;\n}\n\nconst getCardIcon = (type: string) => {\n if (type === 'WALLET') return <Wallet className=\"size-4\" />;\n return <CreditCard className=\"size-4\" />;\n};\n\nconst getCardDisplay = (pm: SavedPaymentMethod): string => {\n const type =\n pm.type === 'CREDIT_CARD' ? 'Credit Card' : pm.type === 'DEBIT_CARD' ? 'Debit Card' : pm.type;\n const last4 = pm.last4 ? ` •••• ${pm.last4}` : '';\n const brand = pm.brand ? ` (${pm.brand})` : '';\n return `${type}${last4}${brand}`;\n};\n\nexport const UnifiedPaymentMethodSelector: FC<UnifiedPaymentMethodSelectorProps> = ({\n savedPaymentMethods,\n selectedPaymentMethodId,\n onSelectSavedCard,\n selectedProvider,\n onSelectProvider,\n stripeEnabled,\n razorpayEnabled,\n canPayWithCredits,\n availableCredits,\n totalAmount,\n billingCountry,\n saveCard,\n onSaveCardChange,\n autoRenewEnabled = false,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showAllSavedCards, setShowAllSavedCards] = useState(false);\n const hasEnoughCredits = availableCredits >= totalAmount;\n const isIndia = billingCountry === 'IN';\n\n // Get default card or first card for collapsible view\n const defaultSavedCard = savedPaymentMethods.find((pm) => pm.isDefault) || savedPaymentMethods[0];\n const otherSavedCards = savedPaymentMethods.filter((pm) => pm.id !== defaultSavedCard?.id);\n\n const handleSelect = (type: 'saved' | 'new', value: string) => {\n if (type === 'saved') {\n // When selecting saved card, set it and auto-determine provider\n const selectedCard = savedPaymentMethods.find((pm) => pm.id === value);\n if (selectedCard) {\n onSelectSavedCard(value);\n // Auto-set provider based on card type\n if (selectedCard.razorpayPaymentMethodId) {\n onSelectProvider('RAZORPAY');\n } else if (selectedCard.stripePaymentMethodId) {\n onSelectProvider('STRIPE');\n }\n }\n } else {\n // When selecting new card payment, clear saved card selection\n onSelectSavedCard(null);\n onSelectProvider(value as 'CREDITS' | 'STRIPE' | 'RAZORPAY');\n }\n };\n\n const isSelected = (type: 'saved' | 'new', value: string): boolean => {\n if (type === 'saved') {\n return selectedPaymentMethodId === value;\n } else {\n return !selectedPaymentMethodId && selectedProvider === value;\n }\n };\n\n return (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n </div>\n ) : (\n <div className=\"space-y-2\">\n {/* Credits Option */}\n {canPayWithCredits && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'CREDITS')}\n disabled={!hasEnoughCredits}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'CREDITS')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n ${!hasEnoughCredits && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <Coins\n className={`size-5 shrink-0 ${isSelected('new', 'CREDITS') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Pay with Credits</div>\n <div className=\"text-xs text-text-secondary\">\n {hasEnoughCredits\n ? `Available: ${availableCredits.toLocaleString()} credits`\n : `Insufficient credits (need ${totalAmount.toLocaleString()})`}\n </div>\n </div>\n {hasEnoughCredits && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-success/10 text-success\">\n Instant\n </span>\n )}\n </button>\n )}\n\n {/* Saved Cards Section */}\n {savedPaymentMethods.length > 0 && (\n <>\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n Saved Cards\n </div>\n\n {/* Default/First Card - Always Shown */}\n {defaultSavedCard && (\n <button\n type=\"button\"\n onClick={() => handleSelect('saved', defaultSavedCard.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', defaultSavedCard.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(defaultSavedCard.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(defaultSavedCard)}\n </div>\n {defaultSavedCard.expiryMonth && defaultSavedCard.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {defaultSavedCard.expiryMonth.toString().padStart(2, '0')}/\n {defaultSavedCard.expiryYear}\n </div>\n )}\n </div>\n {defaultSavedCard.isDefault && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-[var(--color-accent-soft)] text-text-link\">\n Default\n </span>\n )}\n </button>\n )}\n\n {/* Other Cards - Collapsible */}\n {otherSavedCards.length > 0 && (\n <>\n {showAllSavedCards ? (\n <>\n {otherSavedCards.map((pm) => (\n <button\n key={pm.id}\n type=\"button\"\n onClick={() => handleSelect('saved', pm.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', pm.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(pm.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(pm)}\n </div>\n {pm.expiryMonth && pm.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {pm.expiryMonth.toString().padStart(2, '0')}/{pm.expiryYear}\n </div>\n )}\n </div>\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(false)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3 rotate-180\" />\n Hide other cards\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(true)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3\" />\n Show {otherSavedCards.length} more card\n {otherSavedCards.length > 1 ? 's' : ''}\n </button>\n )}\n </>\n )}\n </>\n )}\n\n {/* New Card Options */}\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n {savedPaymentMethods.length > 0\n ? 'Or use new card'\n : canPayWithCredits\n ? 'Or pay with card'\n : 'Pay with Card'}\n </div>\n\n {/* Razorpay (India) */}\n {razorpayEnabled && isIndia && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'RAZORPAY')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'RAZORPAY')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'RAZORPAY') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n UPI / Cards / Netbanking\n </div>\n <div className=\"text-xs text-text-secondary\">Razorpay</div>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n </button>\n )}\n\n {/* Stripe (International) */}\n {stripeEnabled && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'STRIPE')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'STRIPE')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'STRIPE') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Credit / Debit Cards</div>\n <div className=\"text-xs text-text-secondary\">Stripe - Multi-currency</div>\n </div>\n {!isIndia && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n )}\n </button>\n )}\n\n {/* Save Card Checkbox - Only show when using new card (not saved card, not credits) */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n !autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <label className=\"flex items-start gap-3 cursor-pointer group\">\n <input\n type=\"checkbox\"\n checked={saveCard}\n onChange={(e) => onSaveCardChange(e.target.checked)}\n className=\"mt-0.5 size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)] focus:ring-offset-0\"\n />\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary group-hover:text-text-link transition-colors\">\n Save card for future use\n </div>\n <div className=\"text-xs text-text-secondary mt-0.5\">\n Securely save this payment method for faster checkout next time\n </div>\n </div>\n </label>\n </div>\n )}\n\n {/* Auto-renewal notice - Show when auto-renewal is enabled */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-start gap-2 text-xs text-text-secondary bg-bg-sunken p-3 rounded-lg\">\n <svg\n className=\"size-4 mt-0.5 flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div>Your payment method will be securely saved for auto-renewal</div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"UnifiedPaymentMethodSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/UnifiedPaymentMethodSelector.tsx"],"sourcesContent":["/**\n * Unified Payment Method Selector\n * Combines payment providers and saved payment methods into one clear interface\n */\n\nimport { type FC, useState } from 'react';\nimport { Coins, CreditCard, Wallet, ChevronDown } from 'lucide-react';\nimport type { SavedPaymentMethod } from '../types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface UnifiedPaymentMethodSelectorProps {\n // Saved cards\n savedPaymentMethods: SavedPaymentMethod[];\n selectedPaymentMethodId: string | null;\n onSelectSavedCard: (id: string | null) => void;\n\n // Provider selection (for new cards)\n selectedProvider: 'CREDITS' | 'STRIPE' | 'RAZORPAY' | 'AUTO';\n onSelectProvider: (provider: 'CREDITS' | 'STRIPE' | 'RAZORPAY') => void;\n\n // Provider availability\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n canPayWithCredits: boolean;\n availableCredits: number;\n totalAmount: number;\n billingCountry?: string;\n\n // Save card option\n saveCard: boolean;\n onSaveCardChange: (save: boolean) => void;\n\n // Auto-renewal (when enabled, card must be saved - checkbox hidden)\n autoRenewEnabled?: boolean;\n\n isLoading?: boolean;\n}\n\nconst getCardIcon = (type: string) => {\n if (type === 'WALLET') return <Wallet className=\"size-4\" />;\n return <CreditCard className=\"size-4\" />;\n};\n\nconst getCardDisplay = (pm: SavedPaymentMethod): string => {\n const type =\n pm.type === 'CREDIT_CARD' ? 'Credit Card' : pm.type === 'DEBIT_CARD' ? 'Debit Card' : pm.type;\n const last4 = pm.last4 ? ` •••• ${pm.last4}` : '';\n const brand = pm.brand ? ` (${pm.brand})` : '';\n return `${type}${last4}${brand}`;\n};\n\nexport const UnifiedPaymentMethodSelector: FC<UnifiedPaymentMethodSelectorProps> = ({\n savedPaymentMethods,\n selectedPaymentMethodId,\n onSelectSavedCard,\n selectedProvider,\n onSelectProvider,\n stripeEnabled,\n razorpayEnabled,\n canPayWithCredits,\n availableCredits,\n totalAmount,\n billingCountry,\n saveCard,\n onSaveCardChange,\n autoRenewEnabled = false,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showAllSavedCards, setShowAllSavedCards] = useState(false);\n const hasEnoughCredits = availableCredits >= totalAmount;\n const isIndia = billingCountry === 'IN';\n\n // Get default card or first card for collapsible view\n const defaultSavedCard = savedPaymentMethods.find((pm) => pm.isDefault) || savedPaymentMethods[0];\n const otherSavedCards = savedPaymentMethods.filter((pm) => pm.id !== defaultSavedCard?.id);\n\n const handleSelect = (type: 'saved' | 'new', value: string) => {\n if (type === 'saved') {\n // When selecting saved card, set it and auto-determine provider\n const selectedCard = savedPaymentMethods.find((pm) => pm.id === value);\n if (selectedCard) {\n onSelectSavedCard(value);\n // Auto-set provider based on card type\n if (selectedCard.razorpayPaymentMethodId) {\n onSelectProvider('RAZORPAY');\n } else if (selectedCard.stripePaymentMethodId) {\n onSelectProvider('STRIPE');\n }\n }\n } else {\n // When selecting new card payment, clear saved card selection\n onSelectSavedCard(null);\n onSelectProvider(value as 'CREDITS' | 'STRIPE' | 'RAZORPAY');\n }\n };\n\n const isSelected = (type: 'saved' | 'new', value: string): boolean => {\n if (type === 'saved') {\n return selectedPaymentMethodId === value;\n } else {\n return !selectedPaymentMethodId && selectedProvider === value;\n }\n };\n\n return (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n </div>\n ) : (\n <div className=\"space-y-2\">\n {/* Credits Option */}\n {canPayWithCredits && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'CREDITS')}\n disabled={!hasEnoughCredits}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'CREDITS')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n ${!hasEnoughCredits && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <Coins\n className={`size-5 shrink-0 ${isSelected('new', 'CREDITS') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Pay with Credits</div>\n <div className=\"text-xs text-text-secondary\">\n {hasEnoughCredits\n ? `Available: ${availableCredits.toLocaleString()} credits`\n : `Insufficient credits (need ${totalAmount.toLocaleString()})`}\n </div>\n </div>\n {hasEnoughCredits && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-success/10 text-success\">\n Instant\n </span>\n )}\n </button>\n )}\n\n {/* Saved Cards Section */}\n {savedPaymentMethods.length > 0 && (\n <>\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n Saved Cards\n </div>\n\n {/* Default/First Card - Always Shown */}\n {defaultSavedCard && (\n <button\n type=\"button\"\n onClick={() => handleSelect('saved', defaultSavedCard.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', defaultSavedCard.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(defaultSavedCard.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(defaultSavedCard)}\n </div>\n {defaultSavedCard.expiryMonth && defaultSavedCard.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {defaultSavedCard.expiryMonth.toString().padStart(2, '0')}/\n {defaultSavedCard.expiryYear}\n </div>\n )}\n </div>\n {defaultSavedCard.isDefault && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-[var(--color-accent-soft)] text-text-link\">\n Default\n </span>\n )}\n </button>\n )}\n\n {/* Other Cards - Collapsible */}\n {otherSavedCards.length > 0 && (\n <>\n {showAllSavedCards ? (\n <>\n {otherSavedCards.map((pm) => (\n <button\n key={pm.id}\n type=\"button\"\n onClick={() => handleSelect('saved', pm.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', pm.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(pm.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(pm)}\n </div>\n {pm.expiryMonth && pm.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {pm.expiryMonth.toString().padStart(2, '0')}/{pm.expiryYear}\n </div>\n )}\n </div>\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(false)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3 rotate-180\" />\n Hide other cards\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(true)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3\" />\n Show {otherSavedCards.length} more card\n {otherSavedCards.length > 1 ? 's' : ''}\n </button>\n )}\n </>\n )}\n </>\n )}\n\n {/* New Card Options */}\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n {savedPaymentMethods.length > 0\n ? 'Or use new card'\n : canPayWithCredits\n ? 'Or pay with card'\n : 'Pay with Card'}\n </div>\n\n {/* Razorpay (India) */}\n {razorpayEnabled && isIndia && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'RAZORPAY')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'RAZORPAY')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'RAZORPAY') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n UPI / Cards / Netbanking\n </div>\n <div className=\"text-xs text-text-secondary\">Razorpay</div>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n </button>\n )}\n\n {/* Stripe (International) */}\n {stripeEnabled && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'STRIPE')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'STRIPE')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'STRIPE') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Credit / Debit Cards</div>\n <div className=\"text-xs text-text-secondary\">Stripe - Multi-currency</div>\n </div>\n {!isIndia && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n )}\n </button>\n )}\n\n {/* Save Card Checkbox - Only show when using new card (not saved card, not credits) */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n !autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <label className=\"flex items-start gap-3 cursor-pointer group\">\n <input\n type=\"checkbox\"\n checked={saveCard}\n onChange={(e) => onSaveCardChange(e.target.checked)}\n className=\"mt-0.5 size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)] focus:ring-offset-0\"\n />\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary group-hover:text-text-link transition-colors\">\n Save card for future use\n </div>\n <div className=\"text-xs text-text-secondary mt-0.5\">\n Securely save this payment method for faster checkout next time\n </div>\n </div>\n </label>\n </div>\n )}\n\n {/* Auto-renewal notice - Show when auto-renewal is enabled */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-start gap-2 text-xs text-text-secondary bg-bg-sunken p-3 rounded-lg\">\n <svg\n className=\"size-4 mt-0.5 flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div>Your payment method will be securely saved for auto-renewal</div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Quota assigned!</h2>\n <p className=\"text-sm text-text-secondary\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-text-link\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-text-secondary\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-bg-sunken rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-primary\">{q.quotaName}</span>\n <span className=\"text-text-secondary\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-text-primary\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary\">\n Will be assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Quota assigned!</h2>\n <p className=\"text-sm text-text-secondary\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-text-link\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-text-secondary\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-bg-sunken rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-primary\">{q.quotaName}</span>\n <span className=\"text-text-secondary\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-text-primary\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary\">\n Will be assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -205,7 +205,6 @@ function u(u = {}) {
205
205
  };
206
206
  }
207
207
  }, [
208
- y?.id,
209
208
  d?.email,
210
209
  d?.id,
211
210
  w,
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n useCreateBillingOrderCheckoutMutation,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [createBillingOrderMutation] = useCreateBillingOrderCheckoutMutation();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n const hasEnoughCredits = useMemo(() => {\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = subtotalUSD * CREDITS_PER_USD;\n return availableCredits >= creditsRequired;\n }, [availableCredits, subtotalUSD]);\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and non-USD currency\n if (total > 0 && selectedCurrency !== 'USD') {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: 'USD',\n },\n });\n }\n }, [total, selectedCurrency, selectedProvider, convertCurrency, taxAmount, subtotal, items]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes\n setUseCredits(false);\n setCreditsToUse(0);\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && currency !== 'USD' && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: 'USD',\n },\n });\n }\n },\n [total, convertCurrency, items]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [\n billingAccount?.id,\n currentUser?.email,\n currentUser?.id,\n promoCode,\n selectedCurrency,\n subtotal,\n validateCoupon,\n ]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n string | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, create one now.\n // Pass callbackGraphQL so the billing service can call back to the store service\n // after payment completes to mark the order confirmed.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const orderItems = items\n .filter((item) => item.type === 'ORDER')\n .map((item) => ({\n name: item.name,\n description: item.description ?? undefined,\n quantity: item.quantity,\n unitPrice: item.price,\n metadata: {\n productId: item.metadata?.productId ?? undefined,\n variantId: item.metadata?.variantId ?? undefined,\n },\n }));\n\n const { data: billingOrderResult } = await createBillingOrderMutation({\n variables: {\n input: {\n // billingAccountId intentionally not set here — it gets\n // linked at initiatePayment time so the user can switch\n // billing accounts before confirming payment.\n externalOrderId: storeOrderId,\n externalService: 'STORE',\n currency: selectedCurrency,\n subtotal: chargeAmount,\n description: `Store order ${storeOrderId}`,\n items: orderItems,\n callbackGraphQL: {\n endpoint: 'http://global-int-gateway:4002/global/graphql',\n mutationName: 'updateStoreOrderPaymentStatus',\n },\n },\n },\n });\n\n if (billingOrderResult?.createBillingOrder?.success) {\n billingOrderId = billingOrderResult.createBillingOrder.order?.id;\n } else {\n const errMsg =\n billingOrderResult?.createBillingOrder?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n }, [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n createBillingOrderMutation,\n spendCreditsMutation,\n ]);\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n useCreateBillingOrderCheckoutMutation,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [createBillingOrderMutation] = useCreateBillingOrderCheckoutMutation();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n const hasEnoughCredits = useMemo(() => {\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = subtotalUSD * CREDITS_PER_USD;\n return availableCredits >= creditsRequired;\n }, [availableCredits, subtotalUSD]);\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and non-USD currency\n if (total > 0 && selectedCurrency !== 'USD') {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: 'USD',\n },\n });\n }\n }, [total, selectedCurrency, selectedProvider, convertCurrency, taxAmount, subtotal, items]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes\n setUseCredits(false);\n setCreditsToUse(0);\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && currency !== 'USD' && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: 'USD',\n },\n });\n }\n },\n [total, convertCurrency, items]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [currentUser?.email, currentUser?.id, promoCode, selectedCurrency, subtotal, validateCoupon]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n | string\n | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, create one now.\n // Pass callbackGraphQL so the billing service can call back to the store service\n // after payment completes to mark the order confirmed.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const orderItems = items\n .filter((item) => item.type === 'ORDER')\n .map((item) => ({\n name: item.name,\n description: item.description ?? undefined,\n quantity: item.quantity,\n unitPrice: item.price,\n metadata: {\n productId: item.metadata?.productId ?? undefined,\n variantId: item.metadata?.variantId ?? undefined,\n },\n }));\n\n const { data: billingOrderResult } = await createBillingOrderMutation({\n variables: {\n input: {\n // billingAccountId intentionally not set here — it gets\n // linked at initiatePayment time so the user can switch\n // billing accounts before confirming payment.\n externalOrderId: storeOrderId,\n externalService: 'STORE',\n currency: selectedCurrency,\n subtotal: chargeAmount,\n description: `Store order ${storeOrderId}`,\n items: orderItems,\n callbackGraphQL: {\n endpoint: 'http://global-int-gateway:4002/global/graphql',\n mutationName: 'updateStoreOrderPaymentStatus',\n },\n },\n },\n });\n\n if (billingOrderResult?.createBillingOrder?.success) {\n billingOrderId = billingOrderResult.createBillingOrder.order?.id;\n } else {\n const errMsg =\n billingOrderResult?.createBillingOrder?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n }, [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n createBillingOrderMutation,\n spendCreditsMutation,\n ]);\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction, PURCHASE_TYPE } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { normalizeGlobalGatewayBaseUrl } from '../../../shared/utils/gatewayGraphql';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation.\n // apiGatewayUrl is the GraphQL endpoint (e.g. https://graphql.burdenoff.com/global/graphql);\n // strip the /global/graphql suffix to get the base URL, then use the REST path.\n const gatewayBase = normalizeGlobalGatewayBaseUrl(apiGatewayUrl) ?? '';\n const iapValidateUrl = `${gatewayBase}/global/rest/billing/iap/validate`;\n const platform = Capacitor.getPlatform() as 'ios' | 'android';\n // nativePurchaseType: credits are consumable (INAPP); plans and addons are subscriptions (SUBS).\n const nativePurchaseType = isCredits ? 'INAPP' : 'SUBS';\n const response = await fetch(iapValidateUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n body: JSON.stringify({\n platform,\n transactionId: transaction.transactionId,\n productIdentifier: transaction.productIdentifier,\n receipt: transaction.receipt,\n jwsRepresentation: transaction.jwsRepresentation,\n purchaseToken: transaction.purchaseToken,\n appAccountToken,\n billingAccountId,\n purchaseType,\n nativePurchaseType,\n price: product.price,\n currency: product.currencyCode,\n }),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `Validation failed (${response.status})`);\n }\n\n // Acknowledge only after backend confirms the purchase is valid\n await acknowledgeNativePurchase(transaction.purchaseToken ?? transaction.transactionId);\n\n setCheckoutState('success');\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Purchase failed';\n // User cancellation is not an error worth surfacing as \"error\" state\n if (\n message.toLowerCase().includes('cancel') ||\n message.toLowerCase().includes('dismiss') ||\n message.toLowerCase().includes('abort')\n ) {\n setCheckoutState('idle');\n } else {\n setError(message);\n setCheckoutState('error');\n }\n return { success: false };\n }\n },\n [billingAccountId, apiGatewayUrl, authToken, purchaseType]\n );\n\n const restorePurchases = useCallback(async (): Promise<{ success: boolean }> => {\n setIsLoading(true);\n setError(null);\n try {\n await restoreNativePurchases();\n return { success: true };\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Restore failed');\n return { success: false };\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n return {\n isAvailable: isNativeIAPAvailable(),\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n };\n}\n"],"mappings":";;;;;;AAgDA,SAAgB,EAAkB,EAChC,qBACA,iBACA,iBAC4B,EAAE,EAA2B;CACzD,IAAM,EAAE,kBAAe,iBAAc,GAAY,EAC3C,CAAC,GAAU,KAAe,EAAoB,EAAE,CAAC,EACjD,CAAC,GAAe,KAAoB,EAAwB,OAAO,EACnE,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,IAAe,EACnB,OAAO,MAAkC;AACvC,MAAI,CAAC,GAAsB,CAAE;EAC7B,IAAM,IAAM,MAAuB,IAAY,CAAC,EAAU,GAAG,EAAE;AAC3D,QAAI,WAAW,GAEnB;GADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,OAAI;AAEF,MADe,MAAM,EAAkB,EAAI,CACxB;YACZ,GAAK;AACZ,MAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;aAChE;AACR,MAAa,GAAM;;;IAGvB,CAAC,EAAU,CACZ,EAEK,IAAkB,EACtB,OAAO,MAAoD;AACzD,MAAI,CAAC,EAEH,QADA,EAAS,8BAA8B,EAChC,EAAE,SAAS,IAAO;AAI3B,EADA,EAAiB,aAAa,EAC9B,EAAS,KAAK;EAEd,IAAI;AACJ,MAAI;GAIF,IAAM,IAAY,MAAiB,WAI7B,IAAkB,OAAO,YAAY;AAC3C,OAAc,MAAM,EAAsB,EAAQ,YAAY;IAC5D,0BAA0B;IAC1B,cAAc;IACd,gBAAiB,IAAoD,KAAA,IAAvC,EAAQ,kBAAkB,KAAA;IACxD;IACD,CAAC;GAMF,IAAM,IAAiB,GADH,EAA8B,EAAc,IAAI,GAC9B,oCAChC,IAAW,EAAU,aAAa,EAElC,IAAqB,IAAY,UAAU,QAC3C,IAAW,MAAM,MAAM,GAAgB;IAC3C,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,GAAI,IAAY,EAAE,eAAe,UAAU,KAAa,GAAG,EAAE;KAC9D;IACD,MAAM,KAAK,UAAU;KACnB;KACA,eAAe,EAAY;KAC3B,mBAAmB,EAAY;KAC/B,SAAS,EAAY;KACrB,mBAAmB,EAAY;KAC/B,eAAe,EAAY;KAC3B;KACA;KACA;KACA;KACA,OAAO,EAAQ;KACf,UAAU,EAAQ;KACnB,CAAC;IACH,CAAC;AAEF,OAAI,CAAC,EAAS,IAAI;IAChB,IAAM,IAAQ,MAAM,EAAS,MAAM,CAAC,aAAa,EAAE,EAAE;AACrD,UAAU,MAAM,EAAK,WAAW,sBAAsB,EAAS,OAAO,GAAG;;AAO3E,UAHA,MAAM,EAA0B,EAAY,iBAAiB,EAAY,cAAc,EAEvF,EAAiB,UAAU,EACpB,EAAE,SAAS,IAAM;WACjB,GAAK;GACZ,IAAM,IAAU,aAAe,QAAQ,EAAI,UAAU;AAYrD,UATE,EAAQ,aAAa,CAAC,SAAS,SAAS,IACxC,EAAQ,aAAa,CAAC,SAAS,UAAU,IACzC,EAAQ,aAAa,CAAC,SAAS,QAAQ,GAEvC,EAAiB,OAAO,IAExB,EAAS,EAAQ,EACjB,EAAiB,QAAQ,GAEpB,EAAE,SAAS,IAAO;;IAG7B;EAAC;EAAkB;EAAe;EAAW;EAAa,CAC3D,EAEK,IAAmB,EAAY,YAA2C;AAE9E,EADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,MAAI;AAEF,UADA,MAAM,GAAwB,EACvB,EAAE,SAAS,IAAM;WACjB,GAAK;AAEZ,UADA,EAAS,aAAe,QAAQ,EAAI,UAAU,iBAAiB,EACxD,EAAE,SAAS,IAAO;YACjB;AACR,KAAa,GAAM;;IAEpB,EAAE,CAAC;AAEN,QAAO;EACL,aAAa,GAAsB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { normalizeGlobalGatewayBaseUrl } from '../../../shared/utils/gatewayGraphql';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation.\n // apiGatewayUrl is the GraphQL endpoint (e.g. https://graphql.burdenoff.com/global/graphql);\n // strip the /global/graphql suffix to get the base URL, then use the REST path.\n const gatewayBase = normalizeGlobalGatewayBaseUrl(apiGatewayUrl) ?? '';\n const iapValidateUrl = `${gatewayBase}/global/rest/billing/iap/validate`;\n const platform = Capacitor.getPlatform() as 'ios' | 'android';\n // nativePurchaseType: credits are consumable (INAPP); plans and addons are subscriptions (SUBS).\n const nativePurchaseType = isCredits ? 'INAPP' : 'SUBS';\n const response = await fetch(iapValidateUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n body: JSON.stringify({\n platform,\n transactionId: transaction.transactionId,\n productIdentifier: transaction.productIdentifier,\n receipt: transaction.receipt,\n jwsRepresentation: transaction.jwsRepresentation,\n purchaseToken: transaction.purchaseToken,\n appAccountToken,\n billingAccountId,\n purchaseType,\n nativePurchaseType,\n price: product.price,\n currency: product.currencyCode,\n }),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `Validation failed (${response.status})`);\n }\n\n // Acknowledge only after backend confirms the purchase is valid\n await acknowledgeNativePurchase(transaction.purchaseToken ?? transaction.transactionId);\n\n setCheckoutState('success');\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Purchase failed';\n // User cancellation is not an error worth surfacing as \"error\" state\n if (\n message.toLowerCase().includes('cancel') ||\n message.toLowerCase().includes('dismiss') ||\n message.toLowerCase().includes('abort')\n ) {\n setCheckoutState('idle');\n } else {\n setError(message);\n setCheckoutState('error');\n }\n return { success: false };\n }\n },\n [billingAccountId, apiGatewayUrl, authToken, purchaseType]\n );\n\n const restorePurchases = useCallback(async (): Promise<{ success: boolean }> => {\n setIsLoading(true);\n setError(null);\n try {\n await restoreNativePurchases();\n return { success: true };\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Restore failed');\n return { success: false };\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n return {\n isAvailable: isNativeIAPAvailable(),\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePaymentProviders.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/usePaymentProviders.ts"],"sourcesContent":["/**\n * Hook for getting payment provider configuration\n */\n\nimport { useGetPaymentProviderConfigCheckoutQuery } from '../../../../generated/global-operations';\n\ninterface PaymentProviderConfig {\n stripeEnabled: boolean;\n stripePublishableKey: string | null;\n razorpayEnabled: boolean;\n razorpayKeyId: string | null;\n}\n\ninterface UsePaymentProvidersResult {\n config: PaymentProviderConfig | null;\n isLoading: boolean;\n error: Error | null;\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n}\n\nexport function usePaymentProviders(): UsePaymentProvidersResult {\n const { data, loading, error } = useGetPaymentProviderConfigCheckoutQuery({\n fetchPolicy: 'cache-first',\n });\n\n const config = data?.getPaymentProviderConfig ?? null;\n\n return {\n config,\n isLoading: loading,\n error: error ?? null,\n stripeEnabled: config?.stripeEnabled ?? false,\n razorpayEnabled: config?.razorpayEnabled ?? false,\n };\n}\n"],"mappings":";;AAqBA,SAAgB,IAAiD;CAC/D,IAAM,EAAE,SAAM,YAAS,aAAU,EAAyC,EACxE,aAAa,eACd,CAAC,EAEI,IAAS,GAAM,4BAA4B;AAEjD,QAAO;EACL;EACA,WAAW;EACX,OAAO,KAAS;EAChB,eAAe,GAAQ,iBAAiB;EACxC,iBAAiB,GAAQ,mBAAmB;EAC7C"}
1
+ {"version":3,"file":"usePaymentProviders.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/usePaymentProviders.ts"],"sourcesContent":["/**\n * Hook for getting payment provider configuration\n */\n\nimport { useGetPaymentProviderConfigCheckoutQuery } from '../../../../generated/global-operations';\n\ninterface PaymentProviderConfig {\n stripeEnabled: boolean;\n stripePublishableKey: string | null;\n razorpayEnabled: boolean;\n razorpayKeyId: string | null;\n}\n\ninterface UsePaymentProvidersResult {\n config: PaymentProviderConfig | null;\n isLoading: boolean;\n error: Error | null;\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n}\n\nexport function usePaymentProviders(): UsePaymentProvidersResult {\n const { data, loading, error } = useGetPaymentProviderConfigCheckoutQuery({\n fetchPolicy: 'cache-first',\n });\n\n const config = data?.getPaymentProviderConfig ?? null;\n\n return {\n config,\n isLoading: loading,\n error: error ?? null,\n stripeEnabled: config?.stripeEnabled ?? false,\n razorpayEnabled: config?.razorpayEnabled ?? false,\n };\n}\n"],"mappings":";;AAqBA,SAAgB,IAAiD;CAC/D,IAAM,EAAE,SAAM,YAAS,aAAU,EAAyC,EACxE,aAAa,cACf,CAAC,GAEK,IAAS,GAAM,4BAA4B;CAEjD,OAAO;EACL;EACA,WAAW;EACX,OAAO,KAAS;EAChB,eAAe,GAAQ,iBAAiB;EACxC,iBAAiB,GAAQ,mBAAmB;CAC9C;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePaymentStatusPolling.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/usePaymentStatusPolling.ts"],"sourcesContent":["/**\n * Payment Status Polling Hook\n * Polls the backend to check payment status after initiating a payment\n * Supports both card payments (via getPayment) and credit payments (via getCreditTransactionDetails)\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n useGetPaymentStatusLazyQuery,\n useGetCreditTransactionStatusLazyQuery,\n} from '../../../../generated/global-operations';\n\n// Card payment status (uppercase)\nexport type PaymentStatus = 'PENDING' | 'SUCCEEDED' | 'FAILED' | 'REFUNDED' | 'CANCELED';\n\n// Credit transaction status (lowercase from backend)\nexport type CreditTransactionStatus = 'pending' | 'success' | 'failed' | 'refund';\n\n// Unified status for UI\nexport type UnifiedPaymentStatus = 'pending' | 'success' | 'failed' | 'timeout';\n\nexport interface PaymentStatusResult {\n id: string;\n status: PaymentStatus | CreditTransactionStatus;\n amount: number;\n currency?: string;\n paidAt?: string;\n failureReason?: string;\n transactionId?: string;\n gatewayPaymentId?: string;\n // Credit transaction specific\n type?: string;\n billingAccountId?: string;\n productId?: string;\n}\n\nexport interface UsePaymentStatusPollingOptions {\n /** Payment/Transaction ID to poll for */\n paymentId: string | null;\n /** Billing account scope required by the polling queries */\n billingAccountId: string | null;\n /** Type of payment: 'card' for card payments, 'credits' for credit transactions */\n paymentType: 'card' | 'credits';\n /** Polling interval in milliseconds (default: 5000ms / 5 seconds) */\n pollingInterval?: number;\n /** Maximum time to poll in milliseconds (default: 300000ms / 5 minutes) */\n maxPollingTime?: number;\n /** Callback when payment succeeds */\n onSuccess?: (result: PaymentStatusResult) => void;\n /** Callback when payment fails */\n onFailure?: (result: PaymentStatusResult) => void;\n /** Callback when polling times out */\n onTimeout?: () => void;\n /** Whether polling is enabled (default: true when paymentId is provided) */\n enabled?: boolean;\n}\n\nexport interface UsePaymentStatusPollingResult {\n /** Current unified payment status */\n status: UnifiedPaymentStatus | null;\n /** Whether polling is in progress */\n isPolling: boolean;\n /** Time elapsed since polling started (ms) */\n elapsedTime: number;\n /** Error message if any */\n error: string | null;\n /** Payment result data */\n paymentResult: PaymentStatusResult | null;\n /** Manually start polling */\n startPolling: () => void;\n /** Manually stop polling */\n stopPolling: () => void;\n}\n\n// Helper to normalize status to unified format\nfunction normalizeStatus(\n status: PaymentStatus | CreditTransactionStatus,\n paymentType: 'card' | 'credits'\n): UnifiedPaymentStatus {\n if (paymentType === 'credits') {\n // Credit transaction status (lowercase)\n switch (status) {\n case 'success':\n return 'success';\n case 'failed':\n return 'failed';\n case 'pending':\n default:\n return 'pending';\n }\n } else {\n // Card payment status (uppercase)\n switch (status) {\n case 'SUCCEEDED':\n return 'success';\n case 'FAILED':\n case 'CANCELED':\n return 'failed';\n case 'PENDING':\n default:\n return 'pending';\n }\n }\n}\n\nexport function usePaymentStatusPolling(\n options: UsePaymentStatusPollingOptions\n): UsePaymentStatusPollingResult {\n const {\n paymentId,\n billingAccountId,\n paymentType,\n pollingInterval = 5000, // 5 seconds\n maxPollingTime = 300000, // 5 minutes\n onSuccess,\n onFailure,\n onTimeout,\n enabled = true,\n } = options;\n\n const [status, setStatus] = useState<UnifiedPaymentStatus | null>(null);\n const [isPolling, setIsPolling] = useState(false);\n const [elapsedTime, setElapsedTime] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [paymentResult, setPaymentResult] = useState<PaymentStatusResult | null>(null);\n\n // Refs to track polling state\n const pollingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const startTimeRef = useRef<number | null>(null);\n const isPollingRef = useRef(false);\n\n // Refs for callbacks to avoid dependency issues\n const onSuccessRef = useRef(onSuccess);\n const onFailureRef = useRef(onFailure);\n const onTimeoutRef = useRef(onTimeout);\n\n // Refs for config values\n const pollingIntervalMsRef = useRef(pollingInterval);\n const maxPollingTimeMsRef = useRef(maxPollingTime);\n\n // Keep refs updated - this is a common pattern for callbacks in effects\n onSuccessRef.current = onSuccess;\n onFailureRef.current = onFailure;\n onTimeoutRef.current = onTimeout;\n pollingIntervalMsRef.current = pollingInterval;\n maxPollingTimeMsRef.current = maxPollingTime;\n\n // GraphQL queries for both types\n const [fetchCardPaymentStatus] = useGetPaymentStatusLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const [fetchCreditTransactionStatus] = useGetCreditTransactionStatusLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n // Stop polling helper\n const clearPollingIntervals = useCallback(() => {\n if (pollingIntervalRef.current) {\n clearInterval(pollingIntervalRef.current);\n pollingIntervalRef.current = null;\n }\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n // Stop polling\n const stopPolling = useCallback(() => {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n }, [clearPollingIntervals]);\n\n // Manual start polling (for external control)\n const startPolling = useCallback(() => {\n // This is handled by the useEffect, but we expose it for manual control\n // The effect will handle actual polling start\n }, []);\n\n // Auto-start polling when paymentId changes and enabled\n useEffect(() => {\n // Only start if we have a payment ID and aren't already polling\n if (!paymentId || !billingAccountId || !enabled || isPollingRef.current) {\n return;\n }\n\n isPollingRef.current = true;\n setIsPolling(true);\n setElapsedTime(0);\n setError(null);\n setStatus('pending');\n startTimeRef.current = Date.now();\n\n // Define the poll function inline to capture current values\n const doPoll = async () => {\n if (!isPollingRef.current) return;\n\n try {\n let result: PaymentStatusResult | undefined;\n\n if (paymentType === 'credits') {\n const { data, error: queryError } = await fetchCreditTransactionStatus({\n variables: { creditTransactionID: paymentId, billingAccountId },\n });\n\n if (queryError) {\n setError(queryError.message);\n return;\n }\n\n const transaction = data?.getCreditTransactionDetails;\n if (transaction) {\n result = {\n id: transaction.id,\n status: transaction.status,\n amount: transaction.amount,\n transactionId: transaction.transactionId,\n type: transaction.type,\n billingAccountId: transaction.billingAccountId,\n productId: transaction.productId,\n };\n }\n } else {\n const { data, error: queryError } = await fetchCardPaymentStatus({\n variables: { id: paymentId, billingAccountId },\n });\n\n if (queryError) {\n setError(queryError.message);\n return;\n }\n\n const payment = data?.getPayment;\n if (payment) {\n result = {\n id: payment.id,\n status: payment.status,\n amount: payment.amount,\n currency: payment.currency,\n paidAt: payment.paidAt,\n failureReason: payment.failureReason,\n transactionId: payment.transactionId,\n gatewayPaymentId: payment.gatewayPaymentId,\n };\n }\n }\n\n if (!result) {\n return;\n }\n\n const normalizedStatus = normalizeStatus(result.status, paymentType);\n setStatus(normalizedStatus);\n setPaymentResult(result);\n\n // Handle terminal states\n if (normalizedStatus === 'success') {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n onSuccessRef.current?.(result);\n } else if (normalizedStatus === 'failed') {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n onFailureRef.current?.(result);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to check payment status');\n }\n };\n\n // Initial poll\n doPoll();\n\n // Set up polling interval (poll every N seconds)\n pollingIntervalRef.current = setInterval(doPoll, pollingIntervalMsRef.current);\n\n // Set up elapsed time tracking (update every second)\n elapsedIntervalRef.current = setInterval(() => {\n if (startTimeRef.current) {\n const elapsed = Date.now() - startTimeRef.current;\n setElapsedTime(elapsed);\n\n // Check for timeout\n if (elapsed >= maxPollingTimeMsRef.current) {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n setStatus('timeout');\n setError(\n 'Payment verification timed out. Please check your billing history or try again after 24 hours.'\n );\n onTimeoutRef.current?.();\n }\n }\n }, 1000);\n\n // Cleanup on unmount or when paymentId changes\n return () => {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n };\n }, [\n billingAccountId,\n paymentId,\n enabled,\n paymentType,\n fetchCardPaymentStatus,\n fetchCreditTransactionStatus,\n clearPollingIntervals,\n ]);\n\n return {\n status,\n isPolling,\n elapsedTime,\n error,\n paymentResult,\n startPolling,\n stopPolling,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"usePaymentStatusPolling.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/usePaymentStatusPolling.ts"],"sourcesContent":["/**\n * Payment Status Polling Hook\n * Polls the backend to check payment status after initiating a payment\n * Supports both card payments (via getPayment) and credit payments (via getCreditTransactionDetails)\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n useGetPaymentStatusLazyQuery,\n useGetCreditTransactionStatusLazyQuery,\n} from '../../../../generated/global-operations';\n\n// Card payment status (uppercase)\nexport type PaymentStatus = 'PENDING' | 'SUCCEEDED' | 'FAILED' | 'REFUNDED' | 'CANCELED';\n\n// Credit transaction status (lowercase from backend)\nexport type CreditTransactionStatus = 'pending' | 'success' | 'failed' | 'refund';\n\n// Unified status for UI\nexport type UnifiedPaymentStatus = 'pending' | 'success' | 'failed' | 'timeout';\n\nexport interface PaymentStatusResult {\n id: string;\n status: PaymentStatus | CreditTransactionStatus;\n amount: number;\n currency?: string;\n paidAt?: string;\n failureReason?: string;\n transactionId?: string;\n gatewayPaymentId?: string;\n // Credit transaction specific\n type?: string;\n billingAccountId?: string;\n productId?: string;\n}\n\nexport interface UsePaymentStatusPollingOptions {\n /** Payment/Transaction ID to poll for */\n paymentId: string | null;\n /** Billing account scope required by the polling queries */\n billingAccountId: string | null;\n /** Type of payment: 'card' for card payments, 'credits' for credit transactions */\n paymentType: 'card' | 'credits';\n /** Polling interval in milliseconds (default: 5000ms / 5 seconds) */\n pollingInterval?: number;\n /** Maximum time to poll in milliseconds (default: 300000ms / 5 minutes) */\n maxPollingTime?: number;\n /** Callback when payment succeeds */\n onSuccess?: (result: PaymentStatusResult) => void;\n /** Callback when payment fails */\n onFailure?: (result: PaymentStatusResult) => void;\n /** Callback when polling times out */\n onTimeout?: () => void;\n /** Whether polling is enabled (default: true when paymentId is provided) */\n enabled?: boolean;\n}\n\nexport interface UsePaymentStatusPollingResult {\n /** Current unified payment status */\n status: UnifiedPaymentStatus | null;\n /** Whether polling is in progress */\n isPolling: boolean;\n /** Time elapsed since polling started (ms) */\n elapsedTime: number;\n /** Error message if any */\n error: string | null;\n /** Payment result data */\n paymentResult: PaymentStatusResult | null;\n /** Manually start polling */\n startPolling: () => void;\n /** Manually stop polling */\n stopPolling: () => void;\n}\n\n// Helper to normalize status to unified format\nfunction normalizeStatus(\n status: PaymentStatus | CreditTransactionStatus,\n paymentType: 'card' | 'credits'\n): UnifiedPaymentStatus {\n if (paymentType === 'credits') {\n // Credit transaction status (lowercase)\n switch (status) {\n case 'success':\n return 'success';\n case 'failed':\n return 'failed';\n case 'pending':\n default:\n return 'pending';\n }\n } else {\n // Card payment status (uppercase)\n switch (status) {\n case 'SUCCEEDED':\n return 'success';\n case 'FAILED':\n case 'CANCELED':\n return 'failed';\n case 'PENDING':\n default:\n return 'pending';\n }\n }\n}\n\nexport function usePaymentStatusPolling(\n options: UsePaymentStatusPollingOptions\n): UsePaymentStatusPollingResult {\n const {\n paymentId,\n billingAccountId,\n paymentType,\n pollingInterval = 5000, // 5 seconds\n maxPollingTime = 300000, // 5 minutes\n onSuccess,\n onFailure,\n onTimeout,\n enabled = true,\n } = options;\n\n const [status, setStatus] = useState<UnifiedPaymentStatus | null>(null);\n const [isPolling, setIsPolling] = useState(false);\n const [elapsedTime, setElapsedTime] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [paymentResult, setPaymentResult] = useState<PaymentStatusResult | null>(null);\n\n // Refs to track polling state\n const pollingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const elapsedIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const startTimeRef = useRef<number | null>(null);\n const isPollingRef = useRef(false);\n\n // Refs for callbacks to avoid dependency issues\n const onSuccessRef = useRef(onSuccess);\n const onFailureRef = useRef(onFailure);\n const onTimeoutRef = useRef(onTimeout);\n\n // Refs for config values\n const pollingIntervalMsRef = useRef(pollingInterval);\n const maxPollingTimeMsRef = useRef(maxPollingTime);\n\n // Keep refs updated - this is a common pattern for callbacks in effects\n onSuccessRef.current = onSuccess;\n onFailureRef.current = onFailure;\n onTimeoutRef.current = onTimeout;\n pollingIntervalMsRef.current = pollingInterval;\n maxPollingTimeMsRef.current = maxPollingTime;\n\n // GraphQL queries for both types\n const [fetchCardPaymentStatus] = useGetPaymentStatusLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const [fetchCreditTransactionStatus] = useGetCreditTransactionStatusLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n // Stop polling helper\n const clearPollingIntervals = useCallback(() => {\n if (pollingIntervalRef.current) {\n clearInterval(pollingIntervalRef.current);\n pollingIntervalRef.current = null;\n }\n if (elapsedIntervalRef.current) {\n clearInterval(elapsedIntervalRef.current);\n elapsedIntervalRef.current = null;\n }\n }, []);\n\n // Stop polling\n const stopPolling = useCallback(() => {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n }, [clearPollingIntervals]);\n\n // Manual start polling (for external control)\n const startPolling = useCallback(() => {\n // This is handled by the useEffect, but we expose it for manual control\n // The effect will handle actual polling start\n }, []);\n\n // Auto-start polling when paymentId changes and enabled\n useEffect(() => {\n // Only start if we have a payment ID and aren't already polling\n if (!paymentId || !billingAccountId || !enabled || isPollingRef.current) {\n return;\n }\n\n isPollingRef.current = true;\n setIsPolling(true);\n setElapsedTime(0);\n setError(null);\n setStatus('pending');\n startTimeRef.current = Date.now();\n\n // Define the poll function inline to capture current values\n const doPoll = async () => {\n if (!isPollingRef.current) return;\n\n try {\n let result: PaymentStatusResult | undefined;\n\n if (paymentType === 'credits') {\n const { data, error: queryError } = await fetchCreditTransactionStatus({\n variables: { creditTransactionID: paymentId, billingAccountId },\n });\n\n if (queryError) {\n setError(queryError.message);\n return;\n }\n\n const transaction = data?.getCreditTransactionDetails;\n if (transaction) {\n result = {\n id: transaction.id,\n status: transaction.status,\n amount: transaction.amount,\n transactionId: transaction.transactionId,\n type: transaction.type,\n billingAccountId: transaction.billingAccountId,\n productId: transaction.productId,\n };\n }\n } else {\n const { data, error: queryError } = await fetchCardPaymentStatus({\n variables: { id: paymentId, billingAccountId },\n });\n\n if (queryError) {\n setError(queryError.message);\n return;\n }\n\n const payment = data?.getPayment;\n if (payment) {\n result = {\n id: payment.id,\n status: payment.status,\n amount: payment.amount,\n currency: payment.currency,\n paidAt: payment.paidAt,\n failureReason: payment.failureReason,\n transactionId: payment.transactionId,\n gatewayPaymentId: payment.gatewayPaymentId,\n };\n }\n }\n\n if (!result) {\n return;\n }\n\n const normalizedStatus = normalizeStatus(result.status, paymentType);\n setStatus(normalizedStatus);\n setPaymentResult(result);\n\n // Handle terminal states\n if (normalizedStatus === 'success') {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n onSuccessRef.current?.(result);\n } else if (normalizedStatus === 'failed') {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n onFailureRef.current?.(result);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to check payment status');\n }\n };\n\n // Initial poll\n doPoll();\n\n // Set up polling interval (poll every N seconds)\n pollingIntervalRef.current = setInterval(doPoll, pollingIntervalMsRef.current);\n\n // Set up elapsed time tracking (update every second)\n elapsedIntervalRef.current = setInterval(() => {\n if (startTimeRef.current) {\n const elapsed = Date.now() - startTimeRef.current;\n setElapsedTime(elapsed);\n\n // Check for timeout\n if (elapsed >= maxPollingTimeMsRef.current) {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n setStatus('timeout');\n setError(\n 'Payment verification timed out. Please check your billing history or try again after 24 hours.'\n );\n onTimeoutRef.current?.();\n }\n }\n }, 1000);\n\n // Cleanup on unmount or when paymentId changes\n return () => {\n isPollingRef.current = false;\n setIsPolling(false);\n clearPollingIntervals();\n };\n }, [\n billingAccountId,\n paymentId,\n enabled,\n paymentType,\n fetchCardPaymentStatus,\n fetchCreditTransactionStatus,\n clearPollingIntervals,\n ]);\n\n return {\n status,\n isPolling,\n elapsedTime,\n error,\n paymentResult,\n startPolling,\n stopPolling,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useStoreShipping.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useStoreShipping.ts"],"sourcesContent":["/**\n * Store checkout — shipping operations (physical products only)\n * ============================================================================\n *\n * TRANSITIONAL SHIM. These hooks are hand-written against the STORE subgraph\n * (federated behind the global gateway) using inline `gql` documents, mirroring\n * the existing precedent in `modules/usage/hooks/useUsage.ts`.\n *\n * Why hand-written and not codegen'd?\n * - The canonical GraphQL operations live in\n * `src/operations/global/store/{queries,mutations}/*.graphql` (added in this\n * PR as the backend contract), but the committed schema snapshot\n * (`graphql/global.graphql`) is stale and does NOT yet expose\n * `shippingMethods` / `myShippingAddresses` / `createShippingAddress` /\n * `setStoreOrderShipping`. Running `bun run codegen` offline against the\n * stale schema therefore fails, so the generated hooks\n * (`useShippingMethodsQuery`, `useSetStoreOrderShippingMutation`, ...) do\n * not exist yet.\n * - `setStoreOrderShipping` is a NEW mutation that the global-store-svc order\n * module must add (see the coordinating backend PR). Until it ships, the\n * write is inert at runtime.\n *\n * FOLLOW-UP once the backend + a fresh `bun run schema:pull` land: delete this\n * module and switch the imports in `StoreShippingSection.tsx` / `CheckoutPage.tsx`\n * to the generated hooks in `src/generated/global-operations.ts`.\n *\n * Everything here is scoped to physical STORE-order checkout and is never\n * imported on the digital/plan/addon/credits checkout path.\n */\n\nimport { gql, useQuery, useLazyQuery, useMutation } from '@burdenoff/fe-libs/shared/graphql';\n\n/** Synthetic checkout line-item id used to fold shipping cost into the billing total. */\nexport const STORE_SHIPPING_ITEM_ID = 'store-shipping';\n\n// ============================================================================\n// Types (mirror the STORE subgraph shapes in graphql/global.graphql)\n// ============================================================================\n\nexport interface ShippingMethod {\n id: string;\n name: string;\n code: string;\n carrier?: string | null;\n baseCost: number;\n currency: string;\n estimatedMinDays?: number | null;\n estimatedMaxDays?: number | null;\n availableCountries: string[];\n excludedCountries: string[];\n isActive: boolean;\n displayOrder: number;\n}\n\nexport interface ShippingAddress {\n id: string;\n fullName: string;\n phone?: string | null;\n email?: string | null;\n addressLine1: string;\n addressLine2?: string | null;\n city: string;\n state?: string | null;\n postalCode: string;\n country: string;\n isDefault: boolean;\n label?: string | null;\n instructions?: string | null;\n}\n\nexport interface OrderShippingEstimate {\n orderId: string;\n shippingMethodId: string;\n cost: number;\n currency: string;\n methodName: string;\n carrier?: string | null;\n estimatedMinDays?: number | null;\n estimatedMaxDays?: number | null;\n estimatedDeliveryDate?: string | null;\n}\n\nexport interface CreateShippingAddressInput {\n fullName: string;\n phone?: string;\n email?: string;\n addressLine1: string;\n addressLine2?: string;\n city: string;\n state?: string;\n postalCode: string;\n country: string;\n instructions?: string;\n label?: string;\n isDefault?: boolean;\n}\n\nexport interface OrderShippingEstimateInput {\n orderId: string;\n shippingMethodId: string;\n destinationCountry: string;\n destinationPostalCode?: string;\n}\n\nexport interface SetStoreOrderShippingInput {\n orderId: string;\n shippingMethodId: string;\n shippingAddressId?: string;\n}\n\n// setStoreOrderShipping returns the updated StoreOrder directly (product-only `total`,\n// with the server-computed shipping cost persisted on `shippingCost`/`shippingCurrency`).\nexport interface SetStoreOrderShippingResult {\n id: string;\n total: number;\n shippingCost: number | null;\n shippingCurrency: string | null;\n}\n\n// ============================================================================\n// GraphQL documents\n// ============================================================================\n\nconst SHIPPING_METHODS_QUERY = gql`\n query ShippingMethods {\n shippingMethods {\n id\n name\n code\n carrier\n baseCost\n currency\n estimatedMinDays\n estimatedMaxDays\n availableCountries\n excludedCountries\n isActive\n displayOrder\n }\n }\n`;\n\nconst MY_SHIPPING_ADDRESSES_QUERY = gql`\n query MyShippingAddresses {\n myShippingAddresses {\n id\n fullName\n phone\n email\n addressLine1\n addressLine2\n city\n state\n postalCode\n country\n isDefault\n label\n instructions\n }\n }\n`;\n\nconst CREATE_SHIPPING_ADDRESS_MUTATION = gql`\n mutation CreateShippingAddress($input: CreateShippingAddressInput!) {\n createShippingAddress(input: $input) {\n id\n fullName\n phone\n email\n addressLine1\n addressLine2\n city\n state\n postalCode\n country\n isDefault\n label\n instructions\n }\n }\n`;\n\nconst ESTIMATE_ORDER_SHIPPING_QUERY = gql`\n query EstimateOrderShipping($input: OrderShippingEstimateInput!) {\n estimateOrderShipping(input: $input) {\n orderId\n shippingMethodId\n cost\n currency\n methodName\n carrier\n estimatedMinDays\n estimatedMaxDays\n estimatedDeliveryDate\n }\n }\n`;\n\n// NOTE: blocked on global-store-svc adding this mutation. Inert until then.\nconst SET_STORE_ORDER_SHIPPING_MUTATION = gql`\n mutation SetStoreOrderShipping($input: SetStoreOrderShippingInput!) {\n setStoreOrderShipping(input: $input) {\n id\n total\n shippingCost\n shippingCurrency\n }\n }\n`;\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nexport function useShippingMethods() {\n const { data, loading, error } = useQuery<{ shippingMethods: ShippingMethod[] }>(\n SHIPPING_METHODS_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n return { methods: data?.shippingMethods ?? [], loading, error: error ?? null };\n}\n\nexport function useMyShippingAddresses() {\n const { data, loading, error, refetch } = useQuery<{ myShippingAddresses: ShippingAddress[] }>(\n MY_SHIPPING_ADDRESSES_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n return {\n addresses: data?.myShippingAddresses ?? [],\n loading,\n error: error ?? null,\n refetch,\n };\n}\n\nexport function useCreateShippingAddress() {\n const [mutate, { loading }] = useMutation<\n { createShippingAddress: ShippingAddress },\n { input: CreateShippingAddressInput }\n >(CREATE_SHIPPING_ADDRESS_MUTATION);\n\n const createShippingAddress = async (\n input: CreateShippingAddressInput\n ): Promise<ShippingAddress | null> => {\n const result = await mutate({ variables: { input } });\n return result.data?.createShippingAddress ?? null;\n };\n\n return { createShippingAddress, loading };\n}\n\nexport function useEstimateOrderShipping() {\n return useLazyQuery<\n { estimateOrderShipping: OrderShippingEstimate },\n { input: OrderShippingEstimateInput }\n >(ESTIMATE_ORDER_SHIPPING_QUERY, { fetchPolicy: 'network-only' });\n}\n\nexport function useSetStoreOrderShipping() {\n return useMutation<\n { setStoreOrderShipping: SetStoreOrderShippingResult },\n { input: SetStoreOrderShippingInput }\n >(SET_STORE_ORDER_SHIPPING_MUTATION);\n}\n"],"mappings":";;AAiCA,IAAa,IAAyB,kBA0FhC,IAAyB,CAAG;;;;;;;;;;;;;;;;;GAmB5B,IAA8B,CAAG;;;;;;;;;;;;;;;;;;GAoBjC,IAAmC,CAAG;;;;;;;;;;;;;;;;;;GAoBtC,IAAgC,CAAG;;;;;;;;;;;;;;GAiBnC,IAAoC,CAAG;;;;;;;;;;AAe7C,SAAgB,IAAqB;CACnC,IAAM,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA,EAAE,aAAa,qBAAqB,CACrC;AACD,QAAO;EAAE,SAAS,GAAM,mBAAmB,EAAE;EAAE;EAAS,OAAO,KAAS;EAAM;;AAGhF,SAAgB,IAAyB;CACvC,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EACxC,GACA,EAAE,aAAa,qBAAqB,CACrC;AACD,QAAO;EACL,WAAW,GAAM,uBAAuB,EAAE;EAC1C;EACA,OAAO,KAAS;EAChB;EACD;;AAGH,SAAgB,IAA2B;CACzC,IAAM,CAAC,GAAQ,EAAE,gBAAa,EAG5B,EAAiC;AASnC,QAAO;EAAE,uBAPqB,OAC5B,OAEe,MAAM,EAAO,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC,EACvC,MAAM,yBAAyB;EAGf;EAAS;;AAG3C,SAAgB,IAA2B;AACzC,QAAO,EAGL,GAA+B,EAAE,aAAa,gBAAgB,CAAC;;AAGnE,SAAgB,IAA2B;AACzC,QAAO,EAGL,EAAkC"}
1
+ {"version":3,"file":"useStoreShipping.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useStoreShipping.ts"],"sourcesContent":["/**\n * Store checkout — shipping operations (physical products only)\n * ============================================================================\n *\n * TRANSITIONAL SHIM. These hooks are hand-written against the STORE subgraph\n * (federated behind the global gateway) using inline `gql` documents, mirroring\n * the existing precedent in `modules/usage/hooks/useUsage.ts`.\n *\n * Why hand-written and not codegen'd?\n * - The canonical GraphQL operations live in\n * `src/operations/global/store/{queries,mutations}/*.graphql` (added in this\n * PR as the backend contract), but the committed schema snapshot\n * (`graphql/global.graphql`) is stale and does NOT yet expose\n * `shippingMethods` / `myShippingAddresses` / `createShippingAddress` /\n * `setStoreOrderShipping`. Running `bun run codegen` offline against the\n * stale schema therefore fails, so the generated hooks\n * (`useShippingMethodsQuery`, `useSetStoreOrderShippingMutation`, ...) do\n * not exist yet.\n * - `setStoreOrderShipping` is a NEW mutation that the global-store-svc order\n * module must add (see the coordinating backend PR). Until it ships, the\n * write is inert at runtime.\n *\n * FOLLOW-UP once the backend + a fresh `bun run schema:pull` land: delete this\n * module and switch the imports in `StoreShippingSection.tsx` / `CheckoutPage.tsx`\n * to the generated hooks in `src/generated/global-operations.ts`.\n *\n * Everything here is scoped to physical STORE-order checkout and is never\n * imported on the digital/plan/addon/credits checkout path.\n */\n\nimport { gql, useQuery, useLazyQuery, useMutation } from '@burdenoff/fe-libs/shared/graphql';\n\n/** Synthetic checkout line-item id used to fold shipping cost into the billing total. */\nexport const STORE_SHIPPING_ITEM_ID = 'store-shipping';\n\n// ============================================================================\n// Types (mirror the STORE subgraph shapes in graphql/global.graphql)\n// ============================================================================\n\nexport interface ShippingMethod {\n id: string;\n name: string;\n code: string;\n carrier?: string | null;\n baseCost: number;\n currency: string;\n estimatedMinDays?: number | null;\n estimatedMaxDays?: number | null;\n availableCountries: string[];\n excludedCountries: string[];\n isActive: boolean;\n displayOrder: number;\n}\n\nexport interface ShippingAddress {\n id: string;\n fullName: string;\n phone?: string | null;\n email?: string | null;\n addressLine1: string;\n addressLine2?: string | null;\n city: string;\n state?: string | null;\n postalCode: string;\n country: string;\n isDefault: boolean;\n label?: string | null;\n instructions?: string | null;\n}\n\nexport interface OrderShippingEstimate {\n orderId: string;\n shippingMethodId: string;\n cost: number;\n currency: string;\n methodName: string;\n carrier?: string | null;\n estimatedMinDays?: number | null;\n estimatedMaxDays?: number | null;\n estimatedDeliveryDate?: string | null;\n}\n\nexport interface CreateShippingAddressInput {\n fullName: string;\n phone?: string;\n email?: string;\n addressLine1: string;\n addressLine2?: string;\n city: string;\n state?: string;\n postalCode: string;\n country: string;\n instructions?: string;\n label?: string;\n isDefault?: boolean;\n}\n\nexport interface OrderShippingEstimateInput {\n orderId: string;\n shippingMethodId: string;\n destinationCountry: string;\n destinationPostalCode?: string;\n}\n\nexport interface SetStoreOrderShippingInput {\n orderId: string;\n shippingMethodId: string;\n shippingAddressId?: string;\n}\n\n// setStoreOrderShipping returns the updated StoreOrder directly (product-only `total`,\n// with the server-computed shipping cost persisted on `shippingCost`/`shippingCurrency`).\nexport interface SetStoreOrderShippingResult {\n id: string;\n total: number;\n shippingCost: number | null;\n shippingCurrency: string | null;\n}\n\n// ============================================================================\n// GraphQL documents\n// ============================================================================\n\nconst SHIPPING_METHODS_QUERY = gql`\n query ShippingMethods {\n shippingMethods {\n id\n name\n code\n carrier\n baseCost\n currency\n estimatedMinDays\n estimatedMaxDays\n availableCountries\n excludedCountries\n isActive\n displayOrder\n }\n }\n`;\n\nconst MY_SHIPPING_ADDRESSES_QUERY = gql`\n query MyShippingAddresses {\n myShippingAddresses {\n id\n fullName\n phone\n email\n addressLine1\n addressLine2\n city\n state\n postalCode\n country\n isDefault\n label\n instructions\n }\n }\n`;\n\nconst CREATE_SHIPPING_ADDRESS_MUTATION = gql`\n mutation CreateShippingAddress($input: CreateShippingAddressInput!) {\n createShippingAddress(input: $input) {\n id\n fullName\n phone\n email\n addressLine1\n addressLine2\n city\n state\n postalCode\n country\n isDefault\n label\n instructions\n }\n }\n`;\n\nconst ESTIMATE_ORDER_SHIPPING_QUERY = gql`\n query EstimateOrderShipping($input: OrderShippingEstimateInput!) {\n estimateOrderShipping(input: $input) {\n orderId\n shippingMethodId\n cost\n currency\n methodName\n carrier\n estimatedMinDays\n estimatedMaxDays\n estimatedDeliveryDate\n }\n }\n`;\n\n// NOTE: blocked on global-store-svc adding this mutation. Inert until then.\nconst SET_STORE_ORDER_SHIPPING_MUTATION = gql`\n mutation SetStoreOrderShipping($input: SetStoreOrderShippingInput!) {\n setStoreOrderShipping(input: $input) {\n id\n total\n shippingCost\n shippingCurrency\n }\n }\n`;\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nexport function useShippingMethods() {\n const { data, loading, error } = useQuery<{ shippingMethods: ShippingMethod[] }>(\n SHIPPING_METHODS_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n return { methods: data?.shippingMethods ?? [], loading, error: error ?? null };\n}\n\nexport function useMyShippingAddresses() {\n const { data, loading, error, refetch } = useQuery<{ myShippingAddresses: ShippingAddress[] }>(\n MY_SHIPPING_ADDRESSES_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n return {\n addresses: data?.myShippingAddresses ?? [],\n loading,\n error: error ?? null,\n refetch,\n };\n}\n\nexport function useCreateShippingAddress() {\n const [mutate, { loading }] = useMutation<\n { createShippingAddress: ShippingAddress },\n { input: CreateShippingAddressInput }\n >(CREATE_SHIPPING_ADDRESS_MUTATION);\n\n const createShippingAddress = async (\n input: CreateShippingAddressInput\n ): Promise<ShippingAddress | null> => {\n const result = await mutate({ variables: { input } });\n return result.data?.createShippingAddress ?? null;\n };\n\n return { createShippingAddress, loading };\n}\n\nexport function useEstimateOrderShipping() {\n return useLazyQuery<\n { estimateOrderShipping: OrderShippingEstimate },\n { input: OrderShippingEstimateInput }\n >(ESTIMATE_ORDER_SHIPPING_QUERY, { fetchPolicy: 'network-only' });\n}\n\nexport function useSetStoreOrderShipping() {\n return useMutation<\n { setStoreOrderShipping: SetStoreOrderShippingResult },\n { input: SetStoreOrderShippingInput }\n >(SET_STORE_ORDER_SHIPPING_MUTATION);\n}\n"],"mappings":";;AAiCA,IAAa,IAAyB,kBA0FhC,IAAyB,CAAG;;;;;;;;;;;;;;;;;GAmB5B,IAA8B,CAAG;;;;;;;;;;;;;;;;;;GAoBjC,IAAmC,CAAG;;;;;;;;;;;;;;;;;;GAoBtC,IAAgC,CAAG;;;;;;;;;;;;;;GAiBnC,IAAoC,CAAG;;;;;;;;;;AAe7C,SAAgB,IAAqB;CACnC,IAAM,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA,EAAE,aAAa,oBAAoB,CACrC;CACA,OAAO;EAAE,SAAS,GAAM,mBAAmB,CAAC;EAAG;EAAS,OAAO,KAAS;CAAK;AAC/E;AAEA,SAAgB,IAAyB;CACvC,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EACxC,GACA,EAAE,aAAa,oBAAoB,CACrC;CACA,OAAO;EACL,WAAW,GAAM,uBAAuB,CAAC;EACzC;EACA,OAAO,KAAS;EAChB;CACF;AACF;AAEA,SAAgB,IAA2B;CACzC,IAAM,CAAC,GAAQ,EAAE,gBAAa,EAG5B,CAAgC;CASlC,OAAO;EAAE,8BANP,OAGO,MADc,EAAO,EAAE,WAAW,EAAE,SAAM,EAAE,CAAC,GACtC,MAAM,yBAAyB;EAGf;CAAQ;AAC1C;AAEA,SAAgB,IAA2B;CACzC,OAAO,EAGL,GAA+B,EAAE,aAAa,eAAe,CAAC;AAClE;AAEA,SAAgB,IAA2B;CACzC,OAAO,EAGL,CAAiC;AACrC"}