creditCardSetup.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import {useCallback, useEffect, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {t} from 'sentry/locale';
  4. import type {Organization} from 'sentry/types/organization';
  5. import useApi from 'sentry/utils/useApi';
  6. import type {SubmitData} from 'getsentry/components/creditCardForm';
  7. import CreditCardForm from 'getsentry/components/creditCardForm';
  8. import type {PaymentSetupCreateResponse, Subscription} from 'getsentry/types';
  9. type Props = {
  10. onSuccess: (data: Subscription) => void;
  11. organization: Organization;
  12. buttonText?: string;
  13. cancelButtonText?: string;
  14. className?: string;
  15. isModal?: boolean;
  16. onCancel?: () => void;
  17. referrer?: string;
  18. };
  19. /**
  20. * Credit Card form and submit handler for SetupIntent based cards.
  21. */
  22. function CreditCardSetup({
  23. organization,
  24. onSuccess,
  25. onCancel,
  26. isModal,
  27. className,
  28. buttonText = t('Save Changes'),
  29. cancelButtonText = t('Cancel'),
  30. referrer,
  31. }: Props) {
  32. const api = useApi();
  33. const [errorMessage, setErrorMessage] = useState<string | undefined>();
  34. const [intentData, setIntentData] = useState<PaymentSetupCreateResponse | undefined>(
  35. undefined
  36. );
  37. const loadData = useCallback(async () => {
  38. setErrorMessage(undefined);
  39. try {
  40. const payload: PaymentSetupCreateResponse = await api.requestPromise(
  41. `/organizations/${organization.slug}/payments/setup/`,
  42. {method: 'POST'}
  43. );
  44. setIntentData(payload);
  45. } catch (e) {
  46. setErrorMessage(t('Unable to initialize payment setup, please try again later.'));
  47. }
  48. }, [api, organization]);
  49. useEffect(() => {
  50. loadData();
  51. }, [loadData]);
  52. async function handleSubmit({
  53. stripe,
  54. cardElement,
  55. onComplete,
  56. validationErrors,
  57. }: SubmitData) {
  58. if (validationErrors.length) {
  59. onComplete();
  60. setErrorMessage(validationErrors[0]);
  61. return;
  62. }
  63. if (!intentData) {
  64. onComplete();
  65. setErrorMessage(
  66. t('Cannot complete your payment setup at this time, please try again later.')
  67. );
  68. return;
  69. }
  70. let setupResult: stripe.SetupIntentResponse;
  71. try {
  72. setupResult = await stripe.confirmCardSetup(intentData.clientSecret, {
  73. payment_method: {
  74. card: cardElement,
  75. },
  76. });
  77. } catch (error) {
  78. Sentry.withScope(scope => {
  79. scope.setLevel('warning' as any);
  80. scope.setExtra('error', error);
  81. Sentry.captureException(new Error('Could not complete setup intent.'));
  82. });
  83. onComplete();
  84. setErrorMessage(
  85. t('Could not complete your payment setup, please try again later.')
  86. );
  87. return;
  88. }
  89. if (setupResult.error) {
  90. onComplete();
  91. setErrorMessage(setupResult.error.message);
  92. return;
  93. }
  94. if (!setupResult.setupIntent) {
  95. onComplete();
  96. setErrorMessage(
  97. t('Could not complete your payment setup, please try again later.')
  98. );
  99. return;
  100. }
  101. try {
  102. const subscription: Subscription = await api.requestPromise(
  103. `/customers/${organization.slug}/`,
  104. {
  105. method: 'PUT',
  106. data: {
  107. paymentMethod: setupResult.setupIntent.payment_method,
  108. },
  109. }
  110. );
  111. onComplete();
  112. onSuccess(subscription);
  113. } catch (error) {
  114. Sentry.withScope(scope => {
  115. scope.setLevel('warning' as any);
  116. scope.setExtra('error', error);
  117. Sentry.captureException(
  118. new Error('Could not update subscription with payment method.')
  119. );
  120. });
  121. onComplete();
  122. setErrorMessage(
  123. t('Could not complete your payment setup, please try again later.')
  124. );
  125. return;
  126. }
  127. }
  128. return (
  129. <CreditCardForm
  130. className={className}
  131. buttonText={buttonText}
  132. cancelButtonText={cancelButtonText}
  133. error={errorMessage}
  134. footerClassName={isModal ? 'modal-footer' : 'form-actions'}
  135. onCancel={onCancel}
  136. onSubmit={handleSubmit}
  137. referrer={referrer}
  138. />
  139. );
  140. }
  141. export default CreditCardSetup;