StripePaymentConnection.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { ExternalLink } from 'lucide-react'
  2. import { Button } from 'ui'
  3. import { Admonition } from 'ui-patterns/admonition'
  4. import PartnerIcon from '@/components/ui/PartnerIcon'
  5. import { MANAGED_BY } from '@/lib/constants/infrastructure'
  6. export type StripeTokenStatus = 'connected' | 'attention' | 'unknown'
  7. export const STRIPE_DASHBOARD_URL = 'https://dashboard.stripe.com'
  8. export const STRIPE_PROJECTS_DOCS_URL = 'https://docs.stripe.com/projects'
  9. interface StripePaymentConnectionProps {
  10. status?: StripeTokenStatus
  11. tokenLast4?: string | null
  12. tokenExpiresAt?: number | null
  13. }
  14. const formatStripeTokenExpiry = (expiresAt?: number | null) => {
  15. if (!expiresAt) return undefined
  16. return new Date(expiresAt * 1000).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
  17. }
  18. export function StripePaymentConnection({
  19. status = 'connected',
  20. tokenLast4,
  21. tokenExpiresAt,
  22. }: StripePaymentConnectionProps) {
  23. const tokenExpiry = formatStripeTokenExpiry(tokenExpiresAt)
  24. const hasTokenSummary =
  25. tokenLast4 !== null && tokenLast4 !== undefined && tokenExpiry !== undefined
  26. if (status === 'attention') {
  27. return (
  28. <Admonition
  29. type="warning"
  30. title="Stripe payment connection needs attention"
  31. description="The payment token linked to this organisation may be invalid or expired. To keep billing active, review and update it in your Stripe Dashboard."
  32. actions={
  33. <Button asChild type="warning" iconRight={<ExternalLink size={14} />}>
  34. <a href={STRIPE_DASHBOARD_URL} target="_blank" rel="noopener noreferrer">
  35. Review in Stripe Dashboard
  36. </a>
  37. </Button>
  38. }
  39. />
  40. )
  41. }
  42. if (status === 'unknown') {
  43. return (
  44. <Admonition
  45. type="default"
  46. title="Stripe payment setup in progress"
  47. description="Your Stripe payment connection is being configured. Check back shortly."
  48. />
  49. )
  50. }
  51. return (
  52. <div className="flex flex-col items-center gap-y-3 py-4 text-center">
  53. <PartnerIcon
  54. organization={{ managed_by: MANAGED_BY.STRIPE_PROJECTS }}
  55. showTooltip={false}
  56. size="large"
  57. />
  58. <div className="space-y-1">
  59. <p className="text-sm text-foreground">Payment managed through Stripe</p>
  60. <p className="text-sm text-foreground-light max-w-sm text-balance">
  61. Billing for this organisation is handled via a connected Stripe payment token.
  62. </p>
  63. {hasTokenSummary && (
  64. <p className="text-xs text-foreground-light">
  65. Token ending in {tokenLast4} expires {tokenExpiry}.
  66. </p>
  67. )}
  68. </div>
  69. <Button asChild type="default" iconRight={<ExternalLink size={14} />}>
  70. <a
  71. href={`${STRIPE_PROJECTS_DOCS_URL}#manage-billing`}
  72. target="_blank"
  73. rel="noopener noreferrer"
  74. >
  75. Manage via Stripe CLI
  76. </a>
  77. </Button>
  78. </div>
  79. )
  80. }