wallet-core: put contract terms into separate object store
This commit is contained in:
parent
8ac5080607
commit
19f3e6321d
@ -909,6 +909,8 @@ export interface BackupPurchase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature on the contract terms.
|
* Signature on the contract terms.
|
||||||
|
*
|
||||||
|
* FIXME: Better name needed.
|
||||||
*/
|
*/
|
||||||
merchant_sig?: string;
|
merchant_sig?: string;
|
||||||
|
|
||||||
|
@ -1085,18 +1085,16 @@ export enum PurchaseStatus {
|
|||||||
Paid = OperationStatusRange.DORMANT_START + 5,
|
Paid = OperationStatusRange.DORMANT_START + 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial information about the downloaded proposal.
|
||||||
|
* Only contains data that is relevant for indexing on the
|
||||||
|
* "purchases" object stores.
|
||||||
|
*/
|
||||||
export interface ProposalDownload {
|
export interface ProposalDownload {
|
||||||
/**
|
contractTermsHash: string;
|
||||||
* The contract that was offered by the merchant.
|
fulfillmentUrl?: string;
|
||||||
*/
|
currency: string;
|
||||||
contractTermsRaw: any;
|
contractTermsMerchantSig: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracted / parsed data from the contract terms.
|
|
||||||
*
|
|
||||||
* FIXME: Do we need to store *all* that data in duplicate?
|
|
||||||
*/
|
|
||||||
contractData: WalletContractData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PurchasePayInfo {
|
export interface PurchasePayInfo {
|
||||||
@ -1723,6 +1721,7 @@ export interface PeerPullPaymentInitiationRecord {
|
|||||||
* Contract terms for the other party.
|
* Contract terms for the other party.
|
||||||
*
|
*
|
||||||
* FIXME: Nail down type!
|
* FIXME: Nail down type!
|
||||||
|
* FIXME: Put in contractTerms store
|
||||||
*/
|
*/
|
||||||
contractTerms: any;
|
contractTerms: any;
|
||||||
}
|
}
|
||||||
@ -1819,6 +1818,18 @@ export interface CoinAvailabilityRecord {
|
|||||||
freshCoinCount: number;
|
freshCoinCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContractTermsRecord {
|
||||||
|
/**
|
||||||
|
* Contract terms hash.
|
||||||
|
*/
|
||||||
|
h: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract terms JSON.
|
||||||
|
*/
|
||||||
|
contractTermsRaw: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schema definition for the IndexedDB
|
* Schema definition for the IndexedDB
|
||||||
* wallet database.
|
* wallet database.
|
||||||
@ -1937,13 +1948,8 @@ export const WalletStoresV1 = {
|
|||||||
byStatus: describeIndex("byStatus", "purchaseStatus"),
|
byStatus: describeIndex("byStatus", "purchaseStatus"),
|
||||||
byFulfillmentUrl: describeIndex(
|
byFulfillmentUrl: describeIndex(
|
||||||
"byFulfillmentUrl",
|
"byFulfillmentUrl",
|
||||||
"download.contractData.fulfillmentUrl",
|
"download.fulfillmentUrl",
|
||||||
),
|
),
|
||||||
// FIXME: Deduplicate!
|
|
||||||
byMerchantUrlAndOrderId: describeIndex("byMerchantUrlAndOrderId", [
|
|
||||||
"download.contractData.merchantBaseUrl",
|
|
||||||
"download.contractData.orderId",
|
|
||||||
]),
|
|
||||||
byUrlAndOrderId: describeIndex("byUrlAndOrderId", [
|
byUrlAndOrderId: describeIndex("byUrlAndOrderId", [
|
||||||
"merchantBaseUrl",
|
"merchantBaseUrl",
|
||||||
"orderId",
|
"orderId",
|
||||||
@ -2088,6 +2094,13 @@ export const WalletStoresV1 = {
|
|||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
contractTerms: describeStore(
|
||||||
|
"contractTerms",
|
||||||
|
describeContents<ContractTermsRecord>({
|
||||||
|
keyPath: "h",
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +88,7 @@ export async function exportBackup(
|
|||||||
x.exchanges,
|
x.exchanges,
|
||||||
x.exchangeDetails,
|
x.exchangeDetails,
|
||||||
x.coins,
|
x.coins,
|
||||||
|
x.contractTerms,
|
||||||
x.denominations,
|
x.denominations,
|
||||||
x.purchases,
|
x.purchases,
|
||||||
x.refreshGroups,
|
x.refreshGroups,
|
||||||
@ -353,7 +354,7 @@ export async function exportBackup(
|
|||||||
|
|
||||||
const purchaseProposalIdSet = new Set<string>();
|
const purchaseProposalIdSet = new Set<string>();
|
||||||
|
|
||||||
await tx.purchases.iter().forEach((purch) => {
|
await tx.purchases.iter().forEachAsync(async (purch) => {
|
||||||
const refunds: BackupRefundItem[] = [];
|
const refunds: BackupRefundItem[] = [];
|
||||||
purchaseProposalIdSet.add(purch.proposalId);
|
purchaseProposalIdSet.add(purch.proposalId);
|
||||||
for (const refundKey of Object.keys(purch.refunds)) {
|
for (const refundKey of Object.keys(purch.refunds)) {
|
||||||
@ -418,8 +419,18 @@ export async function exportBackup(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let contractTermsRaw = undefined;
|
||||||
|
if (purch.download) {
|
||||||
|
const contractTermsRecord = await tx.contractTerms.get(
|
||||||
|
purch.download.contractTermsHash,
|
||||||
|
);
|
||||||
|
if (contractTermsRecord) {
|
||||||
|
contractTermsRaw = contractTermsRecord.contractTermsRaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
backupPurchases.push({
|
backupPurchases.push({
|
||||||
contract_terms_raw: purch.download?.contractTermsRaw,
|
contract_terms_raw: contractTermsRaw,
|
||||||
auto_refund_deadline: purch.autoRefundDeadline,
|
auto_refund_deadline: purch.autoRefundDeadline,
|
||||||
merchant_pay_sig: purch.merchantPaySig,
|
merchant_pay_sig: purch.merchantPaySig,
|
||||||
pay_info: backupPayInfo,
|
pay_info: backupPayInfo,
|
||||||
@ -428,7 +439,7 @@ export async function exportBackup(
|
|||||||
timestamp_accepted: purch.timestampAccept,
|
timestamp_accepted: purch.timestampAccept,
|
||||||
timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
|
timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
|
||||||
nonce_priv: purch.noncePriv,
|
nonce_priv: purch.noncePriv,
|
||||||
merchant_sig: purch.download?.contractData.merchantSig,
|
merchant_sig: purch.download?.contractTermsMerchantSig,
|
||||||
claim_token: purch.claimToken,
|
claim_token: purch.claimToken,
|
||||||
merchant_base_url: purch.merchantBaseUrl,
|
merchant_base_url: purch.merchantBaseUrl,
|
||||||
order_id: purch.orderId,
|
order_id: purch.orderId,
|
||||||
|
@ -64,6 +64,7 @@ import { checkLogicInvariant } from "../../util/invariants.js";
|
|||||||
import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
|
import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
|
||||||
import { makeCoinAvailable, makeEventId, TombstoneTag } from "../common.js";
|
import { makeCoinAvailable, makeEventId, TombstoneTag } from "../common.js";
|
||||||
import { getExchangeDetails } from "../exchanges.js";
|
import { getExchangeDetails } from "../exchanges.js";
|
||||||
|
import { extractContractData } from "../pay-merchant.js";
|
||||||
import { provideBackupState } from "./state.js";
|
import { provideBackupState } from "./state.js";
|
||||||
|
|
||||||
const logger = new Logger("operations/backup/import.ts");
|
const logger = new Logger("operations/backup/import.ts");
|
||||||
@ -630,49 +631,25 @@ export async function importBackup(
|
|||||||
maxWireFee = Amounts.getZero(amount.currency);
|
maxWireFee = Amounts.getZero(amount.currency);
|
||||||
}
|
}
|
||||||
const download: ProposalDownload = {
|
const download: ProposalDownload = {
|
||||||
contractData: {
|
contractTermsHash,
|
||||||
amount,
|
contractTermsMerchantSig: backupPurchase.merchant_sig!,
|
||||||
contractTermsHash: contractTermsHash,
|
currency: amount.currency,
|
||||||
fulfillmentUrl: parsedContractTerms.fulfillment_url ?? "",
|
fulfillmentUrl: backupPurchase.contract_terms_raw.fulfillment_url,
|
||||||
merchantBaseUrl: parsedContractTerms.merchant_base_url,
|
|
||||||
merchantPub: parsedContractTerms.merchant_pub,
|
|
||||||
merchantSig: backupPurchase.merchant_sig!,
|
|
||||||
orderId: parsedContractTerms.order_id,
|
|
||||||
summary: parsedContractTerms.summary,
|
|
||||||
autoRefund: parsedContractTerms.auto_refund,
|
|
||||||
maxWireFee,
|
|
||||||
payDeadline: parsedContractTerms.pay_deadline,
|
|
||||||
refundDeadline: parsedContractTerms.refund_deadline,
|
|
||||||
wireFeeAmortization:
|
|
||||||
parsedContractTerms.wire_fee_amortization || 1,
|
|
||||||
allowedAuditors: parsedContractTerms.auditors.map((x) => ({
|
|
||||||
auditorBaseUrl: x.url,
|
|
||||||
auditorPub: x.auditor_pub,
|
|
||||||
})),
|
|
||||||
allowedExchanges: parsedContractTerms.exchanges.map((x) => ({
|
|
||||||
exchangeBaseUrl: x.url,
|
|
||||||
exchangePub: x.master_pub,
|
|
||||||
})),
|
|
||||||
timestamp: parsedContractTerms.timestamp,
|
|
||||||
wireMethod: parsedContractTerms.wire_method,
|
|
||||||
wireInfoHash: parsedContractTerms.h_wire,
|
|
||||||
maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
|
|
||||||
merchant: parsedContractTerms.merchant,
|
|
||||||
products: parsedContractTerms.products,
|
|
||||||
summaryI18n: parsedContractTerms.summary_i18n,
|
|
||||||
deliveryDate: parsedContractTerms.delivery_date,
|
|
||||||
deliveryLocation: parsedContractTerms.delivery_location,
|
|
||||||
},
|
|
||||||
contractTermsRaw: backupPurchase.contract_terms_raw,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contractData = extractContractData(
|
||||||
|
backupPurchase.contract_terms_raw,
|
||||||
|
contractTermsHash,
|
||||||
|
download.contractTermsMerchantSig,
|
||||||
|
);
|
||||||
|
|
||||||
let payInfo: PurchasePayInfo | undefined = undefined;
|
let payInfo: PurchasePayInfo | undefined = undefined;
|
||||||
if (backupPurchase.pay_info) {
|
if (backupPurchase.pay_info) {
|
||||||
payInfo = {
|
payInfo = {
|
||||||
coinDepositPermissions: undefined,
|
coinDepositPermissions: undefined,
|
||||||
payCoinSelection: await recoverPayCoinSelection(
|
payCoinSelection: await recoverPayCoinSelection(
|
||||||
tx,
|
tx,
|
||||||
download.contractData,
|
contractData,
|
||||||
backupPurchase.pay_info,
|
backupPurchase.pay_info,
|
||||||
),
|
),
|
||||||
payCoinSelectionUid: backupPurchase.pay_info.pay_coins_uid,
|
payCoinSelectionUid: backupPurchase.pay_info.pay_coins_uid,
|
||||||
|
@ -115,6 +115,7 @@ import {
|
|||||||
throwUnexpectedRequestError,
|
throwUnexpectedRequestError,
|
||||||
} from "../util/http.js";
|
} from "../util/http.js";
|
||||||
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
||||||
|
import { GetReadOnlyAccess } from "../util/query.js";
|
||||||
import {
|
import {
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
@ -256,12 +257,34 @@ function getPayRequestTimeout(purchase: PurchaseRecord): Duration {
|
|||||||
* (Async since in the future this will query the DB.)
|
* (Async since in the future this will query the DB.)
|
||||||
*/
|
*/
|
||||||
export async function expectProposalDownload(
|
export async function expectProposalDownload(
|
||||||
|
ws: InternalWalletState,
|
||||||
p: PurchaseRecord,
|
p: PurchaseRecord,
|
||||||
): Promise<ProposalDownload> {
|
): Promise<{
|
||||||
|
contractData: WalletContractData;
|
||||||
|
contractTermsRaw: any;
|
||||||
|
}> {
|
||||||
if (!p.download) {
|
if (!p.download) {
|
||||||
throw Error("expected proposal to be downloaded");
|
throw Error("expected proposal to be downloaded");
|
||||||
}
|
}
|
||||||
return p.download;
|
const download = p.download;
|
||||||
|
return await ws.db
|
||||||
|
.mktx((x) => [x.contractTerms])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
const contractTerms = await tx.contractTerms.get(
|
||||||
|
download.contractTermsHash,
|
||||||
|
);
|
||||||
|
if (!contractTerms) {
|
||||||
|
throw Error("contract terms not found");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
contractData: extractContractData(
|
||||||
|
contractTerms.contractTermsRaw,
|
||||||
|
download.contractTermsHash,
|
||||||
|
download.contractTermsMerchantSig,
|
||||||
|
),
|
||||||
|
contractTermsRaw: contractTerms.contractTermsRaw,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractContractData(
|
export function extractContractData(
|
||||||
@ -494,7 +517,7 @@ export async function processDownloadProposal(
|
|||||||
logger.trace(`extracted contract data: ${j2s(contractData)}`);
|
logger.trace(`extracted contract data: ${j2s(contractData)}`);
|
||||||
|
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases, x.contractTerms])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const p = await tx.purchases.get(proposalId);
|
const p = await tx.purchases.get(proposalId);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
@ -504,9 +527,15 @@ export async function processDownloadProposal(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p.download = {
|
p.download = {
|
||||||
contractData,
|
contractTermsHash,
|
||||||
contractTermsRaw: proposalResp.contract_terms,
|
contractTermsMerchantSig: contractData.merchantSig,
|
||||||
|
currency: contractData.amount.currency,
|
||||||
|
fulfillmentUrl: contractData.fulfillmentUrl,
|
||||||
};
|
};
|
||||||
|
await tx.contractTerms.put({
|
||||||
|
h: contractTermsHash,
|
||||||
|
contractTermsRaw: proposalResp.contract_terms,
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
fulfillmentUrl &&
|
fulfillmentUrl &&
|
||||||
(fulfillmentUrl.startsWith("http://") ||
|
(fulfillmentUrl.startsWith("http://") ||
|
||||||
@ -636,7 +665,7 @@ async function storeFirstPaySuccess(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
|
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases, x.contractTerms])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const purchase = await tx.purchases.get(proposalId);
|
const purchase = await tx.purchases.get(proposalId);
|
||||||
|
|
||||||
@ -655,7 +684,18 @@ async function storeFirstPaySuccess(
|
|||||||
purchase.timestampFirstSuccessfulPay = now;
|
purchase.timestampFirstSuccessfulPay = now;
|
||||||
purchase.lastSessionId = sessionId;
|
purchase.lastSessionId = sessionId;
|
||||||
purchase.merchantPaySig = paySig;
|
purchase.merchantPaySig = paySig;
|
||||||
const protoAr = purchase.download!.contractData.autoRefund;
|
const dl = purchase.download;
|
||||||
|
checkDbInvariant(!!dl);
|
||||||
|
const contractTermsRecord = await tx.contractTerms.get(
|
||||||
|
dl.contractTermsHash,
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!contractTermsRecord);
|
||||||
|
const contractData = extractContractData(
|
||||||
|
contractTermsRecord.contractTermsRaw,
|
||||||
|
dl.contractTermsHash,
|
||||||
|
dl.contractTermsMerchantSig,
|
||||||
|
);
|
||||||
|
const protoAr = contractData.autoRefund;
|
||||||
if (protoAr) {
|
if (protoAr) {
|
||||||
const ar = Duration.fromTalerProtocolDuration(protoAr);
|
const ar = Duration.fromTalerProtocolDuration(protoAr);
|
||||||
logger.info("auto_refund present");
|
logger.info("auto_refund present");
|
||||||
@ -739,7 +779,7 @@ async function handleInsufficientFunds(
|
|||||||
throw new TalerProtocolViolationError();
|
throw new TalerProtocolViolationError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { contractData } = proposal.download!;
|
const { contractData } = await expectProposalDownload(ws, proposal);
|
||||||
|
|
||||||
const prevPayCoins: PreviousPayCoins = [];
|
const prevPayCoins: PreviousPayCoins = [];
|
||||||
|
|
||||||
@ -1254,11 +1294,7 @@ export async function checkPaymentByProposalId(
|
|||||||
throw Error("existing proposal is in wrong state");
|
throw Error("existing proposal is in wrong state");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const d = proposal.download;
|
const d = await expectProposalDownload(ws, proposal);
|
||||||
if (!d) {
|
|
||||||
logger.error("bad proposal", proposal);
|
|
||||||
throw Error("proposal is in invalid state");
|
|
||||||
}
|
|
||||||
const contractData = d.contractData;
|
const contractData = d.contractData;
|
||||||
const merchantSig = d.contractData.merchantSig;
|
const merchantSig = d.contractData.merchantSig;
|
||||||
if (!merchantSig) {
|
if (!merchantSig) {
|
||||||
@ -1338,7 +1374,7 @@ export async function checkPaymentByProposalId(
|
|||||||
// FIXME: This does not surface the original error
|
// FIXME: This does not surface the original error
|
||||||
throw Error("submitting pay failed");
|
throw Error("submitting pay failed");
|
||||||
}
|
}
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
return {
|
return {
|
||||||
status: PreparePayResultType.AlreadyConfirmed,
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
contractTerms: download.contractTermsRaw,
|
contractTerms: download.contractTermsRaw,
|
||||||
@ -1349,7 +1385,7 @@ export async function checkPaymentByProposalId(
|
|||||||
proposalId,
|
proposalId,
|
||||||
};
|
};
|
||||||
} else if (!purchase.timestampFirstSuccessfulPay) {
|
} else if (!purchase.timestampFirstSuccessfulPay) {
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
return {
|
return {
|
||||||
status: PreparePayResultType.AlreadyConfirmed,
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
contractTerms: download.contractTermsRaw,
|
contractTerms: download.contractTermsRaw,
|
||||||
@ -1364,7 +1400,7 @@ export async function checkPaymentByProposalId(
|
|||||||
purchase.purchaseStatus === PurchaseStatus.Paid ||
|
purchase.purchaseStatus === PurchaseStatus.Paid ||
|
||||||
purchase.purchaseStatus === PurchaseStatus.QueryingRefund ||
|
purchase.purchaseStatus === PurchaseStatus.QueryingRefund ||
|
||||||
purchase.purchaseStatus === PurchaseStatus.QueryingAutoRefund;
|
purchase.purchaseStatus === PurchaseStatus.QueryingAutoRefund;
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
return {
|
return {
|
||||||
status: PreparePayResultType.AlreadyConfirmed,
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
contractTerms: download.contractTermsRaw,
|
contractTerms: download.contractTermsRaw,
|
||||||
@ -1392,11 +1428,9 @@ export async function getContractTermsDetails(
|
|||||||
throw Error(`proposal with id ${proposalId} not found`);
|
throw Error(`proposal with id ${proposalId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!proposal.download || !proposal.download.contractData) {
|
const d = await expectProposalDownload(ws, proposal);
|
||||||
throw Error("proposal is in invalid state");
|
|
||||||
}
|
|
||||||
|
|
||||||
return proposal.download.contractData;
|
return d.contractData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1516,12 +1550,13 @@ export async function runPayForConfirmPay(
|
|||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return tx.purchases.get(proposalId);
|
return tx.purchases.get(proposalId);
|
||||||
});
|
});
|
||||||
if (!purchase?.download) {
|
if (!purchase) {
|
||||||
throw Error("purchase record not available anymore");
|
throw Error("purchase record not available anymore");
|
||||||
}
|
}
|
||||||
|
const d = await expectProposalDownload(ws, purchase);
|
||||||
return {
|
return {
|
||||||
type: ConfirmPayResultType.Done,
|
type: ConfirmPayResultType.Done,
|
||||||
contractTerms: purchase.download.contractTermsRaw,
|
contractTerms: d.contractTermsRaw,
|
||||||
transactionId: makeEventId(TransactionType.Payment, proposalId),
|
transactionId: makeEventId(TransactionType.Payment, proposalId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1599,7 +1634,7 @@ export async function confirmPay(
|
|||||||
throw Error(`proposal with id ${proposalId} not found`);
|
throw Error(`proposal with id ${proposalId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const d = proposal.download;
|
const d = await expectProposalDownload(ws, proposal);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
throw Error("proposal is in invalid state");
|
throw Error("proposal is in invalid state");
|
||||||
}
|
}
|
||||||
@ -1810,7 +1845,7 @@ export async function processPurchasePay(
|
|||||||
const payInfo = purchase.payInfo;
|
const payInfo = purchase.payInfo;
|
||||||
checkDbInvariant(!!payInfo, "payInfo");
|
checkDbInvariant(!!payInfo, "payInfo");
|
||||||
|
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
if (!purchase.merchantPaySig) {
|
if (!purchase.merchantPaySig) {
|
||||||
const payUrl = new URL(
|
const payUrl = new URL(
|
||||||
`orders/${download.contractData.orderId}/pay`,
|
`orders/${download.contractData.orderId}/pay`,
|
||||||
@ -2007,7 +2042,7 @@ export async function prepareRefund(
|
|||||||
const purchase = await ws.db
|
const purchase = await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return tx.purchases.indexes.byMerchantUrlAndOrderId.get([
|
return tx.purchases.indexes.byUrlAndOrderId.get([
|
||||||
parseResult.merchantBaseUrl,
|
parseResult.merchantBaseUrl,
|
||||||
parseResult.orderId,
|
parseResult.orderId,
|
||||||
]);
|
]);
|
||||||
@ -2020,10 +2055,10 @@ export async function prepareRefund(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const awaiting = await queryAndSaveAwaitingRefund(ws, purchase);
|
const awaiting = await queryAndSaveAwaitingRefund(ws, purchase);
|
||||||
const summary = await calculateRefundSummary(purchase);
|
const summary = await calculateRefundSummary(ws, purchase);
|
||||||
const proposalId = purchase.proposalId;
|
const proposalId = purchase.proposalId;
|
||||||
|
|
||||||
const { contractData: c } = await expectProposalDownload(purchase);
|
const { contractData: c } = await expectProposalDownload(ws, purchase);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proposalId,
|
proposalId,
|
||||||
@ -2380,9 +2415,10 @@ async function acceptRefunds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function calculateRefundSummary(
|
async function calculateRefundSummary(
|
||||||
|
ws: InternalWalletState,
|
||||||
p: PurchaseRecord,
|
p: PurchaseRecord,
|
||||||
): Promise<RefundSummary> {
|
): Promise<RefundSummary> {
|
||||||
const download = await expectProposalDownload(p);
|
const download = await expectProposalDownload(ws, p);
|
||||||
let amountRefundGranted = Amounts.getZero(
|
let amountRefundGranted = Amounts.getZero(
|
||||||
download.contractData.amount.currency,
|
download.contractData.amount.currency,
|
||||||
);
|
);
|
||||||
@ -2456,7 +2492,7 @@ export async function applyRefund(
|
|||||||
const purchase = await ws.db
|
const purchase = await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return tx.purchases.indexes.byMerchantUrlAndOrderId.get([
|
return tx.purchases.indexes.byUrlAndOrderId.get([
|
||||||
parseResult.merchantBaseUrl,
|
parseResult.merchantBaseUrl,
|
||||||
parseResult.orderId,
|
parseResult.orderId,
|
||||||
]);
|
]);
|
||||||
@ -2513,8 +2549,8 @@ export async function applyRefundFromPurchaseId(
|
|||||||
throw Error("purchase no longer exists");
|
throw Error("purchase no longer exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = await calculateRefundSummary(purchase);
|
const summary = await calculateRefundSummary(ws, purchase);
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractTermsHash: download.contractData.contractTermsHash,
|
contractTermsHash: download.contractData.contractTermsHash,
|
||||||
@ -2542,7 +2578,7 @@ async function queryAndSaveAwaitingRefund(
|
|||||||
purchase: PurchaseRecord,
|
purchase: PurchaseRecord,
|
||||||
waitForAutoRefund?: boolean,
|
waitForAutoRefund?: boolean,
|
||||||
): Promise<AmountJson> {
|
): Promise<AmountJson> {
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
const requestUrl = new URL(
|
const requestUrl = new URL(
|
||||||
`orders/${download.contractData.orderId}`,
|
`orders/${download.contractData.orderId}`,
|
||||||
download.contractData.merchantBaseUrl,
|
download.contractData.merchantBaseUrl,
|
||||||
@ -2621,7 +2657,7 @@ export async function processPurchaseQueryRefund(
|
|||||||
return OperationAttemptResult.finishedEmpty();
|
return OperationAttemptResult.finishedEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
|
|
||||||
if (purchase.timestampFirstSuccessfulPay) {
|
if (purchase.timestampFirstSuccessfulPay) {
|
||||||
if (
|
if (
|
||||||
|
@ -48,6 +48,7 @@ import {
|
|||||||
WalletRefundItem,
|
WalletRefundItem,
|
||||||
WithdrawalGroupRecord,
|
WithdrawalGroupRecord,
|
||||||
WithdrawalRecordType,
|
WithdrawalRecordType,
|
||||||
|
WalletContractData,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { checkDbInvariant } from "../util/invariants.js";
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
@ -55,7 +56,11 @@ import { RetryTags } from "../util/retries.js";
|
|||||||
import { makeEventId, TombstoneTag } from "./common.js";
|
import { makeEventId, TombstoneTag } from "./common.js";
|
||||||
import { processDepositGroup } from "./deposits.js";
|
import { processDepositGroup } from "./deposits.js";
|
||||||
import { getExchangeDetails } from "./exchanges.js";
|
import { getExchangeDetails } from "./exchanges.js";
|
||||||
import { expectProposalDownload, processPurchasePay } from "./pay-merchant.js";
|
import {
|
||||||
|
expectProposalDownload,
|
||||||
|
extractContractData,
|
||||||
|
processPurchasePay,
|
||||||
|
} from "./pay-merchant.js";
|
||||||
import { processRefreshGroup } from "./refresh.js";
|
import { processRefreshGroup } from "./refresh.js";
|
||||||
import { processTip } from "./tip.js";
|
import { processTip } from "./tip.js";
|
||||||
import {
|
import {
|
||||||
@ -199,7 +204,7 @@ export async function getTransactionById(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
|
|
||||||
const cleanRefunds = filteredRefunds.filter(
|
const cleanRefunds = filteredRefunds.filter(
|
||||||
(x): x is WalletRefundItem => !!x,
|
(x): x is WalletRefundItem => !!x,
|
||||||
@ -214,7 +219,12 @@ export async function getTransactionById(
|
|||||||
const payOpId = RetryTags.forPay(purchase);
|
const payOpId = RetryTags.forPay(purchase);
|
||||||
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
||||||
|
|
||||||
return buildTransactionForPurchase(purchase, refunds, payRetryRecord);
|
return buildTransactionForPurchase(
|
||||||
|
purchase,
|
||||||
|
contractData,
|
||||||
|
refunds,
|
||||||
|
payRetryRecord,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else if (type === TransactionType.Refresh) {
|
} else if (type === TransactionType.Refresh) {
|
||||||
const refreshGroupId = rest[0];
|
const refreshGroupId = rest[0];
|
||||||
@ -268,14 +278,19 @@ export async function getTransactionById(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (t) throw Error("deleted");
|
if (t) throw Error("deleted");
|
||||||
const download = await expectProposalDownload(purchase);
|
const download = await expectProposalDownload(ws, purchase);
|
||||||
const contractData = download.contractData;
|
const contractData = download.contractData;
|
||||||
const refunds = mergeRefundByExecutionTime(
|
const refunds = mergeRefundByExecutionTime(
|
||||||
[theRefund],
|
[theRefund],
|
||||||
Amounts.getZero(contractData.amount.currency),
|
Amounts.getZero(contractData.amount.currency),
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildTransactionForRefund(purchase, refunds[0], undefined);
|
return buildTransactionForRefund(
|
||||||
|
purchase,
|
||||||
|
contractData,
|
||||||
|
refunds[0],
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else if (type === TransactionType.PeerPullDebit) {
|
} else if (type === TransactionType.PeerPullDebit) {
|
||||||
const peerPullPaymentIncomingId = rest[0];
|
const peerPullPaymentIncomingId = rest[0];
|
||||||
@ -572,12 +587,10 @@ function mergeRefundByExecutionTime(
|
|||||||
|
|
||||||
async function buildTransactionForRefund(
|
async function buildTransactionForRefund(
|
||||||
purchaseRecord: PurchaseRecord,
|
purchaseRecord: PurchaseRecord,
|
||||||
|
contractData: WalletContractData,
|
||||||
refundInfo: MergedRefundInfo,
|
refundInfo: MergedRefundInfo,
|
||||||
ort?: OperationRetryRecord,
|
ort?: OperationRetryRecord,
|
||||||
): Promise<Transaction> {
|
): Promise<Transaction> {
|
||||||
const download = await expectProposalDownload(purchaseRecord);
|
|
||||||
const contractData = download.contractData;
|
|
||||||
|
|
||||||
const info: OrderShortInfo = {
|
const info: OrderShortInfo = {
|
||||||
merchant: contractData.merchant,
|
merchant: contractData.merchant,
|
||||||
orderId: contractData.orderId,
|
orderId: contractData.orderId,
|
||||||
@ -617,11 +630,10 @@ async function buildTransactionForRefund(
|
|||||||
|
|
||||||
async function buildTransactionForPurchase(
|
async function buildTransactionForPurchase(
|
||||||
purchaseRecord: PurchaseRecord,
|
purchaseRecord: PurchaseRecord,
|
||||||
|
contractData: WalletContractData,
|
||||||
refundsInfo: MergedRefundInfo[],
|
refundsInfo: MergedRefundInfo[],
|
||||||
ort?: OperationRetryRecord,
|
ort?: OperationRetryRecord,
|
||||||
): Promise<Transaction> {
|
): Promise<Transaction> {
|
||||||
const download = await expectProposalDownload(purchaseRecord);
|
|
||||||
const contractData = download.contractData;
|
|
||||||
const zero = Amounts.getZero(contractData.amount.currency);
|
const zero = Amounts.getZero(contractData.amount.currency);
|
||||||
|
|
||||||
const info: OrderShortInfo = {
|
const info: OrderShortInfo = {
|
||||||
@ -689,7 +701,8 @@ async function buildTransactionForPurchase(
|
|||||||
proposalId: purchaseRecord.proposalId,
|
proposalId: purchaseRecord.proposalId,
|
||||||
info,
|
info,
|
||||||
frozen:
|
frozen:
|
||||||
purchaseRecord.purchaseStatus === PurchaseStatus.PaymentAbortFinished ?? false,
|
purchaseRecord.purchaseStatus === PurchaseStatus.PaymentAbortFinished ??
|
||||||
|
false,
|
||||||
...(ort?.lastError ? { error: ort.lastError } : {}),
|
...(ort?.lastError ? { error: ort.lastError } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -715,6 +728,7 @@ export async function getTransactions(
|
|||||||
x.peerPushPaymentInitiations,
|
x.peerPushPaymentInitiations,
|
||||||
x.planchets,
|
x.planchets,
|
||||||
x.purchases,
|
x.purchases,
|
||||||
|
x.contractTerms,
|
||||||
x.recoupGroups,
|
x.recoupGroups,
|
||||||
x.tips,
|
x.tips,
|
||||||
x.tombstones,
|
x.tombstones,
|
||||||
@ -814,18 +828,28 @@ export async function getTransactions(
|
|||||||
if (!purchase.payInfo) {
|
if (!purchase.payInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (shouldSkipCurrency(transactionsRequest, download.currency)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contractTermsRecord = await tx.contractTerms.get(
|
||||||
|
download.contractTermsHash,
|
||||||
|
);
|
||||||
|
if (!contractTermsRecord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
shouldSkipCurrency(
|
shouldSkipSearch(transactionsRequest, [
|
||||||
transactionsRequest,
|
contractTermsRecord?.contractTermsRaw?.summary || "",
|
||||||
download.contractData.amount.currency,
|
])
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const contractData = download.contractData;
|
|
||||||
if (shouldSkipSearch(transactionsRequest, [contractData.summary])) {
|
const contractData = extractContractData(
|
||||||
return;
|
contractTermsRecord?.contractTermsRaw,
|
||||||
}
|
download.contractTermsHash,
|
||||||
|
download.contractTermsMerchantSig,
|
||||||
|
);
|
||||||
|
|
||||||
const filteredRefunds = await Promise.all(
|
const filteredRefunds = await Promise.all(
|
||||||
Object.values(purchase.refunds).map(async (r) => {
|
Object.values(purchase.refunds).map(async (r) => {
|
||||||
@ -847,19 +871,29 @@ export async function getTransactions(
|
|||||||
|
|
||||||
const refunds = mergeRefundByExecutionTime(
|
const refunds = mergeRefundByExecutionTime(
|
||||||
cleanRefunds,
|
cleanRefunds,
|
||||||
Amounts.getZero(contractData.amount.currency),
|
Amounts.getZero(download.currency),
|
||||||
);
|
);
|
||||||
|
|
||||||
refunds.forEach(async (refundInfo) => {
|
refunds.forEach(async (refundInfo) => {
|
||||||
transactions.push(
|
transactions.push(
|
||||||
await buildTransactionForRefund(purchase, refundInfo, undefined),
|
await buildTransactionForRefund(
|
||||||
|
purchase,
|
||||||
|
contractData,
|
||||||
|
refundInfo,
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const payOpId = RetryTags.forPay(purchase);
|
const payOpId = RetryTags.forPay(purchase);
|
||||||
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
||||||
transactions.push(
|
transactions.push(
|
||||||
await buildTransactionForPurchase(purchase, refunds, payRetryRecord),
|
await buildTransactionForPurchase(
|
||||||
|
purchase,
|
||||||
|
contractData,
|
||||||
|
refunds,
|
||||||
|
payRetryRecord,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user