aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/payto.ts11
-rw-r--r--packages/taler-util/src/taler-types.ts11
-rw-r--r--packages/taler-util/src/types-test.ts2
-rw-r--r--packages/taler-util/src/wallet-types.ts59
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts1
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/pay-merchant.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts69
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts26
-rw-r--r--packages/taler-wallet-core/src/wallet.ts22
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts73
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx92
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts77
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx91
-rw-r--r--packages/taler-wallet-webextension/src/mui/Button.tsx2
19 files changed, 440 insertions, 124 deletions
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index 3073b991c..8eb0b88a8 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -139,12 +139,13 @@ export function parsePaytoUri(s: string): PaytoUri | undefined {
let iban: string | undefined = undefined;
let bic: string | undefined = undefined;
if (parts.length === 1) {
- iban = parts[0]
- } if (parts.length === 2) {
- bic = parts[0]
- iban = parts[1]
+ iban = parts[0];
+ }
+ if (parts.length === 2) {
+ bic = parts[0];
+ iban = parts[1];
} else {
- iban = targetPath
+ iban = targetPath;
}
return {
isKnown: true,
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index d4f96f5cd..292ace94b 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1297,7 +1297,7 @@ export const codecForProduct = (): Codec<Product> =>
.property("price", codecOptional(codecForString()))
.build("Tax");
-export const codecForContractTerms = (): Codec<MerchantContractTerms> =>
+export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> =>
buildCodecForObject<MerchantContractTerms>()
.property("order_id", codecForString())
.property("fulfillment_url", codecOptional(codecForString()))
@@ -1329,7 +1329,14 @@ export const codecForContractTerms = (): Codec<MerchantContractTerms> =>
.property("products", codecOptional(codecForList(codecForProduct())))
.property("extra", codecForAny())
.property("minimum_age", codecOptional(codecForNumber()))
- .build("ContractTerms");
+ .build("MerchantContractTerms");
+
+export const codecForPeerContractTerms = (): Codec<PeerContractTerms> =>
+ buildCodecForObject<PeerContractTerms>()
+ .property("summary", codecForString())
+ .property("amount", codecForString())
+ .property("purse_expiration", codecForTimestamp)
+ .build("PeerContractTerms");
export const codecForMerchantRefundPermission =
(): Codec<MerchantAbortPayRefundDetails> =>
diff --git a/packages/taler-util/src/types-test.ts b/packages/taler-util/src/types-test.ts
index 2915106c2..6acd2c26e 100644
--- a/packages/taler-util/src/types-test.ts
+++ b/packages/taler-util/src/types-test.ts
@@ -15,7 +15,7 @@
*/
import test from "ava";
-import { codecForContractTerms } from "./taler-types.js";
+import { codecForMerchantContractTerms as codecForContractTerms } from "./taler-types.js";
test("contract terms validation", (t) => {
const c = {
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index daeac73fd..4e1563e27 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -53,13 +53,15 @@ import { TalerErrorCode } from "./taler-error-codes.js";
import {
AmountString,
AuditorDenomSig,
- codecForContractTerms,
+ codecForMerchantContractTerms,
CoinEnvelope,
MerchantContractTerms,
+ PeerContractTerms,
DenominationPubKey,
DenomKeyType,
ExchangeAuditor,
UnblindedSignature,
+ codecForPeerContractTerms,
} from "./taler-types.js";
import {
AbsoluteTime,
@@ -253,7 +255,7 @@ export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
buildCodecForObject<ConfirmPayResultDone>()
.property("type", codecForConstString(ConfirmPayResultType.Done))
.property("transactionId", codecForString())
- .property("contractTerms", codecForContractTerms())
+ .property("contractTerms", codecForMerchantContractTerms())
.build("ConfirmPayResultDone");
export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> =>
@@ -383,7 +385,7 @@ export const codecForPreparePayResultPaymentPossible =
buildCodecForObject<PreparePayResultPaymentPossible>()
.property("amountEffective", codecForAmountString())
.property("amountRaw", codecForAmountString())
- .property("contractTerms", codecForContractTerms())
+ .property("contractTerms", codecForMerchantContractTerms())
.property("proposalId", codecForString())
.property("contractTermsHash", codecForString())
.property("noncePriv", codecForString())
@@ -1738,9 +1740,26 @@ export interface PayCoinSelection {
customerDepositFees: AmountString;
}
-export interface InitiatePeerPushPaymentRequest {
+export interface PreparePeerPushPaymentRequest {
+ exchangeBaseUrl?: string;
amount: AmountString;
- partialContractTerms: any;
+}
+
+export const codecForPreparePeerPushPaymentRequest =
+ (): Codec<PreparePeerPushPaymentRequest> =>
+ buildCodecForObject<PreparePeerPushPaymentRequest>()
+ .property("exchangeBaseUrl", codecOptional(codecForString()))
+ .property("amount", codecForAmountString())
+ .build("InitiatePeerPushPaymentRequest");
+
+export interface PreparePeerPushPaymentResponse {
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
+
+export interface InitiatePeerPushPaymentRequest {
+ exchangeBaseUrl?: string;
+ partialContractTerms: PeerContractTerms;
}
export interface InitiatePeerPushPaymentResponse {
@@ -1755,8 +1774,7 @@ export interface InitiatePeerPushPaymentResponse {
export const codecForInitiatePeerPushPaymentRequest =
(): Codec<InitiatePeerPushPaymentRequest> =>
buildCodecForObject<InitiatePeerPushPaymentRequest>()
- .property("amount", codecForAmountString())
- .property("partialContractTerms", codecForAny())
+ .property("partialContractTerms", codecForPeerContractTerms())
.build("InitiatePeerPushPaymentRequest");
export interface CheckPeerPushPaymentRequest {
@@ -1768,13 +1786,13 @@ export interface CheckPeerPullPaymentRequest {
}
export interface CheckPeerPushPaymentResponse {
- contractTerms: any;
+ contractTerms: PeerContractTerms;
amount: AmountString;
peerPushPaymentIncomingId: string;
}
export interface CheckPeerPullPaymentResponse {
- contractTerms: any;
+ contractTerms: PeerContractTerms;
amount: AmountString;
peerPullPaymentIncomingId: string;
}
@@ -1843,21 +1861,34 @@ export const codecForAcceptPeerPullPaymentRequest =
.property("peerPullPaymentIncomingId", codecForString())
.build("AcceptPeerPllPaymentRequest");
+export interface PreparePeerPullPaymentRequest {
+ exchangeBaseUrl: string;
+ amount: AmountString;
+}
+export const codecForPreparePeerPullPaymentRequest =
+ (): Codec<PreparePeerPullPaymentRequest> =>
+ buildCodecForObject<PreparePeerPullPaymentRequest>()
+ .property("amount", codecForAmountString())
+ .property("exchangeBaseUrl", codecForString())
+ .build("PreparePeerPullPaymentRequest");
+
+export interface PreparePeerPullPaymentResponse {
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+}
export interface InitiatePeerPullPaymentRequest {
/**
* FIXME: Make this optional?
*/
exchangeBaseUrl: string;
- amount: AmountString;
- partialContractTerms: any;
+ partialContractTerms: PeerContractTerms;
}
export const codecForInitiatePeerPullPaymentRequest =
(): Codec<InitiatePeerPullPaymentRequest> =>
buildCodecForObject<InitiatePeerPullPaymentRequest>()
- .property("partialContractTerms", codecForAny())
- .property("amount", codecForAmountString())
- .property("exchangeBaseUrl", codecForAmountString())
+ .property("partialContractTerms", codecForPeerContractTerms())
+ .property("exchangeBaseUrl", codecForString())
.build("InitiatePeerPullPaymentRequest");
export interface InitiatePeerPullPaymentResponse {
diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
index 6ce21572e..e9d67eec6 100644
--- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts
@@ -27,7 +27,6 @@ import { SynchronousCryptoWorker } from "./synchronousWorkerNode.js";
*/
export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
-
return new SynchronousCryptoWorker();
}
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 3159c60af..5fd220113 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -26,7 +26,7 @@ import {
BackupRefreshReason,
BackupRefundState,
BackupWgType,
- codecForContractTerms,
+ codecForMerchantContractTerms,
CoinStatus,
DenomKeyType,
DenomSelectionState,
@@ -638,7 +638,7 @@ export async function importBackup(
break;
}
}
- const parsedContractTerms = codecForContractTerms().decode(
+ const parsedContractTerms = codecForMerchantContractTerms().decode(
backupPurchase.contract_terms_raw,
);
const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 4483a57c0..bb391d468 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -34,7 +34,7 @@ import {
Amounts,
ApplyRefundResponse,
codecForAbortResponse,
- codecForContractTerms,
+ codecForMerchantContractTerms,
codecForMerchantOrderRefundPickupResponse,
codecForMerchantOrderStatusPaid,
codecForMerchantPayResponse,
@@ -456,7 +456,7 @@ export async function processDownloadProposal(
let parsedContractTerms: MerchantContractTerms;
try {
- parsedContractTerms = codecForContractTerms().decode(
+ parsedContractTerms = codecForMerchantContractTerms().decode(
proposalResp.contract_terms,
);
} catch (e) {
@@ -1584,7 +1584,7 @@ export async function runPayForConfirmPay(
const numRetry = opRetry?.retryInfo.retryCounter ?? 0;
if (
res.errorDetail.code ===
- TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
+ TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
numRetry < maxRetry
) {
// Pretend the operation is pending instead of reporting
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts
index b6acef2dc..f31a7f37c 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -57,6 +57,10 @@ import {
parsePayPullUri,
parsePayPushUri,
PeerContractTerms,
+ PreparePeerPullPaymentRequest,
+ PreparePeerPullPaymentResponse,
+ PreparePeerPushPaymentRequest,
+ PreparePeerPushPaymentResponse,
RefreshReason,
strcmp,
TalerProtocolTimestamp,
@@ -218,28 +222,30 @@ export async function selectPeerCoins(
return undefined;
}
+export async function preparePeerPushPayment(
+ ws: InternalWalletState,
+ req: PreparePeerPushPaymentRequest,
+): Promise<PreparePeerPushPaymentResponse> {
+ // FIXME: look up for the exchange and calculate fee
+ return {
+ amountEffective: req.amount,
+ amountRaw: req.amount,
+ };
+}
+
export async function initiatePeerToPeerPush(
ws: InternalWalletState,
req: InitiatePeerPushPaymentRequest,
): Promise<InitiatePeerPushPaymentResponse> {
- const instructedAmount = Amounts.parseOrThrow(req.amount);
+ const instructedAmount = Amounts.parseOrThrow(
+ req.partialContractTerms.amount,
+ );
+ const purseExpiration = req.partialContractTerms.purse_expiration;
+ const contractTerms = req.partialContractTerms;
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
- const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
- AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- Duration.fromSpec({ days: 2 }),
- ),
- );
-
- const contractTerms = {
- ...req.partialContractTerms,
- purse_expiration: purseExpiration,
- amount: req.amount,
- };
-
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
const econtractResp = await ws.cryptoApi.encryptContractForMerge({
@@ -751,6 +757,16 @@ export async function checkPeerPullPayment(
};
}
+export async function preparePeerPullPayment(
+ ws: InternalWalletState,
+ req: PreparePeerPullPaymentRequest,
+): Promise<PreparePeerPullPaymentResponse> {
+ //FIXME: look up for exchange details and use purse fee
+ return {
+ amountEffective: req.amount,
+ amountRaw: req.amount,
+ };
+}
/**
* Initiate a peer pull payment.
*/
@@ -769,24 +785,17 @@ export async function initiatePeerPullPayment(
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
- const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
- AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- Duration.fromSpec({ days: 2 }),
- ),
+ const instructedAmount = Amounts.parseOrThrow(
+ req.partialContractTerms.amount,
);
+ const purseExpiration = req.partialContractTerms.purse_expiration;
+ const contractTerms = req.partialContractTerms;
const reservePayto = talerPaytoFromExchangeReserve(
req.exchangeBaseUrl,
mergeReserveInfo.reservePub,
);
- const contractTerms = {
- ...req.partialContractTerms,
- amount: req.amount,
- purse_expiration: purseExpiration,
- };
-
const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
contractTerms,
pursePriv: pursePair.priv,
@@ -796,7 +805,7 @@ export async function initiatePeerPullPayment(
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
const purseFee = Amounts.stringify(
- Amounts.zeroOfCurrency(Amounts.parseOrThrow(req.amount).currency),
+ Amounts.zeroOfCurrency(instructedAmount.currency),
);
const sigRes = await ws.cryptoApi.signReservePurseCreate({
@@ -804,7 +813,7 @@ export async function initiatePeerPullPayment(
flags: WalletAccountMergeFlags.CreateWithPurseFee,
mergePriv: mergePair.priv,
mergeTimestamp: mergeTimestamp,
- purseAmount: req.amount,
+ purseAmount: req.partialContractTerms.amount,
purseExpiration: purseExpiration,
purseFee: purseFee,
pursePriv: pursePair.priv,
@@ -817,7 +826,7 @@ export async function initiatePeerPullPayment(
.mktx((x) => [x.peerPullPaymentInitiations, x.contractTerms])
.runReadWrite(async (tx) => {
await tx.peerPullPaymentInitiations.put({
- amount: req.amount,
+ amount: req.partialContractTerms.amount,
contractTermsHash: hContractTerms,
exchangeBaseUrl: req.exchangeBaseUrl,
pursePriv: pursePair.priv,
@@ -840,7 +849,7 @@ export async function initiatePeerPullPayment(
purse_fee: purseFee,
purse_pub: pursePair.pub,
purse_sig: sigRes.purseSig,
- purse_value: req.amount,
+ purse_value: req.partialContractTerms.amount,
reserve_sig: sigRes.accountSig,
econtract: econtractResp.econtract,
};
@@ -862,7 +871,7 @@ export async function initiatePeerPullPayment(
logger.info(`reserve merge response: ${j2s(resp)}`);
const wg = await internalCreateWithdrawalGroup(ws, {
- amount: Amounts.parseOrThrow(req.amount),
+ amount: instructedAmount,
wgInfo: {
withdrawalType: WithdrawalRecordType.PeerPullCredit,
contractTerms,
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index e36e630f4..b7d0ada3d 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -75,6 +75,10 @@ import {
PrepareDepositResponse,
PreparePayRequest,
PreparePayResult,
+ PreparePeerPullPaymentRequest,
+ PreparePeerPullPaymentResponse,
+ PreparePeerPushPaymentRequest,
+ PreparePeerPushPaymentResponse,
PrepareRefundRequest,
PrepareRefundResult,
PrepareTipRequest,
@@ -164,9 +168,11 @@ export enum WalletApiOperation {
WithdrawFakebank = "withdrawFakebank",
ImportDb = "importDb",
ExportDb = "exportDb",
+ PreparePeerPushPayment = "preparePeerPushPayment",
InitiatePeerPushPayment = "initiatePeerPushPayment",
CheckPeerPushPayment = "checkPeerPushPayment",
AcceptPeerPushPayment = "acceptPeerPushPayment",
+ PreparePeerPullPayment = "preparePeerPullPayment",
InitiatePeerPullPayment = "initiatePeerPullPayment",
CheckPeerPullPayment = "checkPeerPullPayment",
AcceptPeerPullPayment = "acceptPeerPullPayment",
@@ -556,6 +562,15 @@ export type ExportBackupPlainOp = {
/**
* Initiate an outgoing peer push payment.
*/
+export type PreparePeerPushPaymentOp = {
+ op: WalletApiOperation.PreparePeerPushPayment;
+ request: PreparePeerPushPaymentRequest;
+ response: PreparePeerPushPaymentResponse;
+};
+
+/**
+ * Initiate an outgoing peer push payment.
+ */
export type InitiatePeerPushPaymentOp = {
op: WalletApiOperation.InitiatePeerPushPayment;
request: InitiatePeerPushPaymentRequest;
@@ -583,6 +598,15 @@ export type AcceptPeerPushPaymentOp = {
/**
* Initiate an outgoing peer pull payment.
*/
+export type PreparePeerPullPaymentOp = {
+ op: WalletApiOperation.PreparePeerPullPayment;
+ request: PreparePeerPullPaymentRequest;
+ response: PreparePeerPullPaymentResponse;
+};
+
+/**
+ * Initiate an outgoing peer pull payment.
+ */
export type InitiatePeerPullPaymentOp = {
op: WalletApiOperation.InitiatePeerPullPayment;
request: InitiatePeerPullPaymentRequest;
@@ -815,9 +839,11 @@ export type WalletOperations = {
[WalletApiOperation.TestPay]: TestPayOp;
[WalletApiOperation.ExportDb]: ExportDbOp;
[WalletApiOperation.ImportDb]: ImportDbOp;
+ [WalletApiOperation.PreparePeerPushPayment]: PreparePeerPushPaymentOp;
[WalletApiOperation.InitiatePeerPushPayment]: InitiatePeerPushPaymentOp;
[WalletApiOperation.CheckPeerPushPayment]: CheckPeerPushPaymentOp;
[WalletApiOperation.AcceptPeerPushPayment]: AcceptPeerPushPaymentOp;
+ [WalletApiOperation.PreparePeerPullPayment]: PreparePeerPullPaymentOp;
[WalletApiOperation.InitiatePeerPullPayment]: InitiatePeerPullPaymentOp;
[WalletApiOperation.CheckPeerPullPayment]: CheckPeerPullPaymentOp;
[WalletApiOperation.AcceptPeerPullPayment]: AcceptPeerPullPaymentOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 9339b2f8e..caaf6d410 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -57,6 +57,8 @@ import {
codecForListKnownBankAccounts,
codecForPrepareDepositRequest,
codecForPreparePayRequest,
+ codecForPreparePeerPullPaymentRequest,
+ codecForPreparePeerPushPaymentRequest,
codecForPrepareRefundRequest,
codecForPrepareTipRequest,
codecForRetryTransactionRequest,
@@ -186,6 +188,8 @@ import {
checkPeerPushPayment,
initiatePeerPullPayment,
initiatePeerToPeerPush,
+ preparePeerPullPayment,
+ preparePeerPushPayment,
} from "./operations/pay-peer.js";
import { getPendingOperations } from "./operations/pending.js";
import {
@@ -659,7 +663,9 @@ async function getExchanges(
const opRetryRecord = await tx.operationRetries.get(
RetryTags.forExchangeUpdate(r),
);
- exchanges.push(makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError));
+ exchanges.push(
+ makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
+ );
}
});
return { exchanges };
@@ -927,9 +933,9 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
ageCommitmentProof: c.ageCommitmentProof,
spend_allocation: c.spendAllocation
? {
- amount: c.spendAllocation.amount,
- id: c.spendAllocation.id,
- }
+ amount: c.spendAllocation.amount,
+ id: c.spendAllocation.id,
+ }
: undefined,
});
}
@@ -1340,6 +1346,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
await importDb(ws.db.idbHandle(), req.dump);
return [];
}
+ case WalletApiOperation.PreparePeerPushPayment: {
+ const req = codecForPreparePeerPushPaymentRequest().decode(payload);
+ return await preparePeerPushPayment(ws, req);
+ }
case WalletApiOperation.InitiatePeerPushPayment: {
const req = codecForInitiatePeerPushPaymentRequest().decode(payload);
return await initiatePeerToPeerPush(ws, req);
@@ -1352,6 +1362,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
const req = codecForAcceptPeerPushPaymentRequest().decode(payload);
return await acceptPeerPushPayment(ws, req);
}
+ case WalletApiOperation.PreparePeerPullPayment: {
+ const req = codecForPreparePeerPullPaymentRequest().decode(payload);
+ return await preparePeerPullPayment(ws, req);
+ }
case WalletApiOperation.InitiatePeerPullPayment: {
const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
return await initiatePeerPullPayment(ws, req);
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
index 0389a17fb..01dbb6d6d 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts
@@ -59,10 +59,10 @@ export namespace State {
doSelectExchange: ButtonHandler;
create: ButtonHandler;
subject: TextFieldHandler;
+ expiration: TextFieldHandler;
toBeReceived: AmountJson;
- chosenAmount: AmountJson;
+ requestAmount: AmountJson;
exchangeUrl: string;
- invalid: boolean;
error: undefined;
operationError?: TalerErrorDetail;
}
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
index d845e121a..27f05ce03 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -15,8 +15,9 @@
*/
/* eslint-disable react-hooks/rules-of-hooks */
-import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
@@ -49,7 +50,8 @@ export function useComponentState(
const exchangeList = hook.response.exchanges;
return () => {
- const [subject, setSubject] = useState("");
+ const [subject, setSubject] = useState<string | undefined>();
+ const [timestamp, setTimestamp] = useState<string | undefined>()
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
@@ -67,13 +69,59 @@ export function useComponentState(
const exchange = selectedExchange.selected;
+ const hook = useAsyncAsHook(async () => {
+ const resp = await api.wallet.call(WalletApiOperation.PreparePeerPullPayment, {
+ amount: amountStr,
+ exchangeBaseUrl: exchange.exchangeBaseUrl,
+ })
+ return resp
+ })
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined
+ }
+ }
+ if (hook.hasError) {
+ return {
+ status: "loading-uri",
+ error: hook
+ }
+ }
+
+ const { amountEffective, amountRaw } = hook.response
+ const requestAmount = Amounts.parseOrThrow(amountRaw)
+ const toBeReceived = Amounts.parseOrThrow(amountEffective)
+
+ let purse_expiration: TalerProtocolTimestamp | undefined = undefined
+ let timestampError: string | undefined = undefined;
+
+ const t = timestamp === undefined ? undefined : parse(timestamp, "dd/MM/yyyy", new Date())
+
+ if (t !== undefined) {
+ if (Number.isNaN(t.getTime())) {
+ timestampError = 'Should have the format "dd/MM/yyyy"'
+ } else {
+ if (!isFuture(t)) {
+ timestampError = 'Should be in the future'
+ } else {
+ purse_expiration = {
+ t_s: t.getTime() / 1000
+ }
+ }
+ }
+ }
+
async function accept(): Promise<void> {
+ if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, {
- amount: Amounts.stringify(amount),
exchangeBaseUrl: exchange.exchangeBaseUrl,
partialContractTerms: {
+ amount: Amounts.stringify(amount),
summary: subject,
+ purse_expiration
},
});
@@ -86,25 +134,32 @@ export function useComponentState(
throw Error("error trying to accept");
}
}
+ const unableToCreate = !subject || Amounts.isZero(amount) || !purse_expiration
return {
status: "ready",
subject: {
- error: !subject ? "cant be empty" : undefined,
- value: subject,
+ error: subject === undefined ? undefined : !subject ? "Can't be empty" : undefined,
+ value: subject ?? "",
onInput: async (e) => setSubject(e),
},
+ expiration: {
+ error: timestampError,
+ value: timestamp === undefined ? "" : timestamp,
+ onInput: async (e) => {
+ setTimestamp(e)
+ }
+ },
doSelectExchange: selectedExchange.doSelect,
- invalid: !subject || Amounts.isZero(amount),
exchangeUrl: exchange.exchangeBaseUrl,
create: {
- onClick: accept,
+ onClick: unableToCreate ? undefined : accept,
},
cancel: {
onClick: onClose,
},
- chosenAmount: amount,
- toBeReceived: amount,
+ requestAmount,
+ toBeReceived,
error: undefined,
operationError,
};
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
index 77885b0c1..8d4473d8f 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/stories.tsx
@@ -27,11 +27,14 @@ export default {
};
export const Ready = createExample(ReadyView, {
- chosenAmount: {
+ requestAmount: {
currency: "ARS",
value: 1,
fraction: 0,
},
+ expiration: {
+ value: "2/12/12",
+ },
cancel: {},
toBeReceived: {
currency: "ARS",
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
index 4970f590f..f15482953 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/views.tsx
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { format } from "date-fns";
import { h, VNode } from "preact";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
@@ -46,18 +47,40 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
}
export function ReadyView({
- invalid,
exchangeUrl,
subject,
+ expiration,
cancel,
operationError,
create,
toBeReceived,
- chosenAmount,
+ requestAmount,
doSelectExchange,
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
+ async function oneDayExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"),
+ );
+ }
+ }
+
+ async function oneWeekExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"),
+ );
+ }
+ }
+ async function _20DaysExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"),
+ );
+ }
+ }
return (
<WalletAction>
<LogoHeader />
@@ -75,16 +98,6 @@ export function ReadyView({
/>
)}
<section style={{ textAlign: "left" }}>
- <TextField
- label="Subject"
- variant="filled"
- error={subject.error}
- required
- fullWidth
- value={subject.value}
- onChange={subject.onInput}
- />
-
<Part
title={
<div
@@ -107,6 +120,52 @@ export function ReadyView({
kind="neutral"
big
/>
+ <p>
+ <TextField
+ label="Subject"
+ variant="filled"
+ error={subject.error}
+ required
+ fullWidth
+ value={subject.value}
+ onChange={subject.onInput}
+ />
+ </p>
+
+ <p>
+ <TextField
+ label="Expiration"
+ variant="filled"
+ error={expiration.error}
+ required
+ fullWidth
+ value={expiration.value}
+ onChange={expiration.onInput}
+ />
+ <p>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={oneDayExpiration}
+ >
+ 1 day
+ </Button>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={oneWeekExpiration}
+ >
+ 1 week
+ </Button>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={_20DaysExpiration}
+ >
+ 20 days
+ </Button>
+ </p>
+ </p>
<Part
title={<i18n.Translate>Details</i18n.Translate>}
@@ -114,19 +173,14 @@ export function ReadyView({
<InvoiceDetails
amount={{
effective: toBeReceived,
- raw: chosenAmount,
+ raw: requestAmount,
}}
/>
}
/>
</section>
<section>
- <Button
- disabled={invalid}
- onClick={create.onClick}
- variant="contained"
- color="success"
- >
+ <Button onClick={create.onClick} variant="contained" color="success">
<i18n.Translate>Create</i18n.Translate>
</Button>
</section>
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
index 83293438f..8d51ff3e0 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts
@@ -48,11 +48,11 @@ export namespace State {
}
export interface Ready extends BaseInfo {
status: "ready";
- invalid: boolean;
create: ButtonHandler;
toBeReceived: AmountJson;
- chosenAmount: AmountJson;
+ debitAmount: AmountJson;
subject: TextFieldHandler;
+ expiration: TextFieldHandler;
error: undefined;
operationError?: TalerErrorDetail;
}
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
index b229924b2..089f46047 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts
@@ -14,9 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
+import { Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { format, isFuture, parse } from "date-fns";
import { useState } from "preact/hooks";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
@@ -26,17 +28,65 @@ export function useComponentState(
): State {
const amount = Amounts.parseOrThrow(amountStr);
- const [subject, setSubject] = useState("");
+ const [subject, setSubject] = useState<string | undefined>();
+ const [timestamp, setTimestamp] = useState<string | undefined>()
+
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
+
+ const hook = useAsyncAsHook(async () => {
+ const resp = await api.wallet.call(WalletApiOperation.PreparePeerPushPayment, {
+ amount: amountStr
+ })
+ return resp
+ })
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined
+ }
+ }
+ if (hook.hasError) {
+ return {
+ status: "loading-uri",
+ error: hook
+ }
+ }
+
+ const { amountEffective, amountRaw } = hook.response
+ const debitAmount = Amounts.parseOrThrow(amountRaw)
+ const toBeReceived = Amounts.parseOrThrow(amountEffective)
+
+ let purse_expiration: TalerProtocolTimestamp | undefined = undefined
+ let timestampError: string | undefined = undefined;
+
+ const t = timestamp === undefined ? undefined : parse(timestamp, "dd/MM/yyyy", new Date())
+
+ if (t !== undefined) {
+ if (Number.isNaN(t.getTime())) {
+ timestampError = 'Should have the format "dd/MM/yyyy"'
+ } else {
+ if (!isFuture(t)) {
+ timestampError = 'Should be in the future'
+ } else {
+ purse_expiration = {
+ t_s: t.getTime() / 1000
+ }
+ }
+ }
+ }
+
async function accept(): Promise<void> {
+ if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, {
- amount: Amounts.stringify(amount),
partialContractTerms: {
summary: subject,
+ amount: amountStr,
+ purse_expiration
},
});
onSuccess(resp.transactionId);
@@ -48,22 +98,31 @@ export function useComponentState(
throw Error("error trying to accept");
}
}
+
+ const unableToCreate = !subject || Amounts.isZero(amount) || !purse_expiration
+
return {
status: "ready",
- invalid: !subject || Amounts.isZero(amount),
cancel: {
onClick: onClose,
},
subject: {
- error: !subject ? "cant be empty" : undefined,
- value: subject,
+ error: subject === undefined ? undefined : !subject ? "Can't be empty" : undefined,
+ value: subject ?? "",
onInput: async (e) => setSubject(e),
},
+ expiration: {
+ error: timestampError,
+ value: timestamp === undefined ? "" : timestamp,
+ onInput: async (e) => {
+ setTimestamp(e)
+ }
+ },
create: {
- onClick: accept,
+ onClick: unableToCreate ? undefined : accept,
},
- chosenAmount: amount,
- toBeReceived: amount,
+ debitAmount,
+ toBeReceived,
error: undefined,
operationError,
};
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx b/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
index 2746cc153..de781f008 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/stories.tsx
@@ -27,11 +27,14 @@ export default {
};
export const Ready = createExample(ReadyView, {
- chosenAmount: {
+ debitAmount: {
currency: "ARS",
value: 1,
fraction: 0,
},
+ expiration: {
+ value: "20/1/2022",
+ },
create: {},
cancel: {},
toBeReceived: {
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
index bca806c5d..7b1c208b9 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/views.tsx
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { format } from "date-fns";
import { h, VNode } from "preact";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LoadingError } from "../../components/LoadingError.js";
@@ -40,14 +41,37 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
export function ReadyView({
subject,
+ expiration,
toBeReceived,
- chosenAmount,
+ debitAmount,
create,
operationError,
cancel,
- invalid,
}: State.Ready): VNode {
const { i18n } = useTranslationContext();
+
+ async function oneDayExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24, "dd/MM/yyyy"),
+ );
+ }
+ }
+
+ async function oneWeekExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24 * 7, "dd/MM/yyyy"),
+ );
+ }
+ }
+ async function _20DaysExpiration() {
+ if (expiration.onInput) {
+ expiration.onInput(
+ format(new Date().getTime() + 1000 * 60 * 60 * 24 * 20, "dd/MM/yyyy"),
+ );
+ }
+ }
return (
<WalletAction>
<LogoHeader />
@@ -65,34 +89,65 @@ export function ReadyView({
/>
)}
<section style={{ textAlign: "left" }}>
- <TextField
- label="Subject"
- variant="filled"
- error={subject.error}
- required
- fullWidth
- value={subject.value}
- onChange={subject.onInput}
- />
+ <p>
+ <TextField
+ label="Subject"
+ variant="filled"
+ error={subject.error}
+ required
+ fullWidth
+ value={subject.value}
+ onChange={subject.onInput}
+ />
+ </p>
+ <p>
+ <TextField
+ label="Expiration"
+ variant="filled"
+ error={expiration.error}
+ required
+ fullWidth
+ value={expiration.value}
+ onChange={expiration.onInput}
+ />
+ <p>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={oneDayExpiration}
+ >
+ 1 day
+ </Button>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={oneWeekExpiration}
+ >
+ 1 week
+ </Button>
+ <Button
+ variant="outlined"
+ disabled={!expiration.onInput}
+ onClick={_20DaysExpiration}
+ >
+ 20 days
+ </Button>
+ </p>
+ </p>
<Part
title={<i18n.Translate>Details</i18n.Translate>}
text={
<TransferDetails
amount={{
effective: toBeReceived,
- raw: chosenAmount,
+ raw: debitAmount,
}}
/>
}
/>
</section>
<section>
- <Button
- disabled={invalid}
- onClick={create.onClick}
- variant="contained"
- color="success"
- >
+ <Button onClick={create.onClick} variant="contained" color="success">
<i18n.Translate>Create</i18n.Translate>
</Button>
</section>
diff --git a/packages/taler-wallet-webextension/src/mui/Button.tsx b/packages/taler-wallet-webextension/src/mui/Button.tsx
index 0aaa5ee97..bca0d6231 100644
--- a/packages/taler-wallet-webextension/src/mui/Button.tsx
+++ b/packages/taler-wallet-webextension/src/mui/Button.tsx
@@ -290,7 +290,7 @@ export function Button({
return (
<ButtonBase
- disabled={disabled || running}
+ disabled={disabled || running || !doClick}
class={[
theme.typography.button,
theme.shape.roundBorder,