@internetarchive/donation-form 1.0.3-alpha-webdev7960.1 → 1.0.3-webdev-8114.0

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 (211) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +115 -115
  3. package/dist/demo/braintree-endpoint-manager.js.map +1 -1
  4. package/dist/demo/demo-analytics-handler.js.map +1 -1
  5. package/dist/demo/submit-form-with.js.map +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/braintree-manager/braintree-interfaces.js.map +1 -1
  8. package/dist/src/braintree-manager/braintree-manager.js.map +1 -1
  9. package/dist/src/braintree-manager/payment-clients.js.map +1 -1
  10. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay-interface.js.map +1 -1
  11. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-delegate.js.map +1 -1
  12. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-interface.js.map +1 -1
  13. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource.js.map +1 -1
  14. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-manager.js.map +1 -1
  15. package/dist/src/braintree-manager/payment-providers/apple-pay/apple-pay.js.map +1 -1
  16. package/dist/src/braintree-manager/payment-providers/credit-card/credit-card-interface.js.map +1 -1
  17. package/dist/src/braintree-manager/payment-providers/credit-card/credit-card.js.map +1 -1
  18. package/dist/src/braintree-manager/payment-providers/credit-card/hosted-field-configuration.js.map +1 -1
  19. package/dist/src/braintree-manager/payment-providers/credit-card/hosted-field-container.js.map +1 -1
  20. package/dist/src/braintree-manager/payment-providers/google-pay-interface.js.map +1 -1
  21. package/dist/src/braintree-manager/payment-providers/google-pay.js.map +1 -1
  22. package/dist/src/braintree-manager/payment-providers/paypal/paypal-button-datasource.js.map +1 -1
  23. package/dist/src/braintree-manager/payment-providers/paypal/paypal-interface.js.map +1 -1
  24. package/dist/src/braintree-manager/payment-providers/paypal/paypal.js.map +1 -1
  25. package/dist/src/braintree-manager/payment-providers/venmo-interface.js.map +1 -1
  26. package/dist/src/braintree-manager/payment-providers/venmo.js.map +1 -1
  27. package/dist/src/braintree-manager/payment-providers-interface.js.map +1 -1
  28. package/dist/src/braintree-manager/payment-providers.js.map +1 -1
  29. package/dist/src/donation-form-controller.js +123 -123
  30. package/dist/src/donation-form-controller.js.map +1 -1
  31. package/dist/src/donation-form-error.js.map +1 -1
  32. package/dist/src/donation-form.js +107 -107
  33. package/dist/src/donation-form.js.map +1 -1
  34. package/dist/src/form-elements/badged-input.js +47 -47
  35. package/dist/src/form-elements/badged-input.js.map +1 -1
  36. package/dist/src/form-elements/contact-form/autocomplete-field-options.js.map +1 -1
  37. package/dist/src/form-elements/contact-form/contact-form.d.ts +1 -1
  38. package/dist/src/form-elements/contact-form/contact-form.js +174 -179
  39. package/dist/src/form-elements/contact-form/contact-form.js.map +1 -1
  40. package/dist/src/form-elements/contact-form/countries.js.map +1 -1
  41. package/dist/src/form-elements/header/donation-form-header.js +14 -14
  42. package/dist/src/form-elements/header/donation-form-header.js.map +1 -1
  43. package/dist/src/form-elements/header/donation-summary.js +15 -15
  44. package/dist/src/form-elements/header/donation-summary.js.map +1 -1
  45. package/dist/src/form-elements/payment-selector.js +164 -164
  46. package/dist/src/form-elements/payment-selector.js.map +1 -1
  47. package/dist/src/form-elements/total-amount.js +16 -16
  48. package/dist/src/form-elements/total-amount.js.map +1 -1
  49. package/dist/src/modals/confirm-donation-modal-content.js +51 -51
  50. package/dist/src/modals/confirm-donation-modal-content.js.map +1 -1
  51. package/dist/src/modals/error-modal-content.js +22 -22
  52. package/dist/src/modals/error-modal-content.js.map +1 -1
  53. package/dist/src/modals/upsell-modal-content.js +182 -182
  54. package/dist/src/modals/upsell-modal-content.js.map +1 -1
  55. package/dist/src/payment-flow-handlers/donation-flow-modal-manager.js +20 -20
  56. package/dist/src/payment-flow-handlers/donation-flow-modal-manager.js.map +1 -1
  57. package/dist/src/payment-flow-handlers/handlers/applepay-flow-handler.js.map +1 -1
  58. package/dist/src/payment-flow-handlers/handlers/creditcard-flow-handler.js.map +1 -1
  59. package/dist/src/payment-flow-handlers/handlers/googlepay-flow-handler.js.map +1 -1
  60. package/dist/src/payment-flow-handlers/handlers/paypal-flow-handler.js.map +1 -1
  61. package/dist/src/payment-flow-handlers/handlers/venmo-flow-handler.js.map +1 -1
  62. package/dist/src/payment-flow-handlers/handlers/venmo-restoration-state-handler.js.map +1 -1
  63. package/dist/src/payment-flow-handlers/payment-flow-handlers.js.map +1 -1
  64. package/dist/src/recaptcha-manager/recaptcha-manager.js.map +1 -1
  65. package/dist/src/util/promisedSleep.js.map +1 -1
  66. package/dist/test/helpers/fillInContactForm.js.map +1 -1
  67. package/dist/test/mocks/flow-handlers/individual-handlers/mock-applepay-flow-handler.js.map +1 -1
  68. package/dist/test/mocks/flow-handlers/individual-handlers/mock-creditcard-flow-handler.js.map +1 -1
  69. package/dist/test/mocks/flow-handlers/individual-handlers/mock-googlepay-flow-handler.js.map +1 -1
  70. package/dist/test/mocks/flow-handlers/individual-handlers/mock-paypal-flow-handler.js.map +1 -1
  71. package/dist/test/mocks/flow-handlers/individual-handlers/mock-venmo-flow-handler.js.map +1 -1
  72. package/dist/test/mocks/flow-handlers/mock-payment-flow-handlers.js.map +1 -1
  73. package/dist/test/mocks/mock-braintree-manager.js.map +1 -1
  74. package/dist/test/mocks/mock-donation-info.js.map +1 -1
  75. package/dist/test/mocks/mock-endpoint-manager.js.map +1 -1
  76. package/dist/test/mocks/mock-hosted-fields-config.js.map +1 -1
  77. package/dist/test/mocks/mock-hosted-fields-container.js.map +1 -1
  78. package/dist/test/mocks/mock-lazy-loader.js.map +1 -1
  79. package/dist/test/mocks/mock-modal-manager.js.map +1 -1
  80. package/dist/test/mocks/mock-payment-clients.js.map +1 -1
  81. package/dist/test/mocks/mock-paypal-button-renderer.js.map +1 -1
  82. package/dist/test/mocks/mock-recaptcha-manager.js.map +1 -1
  83. package/dist/test/mocks/models/mock-billing-info.js.map +1 -1
  84. package/dist/test/mocks/models/mock-custom-fields.js.map +1 -1
  85. package/dist/test/mocks/models/mock-customer-info.js.map +1 -1
  86. package/dist/test/mocks/models/mock-donation-request.js.map +1 -1
  87. package/dist/test/mocks/models/mock-success-response.js.map +1 -1
  88. package/dist/test/mocks/payment-clients/mock-applepay-client.js.map +1 -1
  89. package/dist/test/mocks/payment-clients/mock-applepay-payment.js.map +1 -1
  90. package/dist/test/mocks/payment-clients/mock-applepay-paymentauthorizedevent.js.map +1 -1
  91. package/dist/test/mocks/payment-clients/mock-applepay-session.js.map +1 -1
  92. package/dist/test/mocks/payment-clients/mock-applepay-sessionmanager.js.map +1 -1
  93. package/dist/test/mocks/payment-clients/mock-applepay-validatemerchantevent.js.map +1 -1
  94. package/dist/test/mocks/payment-clients/mock-braintree-client.js.map +1 -1
  95. package/dist/test/mocks/payment-clients/mock-data-collector.js.map +1 -1
  96. package/dist/test/mocks/payment-clients/mock-googlepay-client.js.map +1 -1
  97. package/dist/test/mocks/payment-clients/mock-googlepay-library.js.map +1 -1
  98. package/dist/test/mocks/payment-clients/mock-grecaptcha.js.map +1 -1
  99. package/dist/test/mocks/payment-clients/mock-hostedfields-client.js.map +1 -1
  100. package/dist/test/mocks/payment-clients/mock-hostedfieldstateobject-generator.js.map +1 -1
  101. package/dist/test/mocks/payment-clients/mock-hostedfieldtokenizepayload.js.map +1 -1
  102. package/dist/test/mocks/payment-clients/mock-paypal-client.js.map +1 -1
  103. package/dist/test/mocks/payment-clients/mock-paypal-library.js.map +1 -1
  104. package/dist/test/mocks/payment-clients/mock-venmo-client.js.map +1 -1
  105. package/dist/test/mocks/payment-providers/individual-providers/mock-applepay-datasource-delegate.js.map +1 -1
  106. package/dist/test/mocks/payment-providers/individual-providers/mock-applepay-handler.js.map +1 -1
  107. package/dist/test/mocks/payment-providers/individual-providers/mock-creditcard-handler.js.map +1 -1
  108. package/dist/test/mocks/payment-providers/individual-providers/mock-googlepay-handler.js.map +1 -1
  109. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-button-datasource-delegate.js.map +1 -1
  110. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-button-datasource.js.map +1 -1
  111. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-handler.js.map +1 -1
  112. package/dist/test/mocks/payment-providers/individual-providers/mock-venmo-handler.js.map +1 -1
  113. package/dist/test/mocks/payment-providers/mock-payment-providers.js.map +1 -1
  114. package/dist/test/tests/braintree-manager.test.js.map +1 -1
  115. package/dist/test/tests/donation-form-controller.test.js +39 -39
  116. package/dist/test/tests/donation-form-controller.test.js.map +1 -1
  117. package/dist/test/tests/donation-form.test.js +4 -4
  118. package/dist/test/tests/donation-form.test.js.map +1 -1
  119. package/dist/test/tests/flow-handlers/donation-flow-modal-manager.test.js +14 -14
  120. package/dist/test/tests/flow-handlers/donation-flow-modal-manager.test.js.map +1 -1
  121. package/dist/test/tests/form-elements/contact-form.test.d.ts +1 -0
  122. package/dist/test/tests/form-elements/contact-form.test.js +132 -0
  123. package/dist/test/tests/form-elements/contact-form.test.js.map +1 -0
  124. package/dist/test/tests/form-elements/donation-summary.test.js.map +1 -1
  125. package/dist/test/tests/form-elements/payment-selector.test.js.map +1 -1
  126. package/dist/test/tests/modals/error-modal-content.test.js +2 -2
  127. package/dist/test/tests/modals/error-modal-content.test.js.map +1 -1
  128. package/dist/test/tests/modals/upsell-modal-content.test.js +31 -31
  129. package/dist/test/tests/modals/upsell-modal-content.test.js.map +1 -1
  130. package/dist/test/tests/models/donation-payment-info.test.js.map +1 -1
  131. package/dist/test/tests/payment-clients.test.js.map +1 -1
  132. package/dist/test/tests/payment-providers/applepay-sessiondatasource.test.js.map +1 -1
  133. package/dist/test/tests/payment-providers/applepay-sessionmanager.test.js.map +1 -1
  134. package/dist/test/tests/payment-providers/applepay.test.js.map +1 -1
  135. package/dist/test/tests/payment-providers/creditcard.test.js.map +1 -1
  136. package/dist/test/tests/payment-providers/googlepay.test.js.map +1 -1
  137. package/dist/test/tests/payment-providers/payment-providers.test.js.map +1 -1
  138. package/dist/test/tests/payment-providers/paypal-button-datasource.test.js.map +1 -1
  139. package/dist/test/tests/payment-providers/paypal.test.js.map +1 -1
  140. package/dist/test/tests/payment-providers/venmo.test.js.map +1 -1
  141. package/dist/test/tests/recaptcha-manager.test.js.map +1 -1
  142. package/package.json +107 -107
  143. package/src/@types/analytics-handler/index.d.ts +8 -8
  144. package/src/@types/braintree-web/LICENSE +21 -21
  145. package/src/@types/braintree-web/index.d.ts +93 -93
  146. package/src/@types/braintree-web/modules/american-express.d.ts +50 -50
  147. package/src/@types/braintree-web/modules/apple-pay.d.ts +213 -213
  148. package/src/@types/braintree-web/modules/client.d.ts +103 -103
  149. package/src/@types/braintree-web/modules/core.d.ts +34 -34
  150. package/src/@types/braintree-web/modules/data-collector.d.ts +13 -13
  151. package/src/@types/braintree-web/modules/google-payment.d.ts +269 -269
  152. package/src/@types/braintree-web/modules/hosted-fields.d.ts +366 -366
  153. package/src/@types/braintree-web/modules/paypal-checkout.d.ts +262 -262
  154. package/src/@types/braintree-web/modules/paypal.d.ts +177 -177
  155. package/src/@types/braintree-web/modules/three-d-secure.d.ts +141 -141
  156. package/src/@types/braintree-web/modules/unionpay.d.ts +224 -224
  157. package/src/@types/braintree-web/modules/us-bank-account.d.ts +81 -81
  158. package/src/@types/braintree-web/modules/venmo.d.ts +110 -110
  159. package/src/@types/braintree-web/package.json +64 -64
  160. package/src/@types/paypal-checkout-components/LICENSE +21 -21
  161. package/src/@types/paypal-checkout-components/index.d.ts +67 -67
  162. package/src/@types/paypal-checkout-components/modules/button.d.ts +50 -50
  163. package/src/@types/paypal-checkout-components/modules/callback-data.d.ts +244 -244
  164. package/src/@types/paypal-checkout-components/modules/configuration.d.ts +114 -114
  165. package/src/@types/paypal-checkout-components/package.json +58 -58
  166. package/src/braintree-manager/braintree-interfaces.ts +172 -172
  167. package/src/braintree-manager/braintree-manager.ts +281 -281
  168. package/src/braintree-manager/payment-clients.ts +146 -146
  169. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-interface.ts +13 -13
  170. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-delegate.ts +8 -8
  171. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-interface.ts +10 -10
  172. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource.ts +119 -119
  173. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-manager.ts +21 -21
  174. package/src/braintree-manager/payment-providers/apple-pay/apple-pay.ts +97 -97
  175. package/src/braintree-manager/payment-providers/credit-card/credit-card-interface.ts +21 -21
  176. package/src/braintree-manager/payment-providers/credit-card/credit-card.ts +130 -130
  177. package/src/braintree-manager/payment-providers/credit-card/hosted-field-configuration.ts +19 -19
  178. package/src/braintree-manager/payment-providers/credit-card/hosted-field-container.ts +85 -85
  179. package/src/braintree-manager/payment-providers/google-pay-interface.ts +8 -8
  180. package/src/braintree-manager/payment-providers/google-pay.ts +59 -59
  181. package/src/braintree-manager/payment-providers/paypal/paypal-button-datasource.ts +218 -218
  182. package/src/braintree-manager/payment-providers/paypal/paypal-interface.ts +13 -13
  183. package/src/braintree-manager/payment-providers/paypal/paypal.ts +78 -78
  184. package/src/braintree-manager/payment-providers/venmo-interface.ts +8 -8
  185. package/src/braintree-manager/payment-providers/venmo.ts +67 -67
  186. package/src/braintree-manager/payment-providers-interface.ts +25 -25
  187. package/src/braintree-manager/payment-providers.ts +147 -147
  188. package/src/donation-form-controller.ts +623 -623
  189. package/src/donation-form-error.ts +6 -6
  190. package/src/donation-form.ts +576 -576
  191. package/src/form-elements/badged-input.ts +109 -109
  192. package/src/form-elements/contact-form/autocomplete-field-options.ts +63 -63
  193. package/src/form-elements/contact-form/contact-form.ts +432 -434
  194. package/src/form-elements/contact-form/countries.ts +252 -252
  195. package/src/form-elements/header/donation-form-header.ts +98 -98
  196. package/src/form-elements/header/donation-summary.ts +61 -61
  197. package/src/form-elements/payment-selector.ts +365 -365
  198. package/src/form-elements/total-amount.ts +46 -46
  199. package/src/modals/confirm-donation-modal-content.ts +168 -168
  200. package/src/modals/error-modal-content.ts +48 -48
  201. package/src/modals/upsell-modal-content.ts +284 -284
  202. package/src/payment-flow-handlers/donation-flow-modal-manager.ts +439 -439
  203. package/src/payment-flow-handlers/handlers/applepay-flow-handler.ts +109 -109
  204. package/src/payment-flow-handlers/handlers/creditcard-flow-handler.ts +232 -232
  205. package/src/payment-flow-handlers/handlers/googlepay-flow-handler.ts +111 -111
  206. package/src/payment-flow-handlers/handlers/paypal-flow-handler.ts +331 -331
  207. package/src/payment-flow-handlers/handlers/venmo-flow-handler.ts +119 -119
  208. package/src/payment-flow-handlers/handlers/venmo-restoration-state-handler.ts +127 -127
  209. package/src/payment-flow-handlers/payment-flow-handlers.ts +218 -218
  210. package/src/recaptcha-manager/recaptcha-manager.ts +123 -123
  211. package/src/util/promisedSleep.ts +3 -3
@@ -1,576 +1,576 @@
1
- import { LitElement, html, css, CSSResult, TemplateResult, PropertyValues } from 'lit';
2
- import { customElement, property, query } from 'lit/decorators.js';
3
-
4
- import lockImg from '@internetarchive/icon-lock/index.js';
5
-
6
- // we have to import the registered component independently from the definition below
7
- // because inside each of these files, we're registering the custom element inside
8
- // these files and by simply importing the class name, you lose that behavior
9
- // See https://github.com/microsoft/TypeScript/issues/9191 for more discussion
10
- import './form-elements/payment-selector';
11
- import './form-elements/header/donation-form-header';
12
-
13
- import { DonationFormHeader } from './form-elements/header/donation-form-header';
14
- import { PaymentSelector } from './form-elements/payment-selector';
15
-
16
- import { BraintreeManagerInterface } from './braintree-manager/braintree-interfaces';
17
-
18
- import {
19
- DonationRequest,
20
- DonationPaymentInfo,
21
- PaymentProvider,
22
- DonorContactInfo,
23
- DonationType,
24
- defaultDonationAmounts,
25
- } from '@internetarchive/donation-form-data-models';
26
-
27
- import { PaymentFlowHandlersInterface } from './payment-flow-handlers/payment-flow-handlers';
28
-
29
- import {
30
- EditDonationAmountSelectionLayout,
31
- EditDonationFrequencySelectionMode,
32
- } from '@internetarchive/donation-form-edit-donation';
33
-
34
- import '@internetarchive/donation-form-section';
35
- import {
36
- DonationFormSection,
37
- DonationFormSectionBadgeMode,
38
- } from '@internetarchive/donation-form-section';
39
- import { UpsellModalCTAMode } from './modals/upsell-modal-content';
40
- import { ContactForm } from './form-elements/contact-form/contact-form';
41
- import './form-elements/total-amount';
42
-
43
- @customElement('donation-form')
44
- export class DonationForm extends LitElement {
45
- @property({ type: Object }) braintreeManager: BraintreeManagerInterface | undefined;
46
-
47
- @property({ type: Object }) paymentFlowHandlers: PaymentFlowHandlersInterface | undefined;
48
-
49
- @property({ type: Object }) donationRequest: DonationRequest | undefined;
50
-
51
- @property({ type: Object }) donationInfo?: DonationPaymentInfo;
52
-
53
- @property({ type: Object }) contactForm?: ContactForm;
54
-
55
- @property({ type: Array }) amountOptions: number[] = defaultDonationAmounts;
56
-
57
- @property({ type: String }) amountSelectionLayout: EditDonationAmountSelectionLayout =
58
- EditDonationAmountSelectionLayout.MultiLine;
59
-
60
- @property({ type: String }) frequencySelectionMode: EditDonationFrequencySelectionMode =
61
- EditDonationFrequencySelectionMode.Button;
62
-
63
- @property({ type: Boolean }) private creditCardVisible = false;
64
-
65
- @property({ type: Boolean }) private contactFormVisible = false;
66
-
67
- @property({ type: Boolean }) private donationInfoValid = true;
68
-
69
- @property({ type: String }) private selectedPaymentProvider?: PaymentProvider;
70
-
71
- @query('#contactFormSection') contactFormSection?: DonationFormSection;
72
-
73
- @query('donation-form-header') donationFormHeader!: DonationFormHeader;
74
-
75
- @query('payment-selector') paymentSelector!: PaymentSelector;
76
-
77
- private paypalButtonNeedsRender = true;
78
-
79
- /** @inheritdoc */
80
- render(): TemplateResult {
81
- return html`
82
- <donation-form-header
83
- .amountOptions=${this.amountOptions}
84
- .amountSelectionLayout=${this.amountSelectionLayout}
85
- .frequencySelectionMode=${this.frequencySelectionMode}
86
- @donationInfoChanged=${this.donationInfoChanged}
87
- @editDonationError=${this.editDonationError}
88
- >
89
- </donation-form-header>
90
-
91
- <donation-form-section
92
- .badgeMode=${DonationFormSectionBadgeMode.HideBadgeLeaveSpacing}
93
- id="total-amount-section"
94
- >
95
- <donation-form-total-amount .donationInfo=${this.donationInfo}>
96
- </donation-form-total-amount>
97
- </donation-form-section>
98
-
99
- <donation-form-section
100
- .sectionBadge=${this.paymentSelectorNumberingStart}
101
- headline="Choose a payment method"
102
- >
103
- <payment-selector
104
- .paymentProviders=${this.braintreeManager?.paymentProviders}
105
- @firstUpdated=${this.paymentSelectorFirstUpdated}
106
- @creditCardSelected=${this.creditCardSelected}
107
- @venmoSelected=${this.venmoSelected}
108
- @applePaySelected=${this.applePaySelected}
109
- @googlePaySelected=${this.googlePaySelected}
110
- @paypalBlockerSelected=${this.paypalBlockerSelected}
111
- @resetPaymentMethod=${async () => {
112
- this.selectedPaymentProvider = undefined;
113
- this.contactFormVisible = false;
114
- this.requestUpdate();
115
- }}
116
- tabindex="0"
117
- >
118
- <slot name="paypal-button" slot="paypal-button"></slot>
119
- </payment-selector>
120
- </donation-form-section>
121
-
122
- <div class="contact-form-section ${this.contactFormVisible ? '' : 'hidden'}">
123
- ${this.contactFormSectionTemplate}
124
- </div>
125
- <slot name="recaptcha"></slot>
126
- `;
127
- }
128
-
129
- async showConfirmationModalDev(options: {
130
- donationType: DonationType;
131
- amount: number;
132
- currencyType: string;
133
- cancelDonationCB: Function;
134
- confirmDonationCB: Function;
135
- }): Promise<void> {
136
- this.paymentFlowHandlers?.showConfirmationStepModal(options);
137
- }
138
-
139
- /**
140
- * This is a developer convenience method that allows us to show the upsell modal without going
141
- * through the purchasing flow. If it's showing the PayPal button, it will trigger
142
- * the PayPal button render
143
- *
144
- * @param {{
145
- * oneTimeAmount: number;
146
- * ctaMode?: UpsellModalCTAMode;
147
- * yesSelected?: (amount: number) => void;
148
- * noSelected?: () => void;
149
- * amountChanged?: (amount: number) => void;
150
- * userClosedModalCallback?: () => void;
151
- * }} options
152
- * @returns {Promise<void>}
153
- * @memberof DonationForm
154
- */
155
- async showUpsellModalDev(options: {
156
- oneTimeAmount: number;
157
- ctaMode?: UpsellModalCTAMode;
158
- yesSelected?: (amount: number) => void;
159
- noSelected?: () => void;
160
- amountChanged?: (amount: number) => void;
161
- userClosedModalCallback?: () => void;
162
- }): Promise<void> {
163
- this.paymentFlowHandlers?.showUpsellModal(options);
164
-
165
- if (options.ctaMode === UpsellModalCTAMode.PayPalUpsellSlot) {
166
- const handler = await this.braintreeManager?.paymentProviders.paypalHandler.get();
167
- const donationInfo = new DonationPaymentInfo({
168
- amount: options.oneTimeAmount,
169
- donationType: DonationType.OneTime,
170
- coverFees: false,
171
- });
172
- handler?.renderPayPalButton({
173
- selector: '#paypal-upsell-button',
174
- style: {
175
- color: 'blue' as paypal.ButtonColorOption,
176
- label: 'paypal' as paypal.ButtonLabelOption,
177
- shape: 'rect' as paypal.ButtonShapeOption,
178
- size: 'responsive' as paypal.ButtonSizeOption,
179
- tagline: false,
180
- },
181
- donationInfo: donationInfo,
182
- });
183
- }
184
- }
185
-
186
- get contactFormSectionTemplate(): TemplateResult {
187
- const headline =
188
- this.selectedPaymentProvider === PaymentProvider.Venmo
189
- ? 'Help us stay in touch'
190
- : 'Enter payment information';
191
-
192
- return html`
193
- <donation-form-section
194
- .sectionBadge=${this.paymentSelectorNumberingStart + 1}
195
- headline=${headline}
196
- id="contactFormSection"
197
- >
198
- <slot name="contact-form"></slot>
199
- <div class="credit-card-fields" class="${this.creditCardVisible ? '' : 'hidden'}">
200
- <slot name="braintree-hosted-fields"></slot>
201
- </div>
202
- </donation-form-section>
203
-
204
- <donation-form-section .sectionBadge=${this.paymentSelectorNumberingStart + 2}>
205
- <slot name="recaptcha"></slot>
206
- <button id="donate-button" @click=${this.donateClicked}>Donate</button>
207
-
208
- <div class="secure-process-note">${lockImg} Your payment will be securely processed</div>
209
- </donation-form-section>
210
- `;
211
- }
212
-
213
- /**
214
- * Where to start the numbering of the payment selector
215
- *
216
- * - If we show the frequency selector in button mode, it becomes section 1, which makes
217
- * the amount selection section 2, and the payment selector section 3.
218
- * - If we show the frequency selector in checkbox mode, it is no longer section 1. The amount
219
- * selector becomes section 1 and the payment selector becomes section 2.
220
- *
221
- * Visually:
222
- *
223
- * Button Mode:
224
- * 1. Frequency selector
225
- * 2. Amount selector
226
- * 3. Payment selector
227
- * 4. Contact info
228
- * 5. Donate button
229
- *
230
- * Checkbox Mode:
231
- * 1. Amount selector (including the monthly checkbox)
232
- * 2. Payment selector <-- changes from 3 to 2
233
- * 3. Contact info <-- changes from 4 to 3
234
- * 4. Donate button <-- changes from 5 to 4
235
- *
236
- * @readonly
237
- * @private
238
- * @type {number}
239
- * @memberof DonationForm
240
- */
241
- private get paymentSelectorNumberingStart(): number {
242
- return this.frequencySelectionMode === EditDonationFrequencySelectionMode.Button ? 3 : 2;
243
- }
244
-
245
- private editDonationError(): void {
246
- this.donationInfoValid = false;
247
- }
248
-
249
- private paymentSelectorFirstUpdated(): void {
250
- if (this.paymentFlowHandlers?.paypalHandler) {
251
- this.renderPayPalButtonIfNeeded();
252
- }
253
- }
254
-
255
- private applePaySelected(e: CustomEvent): void {
256
- this.selectedPaymentProvider = PaymentProvider.ApplePay;
257
- this.contactFormVisible = false;
258
- this.creditCardVisible = false;
259
-
260
- if (!this.donationInfoValid) {
261
- this.showInvalidDonationInfoAlert();
262
- return;
263
- }
264
-
265
- const originalEvent = e.detail.originalEvent;
266
- if (this.donationInfo) {
267
- this.paymentFlowHandlers?.applePayHandler?.paymentInitiated(this.donationInfo, originalEvent);
268
- }
269
- this.emitPaymentFlowStartedEvent();
270
- }
271
-
272
- private googlePaySelected(): void {
273
- this.selectedPaymentProvider = PaymentProvider.GooglePay;
274
- this.contactFormVisible = false;
275
- this.creditCardVisible = false;
276
-
277
- if (!this.donationInfoValid) {
278
- this.showInvalidDonationInfoAlert();
279
- } else {
280
- if (this.donationInfo) {
281
- this.paymentFlowHandlers?.googlePayHandler?.paymentInitiated(this.donationInfo);
282
- }
283
- this.emitPaymentFlowStartedEvent();
284
- }
285
- }
286
-
287
- private async creditCardSelected(): Promise<void> {
288
- if (!this.donationInfoValid) {
289
- this.showInvalidDonationInfoAlert();
290
- return;
291
- }
292
- this.selectedPaymentProvider = PaymentProvider.CreditCard;
293
- this.contactFormVisible = true;
294
- this.creditCardVisible = true;
295
- this.focusContactForm();
296
- }
297
-
298
- private async venmoSelected(): Promise<void> {
299
- if (!this.donationInfoValid) {
300
- this.showInvalidDonationInfoAlert();
301
- return;
302
- }
303
- this.selectedPaymentProvider = PaymentProvider.Venmo;
304
- this.contactFormVisible = true;
305
- this.creditCardVisible = false;
306
- this.focusContactForm();
307
- }
308
-
309
- private paypalBlockerSelected(): void {
310
- this.contactFormVisible = false;
311
- this.creditCardVisible = false;
312
- this.showInvalidDonationInfoAlert();
313
- }
314
-
315
- private async focusContactForm(): Promise<void> {
316
- await this.updateComplete;
317
- if (this.contactFormSection) {
318
- this.contactForm?.focus();
319
- }
320
- }
321
-
322
- private async donateClicked(): Promise<void> {
323
- if (!this.contactForm) {
324
- alert('Please enter contact info.');
325
- return;
326
- }
327
- if (!this.donationInfoValid || !this.donationInfo) {
328
- this.showInvalidDonationInfoAlert();
329
- return;
330
- }
331
-
332
- const contactInfo = this.contactForm.donorContactInfo;
333
-
334
- switch (this.selectedPaymentProvider) {
335
- case PaymentProvider.CreditCard:
336
- this.handleCreditCardDonationFlow(contactInfo, this.donationInfo);
337
- break;
338
- case PaymentProvider.Venmo:
339
- this.handleVenmoDonationFlow(contactInfo, this.donationInfo);
340
- break;
341
- }
342
- }
343
-
344
- private async handleCreditCardDonationFlow(
345
- contactInfo: DonorContactInfo,
346
- donationInfo: DonationPaymentInfo,
347
- ): Promise<void> {
348
- const creditCardFlowHandler = this.paymentFlowHandlers?.creditCardHandler;
349
- const creditCardHandler = await this.braintreeManager?.paymentProviders.creditCardHandler.get();
350
- creditCardHandler?.hideErrorMessage();
351
- const valid = this.contactForm?.reportValidity();
352
- const hostedFieldsResponse = await creditCardFlowHandler?.tokenizeFields();
353
-
354
- if (!valid || hostedFieldsResponse === undefined) {
355
- return;
356
- }
357
-
358
- this.emitPaymentFlowStartedEvent();
359
- creditCardFlowHandler?.paymentInitiated(hostedFieldsResponse, donationInfo, contactInfo);
360
- }
361
-
362
- private async handleVenmoDonationFlow(
363
- contactInfo: DonorContactInfo,
364
- donationInfo: DonationPaymentInfo,
365
- ): Promise<void> {
366
- const valid = this.contactForm?.reportValidity();
367
- if (!valid) {
368
- return;
369
- }
370
- this.paymentFlowHandlers?.venmoHandler?.paymentInitiated(contactInfo, donationInfo);
371
- }
372
-
373
- private emitPaymentFlowStartedEvent(): void {
374
- if (!this.selectedPaymentProvider) {
375
- return;
376
- }
377
- const event = new CustomEvent('paymentFlowStarted', {
378
- detail: { paymentProvider: this.selectedPaymentProvider },
379
- });
380
- this.dispatchEvent(event);
381
- }
382
-
383
- private emitPaymentFlowConfirmedEvent(): void {
384
- if (!this.selectedPaymentProvider) {
385
- return;
386
- }
387
- const event = new CustomEvent('paymentFlowConfirmed', {
388
- detail: { paymentProvider: this.selectedPaymentProvider },
389
- });
390
- this.dispatchEvent(event);
391
- }
392
-
393
- private emitPaymentFlowCancelledEvent(): void {
394
- if (!this.selectedPaymentProvider) {
395
- return;
396
- }
397
- const event = new CustomEvent('paymentFlowCancelled', {
398
- detail: { paymentProvider: this.selectedPaymentProvider },
399
- });
400
- this.dispatchEvent(event);
401
- }
402
-
403
- private emitPaymentFlowErrorEvent(error?: string): void {
404
- if (!this.selectedPaymentProvider) {
405
- return;
406
- }
407
- const event = new CustomEvent('paymentFlowError', {
408
- detail: { paymentProvider: this.selectedPaymentProvider, error: error },
409
- });
410
- this.dispatchEvent(event);
411
- }
412
-
413
- private showInvalidDonationInfoAlert(): void {
414
- alert('Please enter a valid donation amount.');
415
- }
416
-
417
- private async renderPayPalButtonIfNeeded(): Promise<void> {
418
- if (!this.paypalButtonNeedsRender) {
419
- return;
420
- }
421
- this.paypalButtonNeedsRender = false;
422
- if (this.donationInfo) {
423
- await this.paymentFlowHandlers?.paypalHandler?.renderPayPalButton(this.donationInfo);
424
- }
425
- this.paymentSelector.showPaypalButton();
426
- }
427
-
428
- updated(changedProperties: PropertyValues): void {
429
- if (changedProperties.has('donationInfo') && this.donationInfo) {
430
- // The PayPal button has a standalone datasource since we don't initiate the payment
431
- // through code so it has to have the donation info ready when the user taps the button.
432
- this.paymentFlowHandlers?.paypalHandler?.updateDonationInfo(this.donationInfo);
433
- this.donationFormHeader.donationInfo = this.donationInfo;
434
- }
435
-
436
- if (
437
- (changedProperties.has('paymentFlowHandlers') || changedProperties.has('donationInfo')) &&
438
- this.donationInfo &&
439
- this.paymentFlowHandlers
440
- ) {
441
- this.setupFlowHandlers();
442
- }
443
-
444
- if (changedProperties.has('donationInfoValid')) {
445
- this.paymentSelector.donationInfoValid = this.donationInfoValid;
446
- }
447
-
448
- if (changedProperties.has('selectedPaymentProvider')) {
449
- const event = new CustomEvent('paymentProviderSelected', {
450
- detail: {
451
- paymentProvider: this.selectedPaymentProvider,
452
- previousPaymentProvider: changedProperties.get('selectedPaymentProvider'),
453
- },
454
- });
455
- this.dispatchEvent(event);
456
- }
457
- }
458
-
459
- private flowHandlersConfigured = false;
460
-
461
- private setupFlowHandlers(): void {
462
- if (this.flowHandlersConfigured) {
463
- return;
464
- }
465
- this.flowHandlersConfigured = true;
466
- this.bindFlowListenerEvents();
467
- this.renderPayPalButtonIfNeeded();
468
- if (this.donationInfo) {
469
- this.paymentFlowHandlers?.paypalHandler?.updateDonationInfo(this.donationInfo);
470
- }
471
- }
472
-
473
- private flowHandlerListenersBound = false;
474
-
475
- private bindFlowListenerEvents(): void {
476
- if (this.flowHandlerListenersBound) {
477
- return;
478
- }
479
- this.flowHandlerListenersBound = true;
480
-
481
- this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentStarted', () => {
482
- this.selectedPaymentProvider = PaymentProvider.PayPal;
483
- this.emitPaymentFlowStartedEvent();
484
- });
485
- this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentConfirmed', () => {
486
- this.selectedPaymentProvider = PaymentProvider.PayPal;
487
- this.emitPaymentFlowConfirmedEvent();
488
- });
489
- this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentCancelled', () => {
490
- this.selectedPaymentProvider = PaymentProvider.PayPal;
491
- this.emitPaymentFlowCancelledEvent();
492
- });
493
- this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentError', (datasource, error) => {
494
- this.selectedPaymentProvider = PaymentProvider.PayPal;
495
- this.emitPaymentFlowErrorEvent(error);
496
- });
497
-
498
- this.paymentFlowHandlers?.googlePayHandler?.on('paymentCancelled', () => {
499
- this.selectedPaymentProvider = PaymentProvider.GooglePay;
500
- this.emitPaymentFlowCancelledEvent();
501
- });
502
- }
503
-
504
- private donationInfoChanged(e: CustomEvent): void {
505
- const donationInfo: DonationPaymentInfo = e.detail.donationInfo;
506
- this.donationInfo = new DonationPaymentInfo({
507
- amount: donationInfo.amount,
508
- donationType: donationInfo.donationType,
509
- coverFees: donationInfo.coverFees,
510
- });
511
- this.donationInfoValid = true;
512
- const event = new CustomEvent('donationInfoChanged', { detail: { donationInfo } });
513
- this.dispatchEvent(event);
514
- }
515
-
516
- /** @inheritdoc */
517
- static get styles(): CSSResult {
518
- const donateButtonFontSize = css`var(--donateButtonFontSize, 2.6rem)`;
519
- const donateButtonHeight = css`var(--donateButtonHeight, 4rem)`;
520
- const donateButtonColor = css`var(--donateButtonColor, rgba(49, 164, 129, 1))`;
521
- const donateButtonTextColor = css`var(--donateButtonTextColor, #fff)`;
522
- const donateButtonHoverColor = css`var(--donateButtonHoverColor, rgba(39, 131, 103, 1))`;
523
- const totalAmountTopMargin = css`var(--donateTotalAmountTopMargin, 1.5rem)`;
524
- const totalAmountBottomMargin = css`var(--donateTotalAmountBottomMargin, 1.2rem)`;
525
-
526
- return css`
527
- h1 {
528
- margin: 0;
529
- padding: 0;
530
- }
531
-
532
- .hidden {
533
- display: none;
534
- }
535
-
536
- .secure-process-note {
537
- margin-top: 0.5em;
538
- font-size: 0.75em;
539
- text-align: center;
540
- }
541
-
542
- .secure-process-note svg {
543
- width: 1.2rem;
544
- height: 1.5rem;
545
- vertical-align: bottom;
546
- }
547
-
548
- #donate-button {
549
- width: 100%;
550
- appearance: none;
551
- -webkit-appearance: none;
552
- font-size: ${donateButtonFontSize};
553
- font-weight: bold;
554
- text-align: center;
555
- color: ${donateButtonTextColor};
556
- cursor: pointer;
557
- border: none;
558
- border-radius: 5px;
559
- background-color: ${donateButtonColor};
560
- padding-top: 0.5rem;
561
- padding-bottom: 0.5rem;
562
- height: ${donateButtonHeight};
563
- }
564
-
565
- #donate-button:hover {
566
- background-color: ${donateButtonHoverColor};
567
- }
568
-
569
- #total-amount-section {
570
- display: block;
571
- margin-top: ${totalAmountTopMargin};
572
- margin-bottom: ${totalAmountBottomMargin};
573
- }
574
- `;
575
- }
576
- }
1
+ import { LitElement, html, css, CSSResult, TemplateResult, PropertyValues } from 'lit';
2
+ import { customElement, property, query } from 'lit/decorators.js';
3
+
4
+ import lockImg from '@internetarchive/icon-lock/index.js';
5
+
6
+ // we have to import the registered component independently from the definition below
7
+ // because inside each of these files, we're registering the custom element inside
8
+ // these files and by simply importing the class name, you lose that behavior
9
+ // See https://github.com/microsoft/TypeScript/issues/9191 for more discussion
10
+ import './form-elements/payment-selector';
11
+ import './form-elements/header/donation-form-header';
12
+
13
+ import { DonationFormHeader } from './form-elements/header/donation-form-header';
14
+ import { PaymentSelector } from './form-elements/payment-selector';
15
+
16
+ import { BraintreeManagerInterface } from './braintree-manager/braintree-interfaces';
17
+
18
+ import {
19
+ DonationRequest,
20
+ DonationPaymentInfo,
21
+ PaymentProvider,
22
+ DonorContactInfo,
23
+ DonationType,
24
+ defaultDonationAmounts,
25
+ } from '@internetarchive/donation-form-data-models';
26
+
27
+ import { PaymentFlowHandlersInterface } from './payment-flow-handlers/payment-flow-handlers';
28
+
29
+ import {
30
+ EditDonationAmountSelectionLayout,
31
+ EditDonationFrequencySelectionMode,
32
+ } from '@internetarchive/donation-form-edit-donation';
33
+
34
+ import '@internetarchive/donation-form-section';
35
+ import {
36
+ DonationFormSection,
37
+ DonationFormSectionBadgeMode,
38
+ } from '@internetarchive/donation-form-section';
39
+ import { UpsellModalCTAMode } from './modals/upsell-modal-content';
40
+ import { ContactForm } from './form-elements/contact-form/contact-form';
41
+ import './form-elements/total-amount';
42
+
43
+ @customElement('donation-form')
44
+ export class DonationForm extends LitElement {
45
+ @property({ type: Object }) braintreeManager: BraintreeManagerInterface | undefined;
46
+
47
+ @property({ type: Object }) paymentFlowHandlers: PaymentFlowHandlersInterface | undefined;
48
+
49
+ @property({ type: Object }) donationRequest: DonationRequest | undefined;
50
+
51
+ @property({ type: Object }) donationInfo?: DonationPaymentInfo;
52
+
53
+ @property({ type: Object }) contactForm?: ContactForm;
54
+
55
+ @property({ type: Array }) amountOptions: number[] = defaultDonationAmounts;
56
+
57
+ @property({ type: String }) amountSelectionLayout: EditDonationAmountSelectionLayout =
58
+ EditDonationAmountSelectionLayout.MultiLine;
59
+
60
+ @property({ type: String }) frequencySelectionMode: EditDonationFrequencySelectionMode =
61
+ EditDonationFrequencySelectionMode.Button;
62
+
63
+ @property({ type: Boolean }) private creditCardVisible = false;
64
+
65
+ @property({ type: Boolean }) private contactFormVisible = false;
66
+
67
+ @property({ type: Boolean }) private donationInfoValid = true;
68
+
69
+ @property({ type: String }) private selectedPaymentProvider?: PaymentProvider;
70
+
71
+ @query('#contactFormSection') contactFormSection?: DonationFormSection;
72
+
73
+ @query('donation-form-header') donationFormHeader!: DonationFormHeader;
74
+
75
+ @query('payment-selector') paymentSelector!: PaymentSelector;
76
+
77
+ private paypalButtonNeedsRender = true;
78
+
79
+ /** @inheritdoc */
80
+ render(): TemplateResult {
81
+ return html`
82
+ <donation-form-header
83
+ .amountOptions=${this.amountOptions}
84
+ .amountSelectionLayout=${this.amountSelectionLayout}
85
+ .frequencySelectionMode=${this.frequencySelectionMode}
86
+ @donationInfoChanged=${this.donationInfoChanged}
87
+ @editDonationError=${this.editDonationError}
88
+ >
89
+ </donation-form-header>
90
+
91
+ <donation-form-section
92
+ .badgeMode=${DonationFormSectionBadgeMode.HideBadgeLeaveSpacing}
93
+ id="total-amount-section"
94
+ >
95
+ <donation-form-total-amount .donationInfo=${this.donationInfo}>
96
+ </donation-form-total-amount>
97
+ </donation-form-section>
98
+
99
+ <donation-form-section
100
+ .sectionBadge=${this.paymentSelectorNumberingStart}
101
+ headline="Choose a payment method"
102
+ >
103
+ <payment-selector
104
+ .paymentProviders=${this.braintreeManager?.paymentProviders}
105
+ @firstUpdated=${this.paymentSelectorFirstUpdated}
106
+ @creditCardSelected=${this.creditCardSelected}
107
+ @venmoSelected=${this.venmoSelected}
108
+ @applePaySelected=${this.applePaySelected}
109
+ @googlePaySelected=${this.googlePaySelected}
110
+ @paypalBlockerSelected=${this.paypalBlockerSelected}
111
+ @resetPaymentMethod=${async () => {
112
+ this.selectedPaymentProvider = undefined;
113
+ this.contactFormVisible = false;
114
+ this.requestUpdate();
115
+ }}
116
+ tabindex="0"
117
+ >
118
+ <slot name="paypal-button" slot="paypal-button"></slot>
119
+ </payment-selector>
120
+ </donation-form-section>
121
+
122
+ <div class="contact-form-section ${this.contactFormVisible ? '' : 'hidden'}">
123
+ ${this.contactFormSectionTemplate}
124
+ </div>
125
+ <slot name="recaptcha"></slot>
126
+ `;
127
+ }
128
+
129
+ async showConfirmationModalDev(options: {
130
+ donationType: DonationType;
131
+ amount: number;
132
+ currencyType: string;
133
+ cancelDonationCB: Function;
134
+ confirmDonationCB: Function;
135
+ }): Promise<void> {
136
+ this.paymentFlowHandlers?.showConfirmationStepModal(options);
137
+ }
138
+
139
+ /**
140
+ * This is a developer convenience method that allows us to show the upsell modal without going
141
+ * through the purchasing flow. If it's showing the PayPal button, it will trigger
142
+ * the PayPal button render
143
+ *
144
+ * @param {{
145
+ * oneTimeAmount: number;
146
+ * ctaMode?: UpsellModalCTAMode;
147
+ * yesSelected?: (amount: number) => void;
148
+ * noSelected?: () => void;
149
+ * amountChanged?: (amount: number) => void;
150
+ * userClosedModalCallback?: () => void;
151
+ * }} options
152
+ * @returns {Promise<void>}
153
+ * @memberof DonationForm
154
+ */
155
+ async showUpsellModalDev(options: {
156
+ oneTimeAmount: number;
157
+ ctaMode?: UpsellModalCTAMode;
158
+ yesSelected?: (amount: number) => void;
159
+ noSelected?: () => void;
160
+ amountChanged?: (amount: number) => void;
161
+ userClosedModalCallback?: () => void;
162
+ }): Promise<void> {
163
+ this.paymentFlowHandlers?.showUpsellModal(options);
164
+
165
+ if (options.ctaMode === UpsellModalCTAMode.PayPalUpsellSlot) {
166
+ const handler = await this.braintreeManager?.paymentProviders.paypalHandler.get();
167
+ const donationInfo = new DonationPaymentInfo({
168
+ amount: options.oneTimeAmount,
169
+ donationType: DonationType.OneTime,
170
+ coverFees: false,
171
+ });
172
+ handler?.renderPayPalButton({
173
+ selector: '#paypal-upsell-button',
174
+ style: {
175
+ color: 'blue' as paypal.ButtonColorOption,
176
+ label: 'paypal' as paypal.ButtonLabelOption,
177
+ shape: 'rect' as paypal.ButtonShapeOption,
178
+ size: 'responsive' as paypal.ButtonSizeOption,
179
+ tagline: false,
180
+ },
181
+ donationInfo: donationInfo,
182
+ });
183
+ }
184
+ }
185
+
186
+ get contactFormSectionTemplate(): TemplateResult {
187
+ const headline =
188
+ this.selectedPaymentProvider === PaymentProvider.Venmo
189
+ ? 'Help us stay in touch'
190
+ : 'Enter payment information';
191
+
192
+ return html`
193
+ <donation-form-section
194
+ .sectionBadge=${this.paymentSelectorNumberingStart + 1}
195
+ headline=${headline}
196
+ id="contactFormSection"
197
+ >
198
+ <slot name="contact-form"></slot>
199
+ <div class="credit-card-fields" class="${this.creditCardVisible ? '' : 'hidden'}">
200
+ <slot name="braintree-hosted-fields"></slot>
201
+ </div>
202
+ </donation-form-section>
203
+
204
+ <donation-form-section .sectionBadge=${this.paymentSelectorNumberingStart + 2}>
205
+ <slot name="recaptcha"></slot>
206
+ <button id="donate-button" @click=${this.donateClicked}>Donate</button>
207
+
208
+ <div class="secure-process-note">${lockImg} Your payment will be securely processed</div>
209
+ </donation-form-section>
210
+ `;
211
+ }
212
+
213
+ /**
214
+ * Where to start the numbering of the payment selector
215
+ *
216
+ * - If we show the frequency selector in button mode, it becomes section 1, which makes
217
+ * the amount selection section 2, and the payment selector section 3.
218
+ * - If we show the frequency selector in checkbox mode, it is no longer section 1. The amount
219
+ * selector becomes section 1 and the payment selector becomes section 2.
220
+ *
221
+ * Visually:
222
+ *
223
+ * Button Mode:
224
+ * 1. Frequency selector
225
+ * 2. Amount selector
226
+ * 3. Payment selector
227
+ * 4. Contact info
228
+ * 5. Donate button
229
+ *
230
+ * Checkbox Mode:
231
+ * 1. Amount selector (including the monthly checkbox)
232
+ * 2. Payment selector <-- changes from 3 to 2
233
+ * 3. Contact info <-- changes from 4 to 3
234
+ * 4. Donate button <-- changes from 5 to 4
235
+ *
236
+ * @readonly
237
+ * @private
238
+ * @type {number}
239
+ * @memberof DonationForm
240
+ */
241
+ private get paymentSelectorNumberingStart(): number {
242
+ return this.frequencySelectionMode === EditDonationFrequencySelectionMode.Button ? 3 : 2;
243
+ }
244
+
245
+ private editDonationError(): void {
246
+ this.donationInfoValid = false;
247
+ }
248
+
249
+ private paymentSelectorFirstUpdated(): void {
250
+ if (this.paymentFlowHandlers?.paypalHandler) {
251
+ this.renderPayPalButtonIfNeeded();
252
+ }
253
+ }
254
+
255
+ private applePaySelected(e: CustomEvent): void {
256
+ this.selectedPaymentProvider = PaymentProvider.ApplePay;
257
+ this.contactFormVisible = false;
258
+ this.creditCardVisible = false;
259
+
260
+ if (!this.donationInfoValid) {
261
+ this.showInvalidDonationInfoAlert();
262
+ return;
263
+ }
264
+
265
+ const originalEvent = e.detail.originalEvent;
266
+ if (this.donationInfo) {
267
+ this.paymentFlowHandlers?.applePayHandler?.paymentInitiated(this.donationInfo, originalEvent);
268
+ }
269
+ this.emitPaymentFlowStartedEvent();
270
+ }
271
+
272
+ private googlePaySelected(): void {
273
+ this.selectedPaymentProvider = PaymentProvider.GooglePay;
274
+ this.contactFormVisible = false;
275
+ this.creditCardVisible = false;
276
+
277
+ if (!this.donationInfoValid) {
278
+ this.showInvalidDonationInfoAlert();
279
+ } else {
280
+ if (this.donationInfo) {
281
+ this.paymentFlowHandlers?.googlePayHandler?.paymentInitiated(this.donationInfo);
282
+ }
283
+ this.emitPaymentFlowStartedEvent();
284
+ }
285
+ }
286
+
287
+ private async creditCardSelected(): Promise<void> {
288
+ if (!this.donationInfoValid) {
289
+ this.showInvalidDonationInfoAlert();
290
+ return;
291
+ }
292
+ this.selectedPaymentProvider = PaymentProvider.CreditCard;
293
+ this.contactFormVisible = true;
294
+ this.creditCardVisible = true;
295
+ this.focusContactForm();
296
+ }
297
+
298
+ private async venmoSelected(): Promise<void> {
299
+ if (!this.donationInfoValid) {
300
+ this.showInvalidDonationInfoAlert();
301
+ return;
302
+ }
303
+ this.selectedPaymentProvider = PaymentProvider.Venmo;
304
+ this.contactFormVisible = true;
305
+ this.creditCardVisible = false;
306
+ this.focusContactForm();
307
+ }
308
+
309
+ private paypalBlockerSelected(): void {
310
+ this.contactFormVisible = false;
311
+ this.creditCardVisible = false;
312
+ this.showInvalidDonationInfoAlert();
313
+ }
314
+
315
+ private async focusContactForm(): Promise<void> {
316
+ await this.updateComplete;
317
+ if (this.contactFormSection) {
318
+ this.contactForm?.focus();
319
+ }
320
+ }
321
+
322
+ private async donateClicked(): Promise<void> {
323
+ if (!this.contactForm) {
324
+ alert('Please enter contact info.');
325
+ return;
326
+ }
327
+ if (!this.donationInfoValid || !this.donationInfo) {
328
+ this.showInvalidDonationInfoAlert();
329
+ return;
330
+ }
331
+
332
+ const contactInfo = this.contactForm.donorContactInfo;
333
+
334
+ switch (this.selectedPaymentProvider) {
335
+ case PaymentProvider.CreditCard:
336
+ this.handleCreditCardDonationFlow(contactInfo, this.donationInfo);
337
+ break;
338
+ case PaymentProvider.Venmo:
339
+ this.handleVenmoDonationFlow(contactInfo, this.donationInfo);
340
+ break;
341
+ }
342
+ }
343
+
344
+ private async handleCreditCardDonationFlow(
345
+ contactInfo: DonorContactInfo,
346
+ donationInfo: DonationPaymentInfo,
347
+ ): Promise<void> {
348
+ const creditCardFlowHandler = this.paymentFlowHandlers?.creditCardHandler;
349
+ const creditCardHandler = await this.braintreeManager?.paymentProviders.creditCardHandler.get();
350
+ creditCardHandler?.hideErrorMessage();
351
+ const valid = this.contactForm?.reportValidity();
352
+ const hostedFieldsResponse = await creditCardFlowHandler?.tokenizeFields();
353
+
354
+ if (!valid || hostedFieldsResponse === undefined) {
355
+ return;
356
+ }
357
+
358
+ this.emitPaymentFlowStartedEvent();
359
+ creditCardFlowHandler?.paymentInitiated(hostedFieldsResponse, donationInfo, contactInfo);
360
+ }
361
+
362
+ private async handleVenmoDonationFlow(
363
+ contactInfo: DonorContactInfo,
364
+ donationInfo: DonationPaymentInfo,
365
+ ): Promise<void> {
366
+ const valid = this.contactForm?.reportValidity();
367
+ if (!valid) {
368
+ return;
369
+ }
370
+ this.paymentFlowHandlers?.venmoHandler?.paymentInitiated(contactInfo, donationInfo);
371
+ }
372
+
373
+ private emitPaymentFlowStartedEvent(): void {
374
+ if (!this.selectedPaymentProvider) {
375
+ return;
376
+ }
377
+ const event = new CustomEvent('paymentFlowStarted', {
378
+ detail: { paymentProvider: this.selectedPaymentProvider },
379
+ });
380
+ this.dispatchEvent(event);
381
+ }
382
+
383
+ private emitPaymentFlowConfirmedEvent(): void {
384
+ if (!this.selectedPaymentProvider) {
385
+ return;
386
+ }
387
+ const event = new CustomEvent('paymentFlowConfirmed', {
388
+ detail: { paymentProvider: this.selectedPaymentProvider },
389
+ });
390
+ this.dispatchEvent(event);
391
+ }
392
+
393
+ private emitPaymentFlowCancelledEvent(): void {
394
+ if (!this.selectedPaymentProvider) {
395
+ return;
396
+ }
397
+ const event = new CustomEvent('paymentFlowCancelled', {
398
+ detail: { paymentProvider: this.selectedPaymentProvider },
399
+ });
400
+ this.dispatchEvent(event);
401
+ }
402
+
403
+ private emitPaymentFlowErrorEvent(error?: string): void {
404
+ if (!this.selectedPaymentProvider) {
405
+ return;
406
+ }
407
+ const event = new CustomEvent('paymentFlowError', {
408
+ detail: { paymentProvider: this.selectedPaymentProvider, error: error },
409
+ });
410
+ this.dispatchEvent(event);
411
+ }
412
+
413
+ private showInvalidDonationInfoAlert(): void {
414
+ alert('Please enter a valid donation amount.');
415
+ }
416
+
417
+ private async renderPayPalButtonIfNeeded(): Promise<void> {
418
+ if (!this.paypalButtonNeedsRender) {
419
+ return;
420
+ }
421
+ this.paypalButtonNeedsRender = false;
422
+ if (this.donationInfo) {
423
+ await this.paymentFlowHandlers?.paypalHandler?.renderPayPalButton(this.donationInfo);
424
+ }
425
+ this.paymentSelector.showPaypalButton();
426
+ }
427
+
428
+ updated(changedProperties: PropertyValues): void {
429
+ if (changedProperties.has('donationInfo') && this.donationInfo) {
430
+ // The PayPal button has a standalone datasource since we don't initiate the payment
431
+ // through code so it has to have the donation info ready when the user taps the button.
432
+ this.paymentFlowHandlers?.paypalHandler?.updateDonationInfo(this.donationInfo);
433
+ this.donationFormHeader.donationInfo = this.donationInfo;
434
+ }
435
+
436
+ if (
437
+ (changedProperties.has('paymentFlowHandlers') || changedProperties.has('donationInfo')) &&
438
+ this.donationInfo &&
439
+ this.paymentFlowHandlers
440
+ ) {
441
+ this.setupFlowHandlers();
442
+ }
443
+
444
+ if (changedProperties.has('donationInfoValid')) {
445
+ this.paymentSelector.donationInfoValid = this.donationInfoValid;
446
+ }
447
+
448
+ if (changedProperties.has('selectedPaymentProvider')) {
449
+ const event = new CustomEvent('paymentProviderSelected', {
450
+ detail: {
451
+ paymentProvider: this.selectedPaymentProvider,
452
+ previousPaymentProvider: changedProperties.get('selectedPaymentProvider'),
453
+ },
454
+ });
455
+ this.dispatchEvent(event);
456
+ }
457
+ }
458
+
459
+ private flowHandlersConfigured = false;
460
+
461
+ private setupFlowHandlers(): void {
462
+ if (this.flowHandlersConfigured) {
463
+ return;
464
+ }
465
+ this.flowHandlersConfigured = true;
466
+ this.bindFlowListenerEvents();
467
+ this.renderPayPalButtonIfNeeded();
468
+ if (this.donationInfo) {
469
+ this.paymentFlowHandlers?.paypalHandler?.updateDonationInfo(this.donationInfo);
470
+ }
471
+ }
472
+
473
+ private flowHandlerListenersBound = false;
474
+
475
+ private bindFlowListenerEvents(): void {
476
+ if (this.flowHandlerListenersBound) {
477
+ return;
478
+ }
479
+ this.flowHandlerListenersBound = true;
480
+
481
+ this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentStarted', () => {
482
+ this.selectedPaymentProvider = PaymentProvider.PayPal;
483
+ this.emitPaymentFlowStartedEvent();
484
+ });
485
+ this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentConfirmed', () => {
486
+ this.selectedPaymentProvider = PaymentProvider.PayPal;
487
+ this.emitPaymentFlowConfirmedEvent();
488
+ });
489
+ this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentCancelled', () => {
490
+ this.selectedPaymentProvider = PaymentProvider.PayPal;
491
+ this.emitPaymentFlowCancelledEvent();
492
+ });
493
+ this.paymentFlowHandlers?.paypalHandler?.on('payPalPaymentError', (datasource, error) => {
494
+ this.selectedPaymentProvider = PaymentProvider.PayPal;
495
+ this.emitPaymentFlowErrorEvent(error);
496
+ });
497
+
498
+ this.paymentFlowHandlers?.googlePayHandler?.on('paymentCancelled', () => {
499
+ this.selectedPaymentProvider = PaymentProvider.GooglePay;
500
+ this.emitPaymentFlowCancelledEvent();
501
+ });
502
+ }
503
+
504
+ private donationInfoChanged(e: CustomEvent): void {
505
+ const donationInfo: DonationPaymentInfo = e.detail.donationInfo;
506
+ this.donationInfo = new DonationPaymentInfo({
507
+ amount: donationInfo.amount,
508
+ donationType: donationInfo.donationType,
509
+ coverFees: donationInfo.coverFees,
510
+ });
511
+ this.donationInfoValid = true;
512
+ const event = new CustomEvent('donationInfoChanged', { detail: { donationInfo } });
513
+ this.dispatchEvent(event);
514
+ }
515
+
516
+ /** @inheritdoc */
517
+ static get styles(): CSSResult {
518
+ const donateButtonFontSize = css`var(--donateButtonFontSize, 2.6rem)`;
519
+ const donateButtonHeight = css`var(--donateButtonHeight, 4rem)`;
520
+ const donateButtonColor = css`var(--donateButtonColor, rgba(49, 164, 129, 1))`;
521
+ const donateButtonTextColor = css`var(--donateButtonTextColor, #fff)`;
522
+ const donateButtonHoverColor = css`var(--donateButtonHoverColor, rgba(39, 131, 103, 1))`;
523
+ const totalAmountTopMargin = css`var(--donateTotalAmountTopMargin, 1.5rem)`;
524
+ const totalAmountBottomMargin = css`var(--donateTotalAmountBottomMargin, 1.2rem)`;
525
+
526
+ return css`
527
+ h1 {
528
+ margin: 0;
529
+ padding: 0;
530
+ }
531
+
532
+ .hidden {
533
+ display: none;
534
+ }
535
+
536
+ .secure-process-note {
537
+ margin-top: 0.5em;
538
+ font-size: 0.75em;
539
+ text-align: center;
540
+ }
541
+
542
+ .secure-process-note svg {
543
+ width: 1.2rem;
544
+ height: 1.5rem;
545
+ vertical-align: bottom;
546
+ }
547
+
548
+ #donate-button {
549
+ width: 100%;
550
+ appearance: none;
551
+ -webkit-appearance: none;
552
+ font-size: ${donateButtonFontSize};
553
+ font-weight: bold;
554
+ text-align: center;
555
+ color: ${donateButtonTextColor};
556
+ cursor: pointer;
557
+ border: none;
558
+ border-radius: 5px;
559
+ background-color: ${donateButtonColor};
560
+ padding-top: 0.5rem;
561
+ padding-bottom: 0.5rem;
562
+ height: ${donateButtonHeight};
563
+ }
564
+
565
+ #donate-button:hover {
566
+ background-color: ${donateButtonHoverColor};
567
+ }
568
+
569
+ #total-amount-section {
570
+ display: block;
571
+ margin-top: ${totalAmountTopMargin};
572
+ margin-bottom: ${totalAmountBottomMargin};
573
+ }
574
+ `;
575
+ }
576
+ }