LP1904036 Billing; credit card form
authorBill Erickson <berickxx@gmail.com>
Mon, 8 Mar 2021 19:50:49 +0000 (14:50 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:25 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts
Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts
Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts [new file with mode: 0644]

index 13697c6..8ab8da8 100644 (file)
@@ -7,9 +7,11 @@
 </eg-alert-dialog>
 <eg-confirm-dialog #warnPayDialog
   i18n-dialogBody i18n-dialogTitle dialogTitle="Confirm Payment Amount"
-  dialogBody="Are you sure you want to apply a payment of {{payAmount | currency}}?">
+  dialogBody="Are you sure you want to apply a payment of {{paymentAmount | currency}}?">
 </eg-confirm-dialog>
 
+<eg-credit-card-dialog [patron]="patron()" #creditCardDialog>
+</eg-credit-card-dialog>
 
 <!-- SUMMARY  -->
 
     </div>
     <div class="ml-2"><label for="pay-amount" i18n>Payment Received:</label></div>
     <div class="ml-1">
-      <input type="number" class="form-control" [(ngModel)]="payAmount"
+      <input type="number" class="form-control" [(ngModel)]="paymentAmount"
         id="pay-amount" [min]="0"/>
     </div>
     <div class="ml-2 form-check form-check-inline">
index 8279c01..bea0bf2 100644 (file)
@@ -18,6 +18,8 @@ import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
 import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {CreditCardDialogComponent, CreditCardPaymentParams
+    } from '@eg/staff/share/circ/credit-card-dialog.component';
 
 interface BillGridEntry extends CircDisplayInfo {
     xact: IdlObject // mbt
@@ -57,12 +59,13 @@ export class BillsComponent implements OnInit, AfterViewInit {
     sessionVoided = 0;
     paymentType = 'cash_payment';
     checkNumber: string;
-    payAmount: number;
+    paymentAmount: number;
     annotatePayment = false;
     annotation: string;
     entries: BillGridEntry[];
     convertChangeToCredit = false;
     receiptOnPayment = false;
+    ccPaymentParams: CreditCardPaymentParams;
 
     maxPayAmount = 100000;
     warnPayAmount = 1000;
@@ -74,6 +77,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
     @ViewChild('annotateDialog') private annotateDialog: PromptDialogComponent;
     @ViewChild('maxPayDialog') private maxPayDialog: AlertDialogComponent;
     @ViewChild('warnPayDialog') private warnPayDialog: ConfirmDialogComponent;
+    @ViewChild('creditCardDialog') private creditCardDialog: CreditCardDialogComponent;
 
     constructor(
         private router: Router,
@@ -212,7 +216,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
 
     pendingPaymentInfo(): {payment: number, change: number} {
 
-        const amt = this.payAmount || 0;
+        const amt = this.paymentAmount || 0;
 
         if (amt >= this.paidSelected()) {
             const owedSelected = this.owedSelected();
@@ -229,8 +233,8 @@ export class BillsComponent implements OnInit, AfterViewInit {
         if (!this.billGrid) { return true; } // still loading
 
         return (
-            this.payAmount === 0 ||
-            (this.payAmount < 0 && this.paymentType !== 'refund') ||
+            this.paymentAmount === 0 ||
+            (this.paymentAmount < 0 && this.paymentType !== 'refund') ||
             this.billGrid.context.rowSelector.selected().length === 0
         );
     }
@@ -279,17 +283,29 @@ export class BillsComponent implements OnInit, AfterViewInit {
     }
 
     amountExceedsMax(): boolean {
-        if (this.payAmount < this.maxPayAmount) { return false; }
+        if (this.paymentAmount < this.maxPayAmount) { return false; }
         this.maxPayDialog.open().toPromise().then(_ => this.focusPayAmount());
         return true;
     }
 
     addCcArgs(): Promise<any> {
-        return null;
+        this.ccPaymentParams = {};
+
+        if (this.paymentType !== 'credit_card_payment') {
+            return Promise.resolve();
+        }
+
+        return this.creditCardDialog.open().toPromise().then(ccArgs => {
+            if (ccArgs) {
+                this.ccPaymentParams = ccArgs;
+            } else {
+                return Promise.reject('CC dialog canceled');
+            }
+        });
     }
 
     verifyPayAmount(): Promise<any> {
-        if (this.payAmount < this.warnPayAmount) {
+        if (this.paymentAmount < this.warnPayAmount) {
             return Promise.resolve();
         }
 
index 3cf3790..20fc9b9 100644 (file)
@@ -9,6 +9,7 @@ import {ClaimsReturnedDialogComponent} from './claims-returned-dialog.component'
 import {CircComponentsComponent} from './components.component';
 import {CircEventsComponent} from './events-dialog.component';
 import {AddBillingDialogComponent} from './billing-dialog.component';
+import {CreditCardDialogComponent} from './credit-card-dialog.component';
 
 @NgModule({
     declarations: [
@@ -18,6 +19,7 @@ import {AddBillingDialogComponent} from './billing-dialog.component';
         PrecatCheckoutDialogComponent,
         ClaimsReturnedDialogComponent,
         CircEventsComponent,
+        CreditCardDialogComponent,
         AddBillingDialogComponent
     ],
     imports: [
@@ -27,7 +29,8 @@ import {AddBillingDialogComponent} from './billing-dialog.component';
     exports: [
         CircGridComponent,
         CircComponentsComponent,
-        AddBillingDialogComponent
+        AddBillingDialogComponent,
+        CreditCardDialogComponent
     ],
     providers: [
         CircService
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html
new file mode 100644 (file)
index 0000000..9176a4f
--- /dev/null
@@ -0,0 +1,115 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>Credit Card Information</h4>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body" *ngIf="args">
+
+    <div class="card">
+      <div class="card-header" i18n>Credit Card Info</div>
+      <div class="card-body form-validated">
+        <div class="row">
+          <div class="col-lg-4"><label i18n>Process Where</label></div>
+          <div class="col-lg-8">
+            <select class="form-control" [(ngModel)]="args.where_process">
+              <option [value]='1' [disabled]="!supportsExternal" i18n>
+                Process payment through Evergreen
+              </option>
+              <option [value]='0' i18n>
+                Record externally processed payment
+              </option>
+            </select>
+          </div>
+        </div>
+        <ng-container *ngIf="args.where_process == 1">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Approval Code</label></div>
+            <div class="col-lg-8">
+              <input type="text" class="form-control" 
+                required [(ngModel)]="args.approval_code"/>
+            </div>
+          </div>
+        </ng-container>
+        <ng-container *ngIf="args.where_process == 0">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Expire Month</label></div>
+            <div class="col-lg-8">
+              <input type="number" class="form-control" [min]="1"
+                required [(ngModel)]="args.expire_month"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Expire Year</label></div>
+            <div class="col-lg-8">
+              <input type="number" class="form-control" [min]="thisYear"
+                required [(ngModel)]="args.expire_year"/>
+            </div>
+          </div>
+        </ng-container>
+      </div>
+    </div>
+
+    <div class="card mt-2">
+      <div class="card-header" i18n>Optional Fields</div>
+      <div class="card-body form-validated">
+        <div class="row">
+          <div class="col-lg-4"><label i18n>Billing Name (first)</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.billing_first"/>
+          </div>
+        </div>
+        <div class="row mt-2">
+          <div class="col-lg-4"><label i18n>Billing Name (last)</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.billing_last"/>
+          </div>
+        </div>
+
+        <ng-container *ngIf="args.where_process == 0">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Address</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_address"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>City, town or village</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_city"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>State or province</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_state"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>ZIP or postal code</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_zip"/>
+            </div>
+          </div>
+        </ng-container>
+
+        <div class="row mt-2">
+          <div class="col-lg-4"><label i18n>Note</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.note"/>
+          </div>
+        </div>
+
+      </div>
+    </div>
+
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" [disabled]="!saveable()"
+      (click)="submit(args)" i18n>Submit</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts
new file mode 100644 (file)
index 0000000..70965a7
--- /dev/null
@@ -0,0 +1,107 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {CircService} from './circ.service';
+
+export interface CreditCardPaymentParams {
+    where_process?: 0 | 1,
+    approval_code?: string,
+    expire_month?: number,
+    expire_year?: number,
+    billing_first?: string,
+    billing_last?: string,
+    billing_address?: string,
+    billing_city?: string,
+    billing_state?: string,
+    billing_zip?: string,
+    note?: string
+}
+
+/* Dialog for collecting credit card payment information */
+
+@Component({
+  selector: 'eg-credit-card-dialog',
+  templateUrl: 'credit-card-dialog.component.html'
+})
+
+export class CreditCardDialogComponent
+    extends DialogComponent implements OnInit {
+
+    @Input() patron: IdlObject; // au, fleshed with billing address
+    args: CreditCardPaymentParams;
+    supportsExternal: boolean;
+    thisYear = new Date().getFullYear();
+
+    constructor(
+        private modal: NgbModal,
+        private toast: ToastService,
+        private net: NetService,
+        private idl: IdlService,
+        private evt: EventService,
+        private pcrud: PcrudService,
+        private circ: CircService,
+        private org: OrgService,
+        private serverStore: ServerStoreService,
+        private auth: AuthService) {
+        super(modal);
+    }
+
+    ngOnInit() {
+
+        this.onOpen$.subscribe(_ => {
+
+            this.args = {
+                billing_first: this.patron.first_given_name(),
+                billing_last: this.patron.family_name(),
+            };
+
+            const addr =
+                this.patron.billing_address() || this.patron.mailing_address();
+
+            if (addr) {
+                this.args.billing_address = addr.street1() +
+                    (addr.street2() ? ' ' + addr.street2() : '');
+                this.args.billing_city = addr.city();
+                this.args.billing_state = addr.state();
+                this.args.billing_zip = addr.post_code();
+            }
+
+            this.supportsExternal = false;
+
+            this.serverStore.getItem('credit.processor.default')
+            .then(processor => {
+                if (processor && processor !== 'Stripe') {
+                    this.supportsExternal = true;
+                    this.args.where_process = 1;
+                }
+            })
+        });
+    }
+
+    saveable(): boolean {
+        if (!this.args) { return false; }
+
+        if (this.args.where_process === 0) {
+            return Boolean(this.args.approval_code);
+        }
+
+        return Boolean(this.args.expire_month) && Boolean(this.args.expire_year);
+    }
+
+
+    submit() {
+    }
+}
+