change protocol to string amount network format
This commit is contained in:
parent
9fe6dc5965
commit
97f6e68ce3
@ -38,19 +38,19 @@ export class AmountJson {
|
||||
/**
|
||||
* Value, must be an integer.
|
||||
*/
|
||||
@Checkable.Number
|
||||
@Checkable.Number()
|
||||
readonly value: number;
|
||||
|
||||
/**
|
||||
* Fraction, must be an integer. Represent 1/1e8 of a unit.
|
||||
*/
|
||||
@Checkable.Number
|
||||
@Checkable.Number()
|
||||
readonly fraction: number;
|
||||
|
||||
/**
|
||||
* Currency of the amount.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
readonly currency: string;
|
||||
|
||||
/**
|
||||
@ -226,7 +226,7 @@ export function isNonZero(a: AmountJson): boolean {
|
||||
* Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
|
||||
*/
|
||||
export function parse(s: string): AmountJson|undefined {
|
||||
const res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/);
|
||||
const res = s.match(/([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?/);
|
||||
if (!res) {
|
||||
return undefined;
|
||||
}
|
||||
@ -237,6 +237,14 @@ export function parse(s: string): AmountJson|undefined {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseOrThrow(s: string): AmountJson {
|
||||
const res = parse(s);
|
||||
if (!res) {
|
||||
throw Error(`Can't parse amount: "${s}"`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the amount to a float.
|
||||
*/
|
||||
@ -255,3 +263,23 @@ export function fromFloat(floatVal: number, currency: string) {
|
||||
value: Math.floor(floatVal),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to standard human-readable string representation that's
|
||||
* also used in JSON formats.
|
||||
*/
|
||||
export function toString(a: AmountJson) {
|
||||
return `${a.currency}:${a.value + (a.fraction / fractionalBase)}`;
|
||||
}
|
||||
|
||||
export function check(a: any) {
|
||||
if (typeof a !== "string") {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const parsedAmount = parse(a);
|
||||
return !!parsedAmount;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ export namespace Checkable {
|
||||
elementChecker?: any;
|
||||
elementProp?: any;
|
||||
keyProp?: any;
|
||||
stringChecker?: (s: string) => boolean;
|
||||
valueProp?: any;
|
||||
optional?: boolean;
|
||||
extraAllowed?: boolean;
|
||||
@ -109,6 +110,9 @@ export namespace Checkable {
|
||||
if (typeof target !== "string") {
|
||||
throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`);
|
||||
}
|
||||
if (prop.stringChecker && !prop.stringChecker(target)) {
|
||||
throw new SchemaError(`string property ${path} malformed`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@ -316,7 +320,7 @@ export namespace Checkable {
|
||||
/**
|
||||
* Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
|
||||
*/
|
||||
export function Optional(type: any) {
|
||||
export function Optional(type: (target: object, propertyKey: string | symbol) => void | any) {
|
||||
const stub = {};
|
||||
type(stub, "(optional-element)");
|
||||
const elementProp = getCheckableInfo(stub).props[0];
|
||||
@ -342,21 +346,27 @@ export namespace Checkable {
|
||||
/**
|
||||
* Target property must be a number.
|
||||
*/
|
||||
export function Number(target: object, propertyKey: string | symbol): void {
|
||||
export function Number(): (target: object, propertyKey: string | symbol) => void {
|
||||
const deco = (target: object, propertyKey: string | symbol) => {
|
||||
const chk = getCheckableInfo(target);
|
||||
chk.props.push({checker: checkNumber, propertyKey});
|
||||
};
|
||||
return deco;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Target property must be an arbitary object.
|
||||
*/
|
||||
export function AnyObject(target: object, propertyKey: string | symbol): void {
|
||||
export function AnyObject(): (target: object, propertyKey: string | symbol) => void {
|
||||
const deco = (target: object, propertyKey: string | symbol) => {
|
||||
const chk = getCheckableInfo(target);
|
||||
chk.props.push({
|
||||
checker: checkAnyObject,
|
||||
propertyKey,
|
||||
});
|
||||
};
|
||||
return deco;
|
||||
}
|
||||
|
||||
|
||||
@ -366,29 +376,40 @@ export namespace Checkable {
|
||||
* Not useful by itself, but in combination with higher-order annotations
|
||||
* such as List or Map.
|
||||
*/
|
||||
export function Any(target: object, propertyKey: string | symbol): void {
|
||||
export function Any(): (target: object, propertyKey: string | symbol) => void {
|
||||
const deco = (target: object, propertyKey: string | symbol) => {
|
||||
const chk = getCheckableInfo(target);
|
||||
chk.props.push({
|
||||
checker: checkAny,
|
||||
optional: true,
|
||||
propertyKey,
|
||||
});
|
||||
};
|
||||
return deco;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Target property must be a string.
|
||||
*/
|
||||
export function String(target: object, propertyKey: string | symbol): void {
|
||||
export function String(
|
||||
stringChecker?: (s: string) => boolean): (target: object, propertyKey: string | symbol,
|
||||
) => void {
|
||||
const deco = (target: object, propertyKey: string | symbol) => {
|
||||
const chk = getCheckableInfo(target);
|
||||
chk.props.push({ checker: checkString, propertyKey });
|
||||
chk.props.push({ checker: checkString, propertyKey, stringChecker });
|
||||
};
|
||||
return deco;
|
||||
}
|
||||
|
||||
/**
|
||||
* Target property must be a boolean value.
|
||||
*/
|
||||
export function Boolean(target: object, propertyKey: string | symbol): void {
|
||||
export function Boolean(): (target: object, propertyKey: string | symbol) => void {
|
||||
const deco = (target: object, propertyKey: string | symbol) => {
|
||||
const chk = getCheckableInfo(target);
|
||||
chk.props.push({ checker: checkBoolean, propertyKey });
|
||||
};
|
||||
return deco;
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ namespace RpcFunctions {
|
||||
const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
|
||||
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
|
||||
// okay if saturates
|
||||
fees = Amounts.sub(fees, contractTerms.max_fee).amount;
|
||||
fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee)).amount;
|
||||
const total = Amounts.add(fees, totalAmount).amount;
|
||||
|
||||
const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
|
||||
@ -335,7 +335,7 @@ namespace RpcFunctions {
|
||||
const s: CoinPaySig = {
|
||||
coin_pub: cd.coin.coinPub,
|
||||
coin_sig: coinSig,
|
||||
contribution: coinSpend.toJson(),
|
||||
contribution: Amounts.toString(coinSpend.toJson()),
|
||||
denom_pub: cd.coin.denomPub,
|
||||
exchange_url: cd.denom.exchangeBaseUrl,
|
||||
ub_sig: cd.coin.denomSig,
|
||||
|
@ -49,7 +49,7 @@ import {
|
||||
* In the future we might consider adding migration functions for
|
||||
* each version increment.
|
||||
*/
|
||||
export const WALLET_DB_VERSION = 25;
|
||||
export const WALLET_DB_VERSION = 26;
|
||||
|
||||
|
||||
/**
|
||||
@ -212,14 +212,14 @@ export class DenominationRecord {
|
||||
/**
|
||||
* The denomination public key.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
denomPub: string;
|
||||
|
||||
/**
|
||||
* Hash of the denomination public key.
|
||||
* Stored in the database for faster lookups.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
denomPubHash: string;
|
||||
|
||||
/**
|
||||
@ -249,38 +249,38 @@ export class DenominationRecord {
|
||||
/**
|
||||
* Validity start date of the denomination.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
stampStart: string;
|
||||
|
||||
/**
|
||||
* Date after which the currency can't be withdrawn anymore.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
stampExpireWithdraw: string;
|
||||
|
||||
/**
|
||||
* Date after the denomination officially doesn't exist anymore.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
stampExpireLegal: string;
|
||||
|
||||
/**
|
||||
* Data after which coins of this denomination can't be deposited anymore.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
stampExpireDeposit: string;
|
||||
|
||||
/**
|
||||
* Signature by the exchange's master key over the denomination
|
||||
* information.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
masterSig: string;
|
||||
|
||||
/**
|
||||
* Did we verify the signature on the denomination?
|
||||
*/
|
||||
@Checkable.Number
|
||||
@Checkable.Number()
|
||||
status: DenominationStatus;
|
||||
|
||||
/**
|
||||
@ -288,13 +288,13 @@ export class DenominationRecord {
|
||||
* we checked?
|
||||
* Only false when the exchange redacts a previously published denomination.
|
||||
*/
|
||||
@Checkable.Boolean
|
||||
@Checkable.Boolean()
|
||||
isOffered: boolean;
|
||||
|
||||
/**
|
||||
* Base URL of the exchange.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchangeBaseUrl: string;
|
||||
|
||||
/**
|
||||
@ -500,7 +500,7 @@ export class ProposalDownloadRecord {
|
||||
/**
|
||||
* URL where the proposal was downloaded.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
url: string;
|
||||
|
||||
/**
|
||||
@ -512,32 +512,32 @@ export class ProposalDownloadRecord {
|
||||
/**
|
||||
* Signature by the merchant over the contract details.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
merchantSig: string;
|
||||
|
||||
/**
|
||||
* Hash of the contract terms.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
contractTermsHash: string;
|
||||
|
||||
/**
|
||||
* Serial ID when the offer is stored in the wallet DB.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.Number)
|
||||
@Checkable.Optional(Checkable.Number())
|
||||
id?: number;
|
||||
|
||||
/**
|
||||
* Timestamp (in ms) of when the record
|
||||
* was created.
|
||||
*/
|
||||
@Checkable.Number
|
||||
@Checkable.Number()
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* Private key for the nonce.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
noncePriv: string;
|
||||
|
||||
/**
|
||||
|
@ -119,12 +119,19 @@ export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
|
||||
*/
|
||||
export function getTalerStampSec(stamp: string): number | null {
|
||||
const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
|
||||
if (!m) {
|
||||
if (!m || !m[1]) {
|
||||
return null;
|
||||
}
|
||||
return parseInt(m[1], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a timestamp is in the right format.
|
||||
*/
|
||||
export function timestampCheck(stamp: string): boolean {
|
||||
return getTalerStampSec(stamp) !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a JavaScript Date object from a Taler date string.
|
||||
|
@ -26,9 +26,12 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { AmountJson } from "./amounts";
|
||||
import { Checkable } from "./checkable";
|
||||
|
||||
import * as Amounts from "./amounts";
|
||||
|
||||
import { timestampCheck } from "./helpers";
|
||||
|
||||
|
||||
/**
|
||||
* Denomination as found in the /keys response from the exchange.
|
||||
@ -38,70 +41,70 @@ export class Denomination {
|
||||
/**
|
||||
* Value of one coin of the denomination.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
value: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* Public signing key of the denomination.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
denom_pub: string;
|
||||
|
||||
/**
|
||||
* Fee for withdrawing.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
fee_withdraw: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
fee_withdraw: string;
|
||||
|
||||
/**
|
||||
* Fee for depositing.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
fee_deposit: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
fee_deposit: string;
|
||||
|
||||
/**
|
||||
* Fee for refreshing.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
fee_refresh: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
fee_refresh: string;
|
||||
|
||||
/**
|
||||
* Fee for refunding.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
fee_refund: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
fee_refund: string;
|
||||
|
||||
/**
|
||||
* Start date from which withdraw is allowed.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
stamp_start: string;
|
||||
|
||||
/**
|
||||
* End date for withdrawing.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
stamp_expire_withdraw: string;
|
||||
|
||||
/**
|
||||
* Expiration date after which the exchange can forget about
|
||||
* the currency.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
stamp_expire_legal: string;
|
||||
|
||||
/**
|
||||
* Date after which the coins of this denomination can't be
|
||||
* deposited anymore.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
stamp_expire_deposit: string;
|
||||
|
||||
/**
|
||||
* Signature over the denomination information by the exchange's master
|
||||
* signing key.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
master_sig: string;
|
||||
|
||||
/**
|
||||
@ -120,13 +123,13 @@ export class AuditorDenomSig {
|
||||
/**
|
||||
* Denomination public key's hash.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
denom_pub_h: string;
|
||||
|
||||
/**
|
||||
* The signature.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
auditor_sig: string;
|
||||
}
|
||||
|
||||
@ -139,13 +142,13 @@ export class Auditor {
|
||||
/**
|
||||
* Auditor's public key.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
auditor_pub: string;
|
||||
|
||||
/**
|
||||
* Base URL of the auditor.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
auditor_url: string;
|
||||
|
||||
/**
|
||||
@ -197,20 +200,20 @@ export class PaybackConfirmation {
|
||||
/**
|
||||
* public key of the reserve that will receive the payback.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
reserve_pub: string;
|
||||
|
||||
/**
|
||||
* How much will the exchange pay back (needed by wallet in
|
||||
* case coin was partially spent and wallet got restored from backup)
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
amount: AmountJson;
|
||||
@Checkable.String()
|
||||
amount: string;
|
||||
|
||||
/**
|
||||
* Time by which the exchange received the /payback request.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
timestamp: string;
|
||||
|
||||
/**
|
||||
@ -220,7 +223,7 @@ export class PaybackConfirmation {
|
||||
* by the date specified (this allows the exchange delaying the transfer
|
||||
* a bit to aggregate additional payback requests into a larger one).
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange_sig: string;
|
||||
|
||||
/**
|
||||
@ -229,7 +232,7 @@ export class PaybackConfirmation {
|
||||
* explicitly as the client might otherwise be confused by clock skew as to
|
||||
* which signing key was used.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange_pub: string;
|
||||
|
||||
/**
|
||||
@ -263,7 +266,7 @@ export interface CoinPaySig {
|
||||
/**
|
||||
* The amount that is subtracted from this coin with this payment.
|
||||
*/
|
||||
contribution: AmountJson;
|
||||
contribution: string;
|
||||
|
||||
/**
|
||||
* URL of the exchange this coin was withdrawn from.
|
||||
@ -281,13 +284,13 @@ export class ExchangeHandle {
|
||||
/**
|
||||
* Master public signing key of the exchange.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
master_pub: string;
|
||||
|
||||
/**
|
||||
* Base URL of the exchange.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
url: string;
|
||||
|
||||
/**
|
||||
@ -312,67 +315,67 @@ export class ContractTerms {
|
||||
/**
|
||||
* Hash of the merchant's wire details.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
H_wire: string;
|
||||
|
||||
/**
|
||||
* Wire method the merchant wants to use.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
wire_method: string;
|
||||
|
||||
/**
|
||||
* Human-readable short summary of the contract.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.String)
|
||||
@Checkable.Optional(Checkable.String())
|
||||
summary?: string;
|
||||
|
||||
/**
|
||||
* Nonce used to ensure freshness.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.String)
|
||||
@Checkable.Optional(Checkable.String())
|
||||
nonce?: string;
|
||||
|
||||
/**
|
||||
* Total amount payable.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
amount: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
amount: string;
|
||||
|
||||
/**
|
||||
* Auditors accepted by the merchant.
|
||||
*/
|
||||
@Checkable.List(Checkable.AnyObject)
|
||||
@Checkable.List(Checkable.AnyObject())
|
||||
auditors: any[];
|
||||
|
||||
/**
|
||||
* Deadline to pay for the contract.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.String)
|
||||
@Checkable.Optional(Checkable.String())
|
||||
pay_deadline: string;
|
||||
|
||||
/**
|
||||
* Delivery locations.
|
||||
*/
|
||||
@Checkable.Any
|
||||
@Checkable.Any()
|
||||
locations: any;
|
||||
|
||||
/**
|
||||
* Maximum deposit fee covered by the merchant.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
max_fee: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
max_fee: string;
|
||||
|
||||
/**
|
||||
* Information about the merchant.
|
||||
*/
|
||||
@Checkable.Any
|
||||
@Checkable.Any()
|
||||
merchant: any;
|
||||
|
||||
/**
|
||||
* Public key of the merchant.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
merchant_pub: string;
|
||||
|
||||
/**
|
||||
@ -384,57 +387,57 @@ export class ContractTerms {
|
||||
/**
|
||||
* Products that are sold in this contract.
|
||||
*/
|
||||
@Checkable.List(Checkable.AnyObject)
|
||||
@Checkable.List(Checkable.AnyObject())
|
||||
products: any[];
|
||||
|
||||
/**
|
||||
* Deadline for refunds.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
refund_deadline: string;
|
||||
|
||||
/**
|
||||
* Time when the contract was generated by the merchant.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
timestamp: string;
|
||||
|
||||
/**
|
||||
* Order id to uniquely identify the purchase within
|
||||
* one merchant instance.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
order_id: string;
|
||||
|
||||
/**
|
||||
* URL to post the payment to.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
pay_url: string;
|
||||
|
||||
/**
|
||||
* Fulfillment URL to view the product or
|
||||
* delivery status.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
fulfillment_url: string;
|
||||
|
||||
/**
|
||||
* Share of the wire fee that must be settled with one payment.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.Number)
|
||||
@Checkable.Optional(Checkable.Number())
|
||||
wire_fee_amortization?: number;
|
||||
|
||||
/**
|
||||
* Maximum wire fee that the merchant agrees to pay for.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.Value(() => AmountJson))
|
||||
max_wire_fee?: AmountJson;
|
||||
@Checkable.Optional(Checkable.String())
|
||||
max_wire_fee?: string;
|
||||
|
||||
/**
|
||||
* Extra data, interpreted by the mechant only.
|
||||
*/
|
||||
@Checkable.Any
|
||||
@Checkable.Any()
|
||||
extra: any;
|
||||
|
||||
/**
|
||||
@ -480,31 +483,31 @@ export class MerchantRefundPermission {
|
||||
/**
|
||||
* Amount to be refunded.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
refund_amount: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
refund_amount: string;
|
||||
|
||||
/**
|
||||
* Fee for the refund.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
refund_fee: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
refund_fee: string;
|
||||
|
||||
/**
|
||||
* Public key of the coin being refunded.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
coin_pub: string;
|
||||
|
||||
/**
|
||||
* Refund transaction ID between merchant and exchange.
|
||||
*/
|
||||
@Checkable.Number
|
||||
@Checkable.Number()
|
||||
rtransaction_id: number;
|
||||
|
||||
/**
|
||||
* Signature made by the merchant over the refund permission.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
merchant_sig: string;
|
||||
|
||||
/**
|
||||
@ -523,13 +526,13 @@ export interface RefundRequest {
|
||||
* coin's total deposit value (including deposit fee);
|
||||
* must be larger than the refund fee.
|
||||
*/
|
||||
refund_amount: AmountJson;
|
||||
refund_amount: string;
|
||||
|
||||
/**
|
||||
* Refund fee associated with the given coin.
|
||||
* must be smaller than the refund amount.
|
||||
*/
|
||||
refund_fee: AmountJson;
|
||||
refund_fee: string;
|
||||
|
||||
/**
|
||||
* SHA-512 hash of the contact of the merchant with the customer.
|
||||
@ -566,14 +569,14 @@ export class MerchantRefundResponse {
|
||||
/**
|
||||
* Public key of the merchant
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
merchant_pub: string;
|
||||
|
||||
/**
|
||||
* Contract terms hash of the contract that
|
||||
* is being refunded.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
h_contract_terms: string;
|
||||
|
||||
/**
|
||||
@ -629,7 +632,7 @@ export class ReserveSigSingleton {
|
||||
/**
|
||||
* Reserve signature.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
reserve_sig: string;
|
||||
|
||||
/**
|
||||
@ -638,6 +641,30 @@ export class ReserveSigSingleton {
|
||||
static checked: (obj: any) => ReserveSigSingleton;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Response to /reserve/status
|
||||
*/
|
||||
@Checkable.Class()
|
||||
export class ReserveStatus {
|
||||
/**
|
||||
* Reserve signature.
|
||||
*/
|
||||
@Checkable.String()
|
||||
balance: string;
|
||||
|
||||
/**
|
||||
* Reserve history, currently not used by the wallet.
|
||||
*/
|
||||
@Checkable.Any()
|
||||
history: any;
|
||||
|
||||
/**
|
||||
* Create a ReserveSigSingleton from untyped JSON.
|
||||
*/
|
||||
static checked: (obj: any) => ReserveStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of the merchant
|
||||
* to the TipPickupRequest.
|
||||
@ -647,7 +674,7 @@ export class TipResponse {
|
||||
/**
|
||||
* Public key of the reserve
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
reserve_pub: string;
|
||||
|
||||
/**
|
||||
@ -671,37 +698,37 @@ export class TipToken {
|
||||
/**
|
||||
* Expiration for the tip.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
expiration: string;
|
||||
|
||||
/**
|
||||
* URL of the exchange that the tip can be withdrawn from.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange_url: string;
|
||||
|
||||
/**
|
||||
* Merchant's URL to pick up the tip.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
pickup_url: string;
|
||||
|
||||
/**
|
||||
* Merchant-chosen tip identifier.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
tip_id: string;
|
||||
|
||||
/**
|
||||
* Amount of tip.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
amount: AmountJson;
|
||||
@Checkable.String()
|
||||
amount: string;
|
||||
|
||||
/**
|
||||
* URL to navigate after finishing tip processing.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
next_url: string;
|
||||
|
||||
/**
|
||||
@ -721,7 +748,7 @@ export class Payback {
|
||||
/**
|
||||
* The hash of the denomination public key for which the payback is offered.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
h_denom_pub: string;
|
||||
}
|
||||
|
||||
@ -740,7 +767,7 @@ export class KeysJson {
|
||||
/**
|
||||
* The exchange's master public key.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
master_public_key: string;
|
||||
|
||||
/**
|
||||
@ -752,7 +779,7 @@ export class KeysJson {
|
||||
/**
|
||||
* Timestamp when this response was issued.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
list_issue_date: string;
|
||||
|
||||
/**
|
||||
@ -765,13 +792,13 @@ export class KeysJson {
|
||||
* Short-lived signing keys used to sign online
|
||||
* responses.
|
||||
*/
|
||||
@Checkable.Any
|
||||
@Checkable.Any()
|
||||
signkeys: any;
|
||||
|
||||
/**
|
||||
* Protocol version.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.String)
|
||||
@Checkable.Optional(Checkable.String())
|
||||
version?: string;
|
||||
|
||||
/**
|
||||
@ -790,31 +817,31 @@ export class WireFeesJson {
|
||||
/**
|
||||
* Cost of a wire transfer.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
wire_fee: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
wire_fee: string;
|
||||
|
||||
/**
|
||||
* Cost of clising a reserve.
|
||||
*/
|
||||
@Checkable.Value(() => AmountJson)
|
||||
closing_fee: AmountJson;
|
||||
@Checkable.String(Amounts.check)
|
||||
closing_fee: string;
|
||||
|
||||
/**
|
||||
* Signature made with the exchange's master key.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
sig: string;
|
||||
|
||||
/**
|
||||
* Date from which the fee applies.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
start_date: string;
|
||||
|
||||
/**
|
||||
* Data after which the fee doesn't apply anymore.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String(timestampCheck)
|
||||
end_date: string;
|
||||
|
||||
/**
|
||||
@ -834,7 +861,7 @@ export class WireDetailJson {
|
||||
/**
|
||||
* Name of the wire transfer method.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
type: string;
|
||||
|
||||
/**
|
||||
@ -872,7 +899,7 @@ export class Proposal {
|
||||
@Checkable.Value(() => ContractTerms)
|
||||
contract_terms: ContractTerms;
|
||||
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
sig: string;
|
||||
|
||||
/**
|
||||
|
@ -54,14 +54,23 @@ test("amount subtraction (saturation)", (t) => {
|
||||
});
|
||||
|
||||
|
||||
test("amount parsing", (t) => {
|
||||
const a1 = Amounts.parseOrThrow("TESTKUDOS:10");
|
||||
t.is(a1.currency, "TESTKUDOS");
|
||||
t.is(a1.value, 10);
|
||||
t.is(a1.fraction, 0);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
|
||||
test("contract terms validation", (t) => {
|
||||
const c = {
|
||||
H_wire: "123",
|
||||
amount: amt(1, 2, "EUR"),
|
||||
amount: "EUR:1.5",
|
||||
auditors: [],
|
||||
exchanges: [{master_pub: "foo", url: "foo"}],
|
||||
fulfillment_url: "foo",
|
||||
max_fee: amt(1, 2, "EUR"),
|
||||
max_fee: "EUR:1.5",
|
||||
merchant_pub: "12345",
|
||||
order_id: "test_order",
|
||||
pay_deadline: "Date(12346)",
|
||||
|
@ -82,6 +82,7 @@ import {
|
||||
PaybackConfirmation,
|
||||
Proposal,
|
||||
RefundRequest,
|
||||
ReserveStatus,
|
||||
TipPlanchetDetail,
|
||||
TipResponse,
|
||||
TipToken,
|
||||
@ -825,13 +826,22 @@ export class Wallet {
|
||||
return this.submitPay(purchase.contractTermsHash, sessionId);
|
||||
}
|
||||
|
||||
const contractAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
|
||||
|
||||
let wireFeeLimit;
|
||||
if (!proposal.contractTerms.max_wire_fee) {
|
||||
wireFeeLimit = Amounts.getZero(contractAmount.currency);
|
||||
} else {
|
||||
wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
|
||||
}
|
||||
|
||||
const res = await this.getCoinsForPayment({
|
||||
allowedAuditors: proposal.contractTerms.auditors,
|
||||
allowedExchanges: proposal.contractTerms.exchanges,
|
||||
depositFeeLimit: proposal.contractTerms.max_fee,
|
||||
paymentAmount: proposal.contractTerms.amount,
|
||||
depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
|
||||
paymentAmount: Amounts.parseOrThrow(proposal.contractTerms.amount),
|
||||
wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
|
||||
wireFeeLimit: proposal.contractTerms.max_wire_fee || Amounts.getZero(proposal.contractTerms.amount.currency),
|
||||
wireFeeLimit,
|
||||
wireFeeTime: getTalerStampSec(proposal.contractTerms.timestamp) || 0,
|
||||
wireMethod: proposal.contractTerms.wire_method,
|
||||
});
|
||||
@ -907,14 +917,23 @@ export class Wallet {
|
||||
return { status: "paid" };
|
||||
}
|
||||
|
||||
const paymentAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
|
||||
|
||||
let wireFeeLimit;
|
||||
if (proposal.contractTerms.max_wire_fee) {
|
||||
wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
|
||||
} else {
|
||||
wireFeeLimit = Amounts.getZero(paymentAmount.currency);
|
||||
}
|
||||
|
||||
// If not already payed, check if we could pay for it.
|
||||
const res = await this.getCoinsForPayment({
|
||||
allowedAuditors: proposal.contractTerms.auditors,
|
||||
allowedExchanges: proposal.contractTerms.exchanges,
|
||||
depositFeeLimit: proposal.contractTerms.max_fee,
|
||||
paymentAmount: proposal.contractTerms.amount,
|
||||
depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
|
||||
paymentAmount,
|
||||
wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
|
||||
wireFeeLimit: proposal.contractTerms.max_wire_fee || Amounts.getZero(proposal.contractTerms.amount.currency),
|
||||
wireFeeLimit,
|
||||
wireFeeTime: getTalerStampSec(proposal.contractTerms.timestamp) || 0,
|
||||
wireMethod: proposal.contractTerms.wire_method,
|
||||
});
|
||||
@ -1290,11 +1309,11 @@ export class Wallet {
|
||||
if (resp.status !== 200) {
|
||||
throw Error();
|
||||
}
|
||||
const reserveInfo = JSON.parse(resp.responseText);
|
||||
const reserveInfo = ReserveStatus.checked(JSON.parse(resp.responseText));
|
||||
if (!reserveInfo) {
|
||||
throw Error();
|
||||
}
|
||||
reserve.current_amount = reserveInfo.balance;
|
||||
reserve.current_amount = Amounts.parseOrThrow(reserveInfo.balance);
|
||||
await this.q()
|
||||
.put(Stores.reserves, reserve)
|
||||
.finish();
|
||||
@ -1613,7 +1632,7 @@ export class Wallet {
|
||||
exchangeInfo = {
|
||||
auditors: exchangeKeysJson.auditors,
|
||||
baseUrl,
|
||||
currency: exchangeKeysJson.denoms[0].value.currency,
|
||||
currency: Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value).currency,
|
||||
lastUpdateTime: updateTimeSec,
|
||||
lastUsedTime: 0,
|
||||
masterPublicKey: exchangeKeysJson.master_public_key,
|
||||
@ -1670,11 +1689,11 @@ export class Wallet {
|
||||
continue;
|
||||
}
|
||||
const wf: WireFee = {
|
||||
closingFee: fee.closing_fee,
|
||||
closingFee: Amounts.parseOrThrow(fee.closing_fee),
|
||||
endStamp: end,
|
||||
sig: fee.sig,
|
||||
startStamp: start,
|
||||
wireFee: fee.wire_fee,
|
||||
wireFee: Amounts.parseOrThrow(fee.wire_fee),
|
||||
};
|
||||
const valid: boolean = await this.cryptoApi.isValidWireFee(detail.type, wf, exchangeInfo.masterPublicKey);
|
||||
if (!valid) {
|
||||
@ -1829,7 +1848,7 @@ export class Wallet {
|
||||
return balance;
|
||||
}
|
||||
for (const c of t.payReq.coins) {
|
||||
addTo(balance, "pendingIncoming", c.contribution, c.exchange_url);
|
||||
addTo(balance, "pendingIncoming", Amounts.parseOrThrow(c.contribution), c.exchange_url);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
@ -2180,10 +2199,17 @@ export class Wallet {
|
||||
type: "pay",
|
||||
});
|
||||
if (p.timestamp_refund) {
|
||||
const amountsPending = Object.keys(p.refundsPending).map((x) => p.refundsPending[x].refund_amount);
|
||||
const amountsDone = Object.keys(p.refundsDone).map((x) => p.refundsDone[x].refund_amount);
|
||||
const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount);
|
||||
const amountsPending = (
|
||||
Object.keys(p.refundsPending)
|
||||
.map((x) => Amounts.parseOrThrow(p.refundsPending[x].refund_amount))
|
||||
);
|
||||
const amountsDone = (
|
||||
Object.keys(p.refundsDone)
|
||||
.map((x) => Amounts.parseOrThrow(p.refundsDone[x].refund_amount))
|
||||
);
|
||||
const amounts: AmountJson[] = amountsPending.concat(amountsDone);
|
||||
const amount = Amounts.add(Amounts.getZero(p.contractTerms.amount.currency), ...amounts).amount;
|
||||
const amount = Amounts.add(Amounts.getZero(contractAmount.currency), ...amounts).amount;
|
||||
|
||||
history.push({
|
||||
detail: {
|
||||
@ -2357,10 +2383,10 @@ export class Wallet {
|
||||
denomPub: denomIn.denom_pub,
|
||||
denomPubHash,
|
||||
exchangeBaseUrl,
|
||||
feeDeposit: denomIn.fee_deposit,
|
||||
feeRefresh: denomIn.fee_refresh,
|
||||
feeRefund: denomIn.fee_refund,
|
||||
feeWithdraw: denomIn.fee_withdraw,
|
||||
feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit),
|
||||
feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh),
|
||||
feeRefund: Amounts.parseOrThrow(denomIn.fee_refund),
|
||||
feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
|
||||
isOffered: true,
|
||||
masterSig: denomIn.master_sig,
|
||||
stampExpireDeposit: denomIn.stamp_expire_deposit,
|
||||
@ -2368,7 +2394,7 @@ export class Wallet {
|
||||
stampExpireWithdraw: denomIn.stamp_expire_withdraw,
|
||||
stampStart: denomIn.stamp_start,
|
||||
status: DenominationStatus.Unverified,
|
||||
value: denomIn.value,
|
||||
value: Amounts.parseOrThrow(denomIn.value),
|
||||
};
|
||||
return d;
|
||||
}
|
||||
@ -2449,13 +2475,13 @@ export class Wallet {
|
||||
|
||||
const contractTerms: ContractTerms = {
|
||||
H_wire: wireHash,
|
||||
amount: req.amount,
|
||||
amount: Amounts.toString(req.amount),
|
||||
auditors: [],
|
||||
exchanges: [ { master_pub: exchange.masterPublicKey, url: exchange.baseUrl } ],
|
||||
extra: {},
|
||||
fulfillment_url: "",
|
||||
locations: [],
|
||||
max_fee: req.amount,
|
||||
max_fee: Amounts.toString(req.amount),
|
||||
merchant: {},
|
||||
merchant_pub: pub,
|
||||
order_id: "none",
|
||||
@ -2469,7 +2495,9 @@ export class Wallet {
|
||||
|
||||
const contractTermsHash = await this.cryptoApi.hashString(canonicalJson(contractTerms));
|
||||
|
||||
const payCoinInfo = await this.cryptoApi.signDeposit(contractTerms, cds, contractTerms.amount);
|
||||
const payCoinInfo = await (
|
||||
this.cryptoApi.signDeposit(contractTerms, cds, Amounts.parseOrThrow(contractTerms.amount))
|
||||
);
|
||||
|
||||
console.log("pci", payCoinInfo);
|
||||
|
||||
@ -2660,9 +2688,11 @@ export class Wallet {
|
||||
console.warn("coin not found, can't apply refund");
|
||||
return;
|
||||
}
|
||||
const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
|
||||
const refundFee = Amounts.parseOrThrow(perm.refund_fee);
|
||||
c.status = CoinStatus.Dirty;
|
||||
c.currentAmount = Amounts.add(c.currentAmount, perm.refund_amount).amount;
|
||||
c.currentAmount = Amounts.sub(c.currentAmount, perm.refund_fee).amount;
|
||||
c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
|
||||
c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
|
||||
|
||||
return c;
|
||||
};
|
||||
@ -2690,7 +2720,7 @@ export class Wallet {
|
||||
if (!coin0) {
|
||||
throw Error("coin not found");
|
||||
}
|
||||
let feeAcc = Amounts.getZero(refundPermissions[0].refund_amount.currency);
|
||||
let feeAcc = Amounts.getZero(Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency);
|
||||
|
||||
const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, coin0.exchangeBaseUrl).toArray();
|
||||
for (const rp of refundPermissions) {
|
||||
@ -2706,8 +2736,10 @@ export class Wallet {
|
||||
// When it hasn't, the refresh cost is inaccurate. To fix this,
|
||||
// we need introduce a flag to tell if a coin was refunded or
|
||||
// refreshed normally (and what about incremental refunds?)
|
||||
const refreshCost = getTotalRefreshCost(denoms, denom, Amounts.sub(rp.refund_amount, rp.refund_fee).amount);
|
||||
feeAcc = Amounts.add(feeAcc, refreshCost, rp.refund_fee).amount;
|
||||
const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
|
||||
const refundFee = Amounts.parseOrThrow(rp.refund_fee);
|
||||
const refreshCost = getTotalRefreshCost(denoms, denom, Amounts.sub(refundAmount, refundFee).amount);
|
||||
feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
|
||||
}
|
||||
return feeAcc;
|
||||
}
|
||||
@ -2731,14 +2763,15 @@ export class Wallet {
|
||||
if (tipRecord && tipRecord.pickedUp) {
|
||||
return tipRecord;
|
||||
}
|
||||
const tipAmount = Amounts.parseOrThrow(tipToken.amount);
|
||||
await this.updateExchangeFromUrl(tipToken.exchange_url);
|
||||
const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipToken.amount);
|
||||
const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipAmount);
|
||||
const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)));
|
||||
const coinPubs: string[] = planchets.map(x => x.coinPub);
|
||||
const now = (new Date()).getTime();
|
||||
tipRecord = {
|
||||
accepted: false,
|
||||
amount: tipToken.amount,
|
||||
amount: Amounts.parseOrThrow(tipToken.amount),
|
||||
coinPubs,
|
||||
deadline: deadlineSec,
|
||||
exchangeUrl: tipToken.exchange_url,
|
||||
|
@ -53,13 +53,13 @@ export class CreateReserveResponse {
|
||||
* Exchange URL where the bank should create the reserve.
|
||||
* The URL is canonicalized in the response.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange: string;
|
||||
|
||||
/**
|
||||
* Reserve public key of the newly created reserve.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
reservePub: string;
|
||||
|
||||
/**
|
||||
@ -333,13 +333,13 @@ export class CreateReserveRequest {
|
||||
/**
|
||||
* Exchange URL where the bank should create the reserve.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange: string;
|
||||
|
||||
/**
|
||||
* Wire details for the bank account that sent the funds to the exchange.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.Any)
|
||||
@Checkable.Optional(Checkable.Any())
|
||||
senderWire?: object;
|
||||
|
||||
/**
|
||||
@ -359,7 +359,7 @@ export class ConfirmReserveRequest {
|
||||
* Public key of then reserve that should be marked
|
||||
* as confirmed.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
reservePub: string;
|
||||
|
||||
/**
|
||||
@ -384,14 +384,14 @@ export class ReturnCoinsRequest {
|
||||
/**
|
||||
* The exchange to take the coins from.
|
||||
*/
|
||||
@Checkable.String
|
||||
@Checkable.String()
|
||||
exchange: string;
|
||||
|
||||
/**
|
||||
* Wire details for the bank account of the customer that will
|
||||
* receive the funds.
|
||||
*/
|
||||
@Checkable.Any
|
||||
@Checkable.Any()
|
||||
senderWire?: object;
|
||||
|
||||
/**
|
||||
|
@ -42,6 +42,8 @@ import * as ReactDOM from "react-dom";
|
||||
import URI = require("urijs");
|
||||
import { WalletApiError } from "../wxApi";
|
||||
|
||||
import * as Amounts from "../../amounts";
|
||||
|
||||
|
||||
interface DetailState {
|
||||
collapsed: boolean;
|
||||
@ -294,7 +296,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
|
||||
} else {
|
||||
merchantName = <strong>(pub: {c.merchant_pub})</strong>;
|
||||
}
|
||||
const amount = <strong>{renderAmount(c.amount)}</strong>;
|
||||
const amount = <strong>{renderAmount(Amounts.parseOrThrow(c.amount))}</strong>;
|
||||
console.log("payStatus", this.state.payStatus);
|
||||
|
||||
let products = null;
|
||||
|
@ -38,11 +38,11 @@ import {
|
||||
|
||||
import { ImplicitStateComponent, StateHolder } from "../components";
|
||||
import {
|
||||
WalletApiError,
|
||||
createReserve,
|
||||
getCurrency,
|
||||
getExchangeInfo,
|
||||
getReserveCreationInfo,
|
||||
WalletApiError,
|
||||
} from "../wxApi";
|
||||
|
||||
import {
|
||||
|
@ -59,18 +59,24 @@ const RefundDetail = ({purchase, fullRefundFees}: RefundDetailProps) => {
|
||||
}
|
||||
|
||||
const firstRefundKey = [...pendingKeys, ...doneKeys][0];
|
||||
const currency = { ...purchase.refundsDone, ...purchase.refundsPending }[firstRefundKey].refund_amount.currency;
|
||||
if (!firstRefundKey) {
|
||||
return <p>Waiting for refunds ...</p>;
|
||||
}
|
||||
const allRefunds = { ...purchase.refundsDone, ...purchase.refundsPending };
|
||||
const currency = Amounts.parseOrThrow(allRefunds[firstRefundKey].refund_amount).currency;
|
||||
if (!currency) {
|
||||
throw Error("invariant");
|
||||
}
|
||||
|
||||
let amountPending = Amounts.getZero(currency);
|
||||
for (const k of pendingKeys) {
|
||||
amountPending = Amounts.add(amountPending, purchase.refundsPending[k].refund_amount).amount;
|
||||
const refundAmount = Amounts.parseOrThrow(purchase.refundsPending[k].refund_amount);
|
||||
amountPending = Amounts.add(amountPending, refundAmount).amount;
|
||||
}
|
||||
let amountDone = Amounts.getZero(currency);
|
||||
for (const k of doneKeys) {
|
||||
amountDone = Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount;
|
||||
const refundAmount = Amounts.parseOrThrow(purchase.refundsDone[k].refund_amount);
|
||||
amountDone = Amounts.add(amountDone, refundAmount).amount;
|
||||
}
|
||||
|
||||
const hasPending = amountPending.fraction !== 0 || amountPending.value !== 0;
|
||||
@ -130,7 +136,7 @@ class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStat
|
||||
Status of purchase <strong>{summary}</strong> from merchant <strong>{merchantName}</strong>{" "}
|
||||
(order id {purchase.contractTerms.order_id}).
|
||||
</p>
|
||||
<p>Total amount: <AmountDisplay amount={purchase.contractTerms.amount} /></p>
|
||||
<p>Total amount: <AmountDisplay amount={Amounts.parseOrThrow(purchase.contractTerms.amount)} /></p>
|
||||
{purchase.finished
|
||||
? <RefundDetail purchase={purchase} fullRefundFees={this.state.refundFees!} />
|
||||
: <p>Purchase not completed.</p>}
|
||||
@ -147,14 +153,17 @@ class RefundStatusView extends React.Component<RefundStatusViewProps, RefundStat
|
||||
return;
|
||||
}
|
||||
contractTermsHash = await wxApi.acceptRefund(refundUrl);
|
||||
this.setState({ contractTermsHash });
|
||||
}
|
||||
const purchase = await wxApi.getPurchase(contractTermsHash);
|
||||
console.log("got purchase", purchase);
|
||||
const refundsDone = Object.keys(purchase.refundsDone).map((x) => purchase.refundsDone[x]);
|
||||
if (refundsDone.length) {
|
||||
const refundFees = await wxApi.getFullRefundFees({ refundPermissions: refundsDone });
|
||||
this.setState({ purchase, gotResult: true, refundFees });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
|
@ -54,6 +54,7 @@
|
||||
"src/walletTypes.ts",
|
||||
"src/webex/background.ts",
|
||||
"src/webex/chromeBadge.ts",
|
||||
"src/webex/compat.ts",
|
||||
"src/webex/components.ts",
|
||||
"src/webex/messages.ts",
|
||||
"src/webex/notify.ts",
|
||||
|
Loading…
Reference in New Issue
Block a user