aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-06-17 17:38:00 +0200
committerFlorian Dold <florian@dold.me>2024-06-17 17:38:00 +0200
commit351948d85b69f4d7ae1afdab5a30777b15b6abde (patch)
tree7a115aa929135991db08b87eedf19ae15aa19b3e /packages
parentabbab3b029002eb5d603af611db6f411daf5d490 (diff)
downloadwallet-core-351948d85b69f4d7ae1afdab5a30777b15b6abde.tar.xz
wallet-core: draft qr content generation
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/index.ts1
-rw-r--r--packages/taler-util/src/qr.ts161
-rw-r--r--packages/taler-util/src/wallet-types.ts14
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts10
-rw-r--r--packages/taler-wallet-core/src/wallet.ts8
5 files changed, 194 insertions, 0 deletions
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 9f99f2f5a..287e03584 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -50,6 +50,7 @@ export * from "./observability.js";
export * from "./operation.js";
export * from "./payto.js";
export * from "./promises.js";
+export * from "./qr.js";
export * from "./rfc3548.js";
export * from "./taler-crypto.js";
export * from "./taler-types.js";
diff --git a/packages/taler-util/src/qr.ts b/packages/taler-util/src/qr.ts
new file mode 100644
index 000000000..2e8ffc652
--- /dev/null
+++ b/packages/taler-util/src/qr.ts
@@ -0,0 +1,161 @@
+/*
+ This file is part of GNU Taler
+ (C) 2024 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 { Amounts } from "./index.node.js";
+import { parsePaytoUri } from "./payto.js";
+
+type EncodeResult = { type: "ok"; qrContent: string } | { type: "skip" };
+
+/**
+ * See "Schweizer Implementation Guidelines QR-Rechnung".
+ */
+function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
+ const parsedPayto = parsePaytoUri(paytoUri);
+ if (!parsedPayto) {
+ throw Error("invalid payto URI");
+ }
+ if (parsedPayto.targetType !== "iban") {
+ return { type: "skip" };
+ }
+ const amountStr = parsedPayto.params["amount"];
+ const iban = parsedPayto.targetPath;
+ const countryCode = iban.slice(0, 2);
+ const lines = [
+ "SPC", // QRType
+ "0200", // Version
+ "1", // Character set (1: UTF-8)
+ iban, // Beneficiary IBAN
+ // Group: Beneficiary
+ "S", // Address type (S: structured)
+ parsedPayto.params["receiver-name"], // Beneficiary name
+ "", // street
+ "", // apt. nr.
+ "", // postal code
+ "", // town
+ countryCode, // Country
+ // Group: Ultimate Debtor (not used in version 0200)
+ "", // Ultimate Debtor Address type (S: structured)
+ "", // Ultimate Debtor name
+ "", // Ultimate Debtor street
+ "", // Ultimate Debtor apt. nr
+ "", // Ultimate Debtor postal code
+ "", // Ultimate Debtor town
+ "", // Ultimate Debtor country
+ // Group: Amount
+ Amounts.stringifyValue(amountStr, 2), // Amount
+ Amounts.currencyOf(amountStr), // Currency
+ // Group: Debtor
+ "", // Address type (S: structured)
+ "", // Debtor name
+ "", // Debtor street
+ "", // Debtor apt. nr
+ "", // Debtor postal code
+ "", // Debtor town
+ "", // Debtor country
+ // Group: Reference
+ "NON", // reference type
+ "", // Reference
+ // Group: Additional Information
+ parsedPayto.params["message"], // Unstructured data
+ "EPD", // End of payment data
+ ];
+
+ return {
+ type: "ok",
+ qrContent: lines.join("\n"),
+ };
+}
+
+/**
+ * See "Quick Response Code - Guidelines to
+ * Enable the Data Capture for the
+ * Initiation of a SEPA Credit Transfer".
+ */
+function encodePaytoAsEpcQr(paytoUri: string): EncodeResult {
+ const parsedPayto = parsePaytoUri(paytoUri);
+ if (!parsedPayto) {
+ throw Error("invalid payto URI");
+ }
+ if (parsedPayto.targetType !== "iban") {
+ return { type: "skip" };
+ }
+ const amountStr = parsedPayto.params["amount"];
+ Amounts.stringifyValue;
+ const lines = [
+ "BCD", // service tag
+ "002", // version
+ "1", // character set (1: UTF-8)
+ "SCT", // Identification
+ "", // optional BIC
+ parsedPayto.params["receiver-name"], // Beneficiary name
+ parsedPayto.targetPath, // Beneficiary IBAN
+ `${Amounts.currencyOf(amountStr)}${Amounts.stringifyValue(amountStr, 2)}`, // Amount
+ "", // AT-44 Purpose
+ parsedPayto.params["message"], // AT-05 Unstructured remittance information
+ ];
+
+ return {
+ type: "ok",
+ qrContent: lines.join("\n"),
+ };
+}
+
+/**
+ * Specification of a QR code that includes payment information.
+ */
+export interface QrCodeSpec {
+ /**
+ * Type of the QR code.
+ *
+ * Depending on the type, different visual styles
+ * might be applied.
+ */
+ type: string;
+
+ /**
+ * Content of the QR code that should be rendered.
+ */
+ qrContent: string;
+}
+
+/**
+ * Get applicable QR code specifications for the given payto URI.
+ */
+export function getQrCodesForPayto(paytoUri: string): QrCodeSpec[] {
+ const res: QrCodeSpec[] = [];
+ {
+ const qr = encodePaytoAsEpcQr(paytoUri);
+ if (qr.type == "ok") {
+ res.push({
+ type: "epc-qr",
+ qrContent: qr.qrContent,
+ });
+ }
+ }
+ {
+ const qr = encodePaytoAsSwissQrBill(paytoUri);
+ if (qr.type == "ok") {
+ res.push({
+ type: "epc-qr",
+ qrContent: qr.qrContent,
+ });
+ }
+ }
+ return res;
+}
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 360d8b183..2c92d9295 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -55,6 +55,7 @@ import {
} from "./index.js";
import { VersionMatchResult } from "./libtool-version.js";
import { PaytoString, PaytoUri, codecForPaytoString } from "./payto.js";
+import { QrCodeSpec } from "./qr.js";
import { AgeCommitmentProof } from "./taler-crypto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
import {
@@ -3425,3 +3426,16 @@ export const codecForGetDepositWireTypesForCurrencyRequest =
export interface GetDepositWireTypesForCurrencyResponse {
wireTypes: string[];
}
+
+export interface GetQrCodesForPaytoRequest {
+ paytoUri: string;
+}
+
+export const codecForGetQrCodesForPaytoRequest = () =>
+ buildCodecForObject<GetQrCodesForPaytoRequest>()
+ .property("paytoUri", codecForString())
+ .build("GetQrCodesForPaytoRequest");
+
+export interface GetQrCodesForPaytoResponse {
+ codes: QrCodeSpec[];
+}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index c54ec1360..ce8be2927 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -81,6 +81,8 @@ import {
GetExchangeTosResult,
GetPlanForOperationRequest,
GetPlanForOperationResponse,
+ GetQrCodesForPaytoRequest,
+ GetQrCodesForPaytoResponse,
GetWithdrawalDetailsForAmountRequest,
GetWithdrawalDetailsForUriRequest,
HintNetworkAvailabilityRequest,
@@ -269,6 +271,7 @@ export enum WalletApiOperation {
HintNetworkAvailability = "hintNetworkAvailability",
CanonicalizeBaseUrl = "canonicalizeBaseUrl",
GetDepositWireTypesForCurrency = "getDepositWireTypesForCurrency",
+ GetQrCodesForPayto = "getQrCodesForPayto",
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
TestingWaitTransactionState = "testingWaitTransactionState",
@@ -999,6 +1002,12 @@ export type CanonicalizeBaseUrlOp = {
response: CanonicalizeBaseUrlResponse;
};
+export type GetQrCodesForPaytoOp = {
+ op: WalletApiOperation.GetQrCodesForPayto;
+ request: GetQrCodesForPaytoRequest;
+ response: GetQrCodesForPaytoResponse;
+};
+
// group: Database Management
/**
@@ -1382,6 +1391,7 @@ export type WalletOperations = {
[WalletApiOperation.TestingResetAllRetries]: TestingResetAllRetriesOp;
[WalletApiOperation.HintNetworkAvailability]: HintNetworkAvailabilityOp;
[WalletApiOperation.GetDepositWireTypesForCurrency]: GetDepositWireTypesForCurrencyOp;
+ [WalletApiOperation.GetQrCodesForPayto]: GetQrCodesForPaytoOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index a58e3aff0..69f0e5f0b 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -107,6 +107,7 @@ import {
codecForGetExchangeEntryByUrlRequest,
codecForGetExchangeResourcesRequest,
codecForGetExchangeTosRequest,
+ codecForGetQrCodesForPaytoRequest,
codecForGetWithdrawalDetailsForAmountRequest,
codecForGetWithdrawalDetailsForUri,
codecForHintNetworkAvailabilityRequest,
@@ -149,6 +150,7 @@ import {
codecForValidateIbanRequest,
codecForWithdrawTestBalance,
getErrorDetailFromException,
+ getQrCodesForPayto,
j2s,
openPromise,
parsePaytoUri,
@@ -1061,6 +1063,12 @@ async function dispatchRequestInternal(
const req = codecForPreparePayTemplateRequest().decode(payload);
return preparePayForTemplate(wex, req);
}
+ case WalletApiOperation.GetQrCodesForPayto: {
+ const req = codecForGetQrCodesForPaytoRequest().decode(payload);
+ return {
+ codes: getQrCodesForPayto(req.paytoUri),
+ };
+ }
case WalletApiOperation.ConfirmPay: {
const req = codecForConfirmPayRequest().decode(payload);
let transactionId;