Skip to content

Commit

Permalink
move ParsedPaymentOptions to own file
Browse files Browse the repository at this point in the history
also smaller code  updates
  • Loading branch information
nibhar committed Jan 14, 2020
1 parent 33e401f commit 0698600
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 103 deletions.
6 changes: 3 additions & 3 deletions src/lib/PublicRequestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export interface PaymentOptions<C extends Currency, T extends PaymentType> {
currency: C;
expires?: number;
/**
* Amount in the smallest unit of the currency specified in `currency`.
* Amount in the smallest unit of the currency specified as `currency`.
* i.e Luna for Currency.NIM and Satoshi for Currency.BTC
*/
amount: string;
Expand Down Expand Up @@ -115,7 +115,7 @@ export interface MultiCurrencyCheckoutRequest extends BasicRequest {
*/
callbackUrl?: string;
/**
* The csrf token, that will be transmitted for future requests to the callback url
* A CSRF token, that will be transmitted in all requests to the callback url.
*/
csrf?: string;
/**
Expand All @@ -127,7 +127,7 @@ export interface MultiCurrencyCheckoutRequest extends BasicRequest {
*/
time: number;
/**
* ISO 4217 Code of the fiat currency used on the calling site.
* ISO 4217 Code (three letters) of the fiat currency used on the calling site.
*/
fiatCurrency: string;
/**
Expand Down
39 changes: 22 additions & 17 deletions src/lib/RequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class RequestParser {
fiatAmount: checkoutRequest.fiatAmount,
paymentOptions: checkoutRequest.paymentOptions.map((option) => {
if (currencies.has(option.currency)) {
throw new Error('Each Currency can only have one paymentOption');
throw new Error('Only one paymentOption can be provided per cryptocurrency');
} else {
currencies.add(option.currency);
}
Expand Down Expand Up @@ -331,24 +331,29 @@ export class RequestParser {
} as NimiqCheckoutRequest;
case 2:
return {
appName: checkoutRequest.appName,
version: 2,
shopLogoUrl: checkoutRequest.shopLogoUrl,
...checkoutRequest,
extraData: checkoutRequest.data,
callbackUrl: checkoutRequest.callbackUrl,
csrf: checkoutRequest.csrf,
time: checkoutRequest.time,
fiatAmount: checkoutRequest.fiatAmount || undefined,
fiatCurrency: checkoutRequest.fiatCurrency || undefined,
paymentOptions: checkoutRequest.paymentOptions.map((option) => {
switch (option.type) {
case PaymentType.DIRECT:
return option.raw();
default:
throw new Error('paymentOption.type not supported');
}
}),
paymentOptions: checkoutRequest.paymentOptions.map((option) => option.raw()),
} as MultiCurrencyCheckoutRequest;
// return {
// appName: checkoutRequest.appName,
// version: 2,
// shopLogoUrl: checkoutRequest.shopLogoUrl,
// extraData: checkoutRequest.data,
// callbackUrl: checkoutRequest.callbackUrl,
// csrf: checkoutRequest.csrf,
// time: checkoutRequest.time,
// fiatAmount: checkoutRequest.fiatAmount || undefined,
// fiatCurrency: checkoutRequest.fiatCurrency || undefined,
// paymentOptions: checkoutRequest.paymentOptions.map((option) => {
// switch (option.type) {
// case PaymentType.DIRECT:
// return option.raw();
// default:
// throw new Error('paymentOption.type not supported');
// }
// }),
// } as MultiCurrencyCheckoutRequest;
}
case RequestType.ONBOARD:
const onboardRequest = request as ParsedOnboardRequest;
Expand Down
64 changes: 2 additions & 62 deletions src/lib/RequestTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
type BigInteger = import('big-integer').BigInteger; // imports only the type without bundling
import { FormattableNumber, toNonScientificNumberString } from '@nimiq/utils';
import { isMilliseconds } from './Helpers';
import { Currency, PaymentType, PaymentOptionsForCurrencyAndType, RequestType } from './PublicRequestTypes';
import { Currency, PaymentType, RequestType } from './PublicRequestTypes';
import { ParsedPaymentOptions } from './paymentOptions/ParsedPaymentOptions';
import { ParsedNimiqSpecifics, ParsedNimiqDirectPaymentOptions } from './paymentOptions/NimiqPaymentOptions';
import { ParsedEtherSpecifics, ParsedEtherDirectPaymentOptions } from './paymentOptions/EtherPaymentOptions';
import { ParsedBitcoinSpecifics, ParsedBitcoinDirectPaymentOptions } from './paymentOptions/BitcoinPaymentOptions';
Expand Down Expand Up @@ -36,64 +34,6 @@ export type ParsedProtocolSpecificsForCurrency<C extends Currency> =
: C extends Currency.ETH ? ParsedEtherSpecifics
: undefined;

export interface ParsedPaymentOptions<C extends Currency, T extends PaymentType> {
readonly currency: C;
readonly type: T;
readonly decimals: number;
protocolSpecific: ParsedProtocolSpecificsForCurrency<C>;
amount: number | BigInteger;
expires?: number;
constructor: ParsedPaymentOptionsForCurrencyAndType<C, T>;
new(options: PaymentOptionsForCurrencyAndType<C, T>, ...optionalArgs: any[]):
ParsedPaymentOptionsForCurrencyAndType<C, T>;
raw(): PaymentOptionsForCurrencyAndType<C, T>;
}

export abstract class ParsedPaymentOptions<C extends Currency, T extends PaymentType>
implements ParsedPaymentOptions<C, T> {

protected constructor(options: PaymentOptionsForCurrencyAndType<C, T>) {
if (options.currency !== this.currency || options.type !== this.type) {
throw new Error(`Cannot parse given options as ${this.constructor.name}.`);
}
if (!this.isNonNegativeInteger(options.amount)) {
throw new Error('amount must be a non-negative integer');
}
this.expires = typeof options.expires === 'number'
? isMilliseconds(options.expires)
? options.expires
: options.expires * 1000
: undefined;
}

public get baseUnitAmount(): string {
return new FormattableNumber(this.amount).moveDecimalSeparator(-this.decimals).toString();
}

public update<P extends ParsedPaymentOptions<C, T>>(
this: P,
options: PaymentOptionsForCurrencyAndType<C, T>,
...additionalArgs: any[]
) {
const parsedOptions = new this.constructor(options as any, ...additionalArgs); // parse to check validity
this.amount = parsedOptions.amount; // amount must exist on all parsed options
this.expires = parsedOptions.expires || this.expires;
for (const key of
Object.keys(parsedOptions.protocolSpecific) as Array<keyof typeof parsedOptions.protocolSpecific>) {
if (parsedOptions.protocolSpecific[key] === undefined) continue;
this.protocolSpecific[key] = parsedOptions.protocolSpecific[key];
}
}

protected isNonNegativeInteger(value: string | number | bigint | BigInteger) {
try {
return /^\d+$/.test(toNonScientificNumberString(value));
} catch (e) {
return false;
}
}
}

export type AvailableParsedPaymentOptions = ParsedNimiqDirectPaymentOptions
| ParsedEtherDirectPaymentOptions
| ParsedBitcoinDirectPaymentOptions;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/paymentOptions/BitcoinPaymentOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Currency, PaymentType, PaymentOptions } from '../PublicRequestTypes';
import { ParsedPaymentOptions } from '../RequestTypes';
import { ParsedPaymentOptions } from './ParsedPaymentOptions';
import { toNonScientificNumberString } from '@nimiq/utils';

export interface BitcoinSpecifics {
Expand Down
10 changes: 5 additions & 5 deletions src/lib/paymentOptions/EtherPaymentOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import bigInt from 'big-integer';
import { Currency, PaymentType, PaymentOptions } from '../PublicRequestTypes';
import { ParsedPaymentOptions } from '../RequestTypes';
import { ParsedPaymentOptions } from './ParsedPaymentOptions';
import { toNonScientificNumberString } from '@nimiq/utils';

export interface EtherSpecifics {
Expand Down Expand Up @@ -72,14 +72,14 @@ export class ParsedEtherDirectPaymentOptions extends ParsedPaymentOptions<Curren
}

public fiatFee(fiatAmount: number): number {
if (!this.amount || !fiatAmount) {
throw new Error('amount and fiatAmount must be provided');
}

if (this.fee.isZero()) {
return 0;
}

if (!this.amount || !fiatAmount) {
throw new Error('amount and fiatAmount must be provided');
}

const decimalMatch = toNonScientificNumberString(fiatAmount).match(/(?:\D)(\d+)$/);
const decimalCount = decimalMatch ? decimalMatch[1].length : 0;
const conversionFactor = 10 ** decimalCount; // convert amount to smallest unit for bigint calculations
Expand Down
20 changes: 10 additions & 10 deletions src/lib/paymentOptions/NimiqPaymentOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TX_VALIDITY_WINDOW, TX_MIN_VALIDITY_DURATION } from '../Constants';
import { Currency, PaymentType, PaymentOptions } from '../PublicRequestTypes';
import { ParsedPaymentOptions } from '../RequestTypes';
import { ParsedPaymentOptions } from './ParsedPaymentOptions';
import { toNonScientificNumberString } from '@nimiq/utils';

export interface NimiqSpecifics {
Expand Down Expand Up @@ -34,7 +34,7 @@ export class ParsedNimiqDirectPaymentOptions extends ParsedPaymentOptions<Curren
this.amount = parseInt(toNonScientificNumberString(options.amount), 10);

let sender: Nimiq.Address | undefined;
if (options.protocolSpecific.sender !== undefined) {
if (options.protocolSpecific.sender) {
try {
sender = Nimiq.Address.fromUserFriendlyAddress(options.protocolSpecific.sender);
} catch (err) {
Expand All @@ -43,7 +43,7 @@ export class ParsedNimiqDirectPaymentOptions extends ParsedPaymentOptions<Curren
}

let recipient: Nimiq.Address | undefined;
if (options.protocolSpecific.recipient !== undefined) {
if (options.protocolSpecific.recipient) {
try {
recipient = Nimiq.Address.fromUserFriendlyAddress(options.protocolSpecific.recipient);
} catch (err) {
Expand Down Expand Up @@ -82,8 +82,8 @@ export class ParsedNimiqDirectPaymentOptions extends ParsedPaymentOptions<Curren
}

const requiresExtendedTransaction = extraData.byteLength > 0
|| recipientType !== undefined && recipientType !== Nimiq.Account.Type.BASIC
|| flags !== undefined && flags !== Nimiq.Transaction.Flag.NONE;
|| (recipientType !== undefined && recipientType !== Nimiq.Account.Type.BASIC)
|| (flags !== undefined && flags !== Nimiq.Transaction.Flag.NONE);
// Note that the transaction size can be bigger than this, for example if the sender type the user wants to use
// requires an extended transaction or if an extended transaction includes a multi signature proof. The size
// is therefore just an estimate. In the majority of cases the estimate will be accurate though and a fee that
Expand All @@ -102,8 +102,8 @@ export class ParsedNimiqDirectPaymentOptions extends ParsedPaymentOptions<Curren
}

if (options.protocolSpecific.validityDuration !== undefined
&& !this.isNonNegativeInteger(options.protocolSpecific.validityDuration)) {
throw new Error('If provided, validityDuration must be a non-negative integer.');
&& options.protocolSpecific.validityDuration > 0) {
throw new Error('If provided, validityDuration must be a positive integer.');
}

this.protocolSpecific = {
Expand Down Expand Up @@ -147,12 +147,12 @@ export class ParsedNimiqDirectPaymentOptions extends ParsedPaymentOptions<Curren
}

public fiatFee(fiatAmount: number): number {
if (!this.amount || !fiatAmount) {
throw new Error('amount and fiatAmount must be provided');
}
if (!this.fee) {
return 0;
}
if (!this.amount || !fiatAmount) {
throw new Error('amount and fiatAmount must be provided');
}
return this.fee * fiatAmount / this.amount;
}

Expand Down
71 changes: 71 additions & 0 deletions src/lib/paymentOptions/ParsedPaymentOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
type BigInteger = import('big-integer').BigInteger; // imports only the type without bundling
import { FormattableNumber, toNonScientificNumberString } from '@nimiq/utils';
import { isMilliseconds } from '../Helpers';
import {
Currency,
PaymentType,
PaymentOptionsForCurrencyAndType,
} from '../PublicRequestTypes';
import {
ParsedPaymentOptionsForCurrencyAndType,
ParsedProtocolSpecificsForCurrency,
} from '../RequestTypes';

export interface ParsedPaymentOptions<C extends Currency, T extends PaymentType> {
protocolSpecific: ParsedProtocolSpecificsForCurrency<C>;
amount: number | BigInteger;
expires?: number;
constructor: ParsedPaymentOptionsForCurrencyAndType<C, T>;
new(options: PaymentOptionsForCurrencyAndType<C, T>, ...optionalArgs: any[]):
ParsedPaymentOptionsForCurrencyAndType<C, T>;
}

export abstract class ParsedPaymentOptions<C extends Currency, T extends PaymentType>
implements ParsedPaymentOptions<C, T> {
protected constructor(options: PaymentOptionsForCurrencyAndType<C, T>) {
if (options.currency !== this.constructor.currency || options.type !== this.constructor.type) {
throw new Error(`Cannot parse given options as ${this.constructor.name}.`);
}
if (!this.isNonNegativeInteger(options.amount)) {
throw new Error('Amount must be a non-negative integer');
}
this.expires = typeof options.expires === 'number'
? isMilliseconds(options.expires)
? options.expires
: options.expires * 1000
: undefined;
}

public abstract get currency(): C;
public abstract get type(): T;
public abstract get decimals(): number;

public get baseUnitAmount(): string {
return new FormattableNumber(this.amount).moveDecimalSeparator(-this.decimals).toString();
}

public update<P extends ParsedPaymentOptions<C, T>>(
this: P,
options: PaymentOptionsForCurrencyAndType<C, T>,
...additionalArgs: any[]
) {
const parsedOptions = new this.constructor(options as any, ...additionalArgs); // parse to check validity
this.amount = parsedOptions.amount; // amount must exist on all parsed options
this.expires = parsedOptions.expires || this.expires;
for (const key of
Object.keys(parsedOptions.protocolSpecific) as Array<keyof typeof parsedOptions.protocolSpecific>) {
if (parsedOptions.protocolSpecific[key] === undefined) continue;
this.protocolSpecific[key] = parsedOptions.protocolSpecific[key];
}
}

public abstract raw(): PaymentOptionsForCurrencyAndType<C, T>;

protected isNonNegativeInteger(value: string | number | bigint | BigInteger) {
try {
return /^\d+$/.test(toNonScientificNumberString(value));
} catch (e) {
return false;
}
}
}
7 changes: 2 additions & 5 deletions src/views/SignTransactionLedger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ import StatusScreen from '../components/StatusScreen.vue';
import { Static } from '../lib/StaticStore';
import { Getter } from 'vuex-class';
import { State as RpcState } from '@nimiq/rpc';
import {
ParsedCheckoutRequest,
ParsedSignTransactionRequest,
} from '../lib/RequestTypes';
import { ParsedCheckoutRequest, ParsedSignTransactionRequest } from '../lib/RequestTypes';
import { Currency, RequestType } from '../lib/PublicRequestTypes';
import { WalletInfo } from '../lib/WalletInfo';
import { ERROR_CANCELED, TX_VALIDITY_WINDOW, CASHLINK_FUNDING_DATA } from '../lib/Constants';
Expand Down Expand Up @@ -218,7 +215,7 @@ export default class SignTransactionLedger extends Vue {
// The next block is the earliest for which tx are accepted by standard miners
validityStartHeight = blockchainHeight + 1
- TX_VALIDITY_WINDOW
+ nimiqPaymentOption.protocolSpecific.validityDuration!;
+ nimiqPaymentOption.protocolSpecific.validityDuration;
} else {
// this case get's rejected in created
return;
Expand Down

0 comments on commit 0698600

Please sign in to comment.