paymentHistory.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {Fragment, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import {LinkButton} from 'sentry/components/button';
  4. import {DateTime} from 'sentry/components/dateTime';
  5. import Link from 'sentry/components/links/link';
  6. import LoadingError from 'sentry/components/loadingError';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import Pagination from 'sentry/components/pagination';
  9. import {PanelTable} from 'sentry/components/panels/panelTable';
  10. import {IconDownload} from 'sentry/icons';
  11. import {t, tct} from 'sentry/locale';
  12. import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
  13. import type {Organization} from 'sentry/types/organization';
  14. import {useApiQuery} from 'sentry/utils/queryClient';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import withOrganization from 'sentry/utils/withOrganization';
  17. import withSubscription from 'getsentry/components/withSubscription';
  18. import type {InvoiceBase, Subscription} from 'getsentry/types';
  19. import {InvoiceStatus} from 'getsentry/types';
  20. import formatCurrency from 'getsentry/utils/formatCurrency';
  21. import ContactBillingMembers from 'getsentry/views/contactBillingMembers';
  22. import SubscriptionHeader from './subscriptionHeader';
  23. import {trackSubscriptionView} from './utils';
  24. type Props = {
  25. organization: Organization;
  26. subscription: Subscription;
  27. } & RouteComponentProps<unknown, unknown>;
  28. /**
  29. * Invoice/Payment list view.
  30. */
  31. function PaymentHistory({organization, subscription}: Props) {
  32. const location = useLocation();
  33. useEffect(() => {
  34. trackSubscriptionView(organization, subscription, 'receipts');
  35. }, [organization, subscription]);
  36. const {
  37. data: payments,
  38. isPending,
  39. isError,
  40. getResponseHeader,
  41. } = useApiQuery<InvoiceBase[]>(
  42. [
  43. `/customers/${organization.slug}/invoices/`,
  44. {
  45. query: {cursor: location.query.cursor},
  46. },
  47. ],
  48. {
  49. staleTime: 0,
  50. }
  51. );
  52. const paymentsPageLinks = getResponseHeader?.('Link');
  53. if (isPending) {
  54. return (
  55. <Fragment>
  56. <SubscriptionHeader subscription={subscription} organization={organization} />
  57. <LoadingIndicator />
  58. </Fragment>
  59. );
  60. }
  61. if (isError) {
  62. return <LoadingError />;
  63. }
  64. const hasBillingPerms = organization.access?.includes('org:billing');
  65. if (!hasBillingPerms) {
  66. return <ContactBillingMembers />;
  67. }
  68. return (
  69. <Fragment>
  70. <SubscriptionHeader organization={organization} subscription={subscription} />
  71. <div className="ref-payment-list" data-test-id="payment-list">
  72. <PanelTable
  73. headers={[
  74. t('Date'),
  75. <RightAlign key="amount">{t('Amount')}</RightAlign>,
  76. <RightAlign key="status">{t('Status')}</RightAlign>,
  77. <CenterAlign key="status">{t('Receipt')}</CenterAlign>,
  78. ]}
  79. >
  80. {payments.map((payment, i) => {
  81. const url = `/settings/${organization.slug}/billing/receipts/${payment.id}/`;
  82. return (
  83. <Fragment key={i}>
  84. <Column>
  85. <Link to={url}>
  86. <DateTime date={payment.dateCreated} dateOnly year />
  87. </Link>
  88. </Column>
  89. <RightAlign>
  90. <div>{formatCurrency(payment.amountBilled ?? 0)}</div>
  91. {!!payment.amountRefunded && (
  92. <small>
  93. {tct('[amount] refunded', {
  94. amount: formatCurrency(payment.amountRefunded),
  95. })}
  96. </small>
  97. )}
  98. </RightAlign>
  99. <Status>
  100. {payment.isPaid
  101. ? InvoiceStatus.PAID
  102. : payment.isClosed
  103. ? InvoiceStatus.CLOSED
  104. : InvoiceStatus.AWAITING_PAYMENT}
  105. </Status>
  106. <CenterAlign>
  107. <div>
  108. <LinkButton
  109. size="sm"
  110. icon={<IconDownload size="sm" />}
  111. href={payment.receipt.url}
  112. aria-label={t('Download')}
  113. />
  114. </div>
  115. </CenterAlign>
  116. </Fragment>
  117. );
  118. })}
  119. </PanelTable>
  120. {paymentsPageLinks && <Pagination pageLinks={paymentsPageLinks} />}
  121. </div>
  122. </Fragment>
  123. );
  124. }
  125. const Column = styled('div')`
  126. display: grid;
  127. align-items: center;
  128. `;
  129. const RightAlign = styled(Column)`
  130. text-align: right;
  131. `;
  132. const CenterAlign = styled(Column)`
  133. text-align: center;
  134. `;
  135. const Status = styled(RightAlign)`
  136. text-transform: capitalize;
  137. `;
  138. export default withOrganization(withSubscription(PaymentHistory));