Add payment methods screen

This commit is contained in:
2020-12-29 00:10:44 +01:00
parent d997da79ba
commit 84e4910c70
15 changed files with 326 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Socket } from 'ngx-socket-io';
import { BehaviorSubject } from 'rxjs';
/*
* The following interfaces are copied from the backend.
@@ -26,17 +27,16 @@ export interface ICart {
export interface IPaymentMethod {
method: any;
amount: number
amount: number;
}
export enum PaymentStatus {
CANCELLED = -2,
REQUESTED = -1,
PENDING = 0,
PARTIALLY = 1,
UNCONFIRMED = 2,
DONE = 3,
CANCELLED = 4
UNCONFIRMED = 1,
DONE = 2,
}
export interface IInvoice {
selector: string;
paymentMethods: IPaymentMethod[];
@@ -62,33 +62,132 @@ export class BackendService {
SERVER_URL = 'http://localhost:2009';
invoice: IInvoice | null = null;
invoiceUpdate: BehaviorSubject<IInvoice | null>;
constructor(
private socket: Socket,
private http: HttpClient
) {
this.invoiceUpdate = new BehaviorSubject<IInvoice | null>(null);
this.socket.on('status', (data: any) => {
console.log('Status has been updated to: ', data);
});
this.socket.on('subscribe', (success: boolean) => {
if (success) { console.log('We\'re getting the progress of this invoice!'); }
else { console.log('Subscription failed'); }
else { console.log('Subscribtion failed'); }
});
}
subscribeTo(selector: string) {
getSocket(): Socket {
return this.socket;
}
subscribeTo(selector: string): void {
this.socket.on('subscribe', (status: boolean) => {
if (status) {
this.updateInvoice();
console.log('Successfully subscribed to this invoice');
}
else { console.log('Failed to subscribe'); }
});
this.socket.emit('subscribe', { selector });
}
getInvoice(selector: string): Promise<IInvoice> {
updateInvoice(): void {
if (this.invoice !== undefined || this.invoice !== null) {
this.setInvoice(this.invoice?.selector!);
}
}
setInvoice(selector: string): Promise<IInvoice> {
return new Promise(async (resolve, reject) => {
this.http.get(this.SERVER_URL + '/invoice/' + selector, {
observe: 'body',
responseType: 'json'
}).toPromise().then((invoice) => {
resolve(invoice as IInvoice);
this.invoice = invoice as IInvoice;
this.invoiceUpdate.next(this.invoice);
resolve(this.invoice);
}).catch(err => {
reject(err);
});
});
}
setPaymentMethod(method: CryptoUnits): Promise<void> {
return new Promise(async (resolve, reject) => {
this.http.post(`${this.SERVER_URL}/invoice/${this.invoice?.selector}/setmethod`, { method }, {
responseType: 'json'
}).toPromise().then(() => {
this.setInvoice(this.invoice!!.selector);
}).catch(err => {
reject(err);
});
});
}
/**
* @returns Path to icon
*/
getIcon(unit: CryptoUnits): string {
switch (unit) {
case CryptoUnits.BITCOIN:
return 'assets/Bitcoin.svg';
case CryptoUnits.BITCOINCASH:
return 'assets/BitcoinCash.svg';
case CryptoUnits.DOGECOIN:
return 'assets/Dogecoin.png';
case CryptoUnits.ETHEREUM:
return 'assets/Ethereum.svg';
case CryptoUnits.LITECOIN:
return 'assets/Litecoin.svg';
case CryptoUnits.MONERO:
return 'assets/Monero.svg';
}
}
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?
if (CryptoUnits[coin] === symbol.toUpperCase()) {
return coin.charAt(0).toUpperCase() + coin.toLowerCase().slice(1);
}
}
return null;
}
getAmount(): string | undefined {
return this.invoice?.paymentMethods.find(item => {
return item.method === CryptoUnits.BITCOIN;
})?.amount.toFixed(8);
}
getStatus(): string {
switch (this.invoice?.status) {
case PaymentStatus.PENDING:
return 'Pending';
case PaymentStatus.UNCONFIRMED:
return 'Unconfirmed';
case PaymentStatus.DONE:
return 'Paid';
case PaymentStatus.CANCELLED:
return 'Cancelled';
default:
return 'Unknown';
}
}
isInvoiceDone(): boolean {
return this.invoice?.status === PaymentStatus.DONE;
}
isInvoicePending(): boolean {
return this.invoice?.status === PaymentStatus.PENDING;
}
isInvoiceRequested(): boolean {
return this.invoice?.status === PaymentStatus.REQUESTED;
}
}

View File

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

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { BackendService } from '../backend.service';
@Component({
selector: 'app-header',
@@ -7,7 +8,7 @@ import { Component, OnInit } from '@angular/core';
})
export class HeaderComponent implements OnInit {
constructor() { }
constructor(public backend: BackendService) { }
ngOnInit(): void {
}

View File

@@ -19,6 +19,17 @@
/* box-shadow: 1px 1px 112px 23px rgba(0,0,0,0.75); */
}
.smaller {
min-width: 200px;
width: 40vw !important;
}
@media (max-width: 800px) {
.content {
width: 100vw !important;
}
}
.content * {
width: 100%;
}

View File

@@ -6,7 +6,7 @@
<div class="cube"></div>
<div class="cube"></div>
</div>
<div class="content">
<div class="content" [ngClass]="{smaller: this.backend.isInvoiceRequested()}">
<app-header></app-header>
<app-payment></app-payment>
</div>

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { BackendService } from '../backend.service';
@Component({
selector: 'app-pay',
@@ -7,7 +8,7 @@ import { Component, OnInit } from '@angular/core';
})
export class PayComponent implements OnInit {
constructor() { }
constructor(public backend: BackendService) { }
ngOnInit(): void {
}

View File

@@ -1,15 +1,23 @@
.payment {
display: grid;
grid-template-columns: 1fr 1fr;
margin: 0;
padding: 0;
width: 100%;
height: 400px;
height: 500px;
background-color: hsl(0, 0%, 11%);
border-radius: 8px;
transform: translateY(-8px);
}
.request {
transform: translateY(-40px);
}
.main {
display: grid;
height: 400px;
grid-template-columns: 1fr 1fr;
}
.qr {
position: relative;
transform: translateY(25%);
@@ -42,12 +50,12 @@
}
/* Data */
.data {
.main .data {
display: grid;
grid-template-columns: 1fr 50px;
grid-template-rows: 1fr 4rem 4rem 4rem 1fr;
}
#target {
.main #target {
grid-row: 2;
}
#amount {
@@ -57,6 +65,65 @@
grid-row: 4;
}
h3 {
.main h3 {
line-height: 0;
}
#title {
padding-top: 2rem;
line-height: 1;
font-size: 20pt;
font-weight: normal;
text-align: center;
}
#price {
text-align: center;
font-size: 16pt;
}
#list {
overflow-y: scroll;
scroll-behavior: smooth;
-ms-overflow-style: none;
scrollbar-width: none;
margin: 0;
padding: 0;
}
#list::-webkit-scrollbar {
display: none;
}
#list li {
padding-top: 10px;
padding-left: 5%;
padding-right: 5%;
border-radius: 8px;
display: grid;
cursor: pointer;
grid-template-columns: 75px 1fr;
grid-row: 1fr;
list-style: none;
padding-bottom: 1rem;
transition: .3s ease;
line-height: 0;
}
#list li:hover {
box-shadow: 0px 0px 61px 3px rgba(0,0,0,0.3) inset;
}
#list li img {
height: 42px;
width: 42px;
margin: 0 auto;
padding-top: 12px;
grid-row-start: 1;
grid-row-end: 2;
}
#list li p {
grid-row: 1;
grid-column: 2;
}
#list li h4 {
grid-row: 2;
grid-column: 2;
}

View File

@@ -1,9 +1,23 @@
<div class="payment">
<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>
<ul id="list">
<li *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)">
<img [src]="this.backend.getIcon(coin.method)">
<div>
<h4>{{ this.backend.findCryptoBySymbol(coin.method) }}</h4>
<p>{{ coin.amount }} {{ coin.method }}</p>
</div>
</li>
</ul>
</div>
<div class="payment main" *ngIf="!this.backend.isInvoiceRequested() && ready">
<div class="qrWrapper">
<div class="qr">
<img src="assets/Bitcoin.svg">
<qrcode
[qrdata]="'bitcoin:' + invoice!!.receiveAddress"
[qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress"
[width]="256"
[errorCorrectionLevel]="'M'"
[elementType]="'svg'"
@@ -12,16 +26,16 @@
</div>
</div>
<div class="data" *ngIf="ready">
<div class="data">
<!-- Payment data -->
<span id="target">Send to
<h3>{{ invoice!!.receiveAddress }}</h3>
<h3>{{ this.backend.invoice!.receiveAddress }}</h3>
</span>
<span id="amount">Amount
<h3>{{ getAmount() }} BTC</h3>
<h3>{{ this.backend.getAmount() }} BTC</h3>
</span>
<span id="status">Status
<h3>{{ getStatus() }}</h3>
<h3>{{ this.backend.getStatus() }}</h3>
</span>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BackendService, IInvoice, CryptoUnits, PaymentStatus } from '../backend.service';
import { BackendService, IInvoice, CryptoUnits, PaymentStatus, IPaymentMethod } from '../backend.service';
@Component({
selector: 'app-payment',
@@ -12,10 +12,9 @@ export class PaymentComponent implements OnInit {
paymentSelector = '';
choosenPaymentMethod = CryptoUnits.BITCOIN;
ready = false;
invoice: IInvoice | null = null;
constructor(
private backend: BackendService,
public backend: BackendService,
private route: ActivatedRoute
) { }
@@ -27,32 +26,13 @@ export class PaymentComponent implements OnInit {
});
}
async get() {
this.invoice = await this.backend.getInvoice(this.paymentSelector);
chooseMethod(coin: CryptoUnits) {
this.backend.setPaymentMethod(coin);
}
async get(): Promise<void> {
await this.backend.setInvoice(this.paymentSelector);
this.ready = true;
}
getAmount() {
return this.invoice?.paymentMethods.find(item => {
return item.method === CryptoUnits.BITCOIN;
})?.amount.toFixed(8);
}
getStatus() {
switch (this.invoice?.status) {
case PaymentStatus.PENDING:
return 'Pending';
case PaymentStatus.PARTIALLY:
return 'Partly';
case PaymentStatus.UNCONFIRMED:
return 'Unconfirmed';
case PaymentStatus.DONE:
return 'Paid';
case PaymentStatus.CANCELLED:
return 'Cancelled';
default:
return 'Unknown';
}
}
}