Alerts added

Better status handling
This commit is contained in:
2021-01-01 19:33:07 +01:00
parent 84e4910c70
commit b2043e1546
8 changed files with 188 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ import { BehaviorSubject } from 'rxjs';
/*
* The following interfaces are copied from the backend.
*
*
* Checkout src/helper/types.ts and src/models/invoice/invoice.interface.ts
* (in the backend repository) for more information.
*/
@@ -31,11 +31,14 @@ export interface IPaymentMethod {
}
export enum PaymentStatus {
CANCELLED = -2,
REQUESTED = -1,
PENDING = 0,
UNCONFIRMED = 1,
DONE = 2,
TOOLITTLE = -3,
TOOLATE = -2,
CANCELLED = -1,
REQUESTED = 0,
PENDING = 1,
UNCONFIRMED = 2,
DONE = 3,
TOOMUCH = 4
}
export interface IInvoice {
selector: string;
@@ -43,7 +46,7 @@ export interface IInvoice {
receiveAddress: string;
paidWith?: CryptoUnits;
paid?: number;
transcationHashes?: string[];
transcationHash?: string;
cart?: ICart[];
totalPrice?: number;
currency: string;
@@ -62,16 +65,32 @@ export class BackendService {
SERVER_URL = 'http://localhost:2009';
invoice: IInvoice | null = null;
// Fill with empty data
invoice: IInvoice = {
selector: '',
paymentMethods: [],
receiveAddress: '',
paid: 0,
currency: 'USD',
dueBy: Date.now(),
successUrl: '',
cancelUrl: ''
};
invoiceUpdate: BehaviorSubject<IInvoice | null>;
// This value is s
confirmations: number;
constructor(
private socket: Socket,
private http: HttpClient
) {
this.confirmations = 0;
this.invoiceUpdate = new BehaviorSubject<IInvoice | null>(null);
this.socket.on('status', (data: any) => {
console.log('Status has been updated to: ', data);
this.invoice.status = data;
this.invoiceUpdate.next(this.invoice);
});
this.socket.on('subscribe', (success: boolean) => {
if (success) { console.log('We\'re getting the progress of this invoice!'); }
@@ -92,17 +111,24 @@ export class BackendService {
else { console.log('Failed to subscribe'); }
});
this.socket.on('confirmationUpdate', (update: any) => {
this.confirmations = update.count;
});
this.socket.emit('subscribe', { selector });
}
updateInvoice(): void {
if (this.invoice !== undefined || this.invoice !== null) {
this.setInvoice(this.invoice?.selector!);
this.setInvoice(this.invoice.selector);
}
}
setInvoice(selector: string): Promise<IInvoice> {
return new Promise(async (resolve, reject) => {
if (selector === undefined || selector === 'undefined' || selector === '') {
reject();
}
this.http.get(this.SERVER_URL + '/invoice/' + selector, {
observe: 'body',
responseType: 'json'
@@ -118,10 +144,32 @@ export class BackendService {
setPaymentMethod(method: CryptoUnits): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this.invoice === null) { reject('Invoice is not set!'); return; }
this.http.post(`${this.SERVER_URL}/invoice/${this.invoice?.selector}/setmethod`, { method }, {
responseType: 'json'
}).toPromise().then(() => {
this.setInvoice(this.invoice!!.selector);
this.setInvoice(this.invoice.selector);
}).catch(err => {
reject(err);
});
});
}
getConfirmation(): Promise<number> {
return new Promise(async (resolve, reject) => {
if (this.invoice === null || this.invoice.status !== PaymentStatus.UNCONFIRMED) {
reject('Invoice is not set!');
return;
}
this.http.get(`${this.SERVER_URL}/invoice/${this.invoice.selector}/confirmation`, {
observe: 'body',
responseType: 'json'
}).toPromise().then((res: any) => {
this.confirmations = res.confirmation;
this.invoiceUpdate.next(this.invoice);
resolve(res.confirmation);
}).catch(err => {
reject(err);
});
@@ -150,7 +198,7 @@ export class BackendService {
findCryptoBySymbol(symbol: string): string | null {
for (const coin in CryptoUnits) {
// @ts-ignore: This actually works but I thing it's too hacky for TS. Allow me this one, please?
// @ts-ignore: This actually works but I think it's too hacky for TS. Allow me this one, please.
if (CryptoUnits[coin] === symbol.toUpperCase()) {
return coin.charAt(0).toUpperCase() + coin.toLowerCase().slice(1);
}
@@ -173,7 +221,13 @@ export class BackendService {
case PaymentStatus.DONE:
return 'Paid';
case PaymentStatus.CANCELLED:
return 'Cancelled';
return 'Cancelled by user';
case PaymentStatus.TOOLATE:
return 'Expired';
case PaymentStatus.TOOLITTLE:
return 'Paid too little';
case PaymentStatus.TOOMUCH:
return 'Paid too much';
default:
return 'Unknown';
}

View File

@@ -12,9 +12,10 @@
border-bottom-right-radius: 0;
}
.header h2 {
font-weight: bolder;
width: fit-content;
.header img {
height: 3rem;
margin-top: 0.5rem;
margin-bottom: 1rem;
grid-column: 1;
}

View File

@@ -1,4 +1,4 @@
<div class="header">
<h2>LibrePay</h2>
<img src="assets/logo.svg">
<a *ngIf="this.backend.isInvoicePending()">Cancel payment</a>
</div>

View File

@@ -3,15 +3,41 @@
padding: 0;
width: 100%;
height: 500px;
background-color: hsl(0, 0%, 11%);
background-color: #1c1c1c;
border-radius: 8px;
transform: translateY(-8px);
}
/* Apply effect when invoice expired */
.invalid {
filter: grayscale(1);
}
.invalid .qrWrapper, .invalid .data {
filter: blur(10px);
animation: blurFade 0.2s ease;
cursor: not-allowed;
user-select: none;
}
.invalid svg, .invalid img {
display: none;
}
@keyframes blurFade {
from { filter: blur(0) }
to { filter: blur(10px) }
}
.request {
transform: translateY(-40px);
}
.loader {
position: absolute;
display: inline;
transform: translateY(-24px) translateX(32px);
}
/* Styles for payment screen (not for payment choosing) */
.main {
display: grid;
height: 400px;
@@ -38,6 +64,18 @@
z-index: 2;
}
.alert {
text-align: center;
background-color: #C03A08;
color: #fff;
padding: 1rem;
border-radius: 8px;
border: 3px solid #a93206;
}
.alert p {
margin: 0;
}
@keyframes coinRoll {
0% {
transform: translateY(96px) rotate(-45deg) scale(0.5);
@@ -65,6 +103,11 @@
grid-row: 4;
}
.price {
font-size: 10pt;
font-weight: light;
}
.main h3 {
line-height: 0;
}

View File

@@ -1,6 +1,6 @@
<div class="payment request" *ngIf="this.backend.isInvoiceRequested()">
<h3 id="title">Choose your<br>payment method</h3>
<p id="price">{{ this.backend.invoice!!.totalPrice!!.toFixed(2) }} €</p>
<p id="price">{{ this.backend.invoice.totalPrice!.toFixed(2) }} €</p>
<ul id="list">
<li *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)">
@@ -12,12 +12,12 @@
</li>
</ul>
</div>
<div class="payment main" *ngIf="!this.backend.isInvoiceRequested() && ready">
<div class="payment main" *ngIf="!this.backend.isInvoiceRequested() && ready" [ngClass]="{invalid: status === 'Expired' || status === 'Paid too little'}">
<div class="qrWrapper">
<div class="qr">
<img src="assets/Bitcoin.svg">
<qrcode
[qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress"
[qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress + '?amount=' + this.backend.getAmount()"
[width]="256"
[errorCorrectionLevel]="'M'"
[elementType]="'svg'"
@@ -29,13 +29,43 @@
<div class="data">
<!-- Payment data -->
<span id="target">Send to
<h3>{{ this.backend.invoice!.receiveAddress }}</h3>
<h3>{{ this.backend.invoice?.receiveAddress }}</h3>
</span>
<span id="amount">Amount
<h3>{{ this.backend.getAmount() }} BTC</h3>
<h3>{{ this.backend.getAmount() }} BTC <span class="price"> | {{ this.backend.invoice.totalPrice!.toFixed(2) }} €</span></h3>
</span>
<span id="status">Status
<h3>{{ this.backend.getStatus() }}</h3>
<h3>
{{ status }}
<div class="loader">
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<path fill="#f7a12f" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="0 25 25"
to="360 25 25"
dur="1s"
repeatCount="indefinite"/>
</path>
</svg>
</div>
</h3>
<small *ngIf="status === 'Unconfirmed'">Confirmations: {{ this.backend.confirmations }}</small>
</span>
</div>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Expired'">
<p><b>This invoice expired</b>
<br>You cannot pay this invoice anymore. Please try to request a new invoice. </p>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Paid too much'">
<p><b>Looks like you paid too much.</b>
<br>Technically <u><b>this invoice is paid</b></u> but we would like to pay the rest back. Since sending back funds is complicated we would like you to contact support@example.org</p>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Paid too little'">
<p><b>Looks like you paid not the requested amount of money.</b>
<br><u><b>You cannot pay twice!</b></u> Since sending back funds is complicated we would like you to contact support@example.org</p>
</div>

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BackendService, IInvoice, CryptoUnits, PaymentStatus, IPaymentMethod } from '../backend.service';
import { BackendService, CryptoUnits } from '../backend.service';
@Component({
selector: 'app-payment',
@@ -10,13 +11,17 @@ import { BackendService, IInvoice, CryptoUnits, PaymentStatus, IPaymentMethod }
export class PaymentComponent implements OnInit {
paymentSelector = '';
confirmations = 0;
choosenPaymentMethod = CryptoUnits.BITCOIN;
status: string;
ready = false;
constructor(
public backend: BackendService,
private route: ActivatedRoute
) { }
) {
this.status = this.backend.getStatus();
}
ngOnInit(): void {
this.route.params.subscribe(params => {
@@ -24,14 +29,19 @@ export class PaymentComponent implements OnInit {
this.backend.subscribeTo(this.paymentSelector);
this.get();
});
this.backend.invoiceUpdate.subscribe(newInvoice => {
this.status = this.backend.getStatus();
});
}
chooseMethod(coin: CryptoUnits) {
chooseMethod(coin: CryptoUnits): void {
this.backend.setPaymentMethod(coin);
}
async get(): Promise<void> {
await this.backend.setInvoice(this.paymentSelector);
this.backend.getConfirmation().catch();
this.ready = true;
}