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