@internetarchive/donation-form 1.0.3-alpha-webdev7960.1 → 1.0.3-webdev-8122.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 (210) 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.js +159 -157
  38. package/dist/src/form-elements/contact-form/contact-form.js.map +1 -1
  39. package/dist/src/form-elements/contact-form/countries.js.map +1 -1
  40. package/dist/src/form-elements/header/donation-form-header.js +14 -14
  41. package/dist/src/form-elements/header/donation-form-header.js.map +1 -1
  42. package/dist/src/form-elements/header/donation-summary.js +15 -15
  43. package/dist/src/form-elements/header/donation-summary.js.map +1 -1
  44. package/dist/src/form-elements/payment-selector.js +164 -164
  45. package/dist/src/form-elements/payment-selector.js.map +1 -1
  46. package/dist/src/form-elements/total-amount.js +16 -16
  47. package/dist/src/form-elements/total-amount.js.map +1 -1
  48. package/dist/src/modals/confirm-donation-modal-content.js +51 -51
  49. package/dist/src/modals/confirm-donation-modal-content.js.map +1 -1
  50. package/dist/src/modals/error-modal-content.js +22 -22
  51. package/dist/src/modals/error-modal-content.js.map +1 -1
  52. package/dist/src/modals/upsell-modal-content.js +182 -182
  53. package/dist/src/modals/upsell-modal-content.js.map +1 -1
  54. package/dist/src/payment-flow-handlers/donation-flow-modal-manager.js +20 -20
  55. package/dist/src/payment-flow-handlers/donation-flow-modal-manager.js.map +1 -1
  56. package/dist/src/payment-flow-handlers/handlers/applepay-flow-handler.js.map +1 -1
  57. package/dist/src/payment-flow-handlers/handlers/creditcard-flow-handler.js.map +1 -1
  58. package/dist/src/payment-flow-handlers/handlers/googlepay-flow-handler.js +2 -0
  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 +2 -0
  61. package/dist/src/payment-flow-handlers/handlers/paypal-flow-handler.js.map +1 -1
  62. package/dist/src/payment-flow-handlers/handlers/venmo-flow-handler.js.map +1 -1
  63. package/dist/src/payment-flow-handlers/handlers/venmo-restoration-state-handler.js.map +1 -1
  64. package/dist/src/payment-flow-handlers/payment-flow-handlers.js.map +1 -1
  65. package/dist/src/recaptcha-manager/recaptcha-manager.js.map +1 -1
  66. package/dist/src/util/promisedSleep.js.map +1 -1
  67. package/dist/test/helpers/fillInContactForm.js.map +1 -1
  68. package/dist/test/mocks/flow-handlers/individual-handlers/mock-applepay-flow-handler.js.map +1 -1
  69. package/dist/test/mocks/flow-handlers/individual-handlers/mock-creditcard-flow-handler.js.map +1 -1
  70. package/dist/test/mocks/flow-handlers/individual-handlers/mock-googlepay-flow-handler.js.map +1 -1
  71. package/dist/test/mocks/flow-handlers/individual-handlers/mock-paypal-flow-handler.js.map +1 -1
  72. package/dist/test/mocks/flow-handlers/individual-handlers/mock-venmo-flow-handler.js.map +1 -1
  73. package/dist/test/mocks/flow-handlers/mock-payment-flow-handlers.js.map +1 -1
  74. package/dist/test/mocks/mock-braintree-manager.js.map +1 -1
  75. package/dist/test/mocks/mock-donation-info.js.map +1 -1
  76. package/dist/test/mocks/mock-endpoint-manager.js.map +1 -1
  77. package/dist/test/mocks/mock-hosted-fields-config.js.map +1 -1
  78. package/dist/test/mocks/mock-hosted-fields-container.js.map +1 -1
  79. package/dist/test/mocks/mock-lazy-loader.js.map +1 -1
  80. package/dist/test/mocks/mock-modal-manager.js.map +1 -1
  81. package/dist/test/mocks/mock-payment-clients.js.map +1 -1
  82. package/dist/test/mocks/mock-paypal-button-renderer.js.map +1 -1
  83. package/dist/test/mocks/mock-recaptcha-manager.js.map +1 -1
  84. package/dist/test/mocks/models/mock-billing-info.js +2 -0
  85. package/dist/test/mocks/models/mock-billing-info.js.map +1 -1
  86. package/dist/test/mocks/models/mock-custom-fields.js.map +1 -1
  87. package/dist/test/mocks/models/mock-customer-info.js.map +1 -1
  88. package/dist/test/mocks/models/mock-donation-request.js.map +1 -1
  89. package/dist/test/mocks/models/mock-success-response.js.map +1 -1
  90. package/dist/test/mocks/payment-clients/mock-applepay-client.js.map +1 -1
  91. package/dist/test/mocks/payment-clients/mock-applepay-payment.js.map +1 -1
  92. package/dist/test/mocks/payment-clients/mock-applepay-paymentauthorizedevent.js.map +1 -1
  93. package/dist/test/mocks/payment-clients/mock-applepay-session.js.map +1 -1
  94. package/dist/test/mocks/payment-clients/mock-applepay-sessionmanager.js.map +1 -1
  95. package/dist/test/mocks/payment-clients/mock-applepay-validatemerchantevent.js.map +1 -1
  96. package/dist/test/mocks/payment-clients/mock-braintree-client.js.map +1 -1
  97. package/dist/test/mocks/payment-clients/mock-data-collector.js.map +1 -1
  98. package/dist/test/mocks/payment-clients/mock-googlepay-client.js.map +1 -1
  99. package/dist/test/mocks/payment-clients/mock-googlepay-library.js.map +1 -1
  100. package/dist/test/mocks/payment-clients/mock-grecaptcha.js.map +1 -1
  101. package/dist/test/mocks/payment-clients/mock-hostedfields-client.js.map +1 -1
  102. package/dist/test/mocks/payment-clients/mock-hostedfieldstateobject-generator.js.map +1 -1
  103. package/dist/test/mocks/payment-clients/mock-hostedfieldtokenizepayload.js.map +1 -1
  104. package/dist/test/mocks/payment-clients/mock-paypal-client.js.map +1 -1
  105. package/dist/test/mocks/payment-clients/mock-paypal-library.js.map +1 -1
  106. package/dist/test/mocks/payment-clients/mock-venmo-client.js.map +1 -1
  107. package/dist/test/mocks/payment-providers/individual-providers/mock-applepay-datasource-delegate.js.map +1 -1
  108. package/dist/test/mocks/payment-providers/individual-providers/mock-applepay-handler.js.map +1 -1
  109. package/dist/test/mocks/payment-providers/individual-providers/mock-creditcard-handler.js.map +1 -1
  110. package/dist/test/mocks/payment-providers/individual-providers/mock-googlepay-handler.js.map +1 -1
  111. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-button-datasource-delegate.js.map +1 -1
  112. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-button-datasource.js.map +1 -1
  113. package/dist/test/mocks/payment-providers/individual-providers/mock-paypal-handler.js.map +1 -1
  114. package/dist/test/mocks/payment-providers/individual-providers/mock-venmo-handler.js.map +1 -1
  115. package/dist/test/mocks/payment-providers/mock-payment-providers.js.map +1 -1
  116. package/dist/test/tests/braintree-manager.test.js.map +1 -1
  117. package/dist/test/tests/donation-form-controller.test.js +39 -39
  118. package/dist/test/tests/donation-form-controller.test.js.map +1 -1
  119. package/dist/test/tests/donation-form.test.js +4 -4
  120. package/dist/test/tests/donation-form.test.js.map +1 -1
  121. package/dist/test/tests/flow-handlers/donation-flow-modal-manager.test.js +16 -14
  122. package/dist/test/tests/flow-handlers/donation-flow-modal-manager.test.js.map +1 -1
  123. package/dist/test/tests/form-elements/donation-summary.test.js.map +1 -1
  124. package/dist/test/tests/form-elements/payment-selector.test.js.map +1 -1
  125. package/dist/test/tests/modals/error-modal-content.test.js +2 -2
  126. package/dist/test/tests/modals/error-modal-content.test.js.map +1 -1
  127. package/dist/test/tests/modals/upsell-modal-content.test.js +31 -31
  128. package/dist/test/tests/modals/upsell-modal-content.test.js.map +1 -1
  129. package/dist/test/tests/models/donation-payment-info.test.js.map +1 -1
  130. package/dist/test/tests/payment-clients.test.js.map +1 -1
  131. package/dist/test/tests/payment-providers/applepay-sessiondatasource.test.js.map +1 -1
  132. package/dist/test/tests/payment-providers/applepay-sessionmanager.test.js.map +1 -1
  133. package/dist/test/tests/payment-providers/applepay.test.js.map +1 -1
  134. package/dist/test/tests/payment-providers/creditcard.test.js.map +1 -1
  135. package/dist/test/tests/payment-providers/googlepay.test.js.map +1 -1
  136. package/dist/test/tests/payment-providers/payment-providers.test.js.map +1 -1
  137. package/dist/test/tests/payment-providers/paypal-button-datasource.test.js.map +1 -1
  138. package/dist/test/tests/payment-providers/paypal.test.js.map +1 -1
  139. package/dist/test/tests/payment-providers/venmo.test.js.map +1 -1
  140. package/dist/test/tests/recaptcha-manager.test.js.map +1 -1
  141. package/package.json +107 -107
  142. package/src/@types/analytics-handler/index.d.ts +8 -8
  143. package/src/@types/braintree-web/LICENSE +21 -21
  144. package/src/@types/braintree-web/index.d.ts +93 -93
  145. package/src/@types/braintree-web/modules/american-express.d.ts +50 -50
  146. package/src/@types/braintree-web/modules/apple-pay.d.ts +213 -213
  147. package/src/@types/braintree-web/modules/client.d.ts +103 -103
  148. package/src/@types/braintree-web/modules/core.d.ts +34 -34
  149. package/src/@types/braintree-web/modules/data-collector.d.ts +13 -13
  150. package/src/@types/braintree-web/modules/google-payment.d.ts +269 -269
  151. package/src/@types/braintree-web/modules/hosted-fields.d.ts +366 -366
  152. package/src/@types/braintree-web/modules/paypal-checkout.d.ts +262 -262
  153. package/src/@types/braintree-web/modules/paypal.d.ts +177 -177
  154. package/src/@types/braintree-web/modules/three-d-secure.d.ts +141 -141
  155. package/src/@types/braintree-web/modules/unionpay.d.ts +224 -224
  156. package/src/@types/braintree-web/modules/us-bank-account.d.ts +81 -81
  157. package/src/@types/braintree-web/modules/venmo.d.ts +110 -110
  158. package/src/@types/braintree-web/package.json +64 -64
  159. package/src/@types/paypal-checkout-components/LICENSE +21 -21
  160. package/src/@types/paypal-checkout-components/index.d.ts +67 -67
  161. package/src/@types/paypal-checkout-components/modules/button.d.ts +50 -50
  162. package/src/@types/paypal-checkout-components/modules/callback-data.d.ts +244 -244
  163. package/src/@types/paypal-checkout-components/modules/configuration.d.ts +114 -114
  164. package/src/@types/paypal-checkout-components/package.json +58 -58
  165. package/src/braintree-manager/braintree-interfaces.ts +172 -172
  166. package/src/braintree-manager/braintree-manager.ts +281 -281
  167. package/src/braintree-manager/payment-clients.ts +146 -146
  168. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-interface.ts +13 -13
  169. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-delegate.ts +8 -8
  170. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource-interface.ts +10 -10
  171. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-datasource.ts +119 -119
  172. package/src/braintree-manager/payment-providers/apple-pay/apple-pay-session-manager.ts +21 -21
  173. package/src/braintree-manager/payment-providers/apple-pay/apple-pay.ts +97 -97
  174. package/src/braintree-manager/payment-providers/credit-card/credit-card-interface.ts +21 -21
  175. package/src/braintree-manager/payment-providers/credit-card/credit-card.ts +130 -130
  176. package/src/braintree-manager/payment-providers/credit-card/hosted-field-configuration.ts +19 -19
  177. package/src/braintree-manager/payment-providers/credit-card/hosted-field-container.ts +85 -85
  178. package/src/braintree-manager/payment-providers/google-pay-interface.ts +8 -8
  179. package/src/braintree-manager/payment-providers/google-pay.ts +59 -59
  180. package/src/braintree-manager/payment-providers/paypal/paypal-button-datasource.ts +218 -218
  181. package/src/braintree-manager/payment-providers/paypal/paypal-interface.ts +13 -13
  182. package/src/braintree-manager/payment-providers/paypal/paypal.ts +78 -78
  183. package/src/braintree-manager/payment-providers/venmo-interface.ts +8 -8
  184. package/src/braintree-manager/payment-providers/venmo.ts +67 -67
  185. package/src/braintree-manager/payment-providers-interface.ts +25 -25
  186. package/src/braintree-manager/payment-providers.ts +147 -147
  187. package/src/donation-form-controller.ts +623 -623
  188. package/src/donation-form-error.ts +6 -6
  189. package/src/donation-form.ts +576 -576
  190. package/src/form-elements/badged-input.ts +109 -109
  191. package/src/form-elements/contact-form/autocomplete-field-options.ts +63 -63
  192. package/src/form-elements/contact-form/contact-form.ts +436 -434
  193. package/src/form-elements/contact-form/countries.ts +252 -252
  194. package/src/form-elements/header/donation-form-header.ts +98 -98
  195. package/src/form-elements/header/donation-summary.ts +61 -61
  196. package/src/form-elements/payment-selector.ts +365 -365
  197. package/src/form-elements/total-amount.ts +46 -46
  198. package/src/modals/confirm-donation-modal-content.ts +168 -168
  199. package/src/modals/error-modal-content.ts +48 -48
  200. package/src/modals/upsell-modal-content.ts +284 -284
  201. package/src/payment-flow-handlers/donation-flow-modal-manager.ts +439 -439
  202. package/src/payment-flow-handlers/handlers/applepay-flow-handler.ts +109 -109
  203. package/src/payment-flow-handlers/handlers/creditcard-flow-handler.ts +232 -232
  204. package/src/payment-flow-handlers/handlers/googlepay-flow-handler.ts +113 -111
  205. package/src/payment-flow-handlers/handlers/paypal-flow-handler.ts +333 -331
  206. package/src/payment-flow-handlers/handlers/venmo-flow-handler.ts +119 -119
  207. package/src/payment-flow-handlers/handlers/venmo-restoration-state-handler.ts +127 -127
  208. package/src/payment-flow-handlers/payment-flow-handlers.ts +218 -218
  209. package/src/recaptcha-manager/recaptcha-manager.ts +123 -123
  210. 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
+ }