updated preparePay API according to spec

This commit is contained in:
Florian Dold 2020-07-28 15:18:01 +05:30
parent 86fd5f2440
commit 43655adff0
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 91 additions and 53 deletions

View File

@ -550,7 +550,7 @@ export enum TalerErrorCode {
DEPOSIT_INVALID_WIRE_FORMAT_JSON = 1210, DEPOSIT_INVALID_WIRE_FORMAT_JSON = 1210,
/** /**
* The hash of the given wire address does not match the hash specified in the proposal data. * The hash of the given wire address does not match the wire hash specified in the proposal data.
* Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
* (A value of 0 indicates that the error is generated client-side). * (A value of 0 indicates that the error is generated client-side).
*/ */
@ -1403,6 +1403,13 @@ export enum TalerErrorCode {
*/ */
PAY_EXCHANGE_FAILED = 2135, PAY_EXCHANGE_FAILED = 2135,
/**
* The merchant backend couldn't verify the order payment because of a database failure.
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
* (A value of 0 indicates that the error is generated client-side).
*/
PAID_DB_ERROR = 2146,
/** /**
* The order is not known. * The order is not known.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@ -2677,6 +2684,13 @@ export enum TalerErrorCode {
*/ */
MERCHANT_ORDER_GET_REPLY_MALFORMED = 2922, MERCHANT_ORDER_GET_REPLY_MALFORMED = 2922,
/**
* The token used to authenticate the client is invalid for this order.
* Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
* (A value of 0 indicates that the error is generated client-side).
*/
MERCHANT_GET_ORDER_INVALID_TOKEN = 2923,
/** /**
* The signature from the exchange on the deposit confirmation is invalid. Returned with a "400 Bad Request" status code. * The signature from the exchange on the deposit confirmation is invalid. Returned with a "400 Bad Request" status code.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
@ -3062,6 +3076,13 @@ export enum TalerErrorCode {
*/ */
WALLET_CORE_API_OPERATION_UNKNOWN = 7007, WALLET_CORE_API_OPERATION_UNKNOWN = 7007,
/**
* The given taler://pay URI is invalid.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
*/
WALLET_INVALID_TALER_PAY_URI = 7008,
/** /**
* The exchange does not know about the reserve (yet), and thus withdrawal can't progress. * The exchange does not know about the reserve (yet), and thus withdrawal can't progress.
* Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).

View File

@ -43,6 +43,7 @@ import { NodeHttpLib } from "./NodeHttpLib";
import * as nacl from "../crypto/primitives/nacl-fast"; import * as nacl from "../crypto/primitives/nacl-fast";
import { addPaytoQueryParams } from "../util/payto"; import { addPaytoQueryParams } from "../util/payto";
import { handleCoreApiRequest } from "../walletCoreApiHandler"; import { handleCoreApiRequest } from "../walletCoreApiHandler";
import { PreparePayResultType } from "../types/walletTypes";
const logger = new Logger("taler-wallet-cli.ts"); const logger = new Logger("taler-wallet-cli.ts");
@ -58,19 +59,19 @@ async function doPay(
options: { alwaysYes: boolean } = { alwaysYes: true }, options: { alwaysYes: boolean } = { alwaysYes: true },
): Promise<void> { ): Promise<void> {
const result = await wallet.preparePayForUri(payUrl); const result = await wallet.preparePayForUri(payUrl);
if (result.status === "error") { if (result.status === PreparePayResultType.InsufficientBalance) {
console.error("Could not pay:", result.error);
process.exit(1);
return;
}
if (result.status === "insufficient-balance") {
console.log("contract", result.contractTermsRaw); console.log("contract", result.contractTermsRaw);
console.error("insufficient balance"); console.error("insufficient balance");
process.exit(1); process.exit(1);
return; return;
} }
if (result.status === "paid") { if (result.status === PreparePayResultType.AlreadyConfirmed) {
if (result.paid) {
console.log("already paid!"); console.log("already paid!");
} else {
console.log("payment already in progress");
}
process.exit(0); process.exit(0);
return; return;
} }
@ -502,16 +503,17 @@ advancedCli
await withWallet(args, async (wallet) => { await withWallet(args, async (wallet) => {
const res = await wallet.preparePayForUri(args.payPrepare.url); const res = await wallet.preparePayForUri(args.payPrepare.url);
switch (res.status) { switch (res.status) {
case "error": case PreparePayResultType.InsufficientBalance:
console.log("error:", res.error);
break;
case "insufficient-balance":
console.log("insufficient balance"); console.log("insufficient balance");
break; break;
case "paid": case PreparePayResultType.AlreadyConfirmed:
console.log("already paid"); if (res.paid) {
console.log("already paid!");
} else {
console.log("payment in progress");
}
break; break;
case "payment-possible": case PreparePayResultType.PaymentPossible:
console.log("payment possible"); console.log("payment possible");
break; break;
default: default:

View File

@ -48,19 +48,19 @@ import {
OperationErrorDetails, OperationErrorDetails,
PreparePayResult, PreparePayResult,
RefreshReason, RefreshReason,
PreparePayResultType,
} from "../types/walletTypes"; } from "../types/walletTypes";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
import { AmountJson } from "../util/amounts"; import { AmountJson } from "../util/amounts";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { parsePayUri } from "../util/taleruri"; import { parsePayUri } from "../util/taleruri";
import { guardOperationException } from "./errors"; import { guardOperationException, OperationFailedError } from "./errors";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh"; import { createRefreshGroup, getTotalRefreshCost } from "./refresh";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { getTimestampNow, timestampAddDuration } from "../util/time"; import { getTimestampNow, timestampAddDuration } from "../util/time";
import { strcmp, canonicalJson } from "../util/helpers"; import { strcmp, canonicalJson } from "../util/helpers";
import { import { readSuccessResponseJsonOrThrow } from "../util/http";
readSuccessResponseJsonOrThrow, import { TalerErrorCode } from "../TalerErrorCode";
} from "../util/http";
/** /**
* Logger. * Logger.
@ -860,10 +860,13 @@ export async function preparePayForUri(
const uriResult = parsePayUri(talerPayUri); const uriResult = parsePayUri(talerPayUri);
if (!uriResult) { if (!uriResult) {
return { throw OperationFailedError.fromCode(
status: "error", TalerErrorCode.WALLET_INVALID_TALER_PAY_URI,
error: "URI not supported", `invalid taler://pay URI (${talerPayUri})`,
}; {
talerPayUri,
}
);
} }
let proposalId = await startDownloadProposal( let proposalId = await startDownloadProposal(
@ -911,7 +914,7 @@ export async function preparePayForUri(
if (!res) { if (!res) {
console.log("not confirming payment, insufficient coins"); console.log("not confirming payment, insufficient coins");
return { return {
status: "insufficient-balance", status: PreparePayResultType.InsufficientBalance,
contractTermsRaw: d.contractTermsRaw, contractTermsRaw: d.contractTermsRaw,
proposalId: proposal.proposalId, proposalId: proposal.proposalId,
}; };
@ -923,14 +926,14 @@ export async function preparePayForUri(
const totalFees = Amounts.sub(costInfo.totalCost, res.paymentAmount).amount; const totalFees = Amounts.sub(costInfo.totalCost, res.paymentAmount).amount;
return { return {
status: "payment-possible", status: PreparePayResultType.PaymentPossible,
contractTermsRaw: d.contractTermsRaw, contractTermsRaw: d.contractTermsRaw,
proposalId: proposal.proposalId, proposalId: proposal.proposalId,
totalFees, totalFees,
}; };
} }
if (uriResult.sessionId && purchase.lastSessionId !== uriResult.sessionId) { if (purchase.lastSessionId !== uriResult.sessionId) {
console.log( console.log(
"automatically re-submitting payment with different session ID", "automatically re-submitting payment with different session ID",
); );
@ -942,14 +945,28 @@ export async function preparePayForUri(
p.lastSessionId = uriResult.sessionId; p.lastSessionId = uriResult.sessionId;
await tx.put(Stores.purchases, p); await tx.put(Stores.purchases, p);
}); });
await submitPay(ws, proposalId); const r = await submitPay(ws, proposalId);
}
return { return {
status: "paid", status: PreparePayResultType.AlreadyConfirmed,
contractTermsRaw: purchase.contractTermsRaw, contractTermsRaw: purchase.contractTermsRaw,
nextUrl: getNextUrl(purchase.contractData), paid: true,
nextUrl: r.nextUrl,
}; };
} else if (!purchase.timestampFirstSuccessfulPay) {
return {
status: PreparePayResultType.AlreadyConfirmed,
contractTermsRaw: purchase.contractTermsRaw,
paid: false,
};
} else if (purchase.paymentSubmitPending) {
return {
status: PreparePayResultType.AlreadyConfirmed,
contractTermsRaw: purchase.contractTermsRaw,
paid: false,
};
}
// FIXME: we don't handle aborted payments correctly here.
throw Error("BUG: invariant violation (purchase status)");
} }
/** /**

View File

@ -337,34 +337,36 @@ export interface NextUrlResult {
lastSessionId: string | undefined; lastSessionId: string | undefined;
} }
export const enum PreparePayResultType {
PaymentPossible = "payment-possible",
InsufficientBalance = "insufficient-balance",
AlreadyConfirmed = "already-confirmed",
}
export type PreparePayResult = export type PreparePayResult =
| PreparePayResultError
| PreparePayResultInsufficientBalance | PreparePayResultInsufficientBalance
| PreparePayResultPaid | PreparePayResultAlreadyConfirmed
| PreparePayResultPaymentPossible; | PreparePayResultPaymentPossible;
export interface PreparePayResultPaymentPossible { export interface PreparePayResultPaymentPossible {
status: "payment-possible"; status: PreparePayResultType.PaymentPossible;
proposalId: string; proposalId: string;
contractTermsRaw: string; contractTermsRaw: string;
totalFees: AmountJson; totalFees: AmountJson;
} }
export interface PreparePayResultInsufficientBalance { export interface PreparePayResultInsufficientBalance {
status: "insufficient-balance"; status: PreparePayResultType.InsufficientBalance;
proposalId: string; proposalId: string;
contractTermsRaw: any; contractTermsRaw: any;
} }
export interface PreparePayResultError { export interface PreparePayResultAlreadyConfirmed {
status: "error"; status: PreparePayResultType.AlreadyConfirmed;
error: string;
}
export interface PreparePayResultPaid {
status: "paid";
contractTermsRaw: any; contractTermsRaw: any;
nextUrl: string; paid: boolean;
// Only specified if paid.
nextUrl?: string;
} }
export interface BankWithdrawDetails { export interface BankWithdrawDetails {

View File

@ -24,7 +24,7 @@
*/ */
import * as i18n from "../i18n"; import * as i18n from "../i18n";
import { PreparePayResult } from "../../types/walletTypes"; import { PreparePayResult, PreparePayResultType } from "../../types/walletTypes";
import { renderAmount, ProgressButton } from "../renderHtml"; import { renderAmount, ProgressButton } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
@ -58,15 +58,11 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
insufficientBalance = true; insufficientBalance = true;
} }
if (payStatus.status === "error") {
return <span>Error: {payStatus.error}</span>;
}
if (payStatus.status === "payment-possible") { if (payStatus.status === "payment-possible") {
totalFees = payStatus.totalFees; totalFees = payStatus.totalFees;
} }
if (payStatus.status === "paid" && numTries === 0) { if (payStatus.status === PreparePayResultType.AlreadyConfirmed && numTries === 0) {
return ( return (
<span> <span>
You have already paid for this article. Click{" "} You have already paid for this article. Click{" "}