@hardkas/toolkit 0.11.1-alpha → 0.11.2-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +2 -1
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +6 -2
- package/dist/indexer.js.map +1 -1
- package/dist/jobs.d.ts +3 -0
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +16 -1
- package/dist/jobs.js.map +1 -1
- package/dist/payment.d.ts +4 -2
- package/dist/payment.d.ts.map +1 -1
- package/dist/payment.js +30 -12
- package/dist/payment.js.map +1 -1
- package/dist/stores/invoice-store.d.ts +15 -1
- package/dist/stores/invoice-store.d.ts.map +1 -1
- package/dist/stores/invoice-store.js.map +1 -1
- package/dist/wallet.d.ts +10 -1
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +72 -9
- package/dist/wallet.js.map +1 -1
- package/package.json +12 -11
- package/src/index.ts +10 -9
- package/src/indexer.ts +134 -128
- package/src/jobs.ts +63 -46
- package/src/payment.ts +100 -81
- package/src/stores/invoice-store.ts +62 -53
- package/src/wallet.ts +158 -93
- package/test/toolkit.test.ts +140 -109
package/src/indexer.ts
CHANGED
|
@@ -1,128 +1,134 @@
|
|
|
1
|
-
import { EventSubscriber, BackendPlugin } from '@hardkas/core';
|
|
2
|
-
import { ProjectionStoreJson } from '@hardkas/query-store';
|
|
3
|
-
import { ArtifactIndexStoreJson } from '@hardkas/artifacts';
|
|
4
|
-
import { DagApi } from './dag/index.js';
|
|
5
|
-
import { SnapshotParticipant } from './snapshot/types.js';
|
|
6
|
-
|
|
7
|
-
export interface IndexerBackendPlugin extends BackendPlugin {
|
|
8
|
-
balance?(address: string): Promise<bigint>;
|
|
9
|
-
history?(address: string): Promise<unknown[]>;
|
|
10
|
-
utxos?(address: string): Promise<unknown[]>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface IndexerToolkitOptions {
|
|
14
|
-
dataDir?: string;
|
|
15
|
-
backend?: IndexerBackendPlugin;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.artifactIndex
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (state.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
1
|
+
import { EventSubscriber, BackendPlugin } from '@hardkas/core';
|
|
2
|
+
import { ProjectionStoreJson } from '@hardkas/query-store';
|
|
3
|
+
import { ArtifactIndexStoreJson } from '@hardkas/artifacts';
|
|
4
|
+
import { DagApi } from './dag/index.js';
|
|
5
|
+
import { SnapshotParticipant } from './snapshot/types.js';
|
|
6
|
+
|
|
7
|
+
export interface IndexerBackendPlugin extends BackendPlugin {
|
|
8
|
+
balance?(address: string): Promise<bigint>;
|
|
9
|
+
history?(address: string): Promise<unknown[]>;
|
|
10
|
+
utxos?(address: string): Promise<unknown[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IndexerToolkitOptions {
|
|
14
|
+
dataDir?: string;
|
|
15
|
+
backend?: IndexerBackendPlugin;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type WatchHandler = (evt: any) => Promise<void> | void;
|
|
19
|
+
|
|
20
|
+
export class IndexerToolkit implements SnapshotParticipant {
|
|
21
|
+
private subscriber: EventSubscriber;
|
|
22
|
+
private projection?: ProjectionStoreJson;
|
|
23
|
+
private artifactIndex?: ArtifactIndexStoreJson;
|
|
24
|
+
public readonly dag: DagApi;
|
|
25
|
+
public readonly backend?: IndexerBackendPlugin | undefined;
|
|
26
|
+
|
|
27
|
+
private constructor(private readonly options: IndexerToolkitOptions) {
|
|
28
|
+
this.subscriber = new EventSubscriber();
|
|
29
|
+
this.backend = options.backend;
|
|
30
|
+
|
|
31
|
+
if (!this.backend || !this.backend.capabilities.externalState) {
|
|
32
|
+
this.projection = new ProjectionStoreJson({
|
|
33
|
+
dirPath: options.dataDir ? `${options.dataDir}/projections` : '.hardkas/indexer/projections',
|
|
34
|
+
namespace: 'indexer'
|
|
35
|
+
});
|
|
36
|
+
this.artifactIndex = new ArtifactIndexStoreJson({
|
|
37
|
+
filePath: options.dataDir ? `${options.dataDir}/artifacts.json` : '.hardkas/indexer/artifacts.json'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.dag = new DagApi();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static open(options: IndexerToolkitOptions = {}): IndexerToolkit {
|
|
45
|
+
return new IndexerToolkit(options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async connect(): Promise<void> {
|
|
49
|
+
if (this.backend?.connect) {
|
|
50
|
+
await this.backend.connect();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async watch(address: string | string[], handler?: WatchHandler): Promise<void> {
|
|
55
|
+
const addresses = Array.isArray(address) ? address : [address];
|
|
56
|
+
// Polling wrapper as per V1 rules.
|
|
57
|
+
this.subscriber.subscribe({
|
|
58
|
+
source: {}, // mock source for facade
|
|
59
|
+
type: "payment",
|
|
60
|
+
intervalMs: 1000,
|
|
61
|
+
watchedAddresses: addresses,
|
|
62
|
+
handler: async (evt) => {
|
|
63
|
+
// Internally updates projections if address is involved
|
|
64
|
+
if (handler) {
|
|
65
|
+
await handler(evt);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async balance(address: string): Promise<bigint> {
|
|
72
|
+
if (this.backend?.balance) {
|
|
73
|
+
return this.backend.balance(address);
|
|
74
|
+
}
|
|
75
|
+
const balances = this.projection?.get('balances') || {};
|
|
76
|
+
return BigInt(balances[address] || 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async history(address: string): Promise<any[]> {
|
|
80
|
+
if (this.backend?.history) {
|
|
81
|
+
return this.backend.history(address);
|
|
82
|
+
}
|
|
83
|
+
const history = this.projection?.get('history') || {};
|
|
84
|
+
return history[address] || [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async findReceipts(opts: { tags: string[] }): Promise<any[]> {
|
|
88
|
+
if (!this.artifactIndex) return [];
|
|
89
|
+
return this.artifactIndex.find(opts);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async ingestArtifact(artifact: any): Promise<void> {
|
|
93
|
+
if (!this.artifactIndex) return;
|
|
94
|
+
this.artifactIndex.index({
|
|
95
|
+
hash: artifact.id || 'mock_hash',
|
|
96
|
+
schema: artifact.schema || 'unknown.v1',
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
filePath: `/mock/path/${artifact.id}.json`,
|
|
99
|
+
tags: artifact.tags || []
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public async snapshot(): Promise<any> {
|
|
104
|
+
if (this.backend && !this.backend.capabilities.snapshots && this.backend.capabilities.externalState) {
|
|
105
|
+
return {
|
|
106
|
+
backend: this.backend.name,
|
|
107
|
+
externalState: true,
|
|
108
|
+
snapshots: false,
|
|
109
|
+
dag: await this.dag.snapshot()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
projection: this.projection?.getAll() || {},
|
|
114
|
+
artifactIndex: this.artifactIndex?.getAll() || [],
|
|
115
|
+
dag: await this.dag.snapshot()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async restore(state: any): Promise<void> {
|
|
120
|
+
if (state.externalState) {
|
|
121
|
+
// Cannot restore local state over an external backend. Just restore DAG.
|
|
122
|
+
if (state.dag) await this.dag.restore(state.dag);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (state.projection && this.projection) this.projection.setAll(state.projection);
|
|
126
|
+
if (state.artifactIndex && this.artifactIndex) this.artifactIndex.setAll(state.artifactIndex);
|
|
127
|
+
if (state.dag) await this.dag.restore(state.dag);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public async reload(): Promise<void> {
|
|
131
|
+
await this.dag.reload();
|
|
132
|
+
// Projection and artifactIndex read synchronously from memory in this version
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/jobs.ts
CHANGED
|
@@ -1,46 +1,63 @@
|
|
|
1
|
-
import { JobRunner, JobStoreJson, JobHandler, JobRecord } from '@hardkas/jobs';
|
|
2
|
-
import { SnapshotParticipant, SnapshotState } from './snapshot/types.js';
|
|
3
|
-
|
|
4
|
-
export interface JobsToolkitOptions {
|
|
5
|
-
storePath?: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
import { JobRunner, JobStoreJson, JobHandler, JobRecord, JobStore } from '@hardkas/jobs';
|
|
2
|
+
import { SnapshotParticipant, SnapshotState } from './snapshot/types.js';
|
|
3
|
+
|
|
4
|
+
export interface JobsToolkitOptions {
|
|
5
|
+
storePath?: string;
|
|
6
|
+
storage?: any; // e.g. SqliteStorage
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class JobsToolkit implements SnapshotParticipant {
|
|
10
|
+
private runner: JobRunner;
|
|
11
|
+
private store: JobStore;
|
|
12
|
+
|
|
13
|
+
private constructor(options: JobsToolkitOptions) {
|
|
14
|
+
if (options.storage) {
|
|
15
|
+
this.store = options.storage.createJobStore();
|
|
16
|
+
} else {
|
|
17
|
+
this.store = new JobStoreJson({ filePath: options.storePath || '.hardkas-data/jobs.json' });
|
|
18
|
+
}
|
|
19
|
+
this.runner = new JobRunner({ store: this.store });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static open(options: JobsToolkitOptions = {}): JobsToolkit {
|
|
23
|
+
return new JobsToolkit(options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public registerHandler(type: string, handler: JobHandler): void {
|
|
27
|
+
this.runner.registerHandler(type, handler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async enqueue(type: string, args?: any): Promise<string> {
|
|
31
|
+
return this.runner.enqueue(type, args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async resumePendingJobs(): Promise<void> {
|
|
35
|
+
return this.runner.resumePendingJobs();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async getJob(id: string): Promise<JobRecord | undefined> {
|
|
39
|
+
return this.runner.getJob(id);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async snapshot(): Promise<SnapshotState> {
|
|
43
|
+
return this.store.getAll();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public resetAll() {
|
|
47
|
+
// Not all stores support this, but if we really need it, we should add clear() to JobStore.
|
|
48
|
+
// For now, it's a test utility.
|
|
49
|
+
if ('setAll' in this.store) {
|
|
50
|
+
(this.store as any).setAll({});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async restore(state: SnapshotState): Promise<void> {
|
|
55
|
+
this.store.setAll(state as Record<string, JobRecord>);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async reload(): Promise<void> {
|
|
59
|
+
// Runner reads from store on each operation natively in current design,
|
|
60
|
+
// but if it had internal queues/caches, we'd clear them here.
|
|
61
|
+
// E.g., this.runner.clearCache();
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/payment.ts
CHANGED
|
@@ -1,81 +1,100 @@
|
|
|
1
|
-
import { buildKaspaUri } from '@hardkas/tx-builder';
|
|
2
|
-
import { EvidenceBatchExporter } from '@hardkas/artifacts';
|
|
3
|
-
import { InvoiceStoreJson, InvoiceRecord } from './stores/invoice-store.js';
|
|
4
|
-
|
|
5
|
-
export interface PaymentToolkitOptions {
|
|
6
|
-
storePath?: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public async
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
import { buildKaspaUri } from '@hardkas/tx-builder';
|
|
2
|
+
import { EvidenceBatchExporter } from '@hardkas/artifacts';
|
|
3
|
+
import { InvoiceStoreJson, InvoiceRecord, InvoiceStore } from './stores/invoice-store.js';
|
|
4
|
+
|
|
5
|
+
export interface PaymentToolkitOptions {
|
|
6
|
+
storePath?: string;
|
|
7
|
+
storage?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class PaymentToolkit {
|
|
11
|
+
private store: InvoiceStore;
|
|
12
|
+
|
|
13
|
+
private constructor(
|
|
14
|
+
public readonly merchantId: string,
|
|
15
|
+
private readonly options: PaymentToolkitOptions
|
|
16
|
+
) {
|
|
17
|
+
if (options.storage) {
|
|
18
|
+
this.store = options.storage.createInvoiceStore();
|
|
19
|
+
} else {
|
|
20
|
+
this.store = new InvoiceStoreJson({ filePath: options.storePath || '.hardkas-data/invoices.json' });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static openMerchant(merchantId: string, options: PaymentToolkitOptions = {}): PaymentToolkit {
|
|
25
|
+
return new PaymentToolkit(merchantId, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async createInvoice(opts: { amount?: bigint; amountSompi?: bigint; currency?: string }): Promise<InvoiceRecord> {
|
|
29
|
+
if (opts.amount === undefined && opts.amountSompi === undefined) {
|
|
30
|
+
throw new Error("Must provide either amount or amountSompi");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sompi = opts.amountSompi ?? (opts.amount! * 100_000_000n);
|
|
34
|
+
const kas = opts.amount ?? (opts.amountSompi! / 100_000_000n);
|
|
35
|
+
const curr = opts.currency ?? 'KAS';
|
|
36
|
+
|
|
37
|
+
const id = `inv_${Date.now()}`;
|
|
38
|
+
const result = buildKaspaUri({
|
|
39
|
+
address: `kaspa:${this.merchantId}`,
|
|
40
|
+
amountSompi: sompi,
|
|
41
|
+
message: `Invoice ${id}`
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const invoice: InvoiceRecord = {
|
|
45
|
+
id,
|
|
46
|
+
merchantId: this.merchantId,
|
|
47
|
+
amount: kas,
|
|
48
|
+
currency: curr,
|
|
49
|
+
status: 'pending',
|
|
50
|
+
uri: result.uri,
|
|
51
|
+
createdAt: new Date().toISOString(),
|
|
52
|
+
updatedAt: new Date().toISOString()
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await this.store.save(invoice);
|
|
56
|
+
return invoice;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async getInvoice(id: string): Promise<InvoiceRecord | undefined> {
|
|
60
|
+
return await this.store.get(id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async listInvoices(): Promise<InvoiceRecord[]> {
|
|
64
|
+
return await this.store.listByMerchant(this.merchantId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public async stats(): Promise<{ totalInvoices: number; paidInvoices: number }> {
|
|
68
|
+
return await this.store.stats(this.merchantId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async check(invoiceId: string): Promise<string> {
|
|
72
|
+
const inv = await this.store.get(invoiceId);
|
|
73
|
+
return inv ? inv.status : 'not_found';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async receipt(invoiceId: string): Promise<any> {
|
|
77
|
+
const inv = await this.store.get(invoiceId);
|
|
78
|
+
if (!inv) throw new Error("Invoice not found");
|
|
79
|
+
if (inv.status !== "paid") throw new Error("Invoice is not paid");
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
schema: "paymentReceipt.v1",
|
|
83
|
+
invoiceId,
|
|
84
|
+
merchantId: this.merchantId,
|
|
85
|
+
status: "paid",
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
amount: inv.amount,
|
|
88
|
+
currency: inv.currency
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// For lab testing/simulation manually paying an invoice
|
|
93
|
+
public async simulatePay(invoiceId: string): Promise<void> {
|
|
94
|
+
const inv = await this.store.get(invoiceId);
|
|
95
|
+
if (inv) {
|
|
96
|
+
inv.status = 'paid';
|
|
97
|
+
await this.store.save(inv);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|