diff options
Diffstat (limited to 'packages/taler-harness')
5 files changed, 122 insertions, 344 deletions
| diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 4e5d8238c..3659ea538 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -38,6 +38,7 @@ import {    hash,    j2s,    Logger, +  MerchantTemplateAddDetails,    parsePaytoUri,    stringToBytes,    TalerProtocolDuration, @@ -66,15 +67,15 @@ import { CoinConfig } from "./denomStructures.js";  import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";  import {    codecForMerchantOrderPrivateStatusResponse, -  codecForPostOrderResponse, +  codecForMerchantPostOrderResponse,    MerchantInstancesResponse,    MerchantOrderPrivateStatusResponse, -  PostOrderRequest, -  PostOrderResponse, +  MerchantPostOrderRequest, +  MerchantPostOrderResponse,    TipCreateConfirmation,    TipCreateRequest,    TippingReserveStatus, -} from "./merchantApiTypes.js"; +} from "@gnu-taler/taler-util";  import {    createRemoteWallet,    getClientFromRemoteWallet, @@ -1473,15 +1474,31 @@ export namespace MerchantPrivateApi {    export async function createOrder(      merchantService: MerchantServiceInterface,      instanceName: string, -    req: PostOrderRequest, +    req: MerchantPostOrderRequest,      withAuthorization: WithAuthorization = {}, -  ): Promise<PostOrderResponse> { +  ): Promise<MerchantPostOrderResponse> {      const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);      let url = new URL("private/orders", baseUrl);      const resp = await axios.post(url.href, req, {        headers: withAuthorization as Record<string, string>,      }); -    return codecForPostOrderResponse().decode(resp.data); +    return codecForMerchantPostOrderResponse().decode(resp.data); +  } + +  export async function createTemplate( +    merchantService: MerchantServiceInterface, +    instanceName: string, +    req: MerchantTemplateAddDetails, +    withAuthorization: WithAuthorization = {}, +  ) { +    const baseUrl = merchantService.makeInstanceBaseUrl(instanceName); +    let url = new URL("private/templates", baseUrl); +    const resp = await axios.post(url.href, req, { +      headers: withAuthorization as Record<string, string>, +    }); +    if (resp.status !== 204) { +      throw Error("failed to create template"); +    }    }    export async function queryPrivateOrderStatus( diff --git a/packages/taler-harness/src/harness/merchantApiTypes.ts b/packages/taler-harness/src/harness/merchantApiTypes.ts deleted file mode 100644 index 1985e9150..000000000 --- a/packages/taler-harness/src/harness/merchantApiTypes.ts +++ /dev/null @@ -1,336 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE.  See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Test harness for various GNU Taler components. - * Also provides a fault-injection proxy. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { -  MerchantContractTerms, -  Duration, -  Codec, -  buildCodecForObject, -  codecForString, -  codecOptional, -  codecForConstString, -  codecForBoolean, -  codecForNumber, -  codecForMerchantContractTerms, -  codecForAny, -  buildCodecForUnion, -  AmountString, -  AbsoluteTime, -  CoinPublicKeyString, -  EddsaPublicKeyString, -  codecForAmountString, -  TalerProtocolDuration, -  codecForTimestamp, -  TalerProtocolTimestamp, -} from "@gnu-taler/taler-util"; - -export interface PostOrderRequest { -  // The order must at least contain the minimal -  // order detail, but can override all -  order: Partial<MerchantContractTerms>; - -  // if set, the backend will then set the refund deadline to the current -  // time plus the specified delay. -  refund_delay?: TalerProtocolDuration; - -  // specifies the payment target preferred by the client. Can be used -  // to select among the various (active) wire methods supported by the instance. -  payment_target?: string; - -  // FIXME: some fields are missing - -  // Should a token for claiming the order be generated? -  // False can make sense if the ORDER_ID is sufficiently -  // high entropy to prevent adversarial claims (like it is -  // if the backend auto-generates one). Default is 'true'. -  create_token?: boolean; -} - -export type ClaimToken = string; - -export interface PostOrderResponse { -  order_id: string; -  token?: ClaimToken; -} - -export const codecForPostOrderResponse = (): Codec<PostOrderResponse> => -  buildCodecForObject<PostOrderResponse>() -    .property("order_id", codecForString()) -    .property("token", codecOptional(codecForString())) -    .build("PostOrderResponse"); - -export const codecForRefundDetails = (): Codec<RefundDetails> => -  buildCodecForObject<RefundDetails>() -    .property("reason", codecForString()) -    .property("pending", codecForBoolean()) -    .property("amount", codecForString()) -    .property("timestamp", codecForTimestamp) -    .build("PostOrderResponse"); - -export const codecForCheckPaymentPaidResponse = -  (): Codec<CheckPaymentPaidResponse> => -    buildCodecForObject<CheckPaymentPaidResponse>() -      .property("order_status_url", codecForString()) -      .property("order_status", codecForConstString("paid")) -      .property("refunded", codecForBoolean()) -      .property("wired", codecForBoolean()) -      .property("deposit_total", codecForAmountString()) -      .property("exchange_ec", codecForNumber()) -      .property("exchange_hc", codecForNumber()) -      .property("refund_amount", codecForAmountString()) -      .property("contract_terms", codecForMerchantContractTerms()) -      // FIXME: specify -      .property("wire_details", codecForAny()) -      .property("wire_reports", codecForAny()) -      .property("refund_details", codecForAny()) -      .build("CheckPaymentPaidResponse"); - -export const codecForCheckPaymentUnpaidResponse = -  (): Codec<CheckPaymentUnpaidResponse> => -    buildCodecForObject<CheckPaymentUnpaidResponse>() -      .property("order_status", codecForConstString("unpaid")) -      .property("taler_pay_uri", codecForString()) -      .property("order_status_url", codecForString()) -      .property("already_paid_order_id", codecOptional(codecForString())) -      .build("CheckPaymentPaidResponse"); - -export const codecForCheckPaymentClaimedResponse = -  (): Codec<CheckPaymentClaimedResponse> => -    buildCodecForObject<CheckPaymentClaimedResponse>() -      .property("order_status", codecForConstString("claimed")) -      .property("contract_terms", codecForMerchantContractTerms()) -      .build("CheckPaymentClaimedResponse"); - -export const codecForMerchantOrderPrivateStatusResponse = -  (): Codec<MerchantOrderPrivateStatusResponse> => -    buildCodecForUnion<MerchantOrderPrivateStatusResponse>() -      .discriminateOn("order_status") -      .alternative("paid", codecForCheckPaymentPaidResponse()) -      .alternative("unpaid", codecForCheckPaymentUnpaidResponse()) -      .alternative("claimed", codecForCheckPaymentClaimedResponse()) -      .build("MerchantOrderPrivateStatusResponse"); - -export type MerchantOrderPrivateStatusResponse = -  | CheckPaymentPaidResponse -  | CheckPaymentUnpaidResponse -  | CheckPaymentClaimedResponse; - -export interface CheckPaymentClaimedResponse { -  // Wallet claimed the order, but didn't pay yet. -  order_status: "claimed"; - -  contract_terms: MerchantContractTerms; -} - -export interface CheckPaymentPaidResponse { -  // did the customer pay for this contract -  order_status: "paid"; - -  // Was the payment refunded (even partially) -  refunded: boolean; - -  // Did the exchange wire us the funds -  wired: boolean; - -  // Total amount the exchange deposited into our bank account -  // for this contract, excluding fees. -  deposit_total: AmountString; - -  // Numeric error code indicating errors the exchange -  // encountered tracking the wire transfer for this purchase (before -  // we even got to specific coin issues). -  // 0 if there were no issues. -  exchange_ec: number; - -  // HTTP status code returned by the exchange when we asked for -  // information to track the wire transfer for this purchase. -  // 0 if there were no issues. -  exchange_hc: number; - -  // Total amount that was refunded, 0 if refunded is false. -  refund_amount: AmountString; - -  // Contract terms -  contract_terms: MerchantContractTerms; - -  // Ihe wire transfer status from the exchange for this order if available, otherwise empty array -  wire_details: TransactionWireTransfer[]; - -  // Reports about trouble obtaining wire transfer details, empty array if no trouble were encountered. -  wire_reports: TransactionWireReport[]; - -  // The refund details for this order.  One entry per -  // refunded coin; empty array if there are no refunds. -  refund_details: RefundDetails[]; - -  order_status_url: string; -} - -export interface CheckPaymentUnpaidResponse { -  order_status: "unpaid"; - -  // URI that the wallet must process to complete the payment. -  taler_pay_uri: string; - -  order_status_url: string; - -  // Alternative order ID which was paid for already in the same session. -  // Only given if the same product was purchased before in the same session. -  already_paid_order_id?: string; - -  // We do we NOT return the contract terms here because they may not -  // exist in case the wallet did not yet claim them. -} - -export interface RefundDetails { -  // Reason given for the refund -  reason: string; - -  // when was the refund approved -  timestamp: TalerProtocolTimestamp; - -  // has not been taken yet -  pending: boolean; - -  // Total amount that was refunded (minus a refund fee). -  amount: AmountString; -} - -export interface TransactionWireTransfer { -  // Responsible exchange -  exchange_url: string; - -  // 32-byte wire transfer identifier -  wtid: string; - -  // execution time of the wire transfer -  execution_time: AbsoluteTime; - -  // Total amount that has been wire transferred -  // to the merchant -  amount: AmountString; - -  // Was this transfer confirmed by the merchant via the -  // POST /transfers API, or is it merely claimed by the exchange? -  confirmed: boolean; -} - -export interface TransactionWireReport { -  // Numerical error code -  code: number; - -  // Human-readable error description -  hint: string; - -  // Numerical error code from the exchange. -  exchange_ec: number; - -  // HTTP status code received from the exchange. -  exchange_hc: number; - -  // Public key of the coin for which we got the exchange error. -  coin_pub: CoinPublicKeyString; -} - -export interface TippingReserveStatus { -  // Array of all known reserves (possibly empty!) -  reserves: ReserveStatusEntry[]; -} - -export interface ReserveStatusEntry { -  // Public key of the reserve -  reserve_pub: string; - -  // Timestamp when it was established -  creation_time: AbsoluteTime; - -  // Timestamp when it expires -  expiration_time: AbsoluteTime; - -  // Initial amount as per reserve creation call -  merchant_initial_amount: AmountString; - -  // Initial amount as per exchange, 0 if exchange did -  // not confirm reserve creation yet. -  exchange_initial_amount: AmountString; - -  // Amount picked up so far. -  pickup_amount: AmountString; - -  // Amount approved for tips that exceeds the pickup_amount. -  committed_amount: AmountString; - -  // Is this reserve active (false if it was deleted but not purged) -  active: boolean; -} - -export interface TipCreateConfirmation { -  // Unique tip identifier for the tip that was created. -  tip_id: string; - -  // taler://tip URI for the tip -  taler_tip_uri: string; - -  // URL that will directly trigger processing -  // the tip when the browser is redirected to it -  tip_status_url: string; - -  // when does the tip expire -  tip_expiration: AbsoluteTime; -} - -export interface TipCreateRequest { -  // Amount that the customer should be tipped -  amount: AmountString; - -  // Justification for giving the tip -  justification: string; - -  // URL that the user should be directed to after tipping, -  // will be included in the tip_token. -  next_url: string; -} - -export interface MerchantInstancesResponse { -  // List of instances that are present in the backend (see Instance) -  instances: MerchantInstanceDetail[]; -} - -export interface MerchantInstanceDetail { -  // Merchant name corresponding to this instance. -  name: string; - -  // Merchant instance this response is about ($INSTANCE) -  id: string; - -  // Public key of the merchant/instance, in Crockford Base32 encoding. -  merchant_pub: EddsaPublicKeyString; - -  // List of the payment targets supported by this instance. Clients can -  // specify the desired payment target in /order requests.  Note that -  // front-ends do not have to support wallets selecting payment targets. -  payment_targets: string[]; -} diff --git a/packages/taler-harness/src/integrationtests/test-payment-template.ts b/packages/taler-harness/src/integrationtests/test-payment-template.ts new file mode 100644 index 000000000..41e43e28a --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-payment-template.ts @@ -0,0 +1,95 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { ConfirmPayResultType, Duration, PreparePayResultType, TalerProtocolTimestamp } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; +import { +  createSimpleTestkudosEnvironment, +  withdrawViaBank, +  makeTestPayment, +} from "../harness/helpers.js"; + +/** + * Test for taler://payment-template/ URIs + */ +export async function runPaymentTemplateTest(t: GlobalTestState) { +  // Set up test environment + +  const { wallet, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironment(t); + +  await MerchantPrivateApi.createTemplate(merchant, "default", { +    template_id: "template1", +    template_description: "my test template", +    template_contract: { +      minimum_age: 0, +      pay_duration: Duration.toTalerProtocolDuration( +        Duration.fromSpec({ +          minutes: 2, +        }), +      ), +      summary: "hello, I'm a summary", +    }, +  }); + +  // Withdraw digital cash into the wallet. + +  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + +  // Request a template payment + +  const preparePayResult = await wallet.client.call( +    WalletApiOperation.PreparePayForTemplate, +    { +      talerPayTemplateUri: `taler+http://pay-template/localhost:${merchant.port}/template1?amount=TESTKUDOS:1`, +      templateParams: {}, +    }, +  ); + +  console.log(preparePayResult); + +  t.assertTrue( +    preparePayResult.status === PreparePayResultType.PaymentPossible, +  ); + +  // Pay for it + +  const r2 = await wallet.client.call(WalletApiOperation.ConfirmPay, { +    proposalId: preparePayResult.proposalId, +  }); + +  t.assertTrue(r2.type === ConfirmPayResultType.Done); + +  // Check if payment was successful. + +  const orderStatus = await MerchantPrivateApi.queryPrivateOrderStatus( +    merchant, +    { +      orderId: preparePayResult.contractTerms.order_id, +      instance: "default", +    }, +  ); + +  t.assertTrue(orderStatus.order_status === "paid"); + +  await wallet.runUntilDone(); +} + +runPaymentTemplateTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 025f2e514..a20300e02 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -100,6 +100,7 @@ import { runKycTest } from "./test-kyc.js";  import { runPaymentAbortTest } from "./test-payment-abort.js";  import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";  import { runWalletBalanceTest } from "./test-wallet-balance.js"; +import { runPaymentTemplateTest } from "./test-payment-template.js";  /**   * Test runner. @@ -163,6 +164,7 @@ const allTests: TestMainFunction[] = [    runPaymentIdempotencyTest,    runPaymentMultipleTest,    runPaymentTest, +  runPaymentTemplateTest,    runPaymentAbortTest,    runPaymentTransientTest,    runPaymentZeroTest, diff --git a/packages/taler-harness/tsconfig.json b/packages/taler-harness/tsconfig.json index 447d3f946..d022b16e8 100644 --- a/packages/taler-harness/tsconfig.json +++ b/packages/taler-harness/tsconfig.json @@ -21,7 +21,7 @@      "baseUrl": "./src",      "typeRoots": ["./node_modules/@types"]    }, -  "include": ["src/**/*"], +  "include": ["src/**/*", "../taler-util/src/merchant-api-types.ts"],    "references": [      {        "path": "../taler-wallet-core/" | 
