Merge branch 'master' into dev

This commit is contained in:
Rene Vergara 2023-01-27 12:25:45 -06:00
commit 0af45e52ee
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
49 changed files with 6405 additions and 3013 deletions

View file

@ -3,7 +3,73 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [1.4.1] - 2023-01-09
### Added
- Display app version
### Changed
- Copyright year
### Fixed
- Remove unnecessary logging in `fullnode.service.ts`.
## [1.4.0] - 2023-01-09
### Added
- Support for WooComerce:
- New tab in Settings to generate authentication token.
- Display of WooCommerce credentials for configuration.
- New service to interact with WooCommerce-related API endpoints.
- A "Return To Shop" button added to ZGo Invoice component
## [1.3.2] - 2022-10-11
### Added
- New component added to export orders in CSV format. Allows users to download orders.
## [1.3.1] - 2022-10-08
### Fixed
- Bug [#7](https://gitlab.com/pitmutt/zgo/-/issues/7) for saving a viewing key.
## [1.3.0] - 2022-10-01
### Added
- Added new connection for Xero account code
- Added new service for Xero integration
### Changed
- Login updated to price sessions in USD and include the Pro service.
- Settings component updated for compatibility with Android devices
- Settings component updated to use observable when saving Account Code
- xeroService's saveAccountCode function optimized to export observable
- Field for Xero's AccountCode added to Settings component's integration tab
- Listorders component updated to show date in ANSI international format.
- Settings component updated to use owner's invoices field to control
integrations tab (Pro version)
- Orders list updated to show payment confirmation only when service is
activated and a viewing key exists.
- Updated Order and Owner model to include new Xero integration fields
## [1.2.2] - 2022-08-05
### Added
- Convenience buttons on checkout for wallets that are not ZIP-321-compliant
- PmtService Component first alpha version ready for testing
### Fixed
- Memo for checkout orders
## Added ## Added
@ -47,6 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed ### Fixed
- Fixed order memo for checkout
- Fixed display of amounts in item list when using *zatoshis* - Fixed display of amounts in item list when using *zatoshis*
- Fixed sorting of items in list - Fixed sorting of items in list
- Fixed sorting of orders in list - Fixed sorting of orders in list

View file

@ -39,8 +39,8 @@
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "500kb", "maximumWarning": "5mb",
"maximumError": "1mb" "maximumError": "10mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",

6934
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "zgo", "name": "zgo",
"version": "1.2.0", "version": "1.4.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
@ -10,16 +10,17 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.5", "@angular-material-components/datetime-picker": "^8.0.0",
"@angular/cdk": "^13.3.9", "@angular/animations": "^14.2.5",
"@angular/common": "^14.0.5", "@angular/cdk": "^14.2.4",
"@angular/compiler": "^14.0.5", "@angular/common": "^14.2.5",
"@angular/core": "^14.0.5", "@angular/compiler": "^14.2.5",
"@angular/forms": "^14.0.5", "@angular/core": "^14.2.5",
"@angular/material": "^13.3.9", "@angular/forms": "^14.2.5",
"@angular/platform-browser": "^14.0.5", "@angular/material": "^14.2.4",
"@angular/platform-browser-dynamic": "^14.0.5", "@angular/platform-browser": "^14.2.5",
"@angular/router": "^14.0.5", "@angular/platform-browser-dynamic": "^14.2.5",
"@angular/router": "^14.2.5",
"@fortawesome/angular-fontawesome": "^0.10.2", "@fortawesome/angular-fontawesome": "^0.10.2",
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.1.1",
"@fortawesome/fontawesome-svg-core": "^6.1.0", "@fortawesome/fontawesome-svg-core": "^6.1.0",
@ -28,24 +29,28 @@
"@fortawesome/free-solid-svg-icons": "^6.1.0", "@fortawesome/free-solid-svg-icons": "^6.1.0",
"@supercharge/request-ip": "^1.1.2", "@supercharge/request-ip": "^1.1.2",
"angular-local-storage": "^0.7.1", "angular-local-storage": "^0.7.1",
"angular-material-datepicker": "^1.0.2",
"async": "^3.2.2", "async": "^3.2.2",
"coingecko-api": "^1.0.10", "coingecko-api": "^1.0.10",
"easyqrcodejs": "^4.4.6", "easyqrcodejs": "^4.4.6",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"mongoose": "^6.0.13", "mongoose": "^6.0.13",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"sha.js": "^2.4.11",
"stdrpc": "^1.3.0", "stdrpc": "^1.3.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"urlsafe-base64": "^1.0.0", "urlsafe-base64": "^1.0.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"xero-node": "^4.23.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.0.5", "@angular-devkit/build-angular": "^14.2.5",
"@angular/cli": "^14.0.6", "@angular/cli": "^14.2.5",
"@angular/compiler-cli": "^14.0.5", "@angular/compiler-cli": "^14.2.5",
"@types/jasmine": "~3.8.0", "@types/jasmine": "~3.8.0",
"@types/node": "^12.20.33", "@types/node": "^12.20.33",
"@types/request": "^2.48.8",
"@types/urlsafe-base64": "^1.0.28", "@types/urlsafe-base64": "^1.0.28",
"@types/uuid": "^8.3.1", "@types/uuid": "^8.3.1",
"jasmine-core": "~3.8.0", "jasmine-core": "~3.8.0",

View file

@ -9,6 +9,7 @@ import { ListOrdersComponent } from './listorders/listorders.component';
import { AuthGuardService } from './auth-guard.service'; import { AuthGuardService } from './auth-guard.service';
import { NodeResolverService } from './node-resolver.service'; import { NodeResolverService } from './node-resolver.service';
import { PmtserviceComponent } from './pmtservice/pmtservice.component'; import { PmtserviceComponent } from './pmtservice/pmtservice.component';
import { XeroRegComponent } from './xeroreg/xeroreg.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: LoginComponent, resolve: { response: NodeResolverService} }, { path: '', component: LoginComponent, resolve: { response: NodeResolverService} },
@ -19,6 +20,7 @@ const routes: Routes = [
{ path: 'receipt/:orderId', component: ReceiptComponent}, { path: 'receipt/:orderId', component: ReceiptComponent},
{ path: 'invoice/:orderId', component: InvoiceComponent}, { path: 'invoice/:orderId', component: InvoiceComponent},
{ path: 'pmtservice', component: PmtserviceComponent}, { path: 'pmtservice', component: PmtserviceComponent},
{ path: 'xeroauth', component: XeroRegComponent},
{ path: 'login', component: LoginComponent, resolve: { response: NodeResolverService}} { path: 'login', component: LoginComponent, resolve: { response: NodeResolverService}}
]; ];

View file

@ -3,6 +3,7 @@
</main> </main>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="footer" align="center"> <div class="footer" align="center">
<p>&copy; 2022 Vergara Technologies LLC</p> <p>&copy; 2023 Vergara Technologies LLC</p>
<p class="tiny">Version 1.4.1</p>
<p class="tiny">Price data provided by CoinGecko API</p> <p class="tiny">Price data provided by CoinGecko API</p>
</div> </div>

View file

@ -17,6 +17,9 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatStepperModule } from '@angular/material/stepper'; import { MatStepperModule } from '@angular/material/stepper';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -46,6 +49,8 @@ import { PromptInvoiceComponent } from './prompt-invoice/prompt-invoice.componen
import { PromptReceiptComponent } from './prompt-receipt/prompt-receipt.component'; import { PromptReceiptComponent } from './prompt-receipt/prompt-receipt.component';
import { NotifierComponent } from './notifier/notifier.component'; import { NotifierComponent } from './notifier/notifier.component';
import { PmtserviceComponent } from './pmtservice/pmtservice.component'; import { PmtserviceComponent } from './pmtservice/pmtservice.component';
import { XeroRegComponent } from './xeroreg/xeroreg.component';
import { DbExportComponent } from './db-export/db-export.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -73,7 +78,9 @@ import { PmtserviceComponent } from './pmtservice/pmtservice.component';
PromptInvoiceComponent, PromptInvoiceComponent,
PromptReceiptComponent, PromptReceiptComponent,
NotifierComponent, NotifierComponent,
PmtserviceComponent PmtserviceComponent,
XeroRegComponent,
DbExportComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -96,6 +103,9 @@ import { PmtserviceComponent } from './pmtservice/pmtservice.component';
MatAutocompleteModule, MatAutocompleteModule,
MatSlideToggleModule, MatSlideToggleModule,
MatSnackBarModule, MatSnackBarModule,
MatTabsModule,
MatDatepickerModule,
MatNativeDateModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FontAwesomeModule FontAwesomeModule
], ],

View file

@ -1,10 +1,9 @@
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { UntypedFormBuilder, Validators, UntypedFormGroup, FormControl } from '@angular/forms'; import { UntypedFormBuilder, Validators, UntypedFormGroup } from '@angular/forms';
import { MatDialog, MatDialogConfig} from '@angular/material/dialog'; import { MatDialog, MatDialogConfig} from '@angular/material/dialog';
import { ProgressBarMode } from '@angular/material/progress-bar'; import { ProgressBarMode } from '@angular/material/progress-bar';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { filter, startWith, map, switchMap } from 'rxjs/operators';
import { MatStepper } from '@angular/material/stepper'; import { MatStepper } from '@angular/material/stepper';
import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { Country } from '../country.model'; import { Country } from '../country.model';
@ -12,7 +11,6 @@ import { Owner } from '../owner.model';
import { User } from '../user.model'; import { User } from '../user.model';
import { UserService } from '../user.service'; import { UserService } from '../user.service';
import { FullnodeService } from '../fullnode.service'; import { FullnodeService } from '../fullnode.service';
import { SearchOptionsPipe } from '../searchoptions.pipe';
import { ScanComponent } from '../scan/scan.component'; import { ScanComponent } from '../scan/scan.component';
import { TermsComponent } from '../terms/terms.component'; import { TermsComponent } from '../terms/terms.component';
@ -26,16 +24,20 @@ export class BusinessComponent implements OnInit {
@ViewChild('stepper', { static: false}) stepper: MatStepper|undefined; @ViewChild('stepper', { static: false}) stepper: MatStepper|undefined;
intervalHolder: any; intervalHolder: any;
nodeAddress: string = ''; nodeAddress: string = '';
zecPrice: number = 1;
tickets = [ tickets = [
{ {
value: 0.005, value: 1,
viewValue: '1 day: 0.005 ZEC' viewValue: '1 day: USD $1'
},{ },{
value: 0.025, value: 6,
viewValue: '1 week: 0.025 ZEC' viewValue: '1 week: USD $6'
},{ },{
value: 0.1, value: 22,
viewValue: '1 month: 0.1 ZEC' viewValue: '1 month: USD $22'
},{
value: 30,
viewValue: '1 month Pro: USD $30'
} }
]; ];
bizForm: UntypedFormGroup; bizForm: UntypedFormGroup;
@ -74,6 +76,7 @@ export class BusinessComponent implements OnInit {
public ownerUpdate: Observable<Owner>; public ownerUpdate: Observable<Owner>;
public addrUpdate: Observable<string>; public addrUpdate: Observable<string>;
public userUpdate: Observable<User>; public userUpdate: Observable<User>;
public priceUpdate: Observable<number>;
sessionId = ''; sessionId = '';
ownerKnown = false; ownerKnown = false;
termsChecked = false; termsChecked = false;
@ -85,6 +88,10 @@ export class BusinessComponent implements OnInit {
private dialog: MatDialog, private dialog: MatDialog,
private router: Router private router: Router
) { ) {
this.priceUpdate = fullnodeService.priceUpdate;
this.priceUpdate.subscribe(priceInfo => {
this.zecPrice = priceInfo;
});
this.countriesUpdate = userService.countriesUpdate; this.countriesUpdate = userService.countriesUpdate;
this.ownerUpdate = userService.ownerUpdate; this.ownerUpdate = userService.ownerUpdate;
this.userUpdate = userService.userUpdate; this.userUpdate = userService.userUpdate;
@ -189,7 +196,7 @@ export class BusinessComponent implements OnInit {
dialogConfig.disableClose = true; dialogConfig.disableClose = true;
dialogConfig.autoFocus = true; dialogConfig.autoFocus = true;
dialogConfig.data = { dialogConfig.data = {
totalZec: this.payForm.get('session')!.value, totalZec: (this.payForm.get('session')!.value)/this.zecPrice,
addr: this.nodeAddress, addr: this.nodeAddress,
session: this.sessionId, session: this.sessionId,
pay: true pay: true

View file

@ -1,4 +1,5 @@
<div class="container" style="margin-top: 10px;"> <div class="container" style="font-family: 'Spartan', sans-serif;
margin-top: 10px;">
<div class="askPayment"> <div class="askPayment">
Scan to make payment Scan to make payment
@ -14,7 +15,7 @@
</tr> </tr>
</table> </table>
<mat-dialog-actions> <div style="margin-top: 10px;">
<table cellspacing="0" <table cellspacing="0"
width="100%"> width="100%">
<tr> <tr>
@ -32,6 +33,31 @@
</td> </td>
</tr> </tr>
</table> </table>
</mat-dialog-actions> </div>
<div style="text-align: center;
margin-top: 10px;
line-height: 30px;">
Can't scan?<br>Use this <a [href]="zcashUrl">wallet link</a>, or
<div style="display: flex;
justify-content: space-between;">
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAddress()">Copy Address</button>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAmount()">Copy Amount</button>
</div>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyMemo()">Copy Memo</button>
</div>
</div> </div>

View file

@ -2,6 +2,8 @@ import { Inject, Component, OnInit, ViewEncapsulation} from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { NotifierService } from '../notifier.service';
var QRCode = require('easyqrcodejs'); var QRCode = require('easyqrcodejs');
var URLSafeBase64 = require('urlsafe-base64'); var URLSafeBase64 = require('urlsafe-base64');
var Buffer = require('buffer/').Buffer; var Buffer = require('buffer/').Buffer;
@ -22,13 +24,14 @@ export class CheckoutComponent implements OnInit{
constructor( constructor(
private dialogRef: MatDialogRef<CheckoutComponent>, private dialogRef: MatDialogRef<CheckoutComponent>,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
@Inject(MAT_DIALOG_DATA) public data: { totalZec: number, addr: string, orderId: string} @Inject(MAT_DIALOG_DATA) public data: { totalZec: number, addr: string, orderId: string},
) { private notifierService : NotifierService ) {
console.log("Entra a Constructor") console.log("Entra a Constructor")
this.address = data.addr; this.address = data.addr;
this.total = data.totalZec; this.total = data.totalZec;
this.orderId = data.orderId; this.orderId = data.orderId;
this.codeString = `zcash:${this.address}?amount=${this.total.toFixed(6)}&memo=${URLSafeBase64.encode(Buffer.from('Z-Go Order '.concat(this.orderId)))}`; this.codeString = `zcash:${this.address}?amount=${this.total.toFixed(8)}&memo=${URLSafeBase64.encode(Buffer.from('ZGo Order::'.concat(this.orderId)))}`;
this.zcashUrl = this.sanitizer.bypassSecurityTrustUrl(this.codeString); this.zcashUrl = this.sanitizer.bypassSecurityTrustUrl(this.codeString);
} }
@ -37,8 +40,8 @@ export class CheckoutComponent implements OnInit{
{ {
text: this.codeString, text: this.codeString,
logo: "/assets/zcash.png", logo: "/assets/zcash.png",
width: 220, width: 230,
height: 220, height: 230,
logoWidth: 60, logoWidth: 60,
logoHeight: 60, logoHeight: 60,
correctLevel: QRCode.CorrectLevel.H correctLevel: QRCode.CorrectLevel.H
@ -54,4 +57,48 @@ export class CheckoutComponent implements OnInit{
close() { close() {
this.dialogRef.close(false); this.dialogRef.close(false);
} }
copyAddress() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.address);
} catch (err) {
this.notifierService
.showNotification("Error copying address","Close","error");
// console.error("Error", err);
}
}
copyAmount() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.total.toString());
} catch (err) {
this.notifierService
.showNotification("Error while copying ammount","Close","error");
// console.error("Error", err);
}
}
copyMemo() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText("ZGo Order::" + this.orderId);
} catch (err) {
this.notifierService
.showNotification("Error while copying Memo","Close","error");
// console.error("Error", err);
}
}
} }

View file

@ -0,0 +1,68 @@
* {
font-family: 'Spartan', sans-serif;
font-size: 11px;
}
.description {
padding-top: 30px;
padding-bottom: 20px;
font-size: 14px;
font-weight: 700;
text-align: center;
}
.datepicker {
border-color: dimgray;
border-width: 3px;
border-radius: 8px;
background-color: white;
}
.noorders {
font-size: 14px;
font-weight: 700;
text-align: center;
padding-top: 20px;
padding-bottom: 20px;
border-color: dimgray;
border-width: 3px;
border-radius: 8px;
background-color: #f9e79f;
}
.settings-title {
font-family: 'Spartan', sans-serif;
background: #ff5722;
color: white;
font-size: 30px;
text-align: center;
padding: 5px;
}
.daterange {
font-size: 13px;
font-weight: 700;
padding-bottom: 15px;
}
.downloadbtn {
min-width: 120px;
max-width: 150px;
height: 25px;
border-color: dimgray;
border-width: 3px;
border-radius: 8px;
box-shadow: lightgray;
font-family: 'Spartan', sans-serif;
background: #ff5722;
color: white;
font-size: 16px;
text-align: center;
padding: 6px;
}
::ng-deep .downloadbtntxt {
color: white;
font-size: 14px;
font-weight: 600;
}

View file

@ -0,0 +1,54 @@
<div class="settings-title">Export Orders</div>
<div class='description'>
Export orders in a .CSV format file
</div>
<div class="datepicker"
*ngIf="ordersOk()">
<mat-form-field appearance="fill">
<!--
<mat-label >Enter a date range</mat-label>
-->
<div class="daterange">Date range:</div>
<mat-date-range-input [formGroup]="range" [rangePicker]="picker">
<input matStartDate formControlName="start" placeholder="Start date">
<input matEndDate formControlName="end" placeholder="End date">
</mat-date-range-input>
<mat-hint>MM/DD/YYYY MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
<mat-error *ngIf="range.controls.start.hasError('matStartDateInvalid')">Invalid start date</mat-error>
<mat-error *ngIf="range.controls.end.hasError('matEndDateInvalid')">Invalid end date</mat-error>
</mat-form-field>
<br>
<br>
<br>
</div>
<div class="noorders"
*ngIf="!ordersOk()">
<br>
You have no orders created.
<br>
Nothing to do.
<br>
<br>
<br>
</div>
<br>
<br>
<div style="display: flex;
justify-content: space-between;
align-items: center;">
<button mat-raised-button
(click)="closedbExport()">
Close
</button>
<a mat-raised-button *ngIf="checkReady()" color="primary" [href]="fileUrl"
download="orders.csv">Download</a>
</div>
<div style="height: 20px;
margin-top: 10px;">
</div>

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DbExportComponent } from './db-export.component';
describe('DbExportComponent', () => {
let component: DbExportComponent;
let fixture: ComponentFixture<DbExportComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DbExportComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DbExportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,146 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { DomSanitizer } from '@angular/platform-browser';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { Order } from '../order/order.model';
import { FullnodeService } from '../fullnode.service';
import { UserService } from '../user.service';
import { Owner } from '../owner.model';
import { OrderService } from '../order/order.service';
import { NotifierService } from '../notifier.service';
@Component({
selector: 'app-db-export',
templateUrl: './db-export.component.html',
styleUrls: ['./db-export.component.css']
})
export class DbExportComponent implements OnInit {
public orders: Order[] = [];
public ownerUpdate: Observable<Owner>;
public ordersUpdate: Observable<Order[]>;
fileUrl : any;
owner : Owner = {
address: '',
name: '',
currency: 'usd',
tax: false,
taxValue: 0,
vat: false,
vatValue: 0,
first: '',
last: '',
email: '',
street: '',
city: '',
state: '',
postal: '',
phone: '',
paid: false,
website: '',
country: '',
zats: false,
invoices: false,
expiration: new Date(Date.now()).toISOString(),
payconf: false,
viewkey: '',
crmToken: ''
};
_ordersOk = false;
range = new FormGroup({
start: new FormControl<Date | null>(null),
end: new FormControl<Date | null>(null),
});
constructor(private notifierService : NotifierService,
private dialogRef: MatDialogRef<DbExportComponent>,
private sanitizer: DomSanitizer,
public orderService: OrderService,
public userService: UserService) {
this.ownerUpdate = userService.ownerUpdate;
this.orderService.getAllOrders();
this.ordersUpdate = orderService.allOrdersUpdate;
}
ngOnInit(): void {
console.log('db-export Init -->');
this.owner = this.userService.currentOwner();
console.log(this.owner.name);
console.log(this.range);
this.ordersUpdate.subscribe((orders) => {
this.orders = orders;
// console.log('Order -> ' + this.orders[0].timestamp);
if( this.orders.length != 0 ) {
this._ordersOk = true
}
});
}
ordersOk() : boolean {
return this._ordersOk;
}
checkReady() : boolean {
var data : string = '';
var chkRdy : boolean = false;
if ( (this.range.value.start != null ) &&
(this.range.value.end != null) ) {
// process order list
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 8,
maximumFractionDigits: 8,
});
// create header
data = '"Date","Order ID","Currency","Closed?","Amount","Rate","ZEC","Paid?","Invoice"' + "\n";
var iniDate = new Date(this.range.value.start);
var endDate = new Date(this.range.value.end);
for (let i=0; i < this.orders.length; i++){
var date = new Date(this.orders[i]!.timestamp!);
var orderid = String(this.orders[i]._id);
var closed = this.orders[i].closed ? 'Yes' : 'No';
/*
console.log('Order No. ' +
this.orders[i]._id! + ' - totalZec = ' +
this.orders[i].totalZec);
*/
var paid = this.orders[i].paid ? 'Yes' : 'No';
if ( (date >= iniDate) && (date <= endDate) ) {
data = data +
date.getFullYear() + '-' +
(date.getMonth()+1).toString().padStart(2,'0') + '-' +
date.getDate().toString().padStart(2,'0')
+ ',' +
orderid + ',' +
this.orders[i].currency + ',' +
closed + ',' +
this.orders[i].total + ',' +
this.orders[i].price! + ',' +
this.orders[i].totalZec + ',' +
paid + ',"' +
this.orders[i].externalInvoice + '"' +
'\n';
}
}
const blob = new Blob([data], { type: 'application/octet-stream' });
this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
chkRdy = true;
}
return chkRdy;
}
closedbExport() {
this.dialogRef.close();
}
}

View file

@ -1,6 +1,6 @@
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<span align="center"> <span align="center">
<img class="logo" src="/assets/logo-new-white.png" height="40px" /> <img class="logo" src="/assets/logo-new-white_01.png" height="40px" />
</span> </span>
<span class="spacer"></span> <span class="spacer"></span>
<span align="center"> <span align="center">

View file

@ -88,6 +88,65 @@
</td> </td>
</tr> </tr>
</table> </table>
<div style="height: 15px;"></div>
<div width="100%"
style="font-size: 14px;
font-weight: 700;
font-style: italic;
text-align: center;">
Scan the QR code with your wallet to make payment
</div>
<div style="text-align: center;
margin-top: 10px;
line-height: 30px;">
<div style="font-family: 'Spartan';
font-size: 14px;
line-height: 20px;">
Can't scan?<br>Use this <a [href]="zcashUrl">wallet link</a>, or
</div>
<div style="display: flex;
justify-content: space-between;">
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAddress()">Copy Address</button>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAmount()">Copy Amount</button>
</div>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyMemo()" *ngIf="!isWCOrder">Copy Memo</button>
<div style="display: flex;
justify-content: space-between;"
*ngIf="isWCOrder">
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyMemo()">Copy Memo</button>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightcyan;"
mat-raised-button
(click)="backToShop()" >
<fa-icon style="color: #FB4F14;
margin-bottom: -2px;
margin-right: 5px;
font-size: 20px;
cursor: pointer;"
[icon]="faArrowUpRightFromSquare"> </fa-icon>
Return to Shop</button>
</div>
</div>
</div> </div>
</div> </div>

View file

@ -1,9 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ReceiptService } from '../receipt.service'; import { ReceiptService } from '../receipt.service';
import { Order} from '../order/order.model'; import { Order} from '../order/order.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { faCheck, faHourglass } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faHourglass, faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';
import { NotifierService } from '../notifier.service';
var QRCode = require('easyqrcodejs'); var QRCode = require('easyqrcodejs');
var URLSafeBase64 = require('urlsafe-base64'); var URLSafeBase64 = require('urlsafe-base64');
@ -14,17 +17,22 @@ var Buffer = require('buffer/').Buffer;
templateUrl: './invoice.component.html', templateUrl: './invoice.component.html',
styleUrls: ['./invoice.component.css'] styleUrls: ['./invoice.component.css']
}) })
export class InvoiceComponent implements OnInit { export class InvoiceComponent implements OnInit {
faCheck = faCheck; faCheck = faCheck;
faHourglass = faHourglass; faHourglass = faHourglass;
faArrowUpRightFromSquare = faArrowUpRightFromSquare;
orderId; orderId;
public orderUpdate: Observable<Order>; public orderUpdate: Observable<Order>;
public nameUpdate: Observable<string>; public nameUpdate: Observable<string>;
name: string = ''; name: string = '';
error: boolean = false; error: boolean = false;
codeString: string = 'Test'; codeString: string = 'Test';
public isWCOrder : boolean = false;
zcashUrl: SafeUrl = '';
externalURL: string = '';
order:Order = { order:Order = {
_id: '',
address: '', address: '',
session: '', session: '',
timestamp: '', timestamp: '',
@ -48,9 +56,12 @@ export class InvoiceComponent implements OnInit {
constructor( constructor(
private _ActiveRoute:ActivatedRoute, private _ActiveRoute:ActivatedRoute,
private router: Router, private router: Router,
public receiptService: ReceiptService private sanitizer: DomSanitizer,
public receiptService: ReceiptService,
private notifierService : NotifierService
) { ) {
this.orderId = this._ActiveRoute.snapshot.paramMap.get("orderId"); this.orderId = this._ActiveRoute.snapshot.paramMap.get("orderId");
console.log('constructor - orderId -> ' + this.orderId);
this.orderUpdate = receiptService.orderUpdate; this.orderUpdate = receiptService.orderUpdate;
this.nameUpdate = receiptService.nameUpdate; this.nameUpdate = receiptService.nameUpdate;
receiptService.getOrderById(this.orderId!).subscribe(response => { receiptService.getOrderById(this.orderId!).subscribe(response => {
@ -66,6 +77,7 @@ export class InvoiceComponent implements OnInit {
logoHeight: 50, logoHeight: 50,
correctLevel: QRCode.CorrectLevel.H correctLevel: QRCode.CorrectLevel.H
}); });
this.error = false;
} else { } else {
this.error = true; this.error = true;
this.codeString = 'Test'; this.codeString = 'Test';
@ -73,6 +85,11 @@ export class InvoiceComponent implements OnInit {
}); });
this.orderUpdate.subscribe(order => { this.orderUpdate.subscribe(order => {
this.order = order; this.order = order;
if ( order.session.substring(0,1) == 'W') {
this.isWCOrder = true;
}
this.codeString = `zcash:${this.order.address}?amount=${this.order.totalZec.toFixed(8)}&memo=${URLSafeBase64.encode(Buffer.from('ZGo Order::'.concat(this.orderId!)))}`;
this.zcashUrl = this.sanitizer.bypassSecurityTrustUrl(this.codeString);
}); });
this.nameUpdate.subscribe(name => { this.nameUpdate.subscribe(name => {
this.name = name; this.name = name;
@ -82,6 +99,25 @@ export class InvoiceComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
backToShop() {
if ( this.isWCOrder ) {
// console.log('External Invoice -> ' + this.order.externalInvoice );
const b64URL:string = this.order.externalInvoice.substring(0,this.order.externalInvoice.indexOf("-"));
// console.log('encodedURL -> ' + b64URL );
const shopURL: string = Buffer.from(b64URL, 'base64').toString();
const tmp_orderid = this.order.externalInvoice.substring(this.order.externalInvoice.indexOf('-')+1);
const wc_order_key = tmp_orderid.substring(tmp_orderid.indexOf('-')+1);
const wc_orderid = tmp_orderid.substring(0,tmp_orderid.indexOf('-'));
// console.log('wc_order_id -> ' + wc_orderid);
// console.log('wc_order_key -> ' + wc_order_key);
// console.log('new URL -> ' + shopURL + '/checkout/order-received/' + wc_orderid + '/?key=' + wc_order_key);
if ( shopURL ) {
// console.log('Opening URL....' + shopURL);
window.open( shopURL + '/checkout/order-received/' + wc_orderid + '/?key=' + wc_order_key,"_blank");
}
}
}
getIconStyle(order : Order) { getIconStyle(order : Order) {
if( order.paid ) if( order.paid )
return "font-size: 14px; color: #72cc50; margin-bottom: -2px;"; return "font-size: 14px; color: #72cc50; margin-bottom: -2px;";
@ -89,4 +125,49 @@ export class InvoiceComponent implements OnInit {
} }
copyAddress() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.order.address);
} catch (err) {
this.notifierService
.showNotification("Error copying address","Close","error");
// console.error("Error", err);
}
}
copyAmount() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.order.totalZec.toString());
} catch (err) {
this.notifierService
.showNotification("Error while copying ammount","Close","error");
// console.error("Error", err);
}
}
copyMemo() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText("ZGo Order::" + this.order._id);
} catch (err) {
this.notifierService
.showNotification("Error while copying Memo","Close","error");
// console.error("Error", err);
}
}
} }

View file

@ -97,8 +97,8 @@ img.icon{
} }
.orderListTitle { .orderListTitle {
font-family: 'Roboto Mono'; font-family: 'Roboto Mono' !important;
font-size: 16px; font-size: 14px;
font-weight: 600; font-weight: 600;
} }

View file

@ -1,9 +1,23 @@
<app-header></app-header> <app-header></app-header>
<div align="center"> <div align="center">
<h3 class="text">{{(ownerUpdate | async)!.name}}</h3> <h3 class="text">{{(ownerUpdate | async)!.name}}</h3>
<table >
<tr>
<td width="45%">
<button class="text" mat-raised-button [routerLink]="['/shop']" color="primary"> <button class="text" mat-raised-button [routerLink]="['/shop']" color="primary">
Back to Shop Back to Shop
</button> </button>
</td>
<td width="10%">
</td>
<td width="45%">
<button mat-raised-button color="primary"
class="text" (click)="openDbExport()">
Export Orders
</button>
</td>
</tr>
</table>
</div> </div>
<table class="totalsTbl" width="100%"> <table class="totalsTbl" width="100%">
<tr class="totalsHdr"> <tr class="totalsHdr">
@ -31,12 +45,13 @@
<div class="orderList"> <div class="orderList">
<mat-accordion *ngIf = "orders.length > 0"> <mat-accordion *ngIf = "orders.length > 0">
<mat-expansion-panel *ngFor = "let order of orders"> <mat-expansion-panel *ngFor = "let order of orders">
<mat-expansion-panel-header [collapsedHeight]="'30px'" [expandedHeight]="'30px'" > <mat-expansion-panel-header [collapsedHeight]="'35px'" [expandedHeight]="'30px'" >
<mat-panel-title> <mat-panel-title>
<div class="orderListTitle"> <div class="orderListTitle">
<img src="/assets/zec_rv.png" <img src="/assets/zec_rv.png"
style="height: 16px; style="height: 14px;
margin-bottom: -2px;" margin-bottom: -2px;
padding-right: 3px;"
>{{order.totalZec | number: '1.08'}} >{{order.totalZec | number: '1.08'}}
</div> </div>
</mat-panel-title> </mat-panel-title>
@ -44,10 +59,14 @@
<table width="100%"> <table width="100%">
<tr> <tr>
<td> <td>
<fa-icon [icon]="getIcon(order)" [style]="getIconStyle(order)" ></fa-icon> <fa-icon *ngIf="payConf"
[icon]="getIcon(order)" [style]="getIconStyle(order)" ></fa-icon>
</td> </td>
<td align="center"> <td align="center"
{{order.timestamp | date: 'short'}} style="font-family: 'Roboto Mono' !important;
font-weight: 700 ;
font-size: 14px;">
{{order.timestamp | date: 'YYYY-MM-dd, HH:mm'}}
</td> </td>
</tr> </tr>
</table> </table>

View file

@ -9,6 +9,7 @@ import { OrderService } from '../order/order.service';
import { MatDialog, MatDialogConfig} from '@angular/material/dialog'; import { MatDialog, MatDialogConfig} from '@angular/material/dialog';
import { PromptInvoiceComponent } from '../prompt-invoice/prompt-invoice.component'; import { PromptInvoiceComponent } from '../prompt-invoice/prompt-invoice.component';
import { PromptReceiptComponent } from '../prompt-receipt/prompt-receipt.component'; import { PromptReceiptComponent } from '../prompt-receipt/prompt-receipt.component';
import { DbExportComponent } from '../db-export/db-export.component';
import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; import { faTimesCircle } from '@fortawesome/free-solid-svg-icons';
@ -40,10 +41,36 @@ export class ListOrdersComponent implements OnInit, OnDestroy{
faCheckCircle = faCheckCircle; faCheckCircle = faCheckCircle;
faHourglass = faHourglass; faHourglass = faHourglass;
faTrash = faTrash; faTrash = faTrash;
payConf : boolean = false;
owner : Owner = {
address: '',
name: '',
currency: 'usd',
tax: false,
taxValue: 0,
vat: false,
vatValue: 0,
first: '',
last: '',
email: '',
street: '',
city: '',
state: '',
postal: '',
phone: '',
paid: false,
website: '',
country: '',
zats: false,
invoices: false,
expiration: new Date(Date.now()).toISOString(),
payconf: false,
viewkey: '',
crmToken: ''
};
// ------------------------------------- // -------------------------------------
constructor( constructor(
public orderService: OrderService, public orderService: OrderService,
public userService: UserService, public userService: UserService,
@ -55,13 +82,24 @@ export class ListOrdersComponent implements OnInit, OnDestroy{
} }
ngOnInit(){ ngOnInit(){
// console.log('listOrders Init -->');
this.owner = this.userService.currentOwner();
// console.log(this.owner.name);
this.payConf = this.owner.payconf;
// this.payConf = true;
// console.log('payConf = ', this.payConf);
this.ordersUpdate.subscribe((orders) => { this.ordersUpdate.subscribe((orders) => {
this.total = 0; this.total = 0;
this.todayTotal = 0; this.todayTotal = 0;
var today = new Date(); var today = new Date();
this.orders = orders; this.orders = orders;
console.log(this.ownerUpdate);
for (let i=0; i < this.orders.length; i++){ for (let i=0; i < this.orders.length; i++){
this.total += this.orders[i].totalZec; this.total += this.orders[i].totalZec;
//
var date = new Date(this.orders[i]!.timestamp!); var date = new Date(this.orders[i]!.timestamp!);
var diff = (today.getTime() / 1000) - (date.getTime()/1000); var diff = (today.getTime() / 1000) - (date.getTime()/1000);
if (diff < (24*3600)){ if (diff < (24*3600)){
@ -131,4 +169,20 @@ export class ListOrdersComponent implements OnInit, OnDestroy{
}); });
} }
openDbExport(){
const dialogConfig = new MatDialogConfig();
console.log('openDbExport ---');
dialogConfig.disableClose = false;
dialogConfig.autoFocus = true;
dialogConfig.data = this.owner;
const dialogRef = this.dialog.open(DbExportComponent, dialogConfig);
dialogRef.afterClosed().subscribe((val) => {
console.log('Returning to order list');
});
}
} }

View file

@ -1,6 +1,6 @@
<div align="center" class="text"> <div align="center" class="text">
<mat-card class="coolcard"> <mat-card class="coolcard">
<img src="/assets/logo-new-white.png" height="120px" /> <img src="/assets/logo-new-white_01.png" height="120px" />
<p class="text">Last block seen: <span class="numbers">{{ heightUpdate | async }}</span></p> <p class="text">Last block seen: <span class="numbers">{{ heightUpdate | async }}</span></p>
</mat-card> </mat-card>
</div> </div>

View file

@ -71,18 +71,6 @@ export class LoginComponent implements OnInit, AfterViewInit {
public userUpdate:Observable<User>; public userUpdate:Observable<User>;
public ownerUpdate:Observable<Owner>; public ownerUpdate:Observable<Owner>;
public txsUpdate: Observable<Tx[]>; public txsUpdate: Observable<Tx[]>;
tickets = [
{
value: 0.001,
viewValue: '1 hour: 0.001 ZEC'
},{
value: 0.005,
viewValue: '1 day: 0.005 ZEC'
},{
value: 0.025,
viewValue: '1 week: 0.025 ZEC'
}
];
prompt: boolean = false; prompt: boolean = false;
confirmedMemo: boolean = false; confirmedMemo: boolean = false;
targetBlock: number = 0; targetBlock: number = 0;

View file

@ -9,7 +9,7 @@ export class NotifierService {
constructor(private snackBar:MatSnackBar) { } constructor(private snackBar:MatSnackBar) { }
showNotification(displayMessage:string, buttonText: string, messageType: 'error' | 'success') { showNotification(displayMessage:string, buttonText: string, messageType: 'error' | 'success' | 'warning') {
this.snackBar.openFromComponent(NotifierComponent, { this.snackBar.openFromComponent(NotifierComponent, {
data: { data: {
message: displayMessage, message: displayMessage,
@ -19,7 +19,16 @@ export class NotifierService {
duration: 4000, duration: 4000,
verticalPosition: 'top', verticalPosition: 'top',
panelClass: [messageType] panelClass: [messageType]
}) });
this.playSound();
}
playSound() {
// console.log('Play sound called...');
let audio = new Audio();
audio.src = '../assets/notifier_1.mp3';
audio.load();
audio.play();
} }
} }

Binary file not shown.

View file

@ -21,7 +21,7 @@
} }
::ng-deep .mat-snack-bar-container.error { ::ng-deep .mat-snack-bar-container.error {
background: antiquewhite; background: navajowhite;
color: red; color: red;
} }
@ -29,3 +29,9 @@
background: whitesmoke; background: whitesmoke;
color: black; color: black;
} }
::ng-deep .mat-snack-bar-container.warning {
background: antiquewhite;
color: black;
}

View file

@ -1,3 +1,4 @@
<div class="notifier" > <div class="notifier" >
<div class="notifier-type"> <div class="notifier-type">
{{ data.type | titlecase }} {{ data.type | titlecase }}

View file

@ -32,6 +32,7 @@ export class OrderComponent implements OnInit{
faInvoice = faFileInvoiceDollar; faInvoice = faFileInvoiceDollar;
public order: Order = { public order: Order = {
_id: '',
address: '', address: '',
session: '', session: '',
timestamp: '', timestamp: '',
@ -79,6 +80,8 @@ export class OrderComponent implements OnInit{
this.orderUpdate = orderService.orderUpdate; this.orderUpdate = orderService.orderUpdate;
this.orderUpdate.subscribe((order) => { this.orderUpdate.subscribe((order) => {
this.order = order; this.order = order;
console.log('this.order > ' + JSON.stringify(this.order));
// ------------------------------------------------ // ------------------------------------------------
this.oLines = []; this.oLines = [];
this.myLines = this.order.lines; this.myLines = this.order.lines;
@ -183,6 +186,8 @@ export class OrderComponent implements OnInit{
orderId: this.order._id orderId: this.order._id
}; };
console.log ('order_id : ' + this.order._id);
const dialogRef = this.dialog.open(PromptInvoiceComponent, dialogConfig); const dialogRef = this.dialog.open(PromptInvoiceComponent, dialogConfig);
dialogRef.afterClosed().subscribe((val) => { dialogRef.afterClosed().subscribe((val) => {
if (val) { if (val) {

View file

@ -0,0 +1,111 @@
.invoice {
font-family: Roboto Mono !important;
}
.zecSign {
margin-bottom: -4px;
font-size: 18px;
height: 18px;
}
.invoiceHeader {
display: flex;
font-family: Spartan;
font-weight: 700;
font-size: 26px;
color: white;
justify-content: space-between;
line-height: 40px;
padding: 10px;
vertical-align: center;
max-width: 600px;
background: #ff5722;
}
.invoiceDetail {
font-family: Roboto Mono !important;
padding: 10px;
max-width: 600px;
}
.invoiceHdrTxt1 {
font-family: Spartan !important;
text-align: center;
font-size: 30px;
font-weight: 700;
}
.invoiceHdrTxt2 {
font-family: Spartan !important;
text-align: center;
font-size: 16px;
font-weight: 400;
}
.invoiceHdrTxt3 {
font-family: Spartan !important;
text-align: center;
font-size: 12px;
font-weight: 300;
}
.detailTitle1 {
border-top: solid 2px;
border-bottom: solid 2px;
border-color: navy;
text-align: left;
}
.detailTitle2 {
border-top: solid 2px;
border-bottom: solid 2px;
border-color: navy;
text-align: right;
}
.detailLineRight {
border-top: solid 2px;
border-bottom: solid 2px;
border-color: navy;
text-align: right;
}
.detailLineLeft {
border-top: solid 2px;
border-bottom: solid 2px;
border-color: navy;
text-align: right;
}
.invoice-title {
font-size: 16px;
font-weight: 700;
background: lightcyan;
line-height: 30px;
padding: 5px;
}
.invoice-detail {
line-height: 20px;
font-size: 16px;
font-weight: 400;
padding-top: 4px;
padding-bottom: 4px;
}
.invoice-total {
margin-top: 40px;
}
.qrcode {
display: flex;
float: right;
}
.zecData {
width: auto;
font-family: Spartan !important;
font-size: 16px;
font-weight: 700;
text-align: right;
}

View file

@ -1 +1,255 @@
<p>{{ pmtData.ownerId }}</p> <div style="font-family: 'Spartan';
display: flex;
justify-content: center;
align-items: center;">
<div class="container">
<div class="invoiceHeader">
<img class="logo" src="/assets/logo-new-white.png" height="40px" />
</div>
<div *ngIf="reportType==1">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Invalid Owner ID!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==2">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Payment service not<br>
enabled for<br>
{{ owner.name}}
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==3">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Connection to Xero<br>server failed!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==4">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Invoice<br>{{ pmtData.invoice }}<br>not found!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==5">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Invoice<br>{{ pmtData.invoice }}<br>type invalid!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==6">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Invoice <br>{{ pmtData.invoice }}<br>already paid!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==7">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Currency <br>[ {{ pmtData.currency }} ]<br>not supported!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div *ngIf="reportType==8">
<div style="height: 50px;">
</div>
<div style="font-weight: 700;
font-size: 25px;
text-align: center;">
Amount does not<br>
match value<br>
reported by Xero!!
</div>
<div style="height: 40px;">
</div>
Payment request was not processed!!
<div style="height: 20px;">
</div>
</div>
<div class="invoiceDetail"
*ngIf="reportType==0"
id="invoice">
<div class="invoiceHdrTxt1">Invoice</div>
<div class="invoiceHdrTxt2">Order ID: {{orderId}}</div>
<div class="invoiceHdrTxt3">Date:{{order.timestamp | date}}
</div>
<div style="height: 10px;"></div>
<div class="zecData">Zcash Price: {{order.price | number: '1.02' | currency: order.currency.toUpperCase()}}</div>
<div style="height: 2px;"></div>
<div class="zecData">Total: <img class="zecSign" src="/assets/zec_rv.png" />{{order.totalZec | number: '1.08'}}
</div>
<div>
<div style="height: 10px;"></div>
<table style="width: 100%;"
cellspacing="0">
<tr class="invoice-title">
<th width="55%"
class="detailTitle1">
Item
</th>
<th width="15%"
class="detailTitle1">
Qty.
</th>
<th width="30%"
class="detailTitle2">
Price ({{order.currency.toUpperCase()}})
</th>
</tr>
<tr class="invoice-detail"
*ngFor="let item of order.lines">
<td width="55%"
align="left">
{{item.name}}
</td>
<td width="15%"
align="center">
{{item.qty}}
</td>
<td width="30%"
align="right">
{{( item.qty * item.cost ) | number : '1.02' | currency: order.currency.toUpperCase()}}
</td>
</tr>
<tr class="invoice-title">
<th width="55%"
class="detailLineRight">
Invoice Total:
</th>
<th width="15%"
class="detailLineLeft">
</th>
<th width="30%"
class="detailLineRight">
{{ order.total | currency: order.currency.toUpperCase()}}
</th>
</tr>
</table>
<div style="height: 15px;"></div>
<table>
<tr>
<td width="75%"
style="font-size: 20px;
font-weight: 700;
font-style: italic;
text-align: center;">
<p *ngIf="order.paid">
<fa-icon [icon]="faCheck"
color="primary"></fa-icon>&nbsp;Payment confirmed</p>
<p *ngIf="!order.paid">
<fa-icon [style]="getIconStyle(order)"
[icon]="faHourglass"></fa-icon>&nbsp;Payment pending!!</p>
</td>
<td width="25%">
<div style="text-align: right;"
id="payment-qr"
*ngIf="!order.paid"></div>
</td>
</tr>
</table>
<div style="height: 15px;"></div>
<div width="100%"
style="font-size: 14px;
font-weight: 700;
font-style: italic;
text-align: center;">
Scan the QR code with your wallet to make payment
</div>
<div style="text-align: center;
margin-top: 10px;
line-height: 30px;">
<div style="font-family: 'Spartan';
font-size: 14px;
line-height: 20px;">
Can't scan?<br>Use this <a [href]="zcashUrl">wallet link</a>, or
</div>
<div style="display: flex;
justify-content: space-between;">
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAddress()">Copy Address</button>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyAmount()">Copy Amount</button>
</div>
<button style="margin-top: 20px;
font-weight: 700;
background-color: lightgray;"
mat-raised-button
(click)="copyMemo()">Copy Memo</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,6 +1,20 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from "@angular/router"; import { Router, ActivatedRoute, Params } from "@angular/router";
import { PmtData } from "./pmtservice.model" import { HttpClient, HttpParams, HttpHeaders } from "@angular/common/http";
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { PmtData } from "./pmtservice.model";
import { XeroInvoice } from "./xeroinvoice.model";
import { Owner } from '../owner.model';
// import { Item } from '../items/item.model'
import { Order } from '../order/order.model'
import { ConfigData } from '../configdata';
import { faCheck, faHourglass } from '@fortawesome/free-solid-svg-icons';
import { NotifierService } from '../notifier.service';
var QRCode = require('easyqrcodejs');
var URLSafeBase64 = require('urlsafe-base64');
var Buffer = require('buffer/').Buffer;
@Component({ @Component({
selector: 'app-pmtservice', selector: 'app-pmtservice',
@ -10,6 +24,11 @@ import { PmtData } from "./pmtservice.model"
export class PmtserviceComponent implements OnInit { export class PmtserviceComponent implements OnInit {
faCheck = faCheck;
faHourglass = faHourglass;
beUrl = ConfigData.Be_URL;
private reqHeaders: HttpHeaders = new HttpHeaders();
public pmtData : PmtData = { public pmtData : PmtData = {
ownerId :'', ownerId :'',
@ -19,18 +38,354 @@ export class PmtserviceComponent implements OnInit {
shortcode: '' shortcode: ''
}; };
constructor(private activatedRoute: ActivatedRoute) {} public invData : XeroInvoice = {
inv_Type : '',
inv_Id : '',
inv_No : '',
inv_Contact : '',
inv_Currency : '',
inv_CurrencyRate : 0,
inv_Status : '',
inv_Total : 0,
inv_Date : new Date(),
inv_shortCode : '',
inv_ProcDate : new Date()
};
public owner: Owner = {
_id: '',
address: '',
name: '',
currency: 'usd',
tax: false,
taxValue: 0,
vat: false,
vatValue: 0,
first: '',
last: '',
email: '',
street: '',
city: '',
state: '',
postal: '',
phone: '',
paid: false,
website: '',
country: '',
zats: false,
invoices: false,
expiration: new Date(Date.now()).toISOString(),
payconf: false,
viewkey: '',
crmToken: ''
};
public order: Order = {
_id : '',
address: '',
session: '',
timestamp: '',
closed: false,
currency: '',
price: 0,
total: 0,
totalZec: 0,
paid: false,
externalInvoice: '',
shortCode: '',
lines: [
{
qty: 1,
name: '',
cost:0
}
]
};
private invData_raw : string = '';
private invData_buff : any = null;
public reportType = 1000;
public Status = 0;
codeString: string = '';
zcashUrl: SafeUrl = '';
zPrice: number = 1.0;
name: string = '';
error: boolean = false;
orderId : string = '';
constructor(private activatedRoute : ActivatedRoute,
private http : HttpClient,
private sanitizer: DomSanitizer,
private notifierService : NotifierService ) {}
ngOnInit() { ngOnInit() {
var auth = 'Basic ' + Buffer.from(ConfigData.UsrPwd).toString('base64');
this.reqHeaders = new HttpHeaders().set('Authorization', auth);
this.activatedRoute.queryParams.subscribe((params) => { this.activatedRoute.queryParams.subscribe((params) => {
this.pmtData.ownerId = params["ownerid"]; this.pmtData.ownerId = params["owner"];
this.pmtData.invoice = params["invoice"]; this.pmtData.invoice = params["invoiceNo"];
this.pmtData.amount = params["amount"]; this.pmtData.amount = params["amount"];
this.pmtData.currency = params["currency"]; this.pmtData.currency = params["currency"];
this.pmtData.shortcode = params["shortcode"]; this.pmtData.shortcode = params["shortCode"];
// console.log(this.pmtData);
this.getInvoiceData( this.pmtData );
});
}
getInvoiceData( reqData : PmtData ) {
//
// Verify owner id ( Status = 1 if not exists )
// ( Status = 2 if service not available for user )
//
// console.log('getOwner -> '+ reqData.ownerId);
// console.log('received amount -> ' + reqData.amount);
const ownParams = new HttpParams().append('id', reqData.ownerId);
let obs = this.http.get<{message:string, owner: any}>
( this.beUrl+'api/ownerid',
{ headers: this.reqHeaders,
params: ownParams,
observe: 'response'});
obs.subscribe((OwnerDataResponse) => {
//console.log('api/getowner', OwnerDataResponse.status);
if (OwnerDataResponse.status == 200) {
this.owner = OwnerDataResponse.body!.owner;
console.log('Owner => ' + this.owner.name );
//
// ==> remove "== false" for production enviroment
//
if ( this.owner.invoices ) {
// process data
console.log("Owner check passed!!!");
this.getXeroInvoiceData( reqData );
} else {
console.log("Owner check failed!!!")
this.reportType = 2;
};
} else {
if ( OwnerDataResponse.status == 204 ) {
console.log('Res.Status = ' + OwnerDataResponse.status)
console.log('Owner id not found!!!');
this.reportType = 1;
}
}});
}
getXeroInvoiceData( reqData : PmtData ) {
/*
// Call test Xero API
let url : string = "http://localhost:3000/xero/" + reqData.invoice;
this.http
.get<any>(url)
*/
console.log('>> find current zcash price');
this.getPrice(this.owner.currency);
console.log('get Invoice -> ' + reqData.invoice);
let invParams = new HttpParams();
invParams = invParams.append('address', this.owner.address);
invParams = invParams.append('inv', reqData.invoice);
let inv = this.http.get<{message:string, invData: any}>
( this.beUrl+'api/invdata',
{ headers: this.reqHeaders,
params: invParams,
observe: 'response'});
inv.subscribe( invDataResponse => {
// console.log('Response from ZGo-Xero');
// console.log(invDataResponse.status);
this.invData_buff = invDataResponse.body;
this.invData.inv_Type = this.invData_buff.invdata.inv_Type;
this.invData.inv_Id = this.invData_buff.invdata.inv_Id;
this.invData.inv_No = this.invData_buff.invdata.inv_No;
this.invData.inv_Contact = this.invData_buff.invdata.inv_Contact;
this.invData.inv_Currency = this.invData_buff.invdata.inv_Currency;
this.invData.inv_CurrencyRate = this.invData_buff.invdata.inv_CurrencyRate;
this.invData.inv_Total = this.invData_buff.invdata.inv_Total;
this.invData.inv_Status = this.invData_buff.invdata.inv_Status;
this.invData.inv_Date = this.invData_buff.invdata.inv_Date;
this.invData.inv_shortCode = reqData.shortcode;
/*
console.log('>>> inv_Type -> ' + this.invData.inv_Type);
console.log('>>> inv_Id -> ' + this.invData.inv_Id);
console.log('>>> inv_No -> ' + this.invData.inv_No);
console.log('>>> inv_Contact -> ' + this.invData.inv_Contact);
console.log('>>> inv_Currency-> ' + this.invData.inv_Currency);
console.log('>>> inv_CurrencyRate -> ' + this.invData.inv_CurrencyRate);
console.log('>>> inv_Total -> ' + this.invData.inv_Total);
console.log('>>> inv_Status-> ' + this.invData.inv_Status);
console.log('>>> inv_Date -> ' + this.invData.inv_Date);
*/
if ( this.invData.inv_Type == 'ACCREC' ) {
console.log('Invoice type is correct!!');
// Test if invoice is not already paid
if ( this.invData.inv_Status == 'AUTHORISED') {
console.log('invoice is payable');
// Test if Invoice's currency is supported
if ( this.invData.inv_Currency == reqData.currency ) {
console.log('Invoice currency supported');
// Test if requested amount is as reported by Xero
if ( this.invData.inv_Total == reqData.amount ) {
console.log('Invoice amount Ok - create Order');
// =====> Create order here
this.createOrder();
//
} else {
console.log('Invoice amount does not match')
this.reportType = 8;
}
} else {
console.log('Invoice currency not supported');
this.reportType = 7;
}
} else {
console.log('Invoice already paid');
this.reportType = 6;
}
} else {
console.log('Invoice type is invalid' );
this.reportType = 5;
}
},
error => {
console.log("Error while getting invData!!!");
console.log(error);
console.log(error.status);
if ( error.status == 500 ) {
// Assume that invoice was not found by haskell server
this.reportType = 4;
}
console.log(this.pmtData);
}); });
} }
createOrder() {
this.reportType = 0;
// console.log('Starting order generation');
// console.log('>> find current zcash price');
this.order = {
_id: '',
address: this.owner.address,
session: 'Xero-' + this.owner._id,
currency: this.owner.currency,
timestamp: new Date(Date.now()).toISOString(),
closed: true,
totalZec: this.invData.inv_Total/this.zPrice,
price: this.zPrice,
total: this.invData.inv_Total,
paid: false,
externalInvoice: this.invData.inv_No,
shortCode: this.invData.inv_shortCode,
lines: [{qty: 1,
name: 'Invoice from ' + this.owner.name + '[' + this.invData.inv_No + ']',
cost: this.invData.inv_Total}]
};
let obs = this.http.post<{message: string, order: Order}>
(this.beUrl+'api/orderx',
{payload: this.order},
{ headers: this.reqHeaders }
);
obs.subscribe((orderData) => {
// console.log('Order created');
// console.log(orderData.order);
this.order = orderData.order
console.log('>> order -> ' + JSON.stringify(this.order));
this.orderId = String(this.order._id);
// console.log('Generating QRCode....')
this.codeString = `zcash:${this.order.address}?amount=${this.order.totalZec.toFixed(8)}&memo=${URLSafeBase64.encode(Buffer.from('ZGo Order::'.concat(this.orderId)))}`;
var qrcode = new QRCode(document.getElementById("payment-qr"), {
text: this.codeString,
logo: "/assets/zcash.png",
width: 180,
height: 180,
logoWidth: 50,
logoHeight: 50,
correctLevel: QRCode.CorrectLevel.H
});
this.codeString = `zcash:${this.order.address}?amount=${this.order.totalZec.toFixed(8)}&memo=${URLSafeBase64.encode(Buffer.from('ZGo Order::'.concat(this.orderId)))}`;
this.zcashUrl = this.sanitizer.bypassSecurityTrustUrl(this.codeString);
}, error => {
console.log(error.message);
});
}
getIconStyle(order : Order) {
if( order.paid )
return "font-size: 14px; color: #72cc50; margin-bottom: -2px;";
return "color: #FB4F14; margin-bottom: -2px; cursor: pointer;";
}
getPrice(currency: string){
//var currency = 'usd';
const params = new HttpParams().append('currency', currency);
let obs = this.http.get<{message: string, price: any}>(this.beUrl+'api/price', { headers:this.reqHeaders, params: params, observe: 'response'});
obs.subscribe((PriceData) => {
if (PriceData.status == 200) {
this.zPrice = PriceData.body!.price.price;
console.log("price", this.zPrice);
} else {
console.log('No price found for currency', currency);
this.zPrice = 1.0;
}
});
return obs;
}
copyAddress() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.order.address);
} catch (err) {
this.notifierService
.showNotification("Error copying address","Close","error");
// console.error("Error", err);
}
}
copyAmount() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText(this.order.totalZec.toString());
} catch (err) {
this.notifierService
.showNotification("Error while copying ammount","Close","error");
// console.error("Error", err);
}
}
copyMemo() {
if (!navigator.clipboard) {
// alert("Copy functionality not supported");
this.notifierService
.showNotification("Copy functionality not supported","Close","error");
}
try {
navigator.clipboard.writeText("ZGo Order::" + this.orderId);
} catch (err) {
this.notifierService
.showNotification("Error while copying Memo","Close","error");
// console.error("Error", err);
}
}
} }

View file

@ -1 +1,7 @@
http://localhost:4200/pmtservice?ownerid=Rene&amount=30&currency=USD&invoice=INV-003234&shortcode=abcde http://localhost:4200/pmtservice?ownerid=62cca13f5530331e2a97c78e&invoiceNo=INV-0034&currency=USD&amount=753.95&shortCode=!w8T62
https://test.zgo.cash/api/invdata?address=zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8&inv=INV-0034
https://www.paymentservice.com/?invoiceNo=[INVOICENUMBER]&currency=[CURRENCY]&amount=[AMOUNTDUE]&shortCode=[SHORTCODE]
https://app.zgo.cash/invoice/

View file

@ -0,0 +1,13 @@
export interface XeroInvoice {
inv_Type : string;
inv_Id : string;
inv_No : string;
inv_Contact : string;
inv_Currency : string;
inv_CurrencyRate : number;
inv_Total : number;
inv_Status : string;
inv_Date : Date;
inv_shortCode : string;
inv_ProcDate : Date;
}

View file

@ -23,3 +23,37 @@
.mat-slide-toggle-content { .mat-slide-toggle-content {
font-family: 'Spartan', sans-serif; font-family: 'Spartan', sans-serif;
} }
.full-width {
width: 100%;
}
.urlLabel {
font-family: "Spartan";
font-size: 13px;
color: dimgray;
}
.urlDetail {
font-family: "Spartan";
font-size: 10px;
color: black;
}
.urlCopyBtn {
cursor: pointer;
color: dodgerblue;
}
.small {
font-size: 12px;
background: #dddddd;
}
.heading {
padding-top: 10px;
}
.toolbar {
padding: 12px;
}

View file

@ -1,7 +1,14 @@
<div class="settings-title">Settings</div> <div class="settings-title">Settings</div>
<div class="container" style="margin-top: 10px;"> <div class="container"
style="margin-top: 10px;
height: 450px;
margin-left: 10px;
margin-right: 10px;">
<mat-tab-group mat-tab-align-tabs="start">
<mat-tab label="Main" style="height: 400px;">
<div class="container" style="margin-bottom: 20px;">
<mat-dialog-content [formGroup]="settingsForm"> <mat-dialog-content [formGroup]="settingsForm">
<mat-form-field class="settings-field" [style.width.%]="100"> <mat-form-field class="settings-field" [style.width.%]="100">
<mat-label>Name</mat-label> <mat-label>Name</mat-label>
@ -31,18 +38,28 @@
Confirm payments? Confirm payments?
</mat-slide-toggle> </mat-slide-toggle>
<pre></pre> <pre></pre>
<mat-form-field [style.width.%]="100"> <mat-form-field class="full-width"
appearance="fill">
<mat-label>Viewing key</mat-label> <mat-label>Viewing key</mat-label>
<input matInput placeholder="Your wallet viewing key" <textarea matInput placeholder="Your wallet viewing key"
formControlName="vKey"> formControlName="vKey">
</textarea>
</mat-form-field> </mat-form-field>
<!--
<pre></pre>
<mat-slide-toggle formControlName="proVersion"
class="settings-toggle"
(change)="onChangeProVersion($event)">
Enable Integrations
</mat-slide-toggle>
-->
</mat-dialog-content> </mat-dialog-content>
</div>
<mat-dialog-actions style="display: flex; <div class="container"
style="display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;">
margin-top: 12px;">
<button mat-raised-button <button mat-raised-button
(click)="close()"> (click)="close()">
Cancel Cancel
@ -52,5 +69,195 @@
(click)="save()"> (click)="save()">
Save Save
</button> </button>
</mat-dialog-actions> </div>
<div style="height: 20px;
margin-top: 10px;">
</div>
</mat-tab>
<mat-tab *ngIf="proVersion"
label="Integrations"
style="align-items: center;">
<mat-tab-group mat-tab-align-tabs="start">
<mat-tab label="Xero">
<div class="container" style="margin-bottom: 20px;">
<mat-dialog-content [formGroup]="accCodForm">
<div style="height: 10px;
margin-top: 10px;">
</div>
<div class="container"
style="height: 300;">
<p style="text-align:center">
<a mat-raised-button
color="primary"
href="{{this.xeroLink}}">
{{ linkMsg }}
</a>
</p>
<table *ngIf="linked2Xero"
[style.width.%]="100"
style="margin-top: 10px;">
<thead style="width: 100%;">
<tr>
<th class="urlLabel"
style="text-align: left;"
width="94%">Payment Service URL:
</th>
<th></th>
</tr>
</thead>
<tbody>
<td class="urlDetail"
style="text-align: left;"
width="94%">
<div>
<textarea disabled
style="font-size: 10px !important;
border: none;
outline: none;
min-height: 150px;
width: 95%;"
cdkTextareaAutosize
cdkAutosizeMinRows="6"
cdkAutosizeMaxRows="10">{{ pmtServiceURL }}
</textarea>
</div>
</td>
<td class="urlCopyBtn">
<a (click)='copyUrl()' >
<fa-icon [icon]="faCopy"
class="copy-button">
</fa-icon>
</a>
</td>
</tbody>
</table>
<div style="height: 10px;
margin-top: 10px;">
</div>
<mat-form-field *ngIf="linked2Xero"
class="settings-field"
[style.width.%]="100">
<mat-label>Account Code</mat-label>
<input matInput
width="100%"
placeholder="9999999999"
formControlName="xAcc"
(keyup)="checkStatus($event)">
</mat-form-field>
</div>
</mat-dialog-content>
</div>
<div class="container"
style="display: flex;
justify-content: space-between;
align-items: center;">
<button mat-raised-button
(click)="closeIntegration()">
Close
</button>
<button *ngIf="saveAccOk"
mat-raised-button
color="primary"
(click)="saveAccCod()">
Save Code
</button>
</div>
<div style="height: 20px;
margin-top: 10px;">
</div>
</mat-tab>
<mat-tab label="WooCommerce">
<div >
<div *ngIf="wooOwner == ''" align="center">
<button mat-raised-button color="primary" (click)="generateWooToken()">
Generate Token
</button>
</div>
<table *ngIf="wooOwner != ''">
<tbody>
<tr>
<td class="heading" style="width: 100%;">Owner:</td>
</tr>
<tr>
<td>
<div>
<textarea disabled
style="border: none;
outline: none;
min-height: 150px;
width: 94%;"
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="3">{{wooOwner}}
</textarea>
</div>
</td>
<td class="urlCopyBtn">
<a (click)='copyWooOwner()' >
<fa-icon [icon]="faCopy"
class="copy-button">
</fa-icon>
</a>
</td>
</tr>
<tr>
<td class="heading" style="width: 60%;">Token:</td>
</tr>
<tr>
<td>
<div>
<textarea disabled
style="border: none;
outline: none;
min-height: 150px;
width: 94%;"
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="3">{{wooToken}}
</textarea>
</div>
</td>
<td class="urlCopyBtn">
<a (click)='copyWooToken()' >
<fa-icon [icon]="faCopy"
class="copy-button">
</fa-icon>
</a>
</td>
</tr>
<tr>
<td class="heading" style="width: 60%;">URL:</td>
</tr>
<tr>
<td>
<div>
<textarea disabled
style="border: none;
outline: none;
min-height: 150px;
width: 94%;"
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="3">{{wooUrl}}
</textarea>
</div>
</td>
</tr>
</tbody>
</table>
<div class="toolbar" align="center">
<button mat-raised-button
(click)="close()">
Close
</button>
</div>
</div>
</mat-tab>
</mat-tab-group>
</mat-tab>
</mat-tab-group>
</div> </div>

View file

@ -2,8 +2,14 @@ import { Inject, Component, OnInit, OnDestroy, ViewEncapsulation } from '@angula
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { UntypedFormBuilder, Validators, UntypedFormGroup, FormControl } from '@angular/forms'; import { UntypedFormBuilder, Validators, UntypedFormGroup, FormControl } from '@angular/forms';
import { User } from '../user.model'; import { Observable } from 'rxjs';
import { Owner } from '../owner.model'; import { Owner } from '../owner.model';
import { XeroService } from '../xero.service';
import { WoocommerceService } from '../woocommerce.service';
import { NotifierService } from '../notifier.service';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@ -13,10 +19,20 @@ import { Owner } from '../owner.model';
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
// ------------------------------------
//
faCopy = faCopy;
// ------------------------------------
settingsForm: UntypedFormGroup; settingsForm: UntypedFormGroup;
accCodForm: UntypedFormGroup;
owner: Owner; owner: Owner;
useZats: boolean; useZats: boolean;
proVersion: boolean = false;
useVKey: boolean = false; useVKey: boolean = false;
linkMsg: string = 'Link to Xero';
xeroAccCod: string = '';
saveAccOk: boolean = false;
coins = [ coins = [
{ {
label: 'US Dollar', label: 'US Dollar',
@ -33,11 +49,31 @@ export class SettingsComponent implements OnInit {
},{ },{
label: 'Australian Dollar', label: 'Australian Dollar',
symbol: 'aud' symbol: 'aud'
},{
label: 'New Zealand Dollar',
symbol: 'nzd'
} }
]; ];
xeroLink: string = '';
localToken: string = '';
clientId: string = '';
wooOwner: string = '';
wooToken: string = '';
wooUrl: string = '';
wooOwnerUpdate: Observable<string>;
wooTokenUpdate: Observable<string>;
wooUrlUpdate: Observable<string>;
clientIdUpdate: Observable<string>;
accCodeUpdate: Observable<string>;
linked2Xero : boolean = false;
pmtServiceURL : string = '';
constructor( constructor(
private notifierService : NotifierService,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
public xeroService: XeroService,
public wooService: WoocommerceService,
private dialogRef: MatDialogRef<SettingsComponent>, private dialogRef: MatDialogRef<SettingsComponent>,
@Inject(MAT_DIALOG_DATA) public data: Owner) { @Inject(MAT_DIALOG_DATA) public data: Owner) {
this.useZats = data.zats; this.useZats = data.zats;
@ -47,18 +83,74 @@ export class SettingsComponent implements OnInit {
currency: [data.currency, Validators.required], currency: [data.currency, Validators.required],
useZats: [data.zats, Validators.required], useZats: [data.zats, Validators.required],
useVKey: [data.payconf, Validators.required], useVKey: [data.payconf, Validators.required],
// proVersion: [data.invoices, Validators.required],
vKey: [data.viewkey] vKey: [data.viewkey]
}); });
this.accCodForm = fb.group ({
xAcc: [this.xeroAccCod]
});
if (data.payconf) { if (data.payconf) {
this.settingsForm.get('vKey')!.enable(); this.settingsForm.get('vKey')!.enable();
} }
this.owner = data; this.owner = data;
this.proVersion = this.owner.invoices;
if ( this.owner.crmToken !== '' ) {
this.linked2Xero = true;
}
this.clientIdUpdate = xeroService.clientIdUpdate;
xeroService.getXeroConfig();
this.clientIdUpdate.subscribe(clientId => {
this.clientId = clientId;
this.xeroLink = `https://login.xero.com/identity/connect/authorize?response_type=code&client_id=${this.clientId}&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fxeroauth&scope=accounting.transactions offline_access&state=${this.owner.address.substring(0, 6)}`
});
this.accCodeUpdate = xeroService.accCodeUpdate;
xeroService.getXeroAccountCode(this.owner.address);
this.accCodeUpdate.subscribe(accData => {
this.xeroAccCod = accData;
console.log("xeroAccCod -> [" + this.xeroAccCod + "]");
this.accCodForm.get('xAcc')!.setValue(this.xeroAccCod);
});
this.wooOwnerUpdate = wooService.ownerUpdate;
this.wooTokenUpdate = wooService.tokenUpdate;
this.wooUrlUpdate = wooService.siteurlUpdate;
wooService.getWooToken(this.owner._id!);
this.wooOwnerUpdate.subscribe(owData => {
this.wooOwner = owData;
});
this.wooTokenUpdate.subscribe(tkData => {
this.wooToken = tkData;
});
this.wooUrlUpdate.subscribe(uData => {
this.wooUrl = uData;
});
} }
ngOnInit() { ngOnInit() {
this.settingsForm.get('vKey')!.disable();
this.linkMsg = 'Link to Xero';
this.pmtServiceURL + '';
if ( this.linked2Xero ) {
this.linkMsg = 'Relink to Xero';
this.pmtServiceURL = 'https://zgo.cash/pmtservice?owner=' +
this.owner._id +
'&invoiceNo=[INVOICENUMBER]&currency=[CURRENCY]&amount=[AMOUNTDUE]&shortCode=[SHORTCODE]';
}
}
safeURL(s: string){
return s.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
} }
close() { close() {
this.dialogRef.close();
}
closeIntegration() {
if ( (this.xeroAccCod == '') && (this.linked2Xero) )
this.notifierService
.showNotification("Xero Payment confirmation disabled!!","Close",'warning');
this.dialogRef.close(); this.dialogRef.close();
} }
@ -67,9 +159,8 @@ export class SettingsComponent implements OnInit {
this.owner.currency = this.settingsForm.value.currency; this.owner.currency = this.settingsForm.value.currency;
this.owner.zats = this.settingsForm.value.useZats; this.owner.zats = this.settingsForm.value.useZats;
this.owner.payconf = this.settingsForm.value.useVKey; this.owner.payconf = this.settingsForm.value.useVKey;
this.owner.viewkey = this.settingsForm.value.vKey; this.owner.viewkey = this.settingsForm.value.vKey;
//this.owner.invoices = this.settingsForm.value.proVersion
this.dialogRef.close(this.owner); this.dialogRef.close(this.owner);
} }
@ -77,6 +168,10 @@ export class SettingsComponent implements OnInit {
this.useZats = ob.checked; this.useZats = ob.checked;
} }
onChangeProVersion(ob: MatSlideToggleChange) {
this.proVersion = ob.checked;
}
onChangeVKeyOn(ob: MatSlideToggleChange) { onChangeVKeyOn(ob: MatSlideToggleChange) {
// console.log("Viewing key switch is " + // console.log("Viewing key switch is " +
// ( ob.checked ? "[ON]." : "[OFF]." ) ); // ( ob.checked ? "[ON]." : "[OFF]." ) );
@ -89,4 +184,100 @@ export class SettingsComponent implements OnInit {
this.settingsForm.get('vKey')!.disable(); this.settingsForm.get('vKey')!.disable();
} }
copyUrl() {
// console.log("Inside copyUrl()");
if (navigator.clipboard) {
};
try {
navigator.clipboard.writeText(this.pmtServiceURL);
this.notifierService
.showNotification("ZGo URL copied to Clipboard!!","Close",'success');
} catch (err) {
// console.error("Error", err);
this.notifierService
.showNotification("Functionality not available for your browser. Use send button instead.","Close",'error');
}
}
copyWooOwner(){
try {
navigator.clipboard.writeText(this.wooOwner);
this.notifierService.showNotification("Owner ID copied to clipboard", "Close", "success");
} catch (err) {
this.notifierService.showNotification("Copying not available in your browser", "Close", "error");
}
}
copyWooToken(){
try {
navigator.clipboard.writeText(this.wooToken);
this.notifierService.showNotification("WooCommerce Token copied to clipboard", "Close", "success");
} catch (err) {
this.notifierService.showNotification("Copying not available in your browser", "Close", "error");
}
}
generateWooToken(){
this.wooService.createWooToken(this.owner._id!).subscribe(responseData => {
if (responseData.status == 202) {
this.notifierService.showNotification("WooCommerce Token generated!", "Close", "success");
this.wooService.getWooToken(this.owner._id!);
this.wooOwnerUpdate.subscribe(owData => {
this.wooOwner = owData;
});
this.wooTokenUpdate.subscribe(tkData => {
this.wooToken = tkData;
});
this.wooUrlUpdate.subscribe(uData => {
this.wooUrl = uData;
});
close();
} else {
this.notifierService.showNotification("WooCommerce Token generation failed.", "Close", "error");
}
});
}
saveAccCod() {
this.xeroAccCod = this.accCodForm.value.xAcc;
console.log(">>> " + this.xeroAccCod);
if ( this.xeroAccCod.length <= 10 ) {
const obs = this.xeroService
.setXeroAccountCode(this.owner.address,
this.xeroAccCod);
obs.subscribe(responseData => {
if (responseData.status == 202) {
console.log('Account saved');
this.notifierService
.showNotification("Account Code saved!!","Close",'success');
} else {
console.log('Account not saved -> status[' + responseData.status + ']');
this.notifierService
.showNotification("Account Code not saved","Close",'error');
}
}, error => {
console.log('Error saving Account Code -> ' + error.msg)
});
} else {
this.notifierService
.showNotification("Invalid Account code (10 chars max.)","Close",'error');
};
}
/*
xeroAccCodChanged( arg: any ) {
console.log("Account Code changed: " + arg.target.value);
// console.log(arg);
this.saveAccOk = (arg.target.value != this.xeroAccCod );
}
*/
checkStatus( arg : any ) {
console.log('onChange - checkStatus');
console.log(arg.target.value);
this.saveAccOk = (arg.target.value != this.xeroAccCod );
}
} }

View file

@ -197,4 +197,8 @@ export class UserService{
return obs; return obs;
} }
currentOwner() : Owner {
return this.dataStore.owner;
}
} }

View file

@ -73,6 +73,9 @@ export class ViewerComponent implements OnInit {
this.ownerUpdate.subscribe((owner) => { this.ownerUpdate.subscribe((owner) => {
this.owner = owner; this.owner = owner;
}); });
// console.log(this.owner._id);
this.userUpdate = userService.userUpdate; this.userUpdate = userService.userUpdate;
this.userUpdate.subscribe((user) => { this.userUpdate.subscribe((user) => {
this.user = user; this.user = user;

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { WoocommerceService } from './woocommerce.service';
describe('WoocommerceService', () => {
let service: WoocommerceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WoocommerceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { ConfigData } from './configdata';
var Buffer = require('buffer/').Buffer;
@Injectable({
providedIn: 'root'
})
export class WoocommerceService {
beUrl = ConfigData.Be_URL;
private reqHeaders: HttpHeaders;
private ownerId: string = '';
private token: string = '';
private siteurl: string = '';
private _ownerIdUpdated: BehaviorSubject<string> = new BehaviorSubject(this.ownerId);
private _tokenUpdated: BehaviorSubject<string> = new BehaviorSubject(this.token);
private _siteurlUpdated: BehaviorSubject<string> = new BehaviorSubject(this.siteurl);
public readonly ownerUpdate: Observable<string> = this._ownerIdUpdated.asObservable();
public readonly tokenUpdate: Observable<string> = this._tokenUpdated.asObservable();
public readonly siteurlUpdate: Observable<string> = this._siteurlUpdated.asObservable();
constructor(
private http: HttpClient
) {
var auth = 'Basic ' + Buffer.from(ConfigData.UsrPwd).toString('base64');
this.reqHeaders = new HttpHeaders().set('Authorization', auth);
this._ownerIdUpdated.next(Object.assign({}, this).ownerId);
this._tokenUpdated.next(Object.assign({}, this).token);
this._siteurlUpdated.next(Object.assign({}, this).siteurl);
}
getWooToken(ownerId: string) {
const params = new HttpParams().append('ownerid', ownerId);
let obs = this.http.get<{ownerid: string, token: string, siteurl: string}>(this.beUrl + 'api/wootoken', {headers: this.reqHeaders, params: params, observe: 'response'});
obs.subscribe(tokenResponse => {
if (tokenResponse.status == 200) {
this.ownerId = tokenResponse.body!.ownerid;
this.token = tokenResponse.body!.token;
this.siteurl = tokenResponse.body!.siteurl;
this._ownerIdUpdated.next(Object.assign({}, this).ownerId);
this._tokenUpdated.next(Object.assign({}, this).token);
this._siteurlUpdated.next(Object.assign({}, this).siteurl);
} else {
console.log('No WooCommerce token found');
}
});
return obs;
}
createWooToken(ownerId: string) {
const params = new HttpParams().append('ownerid', ownerId);
let obs = this.http.post(this.beUrl+'api/wootoken', {}, {headers: this.reqHeaders, params: params, observe: 'response'});
obs.subscribe(responseData => {
if (responseData.status == 202) {
console.log('WooToken created.');
} else {
console.log('WooToken creation failed.');
}
});
return obs;
}
}

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { XeroService } from './xero.service';
describe('XeroService', () => {
let service: XeroService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(XeroService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

103
src/app/xero.service.ts Normal file
View file

@ -0,0 +1,103 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { ConfigData } from './configdata';
var Buffer = require('buffer/').Buffer;
@Injectable({
providedIn: 'root'
})
export class XeroService {
beUrl = ConfigData.Be_URL;
clientId: string = '';
//clientSecret: string = '';
xeroToken: any = {
accessToken: '',
refreshToken: '',
expiresIn: 0,
scope: '',
tokenType: ''
};
xeroAcc: string = '';
savedAcc : boolean = false;
public savedAccObs = new Observable((observer) => {
console.log("starting savedAccObs");
setTimeout(() => {observer.next(this.savedAcc)},1000);
})
private _clientIdUpdated: BehaviorSubject<string> = new BehaviorSubject(this.clientId);
//private _clientSecretUpdated: BehaviorSubject<string> = new BehaviorSubject(this.clientSecret);
private _tokenUpdated: BehaviorSubject<any> = new BehaviorSubject(this.xeroToken);
private _accCodeUpdated: BehaviorSubject<string> = new BehaviorSubject(this.xeroAcc);
public readonly clientIdUpdate: Observable<string> = this._clientIdUpdated.asObservable();
//public readonly clientSecretUpdate: Observable<string> = this._clientSecretUpdated.asObservable();
public readonly tokenUpdate: Observable<any> = this._tokenUpdated.asObservable();
public readonly accCodeUpdate: Observable<string> = this._accCodeUpdated.asObservable();
private reqHeaders: HttpHeaders;
constructor(
private http: HttpClient
) {
var auth = 'Basic ' + Buffer.from(ConfigData.UsrPwd).toString('base64');
this.reqHeaders = new HttpHeaders().set('Authorization', auth);
this.getXeroConfig();
}
getXeroConfig(){
let obs = this.http.get<{message: string, xeroConfig: any}>(this.beUrl+'api/xero', { headers:this.reqHeaders, observe: 'response'});
obs.subscribe(xeroDataResponse => {
if (xeroDataResponse.status == 200) {
this.clientId = xeroDataResponse.body!.xeroConfig.clientId;
//this.clientSecret = xeroDataResponse.body!.xeroConfig.clientSecret;
this._clientIdUpdated.next(Object.assign({}, this).clientId);
//this._clientSecretUpdated.next(Object.assign({}, this).clientSecret);
} else {
console.log('No config in DB!');
}
});
return obs;
}
getXeroAccessToken(code: string, address: string){
const params = new HttpParams().append('code', code).append('address', address);
let obs = this.http.get(this.beUrl + 'api/xerotoken' , {headers: this.reqHeaders, params: params, observe: 'response'});
return obs;
}
getXeroAccountCode(address: string){
const params = new HttpParams().append('address', address);
let obs = this.http.get<{message: string, code: string}>(this.beUrl + 'api/xeroaccount', {headers: this.reqHeaders, params: params, observe: 'response'});
obs.subscribe(accountResponse => {
if (accountResponse.status == 200) {
this.xeroAcc = accountResponse.body!.code;
this._accCodeUpdated.next(Object.assign({}, this).xeroAcc);
} else {
console.log('No account in DB');
}
});
return obs;
}
setXeroAccountCode(address: string, code: string) {
const params = new HttpParams().append('address', address).append('code', code);
let obs = this.http.post(this.beUrl + 'api/xeroaccount', {}, {headers: this.reqHeaders, params: params, observe: 'response'});
/*
obs.subscribe(responseData => {
if (responseData.status == 202) {
console.log('Account saved');
this.savedAcc = true;
}
}, error => {
this.savedAcc = false;
console.log("error : " + error.msg)
});
*/
return obs;
}
}

View file

@ -0,0 +1,3 @@
.text {
font-family: 'Spartan', sans-serif;
}

View file

@ -0,0 +1,6 @@
<div *ngIf="!flag" align="center" class="text">
<h1>Connecting to Xero...</h1>
</div>
<div *ngIf="flag" align="center" class="text">
<h1>Connected to Xero!</h1>
</div>

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestComponent } from './test.component';
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TestComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,98 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { UserService } from '../user.service';
import { XeroService } from '../xero.service';
import { Owner } from '../owner.model';
import { Observable } from 'rxjs';
var Buffer = require('buffer/').Buffer;
function sleep(ms:number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function redirect(r: Router) {
await sleep(2000);
r.navigate(['/shop']);
}
@Component({
selector: 'app-xeroreg',
templateUrl: './xeroreg.component.html',
styleUrls: ['./xeroreg.component.css']
})
export class XeroRegComponent implements OnInit {
public owner:Owner = {
address: '',
name: '',
currency: '',
tax: false,
taxValue:0,
vat: false,
vatValue: 0,
first: '',
last: '',
email: '',
street: '',
city: '',
state: '',
postal: '',
phone: '',
paid: false,
website: '',
country: '',
zats: false,
invoices: false,
expiration: new Date(Date.now()).toISOString(),
payconf: false,
viewkey: '',
crmToken: ''
};
public ownerUpdate:Observable<Owner>;
public flag: boolean = false;
constructor(
public xeroService: XeroService,
public userService: UserService,
private router: Router,
private activatedRoute: ActivatedRoute
) {
this.ownerUpdate = userService.ownerUpdate;
this.ownerUpdate.subscribe((owner) => {
this.owner = owner;
});
this.userService.findUser();
}
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.ownerUpdate.subscribe((owner) => {
this.owner = owner;
this.activatedRoute.queryParams.subscribe((params) => {
console.log(params);
if (params.state === this.owner.address.substring(0,6)) {
this.xeroService.getXeroAccessToken(params.code, this.owner.address).subscribe(tokenData => {
if (tokenData.status == 200) {
console.log(tokenData.body!);
this.flag = true;
redirect(this.router);
} else {
console.log('Error: '+tokenData.status);
this.flag = false;
}
});
} else {
console.log('Error: State mismatch');
}
});
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
src/assets/notifier.mp3 Normal file

Binary file not shown.

BIN
src/assets/notifier_1.mp3 Normal file

Binary file not shown.