Skip to content

Commit 83d4131

Browse files
authored
Payment Method UI - credit card (#34)
1 parent e4cad2f commit 83d4131

18 files changed

Lines changed: 978 additions & 69 deletions

.husky/pre-commit

100755100644
File mode changed.

demo/index.html

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@
4545
<button id="toggle-receipts">Toggle receipts</button>
4646
<button id="clear-updates">Clear updates</button>
4747
<button id="toggle-plans">Toggle plans</button>
48-
<button id="toggle-editable-perms">Toggle Editable Permissions</button>
48+
<button id="toggle-prefilled-email">Toggle pre-filled email</button>
49+
<button id="toggle-edit-payment-method">Toggle edit payment method</button>
50+
51+
<br>
52+
<input type="checkbox" id="force-successful-requests" name="force-successful-requests">
53+
<label for="force-successful-requests">Force successful requests</label>
4954

5055
<p id="interaction-status-area" style="min-height: 50px;margin: 10px;"></p>
5156
</div>
@@ -59,11 +64,16 @@
5964
max-width: 800px;
6065
}
6166
</style>
62-
<ia-monthly-giving-circle canEdit></ia-monthly-giving-circle>
67+
<ia-monthly-giving-circle
68+
canEditPaymentMethod
69+
braintreeAuthToken="sandbox_x634jsj7_7zybks4ybp63pbmd"
70+
></ia-monthly-giving-circle>
6371
</div>
6472

6573
<script type="module" src="../dist/src/monthly-giving-circle.js"></script>
6674
<script type="module">
75+
import { PaymentClients, HostingEnvironment } from '@internetarchive/donation-form';
76+
import { LazyLoaderService } from 'https://esm.archive.org/@internetarchive/lazy-loader-service';
6777
import { MonthlyPlan, Receipt } from '../dist/index.js';
6878

6979
let updateNotices = [];
@@ -256,23 +266,57 @@
256266
}),
257267
];
258268

269+
const paymentConfig = {
270+
referrer: '',
271+
origin: '',
272+
braintreeAuthToken: 'sandbox_x634jsj7_7zybks4ybp63pbmd',
273+
venmoProfileId: '1953896702662410263',
274+
googlePayMerchantId: '',
275+
environment: 'dev',
276+
paymentClients: new PaymentClients(
277+
new LazyLoaderService(),
278+
HostingEnvironment.Development,
279+
),
280+
endpointManager: {
281+
// eslint-disable-next-line arrow-body-style, @typescript-eslint/no-unused-vars
282+
submitData: async (_request) => {
283+
// eslint-disable-next-line no-debugger
284+
debugger;
285+
},
286+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
287+
donationSuccessful: (_response) => {
288+
// eslint-disable-next-line no-debugger
289+
debugger;
290+
},
291+
},
292+
};
293+
259294

260295
let showReceipts = true;
261296

262297
const uxMessageInfoArea = document.getElementById('interaction-status-area');
263298
const coinFlip = () => Math.floor(Math.random() + 0.5);
264299

300+
const flipCoin = () => {
301+
const forceSuccess = document.getElementById('force-successful-requests').checked;
302+
if (forceSuccess) {
303+
return 1;
304+
}
305+
return coinFlip();
306+
}
307+
265308
const mgcComponent = document.querySelector('ia-monthly-giving-circle');
266309

267310
// load start data
311+
mgcComponent.paymentConfig = paymentConfig;
268312
mgcComponent.receipts = receiptsData;
269313
mgcComponent.plans = plansArray;
270314

271315
// event handlers
272316
mgcComponent.addEventListener('EmailReceiptRequest', (e) => {
273317

274318
const { donation } = e.detail;
275-
const heads = coinFlip() === 1;
319+
const heads = flipCoin() === 1;
276320
const successOrFail = heads ? 'success' : 'fail';
277321
const returnTiming = heads ? 1500 : 5000;
278322

@@ -314,7 +358,7 @@
314358
const { plan, amountOptions } = e.detail;
315359

316360
// either error or succeed
317-
const heads = coinFlip() === 1;
361+
const heads = flipCoin() === 1;
318362
const successOrFail = heads ? 'success' : 'fail';
319363
const returnTiming = heads ? 1500 : 5000;
320364

@@ -348,7 +392,7 @@
348392
const { newDate, plan } = e.detail;
349393

350394

351-
const heads = coinFlip() === 1;
395+
const heads = flipCoin() === 1;
352396
const successOrFail = heads ? 'success' : 'fail';
353397
const returnTiming = heads ? 1500 : 5000;
354398

@@ -378,6 +422,39 @@
378422
}, returnTiming);
379423
});
380424

425+
mgcComponent.addEventListener('UpdatePaymentMethod', (e) => {
426+
const { plan, newPaymentMethodRequest } = e.detail;
427+
428+
429+
const heads = flipCoin() === 1;
430+
const successOrFail = heads ? 'success' : 'fail';
431+
const returnTiming = heads ? 1500 : 4000;
432+
433+
uxMessageInfoArea.innerText = `Updating payment method for plan: ${plan ? plan.id : 'no plan'} -- Update will return ${successOrFail} in ${returnTiming} ms`;
434+
const message = successOrFail === 'success' ? 'Payment method updated' : 'Payment method failed to update';
435+
436+
if (heads && plan) {
437+
console.log('demo - setting new payment method', newPaymentMethodRequest);
438+
plan.setNewPaymentMethod(newPaymentMethodRequest);
439+
}
440+
441+
const update = {
442+
message,
443+
status: successOrFail,
444+
plan,
445+
donationId: plan ? plan.id : null,
446+
action: 'paymentMethodUpdate'
447+
};
448+
449+
updateNotices = [update, ...updateNotices];
450+
451+
setTimeout(() => {
452+
mgcComponent.updateReceived(update);
453+
console.log('Amount Update Request --- index.html ----', update);
454+
uxMessageInfoArea.innerText = '';
455+
}, returnTiming);
456+
});
457+
381458
// options hooks
382459
document.getElementById('toggle-receipts').addEventListener('click', async () => {
383460
if (showReceipts) {
@@ -396,12 +473,17 @@
396473
await mgcComponent.updateComplete;
397474
});
398475
document.getElementById('toggle-plans').addEventListener('click', async () => {
399-
mgcComponent.plans = mgcComponent.plans.length ? [] : plansArray;
476+
mgcComponent.plans = mgcComponent.plans .length ? [] : plansArray;
400477
await mgcComponent.updateComplete;
401478
});
402479

403-
document.getElementById('toggle-editable-perms').addEventListener('click', async () => {
404-
mgcComponent.canEdit = !mgcComponent.canEdit;
480+
document.getElementById('toggle-prefilled-email').addEventListener('click', async () => {
481+
mgcComponent.patronEmail = mgcComponent.patronEmail ? '' : 'you@thisemail.com';
482+
await mgcComponent.updateComplete;
483+
});
484+
485+
document.getElementById('toggle-edit-payment-method').addEventListener('click', async () => {
486+
mgcComponent.canEditPaymentMethod = !mgcComponent.canEditPaymentMethod;
405487
await mgcComponent.updateComplete;
406488
});
407489
</script>

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { Receipt } from './src/models/receipt';
44
export type { AReceipt } from './src/models/receipt';
55
export { MonthlyPlan } from './src/models/plan';
66
export type { Plan, BtData } from './src/models/plan';
7+
export type { PaymentMethodRequest } from './src/models/payment-method-request';

package-lock.json

Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
"@internetarchive/donation-form-data-models": "^0.3.5",
3232
"@internetarchive/donation-form-section": "^0.3.6",
3333
"@internetarchive/iaux-notification-toast": "^0.0.0-alpha2",
34+
"@internetarchive/icon-calendar": "^1.3.4",
35+
"@internetarchive/icon-credit-card": "^1.3.4",
3436
"@internetarchive/icon-donate": "^1.3.4",
37+
"@internetarchive/icon-lock": "^1.3.4",
38+
"@internetarchive/lazy-loader-service": "^0.2.0",
39+
"@types/braintree": "^3.4.0",
3540
"lit": "^2.8.0"
3641
},
3742
"devDependencies": {

src/edit-plan-form.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
import { LitElement, html } from 'lit';
2-
import { customElement, property } from 'lit/decorators.js';
2+
import { customElement, property, query } from 'lit/decorators.js';
33

4+
import '@internetarchive/donation-form/dist/src/form-elements/badged-input';
5+
6+
import { HostingEnvironment } from '@internetarchive/donation-form';
7+
import type { PaymentConfig } from './form-sections/parts/braintree-manager';
48
import type { MonthlyPlan } from './models/plan';
59
import './form-sections/amount';
610
import './form-sections/date';
711
import './form-sections/cancel';
12+
import './form-sections/payment-method';
813
import type { MGCEditPlanAmount } from './form-sections/amount';
914
import type { MGCEditPlanDate } from './form-sections/date';
15+
import type { MGCEditPaymentMethod } from './form-sections/payment-method';
1016

1117
@customElement('ia-mgc-edit-plan')
1218
export class IauxEditPlanForm extends LitElement {
1319
@property({ type: Object }) plan?: MonthlyPlan;
1420

21+
@property({ type: String }) patronEmail: string = '';
22+
23+
@property({ type: Boolean }) canEditPaymentMethod: boolean = false;
24+
1525
@property({ type: Object }) updateAmountHandler?: (
1626
plan: MonthlyPlan,
1727
amountUpdates: {
@@ -22,10 +32,36 @@ export class IauxEditPlanForm extends LitElement {
2232
},
2333
) => void;
2434

35+
@property({ type: Object }) paymentConfig: PaymentConfig = {
36+
referrer: '',
37+
origin: '',
38+
braintreeAuthToken: '',
39+
venmoProfileId: '',
40+
googlePayMerchantId: '',
41+
environment: HostingEnvironment.Development,
42+
paymentClients: undefined,
43+
endpointManager: undefined,
44+
};
45+
46+
@query('#braintree-creditcard') braintreeNumberInput!: HTMLDivElement;
47+
48+
@query('#braintree-expiration') braintreeExpirationDateInput!: HTMLDivElement;
49+
50+
@query('#braintree-cvv') braintreeCVVInput!: HTMLDivElement;
51+
52+
@query('#braintree-error-message') braintreeErrorMessage!: HTMLDivElement;
53+
2554
createRenderRoot() {
2655
return this;
2756
}
2857

58+
paymentMethodUpdates(status: 'success' | 'fail') {
59+
const paymentMethodForm = this.querySelector(
60+
'ia-mgc-edit-payment-method',
61+
) as MGCEditPaymentMethod;
62+
paymentMethodForm!.paymentMethodUpdated(status);
63+
}
64+
2965
amountUpdates(status: 'success' | 'fail') {
3066
const amountForm = this.querySelector(
3167
'ia-mgc-edit-plan-amount',
@@ -55,6 +91,27 @@ export class IauxEditPlanForm extends LitElement {
5591
}
5692
}}
5793
></ia-mgc-edit-plan-amount>
94+
${this.canEditPaymentMethod
95+
? html`
96+
<hr />
97+
<ia-mgc-edit-payment-method
98+
.plan=${this.plan}
99+
.patronEmail=${this.patronEmail}
100+
.paymentConfig=${this.paymentConfig}
101+
@UpdatePaymentMethod=${(e: CustomEvent) => {
102+
const { newPaymentMethodRequest } = e.detail;
103+
if (this.plan) {
104+
this.dispatchEvent(
105+
new CustomEvent('UpdatePaymentMethod', {
106+
detail: { plan: this.plan, newPaymentMethodRequest },
107+
}),
108+
);
109+
}
110+
}}
111+
>
112+
</ia-mgc-edit-payment-method>
113+
`
114+
: ''}
58115
<hr />
59116
<ia-mgc-edit-date
60117
@updateDate=${(e: CustomEvent) => {

src/form-sections/amount.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export class MGCEditPlanAmount extends LitElement {
8181

8282
this.updateStatus = status;
8383
this.updateMessage =
84-
status === 'success' ? 'Amount updated' : 'Failed. Try again.';
84+
status === 'success'
85+
? 'Amount updated'
86+
: 'Failed to update date, please try again';
8587

8688
if (status === 'success') {
8789
this.closeForm();

src/form-sections/cancel.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { customElement, property, query } from 'lit/decorators.js';
1010
import type { MonthlyPlan } from '../models/plan';
1111

1212
import type { MGCButton } from '../presentational/mgc-button';
13-
import '@internetarchive/donation-form/dist/src/form-elements/contact-form/contact-form.js';
1413

1514
import '@internetarchive/donation-form-section';
1615

src/form-sections/date.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,10 @@ export class MGCEditPlanDate extends LitElement {
212212
}
213213

214214
// Check if there has been a donation in the last month
215-
const lastDonationDate = this.plan?.payment.lastBillingDate.date
216-
? new Date(this.plan?.payment.lastBillingDate.date)
217-
: null;
215+
const lastDonationDate =
216+
this.plan?.payment && this.plan.payment.lastBillingDate?.date
217+
? new Date(this.plan.payment.lastBillingDate.date)
218+
: null;
218219

219220
if (lastDonationDate) {
220221
const lastDonationMonth = lastDonationDate.getMonth();

0 commit comments

Comments
 (0)