@gr4vy/sdk 2.1.2 → 2.1.3
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/README.md +8 -2
- package/jsr.json +1 -1
- package/lib/config.d.ts +2 -2
- package/lib/config.js +2 -2
- package/package.json +4 -2
- package/scripts/endpoint-coverage.mjs +136 -0
- package/src/lib/config.ts +2 -2
- package/tests/backoffice/account-updater.test.ts +24 -0
- package/tests/backoffice/audit-logs.test.ts +20 -0
- package/tests/backoffice/merchant-accounts.test.ts +77 -0
- package/tests/backoffice/payment-services.test.ts +95 -0
- package/tests/backoffice/payouts.test.ts +49 -0
- package/tests/backoffice/reports.test.ts +69 -0
- package/tests/backoffice/three-ds-scenarios.test.ts +34 -0
- package/tests/flows/buyer-lifecycle.test.ts +74 -0
- package/tests/flows/transaction-lifecycle.test.ts +151 -0
- package/tests/processing/buyers.test.ts +119 -0
- package/tests/processing/checkout-sessions.test.ts +99 -0
- package/tests/processing/digital-wallets.test.ts +154 -0
- package/tests/processing/gift-cards.test.ts +45 -0
- package/tests/processing/payment-links.test.ts +34 -0
- package/tests/processing/payment-methods.test.ts +121 -0
- package/tests/processing/transactions.test.ts +118 -0
- package/tests/utils/arbitraries.ts +87 -0
- package/tests/utils/fields.ts +65 -0
- package/tests/utils/fixtures.ts +84 -0
- package/tests/utils/poll.ts +40 -0
- package/tests/utils/setup.ts +66 -10
- package/tests/utils/transactions.ts +26 -0
- package/vitest.config.ts +31 -0
- package/tests/checkout-sessions.test.ts +0 -109
package/README.md
CHANGED
|
@@ -1090,13 +1090,19 @@ You can also enable a default debug logger by setting an environment variable `G
|
|
|
1090
1090
|
|
|
1091
1091
|
## Testing
|
|
1092
1092
|
|
|
1093
|
-
|
|
1093
|
+
The SDK has an end-to-end test suite that runs against the E2E sandbox. Place
|
|
1094
|
+
the `private_key.pem` for the test environment in the repo root (or export
|
|
1095
|
+
`PRIVATE_KEY`), then:
|
|
1094
1096
|
|
|
1095
1097
|
```sh
|
|
1096
1098
|
npm install
|
|
1097
|
-
npx vitest
|
|
1099
|
+
npx vitest run
|
|
1098
1100
|
```
|
|
1099
1101
|
|
|
1102
|
+
See **[TESTING.md](TESTING.md)** for the full guide — suite layout, parallelism
|
|
1103
|
+
and CI sharding, property-based tests, coverage / endpoint-reach reporting, and
|
|
1104
|
+
how testing ties into the release pipeline.
|
|
1105
|
+
|
|
1100
1106
|
## Contributions
|
|
1101
1107
|
|
|
1102
1108
|
While we value open-source contributions to this SDK, this library is generated programmatically.
|
package/jsr.json
CHANGED
package/lib/config.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export declare function serverURLFromOptions(options: SDKOptions): URL | null;
|
|
|
44
44
|
export declare const SDK_METADATA: {
|
|
45
45
|
readonly language: "typescript";
|
|
46
46
|
readonly openapiDocVersion: "1.0.0";
|
|
47
|
-
readonly sdkVersion: "2.1.
|
|
47
|
+
readonly sdkVersion: "2.1.3";
|
|
48
48
|
readonly genVersion: "2.889.1";
|
|
49
|
-
readonly userAgent: "speakeasy-sdk/typescript 2.1.
|
|
49
|
+
readonly userAgent: "speakeasy-sdk/typescript 2.1.3 2.889.1 1.0.0 @gr4vy/sdk";
|
|
50
50
|
};
|
|
51
51
|
//# sourceMappingURL=config.d.ts.map
|
package/lib/config.js
CHANGED
|
@@ -37,8 +37,8 @@ function serverURLFromOptions(options) {
|
|
|
37
37
|
exports.SDK_METADATA = {
|
|
38
38
|
language: "typescript",
|
|
39
39
|
openapiDocVersion: "1.0.0",
|
|
40
|
-
sdkVersion: "2.1.
|
|
40
|
+
sdkVersion: "2.1.3",
|
|
41
41
|
genVersion: "2.889.1",
|
|
42
|
-
userAgent: "speakeasy-sdk/typescript 2.1.
|
|
42
|
+
userAgent: "speakeasy-sdk/typescript 2.1.3 2.889.1 1.0.0 @gr4vy/sdk",
|
|
43
43
|
};
|
|
44
44
|
//# sourceMappingURL=config.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gr4vy/sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"author": "Gr4vy",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@eslint/js": "^9.26.0",
|
|
21
21
|
"@types/jsonwebtoken": "^9.0.10",
|
|
22
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
22
23
|
"eslint": "^9.26.0",
|
|
24
|
+
"fast-check": "^4.8.0",
|
|
23
25
|
"globals": "^15.14.0",
|
|
24
26
|
"timekeeper": "^2.3.1",
|
|
25
27
|
"typescript": "~5.8.3",
|
|
26
28
|
"typescript-eslint": "^8.26.0",
|
|
27
|
-
"vitest": "^4.1.
|
|
29
|
+
"vitest": "^4.1.7"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"jsonwebtoken": "^9.0.3",
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Summarises test coverage for the PR comment.
|
|
2
|
+
//
|
|
3
|
+
// Endpoint reach is computed from *observed HTTP calls*, not statement coverage:
|
|
4
|
+
// the test fetcher (tests/utils/setup.ts, gated on GR4VY_TRACK_HTTP) logs the
|
|
5
|
+
// method + path of every request it sends to coverage/http/*.jsonl. We map each
|
|
6
|
+
// `src/funcs/*.ts` operation to its (method, path-template) and mark it reached
|
|
7
|
+
// only if a matching request was actually sent — so a function that returns at
|
|
8
|
+
// local validation before issuing a request does not count.
|
|
9
|
+
//
|
|
10
|
+
// Code-coverage percentages (for the instrumented SDK surface) are read from the
|
|
11
|
+
// vitest v8 coverage-summary.json as a secondary signal.
|
|
12
|
+
//
|
|
13
|
+
// Writes coverage/endpoint-coverage.md (for the PR comment) and prints it.
|
|
14
|
+
// Exits 0 always — this is a report, not a gate.
|
|
15
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
18
|
+
const COVERAGE_DIR = "coverage";
|
|
19
|
+
const FUNCS_DIR = "src/funcs";
|
|
20
|
+
|
|
21
|
+
// 1. Build the operation catalogue from the generated funcs.
|
|
22
|
+
const opCatalogue = readdirSync(FUNCS_DIR)
|
|
23
|
+
.filter((f) => f.endsWith(".ts"))
|
|
24
|
+
.map((file) => {
|
|
25
|
+
const src = readFileSync(join(FUNCS_DIR, file), "utf8");
|
|
26
|
+
const pathMatch = src.match(/pathToFunc\(\s*"([^"]+)"/);
|
|
27
|
+
const methodMatch = src.match(/method:\s*"(GET|POST|PUT|PATCH|DELETE)"/);
|
|
28
|
+
if (!pathMatch || !methodMatch) return null;
|
|
29
|
+
const template = pathMatch[1];
|
|
30
|
+
const regex = new RegExp(
|
|
31
|
+
"^" + template.replace(/\{[^/}]+\}/g, "[^/]+").replace(/\//g, "\\/") + "$"
|
|
32
|
+
);
|
|
33
|
+
const params = (template.match(/\{[^/}]+\}/g) ?? []).length;
|
|
34
|
+
return { op: file.replace(/\.ts$/, ""), method: methodMatch[1], template, regex, params };
|
|
35
|
+
})
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
|
|
38
|
+
// 2. Load observed HTTP calls (per-worker files).
|
|
39
|
+
let calls = [];
|
|
40
|
+
try {
|
|
41
|
+
const dir = join(COVERAGE_DIR, "http");
|
|
42
|
+
for (const f of readdirSync(dir).filter((f) => f.endsWith(".jsonl"))) {
|
|
43
|
+
for (const line of readFileSync(join(dir, f), "utf8").split("\n")) {
|
|
44
|
+
if (!line.trim()) continue;
|
|
45
|
+
// Skip malformed/truncated lines (e.g. a worker that crashed mid-write) —
|
|
46
|
+
// this is best-effort instrumentation and must never fail the report.
|
|
47
|
+
try {
|
|
48
|
+
calls.push(JSON.parse(line));
|
|
49
|
+
} catch {
|
|
50
|
+
// ignore this line
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// no logs — fetcher tracking was not enabled for this run
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const httpTracked = calls.length > 0;
|
|
59
|
+
|
|
60
|
+
// 3. Match calls to operations.
|
|
61
|
+
const reached = new Set();
|
|
62
|
+
if (httpTracked) {
|
|
63
|
+
for (const { method, pathname } of calls) {
|
|
64
|
+
// A literal path like /buyers/gift-cards also matches a parameterised
|
|
65
|
+
// template like /buyers/{buyer_id}, so credit the most specific operation
|
|
66
|
+
// (fewest path params, then longest template).
|
|
67
|
+
const op = opCatalogue
|
|
68
|
+
.filter((o) => o.method === method && o.regex.test(pathname))
|
|
69
|
+
.sort((a, b) => a.params - b.params || b.template.length - a.template.length)[0];
|
|
70
|
+
if (op) reached.add(op.op);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const missed = opCatalogue
|
|
75
|
+
.map((o) => o.op)
|
|
76
|
+
.filter((op) => !reached.has(op))
|
|
77
|
+
.sort();
|
|
78
|
+
|
|
79
|
+
// 4. Code-coverage percentages (secondary signal).
|
|
80
|
+
let total = {};
|
|
81
|
+
try {
|
|
82
|
+
total =
|
|
83
|
+
JSON.parse(readFileSync(join(COVERAGE_DIR, "coverage-summary.json"), "utf8"))
|
|
84
|
+
.total ?? {};
|
|
85
|
+
} catch {
|
|
86
|
+
// coverage summary missing — leave percentages blank
|
|
87
|
+
}
|
|
88
|
+
const pct = (m) => (total[m]?.pct != null ? `${total[m].pct.toFixed(1)}%` : "n/a");
|
|
89
|
+
|
|
90
|
+
// 5. Render.
|
|
91
|
+
const lines = [];
|
|
92
|
+
lines.push("### 🧪 Test coverage");
|
|
93
|
+
lines.push("");
|
|
94
|
+
if (!httpTracked) {
|
|
95
|
+
lines.push(
|
|
96
|
+
"> ⚠️ HTTP call tracking was not enabled (set `GR4VY_TRACK_HTTP=1` for the " +
|
|
97
|
+
"coverage run), so endpoint reach could not be computed from observed requests."
|
|
98
|
+
);
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
const reachPct = opCatalogue.length
|
|
102
|
+
? ((reached.size / opCatalogue.length) * 100).toFixed(1)
|
|
103
|
+
: "0.0";
|
|
104
|
+
lines.push("| Metric | Value |");
|
|
105
|
+
lines.push("| --- | --- |");
|
|
106
|
+
lines.push(
|
|
107
|
+
`| **Endpoints reached (HTTP)** | ${reached.size} / ${opCatalogue.length} (${reachPct}%) |`
|
|
108
|
+
);
|
|
109
|
+
lines.push(`| Statements | ${pct("statements")} |`);
|
|
110
|
+
lines.push(`| Branches | ${pct("branches")} |`);
|
|
111
|
+
lines.push(`| Functions | ${pct("functions")} |`);
|
|
112
|
+
lines.push(`| Lines | ${pct("lines")} |`);
|
|
113
|
+
lines.push("");
|
|
114
|
+
if (httpTracked && missed.length) {
|
|
115
|
+
lines.push(
|
|
116
|
+
`> ⚠️ **${missed.length} endpoint operation(s) have no E2E test.** ` +
|
|
117
|
+
`Newly generated endpoints show up here — consider adding tests for them.`
|
|
118
|
+
);
|
|
119
|
+
lines.push("");
|
|
120
|
+
for (const name of missed) lines.push(`- \`${name}\``);
|
|
121
|
+
} else if (httpTracked) {
|
|
122
|
+
lines.push("✅ Every endpoint operation was reached by a real request.");
|
|
123
|
+
}
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push(
|
|
126
|
+
"<sub>Endpoint reach is measured from HTTP requests actually sent by the " +
|
|
127
|
+
"suite (see tests/utils/setup.ts). Code coverage is for `src/funcs` + " +
|
|
128
|
+
"`src/sdk`. See [TESTING.md](../TESTING.md).</sub>"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const markdown = lines.join("\n");
|
|
132
|
+
// Ensure the output dir exists — the script may run even when --coverage wasn't,
|
|
133
|
+
// and it must never fail the job just because of a missing directory.
|
|
134
|
+
mkdirSync(COVERAGE_DIR, { recursive: true });
|
|
135
|
+
writeFileSync(join(COVERAGE_DIR, "endpoint-coverage.md"), markdown + "\n");
|
|
136
|
+
console.log(markdown);
|
package/src/lib/config.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function serverURLFromOptions(options: SDKOptions): URL | null {
|
|
|
77
77
|
export const SDK_METADATA = {
|
|
78
78
|
language: "typescript",
|
|
79
79
|
openapiDocVersion: "1.0.0",
|
|
80
|
-
sdkVersion: "2.1.
|
|
80
|
+
sdkVersion: "2.1.3",
|
|
81
81
|
genVersion: "2.889.1",
|
|
82
|
-
userAgent: "speakeasy-sdk/typescript 2.1.
|
|
82
|
+
userAgent: "speakeasy-sdk/typescript 2.1.3 2.889.1 1.0.0 @gr4vy/sdk",
|
|
83
83
|
} as const;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { cardPaymentMethod } from "../utils/fixtures";
|
|
4
|
+
import { setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let gr4vy: Gr4vy;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ client: gr4vy } = await setupMerchant());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("Account Updater", () => {
|
|
13
|
+
// The Real-Time Account Updater (Loon) is not configured on the mock merchant,
|
|
14
|
+
// so creating a job is exercised at the request level: a real stored payment
|
|
15
|
+
// method id is submitted and the API is expected to reject the job because the
|
|
16
|
+
// service is not enabled, rather than the test skipping the endpoint.
|
|
17
|
+
test("creating a job is exercised at the request level", async () => {
|
|
18
|
+
const method = await gr4vy.paymentMethods.create(cardPaymentMethod());
|
|
19
|
+
|
|
20
|
+
await expect(
|
|
21
|
+
gr4vy.accountUpdater.jobs.create({ paymentMethodIds: [method.id] })
|
|
22
|
+
).rejects.toThrow();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { buyer } from "../utils/fixtures";
|
|
4
|
+
import { setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let gr4vy: Gr4vy;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ client: gr4vy } = await setupMerchant());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("Audit Logs", () => {
|
|
13
|
+
test("listing audit logs returns a page", async () => {
|
|
14
|
+
// Perform a mutating action so the account has at least one audit entry.
|
|
15
|
+
await gr4vy.buyers.create(buyer());
|
|
16
|
+
|
|
17
|
+
const page = await gr4vy.auditLogs.list({});
|
|
18
|
+
expect(page).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
3
|
+
import { Gr4vy } from "../../src";
|
|
4
|
+
import { createGr4vyClient, setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let admin: Gr4vy;
|
|
7
|
+
let privateKey: string;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
// merchant-accounts are an admin-level resource, so use an un-scoped client.
|
|
11
|
+
({ privateKey } = await setupMerchant());
|
|
12
|
+
admin = createGr4vyClient(privateKey);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const newMerchantId = () => crypto.randomBytes(8).toString("hex");
|
|
16
|
+
|
|
17
|
+
describe("Merchant Accounts", () => {
|
|
18
|
+
test("create → get → list → update (partial)", async () => {
|
|
19
|
+
const id = newMerchantId();
|
|
20
|
+
const created = await admin.merchantAccounts.create({
|
|
21
|
+
id,
|
|
22
|
+
displayName: "Original Display",
|
|
23
|
+
});
|
|
24
|
+
expect(created.id).toBe(id);
|
|
25
|
+
|
|
26
|
+
const fetched = await admin.merchantAccounts.get(id);
|
|
27
|
+
expect(fetched.id).toBe(id);
|
|
28
|
+
|
|
29
|
+
const page = await admin.merchantAccounts.list();
|
|
30
|
+
expect(page).toBeDefined();
|
|
31
|
+
|
|
32
|
+
// Partial update: change only the display name, leave everything else.
|
|
33
|
+
const updated = await admin.merchantAccounts.update(
|
|
34
|
+
{ displayName: "Renamed Display" },
|
|
35
|
+
id
|
|
36
|
+
);
|
|
37
|
+
expect(updated.displayName).toBe("Renamed Display");
|
|
38
|
+
expect(updated.id).toBe(id);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("3DS configuration", () => {
|
|
42
|
+
// 3DS configuration is keyed per scheme/currency on the merchant account.
|
|
43
|
+
test("create → list → update (partial) → delete", async () => {
|
|
44
|
+
const id = newMerchantId();
|
|
45
|
+
await admin.merchantAccounts.create({ id, displayName: id });
|
|
46
|
+
|
|
47
|
+
const created =
|
|
48
|
+
await admin.merchantAccounts.threeDsConfiguration.create(
|
|
49
|
+
{
|
|
50
|
+
merchantAcquirerBin: "516327",
|
|
51
|
+
merchantAcquirerId: "123456789012345",
|
|
52
|
+
merchantName: "Gr4vy E2E",
|
|
53
|
+
merchantCountryCode: "840",
|
|
54
|
+
merchantCategoryCode: "1234",
|
|
55
|
+
merchantUrl: "https://example.com",
|
|
56
|
+
scheme: "visa",
|
|
57
|
+
metadata: { env: "e2e" },
|
|
58
|
+
},
|
|
59
|
+
id
|
|
60
|
+
);
|
|
61
|
+
expect(created.id).toBeDefined();
|
|
62
|
+
|
|
63
|
+
const list = await admin.merchantAccounts.threeDsConfiguration.list(id);
|
|
64
|
+
expect(list).toBeDefined();
|
|
65
|
+
|
|
66
|
+
const updated =
|
|
67
|
+
await admin.merchantAccounts.threeDsConfiguration.update(
|
|
68
|
+
{ merchantName: "Renamed Merchant" },
|
|
69
|
+
id,
|
|
70
|
+
created.id!
|
|
71
|
+
);
|
|
72
|
+
expect(updated.merchantName).toBe("Renamed Merchant");
|
|
73
|
+
|
|
74
|
+
await admin.merchantAccounts.threeDsConfiguration.delete(id, created.id!);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { PaymentServiceCreate } from "../../src/models/components";
|
|
4
|
+
import { setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let gr4vy: Gr4vy;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ client: gr4vy } = await setupMerchant());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const mockCardService = (
|
|
13
|
+
overrides: Partial<PaymentServiceCreate> = {}
|
|
14
|
+
): PaymentServiceCreate => ({
|
|
15
|
+
displayName: "Mock card service",
|
|
16
|
+
paymentServiceDefinitionId: "mock-card",
|
|
17
|
+
acceptedCurrencies: ["USD"],
|
|
18
|
+
acceptedCountries: ["US"],
|
|
19
|
+
fields: [{ key: "merchant_id", value: "test" }],
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("Payment Services", () => {
|
|
24
|
+
test("create → get → list → update (partial) → delete", async () => {
|
|
25
|
+
const created = await gr4vy.paymentServices.create(mockCardService());
|
|
26
|
+
expect(created.id).toBeDefined();
|
|
27
|
+
|
|
28
|
+
const fetched = await gr4vy.paymentServices.get(created.id!);
|
|
29
|
+
expect(fetched.id).toBe(created.id);
|
|
30
|
+
|
|
31
|
+
const page = await gr4vy.paymentServices.list({});
|
|
32
|
+
expect(page).toBeDefined();
|
|
33
|
+
|
|
34
|
+
// Partial update: change only the display name.
|
|
35
|
+
const updated = await gr4vy.paymentServices.update(
|
|
36
|
+
{ displayName: "Renamed service" },
|
|
37
|
+
created.id!
|
|
38
|
+
);
|
|
39
|
+
expect(updated.displayName).toBe("Renamed service");
|
|
40
|
+
|
|
41
|
+
await gr4vy.paymentServices.delete(created.id!);
|
|
42
|
+
await expect(() => gr4vy.paymentServices.get(created.id!)).rejects.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Credential verification and session creation are not meaningfully supported
|
|
46
|
+
// by the mock-card connector (verify returns a non-JSON body; session is
|
|
47
|
+
// explicitly unsupported), so both are exercised at the request level.
|
|
48
|
+
test("verify is exercised at the request level", async () => {
|
|
49
|
+
await expect(
|
|
50
|
+
gr4vy.paymentServices.verify({
|
|
51
|
+
paymentServiceDefinitionId: "mock-card",
|
|
52
|
+
fields: [{ key: "merchant_id", value: "test" }],
|
|
53
|
+
})
|
|
54
|
+
).rejects.toThrow();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("create a session is exercised at the request level", async () => {
|
|
58
|
+
const created = await gr4vy.paymentServices.create(mockCardService());
|
|
59
|
+
await expect(
|
|
60
|
+
gr4vy.paymentServices.session({}, created.id!)
|
|
61
|
+
).rejects.toThrow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("definitions, options and card schemes", () => {
|
|
65
|
+
test("list and get payment service definitions", async () => {
|
|
66
|
+
const page = await gr4vy.paymentServiceDefinitions.list();
|
|
67
|
+
expect(page).toBeDefined();
|
|
68
|
+
|
|
69
|
+
const definition =
|
|
70
|
+
await gr4vy.paymentServiceDefinitions.get("mock-card");
|
|
71
|
+
expect(definition.id).toBe("mock-card");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("create a session for a definition is exercised at the request level", async () => {
|
|
75
|
+
// Session creation is not supported for the mock-card definition.
|
|
76
|
+
await expect(
|
|
77
|
+
gr4vy.paymentServiceDefinitions.session({}, "mock-card")
|
|
78
|
+
).rejects.toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("list payment options for a cart", async () => {
|
|
82
|
+
const options = await gr4vy.paymentOptions.list({
|
|
83
|
+
country: "US",
|
|
84
|
+
currency: "USD",
|
|
85
|
+
amount: 1299,
|
|
86
|
+
});
|
|
87
|
+
expect(options).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("list card scheme definitions", async () => {
|
|
91
|
+
const schemes = await gr4vy.cardSchemeDefinitions.list();
|
|
92
|
+
expect(schemes).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { APPROVING_CARD } from "../utils/fixtures";
|
|
4
|
+
import { setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let gr4vy: Gr4vy;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ client: gr4vy } = await setupMerchant());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("Payouts", () => {
|
|
13
|
+
test("listing payouts returns a page", async () => {
|
|
14
|
+
const page = await gr4vy.payouts.list();
|
|
15
|
+
expect(page).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// The mock-card service is not a payout-capable connection, so creating a
|
|
19
|
+
// payout is exercised at the request level — the SDK serialises the payload
|
|
20
|
+
// and the API rejects it for a real reason rather than the test skipping it.
|
|
21
|
+
test("create is exercised at the request level", async () => {
|
|
22
|
+
const service = await gr4vy.paymentServices.create({
|
|
23
|
+
displayName: "Mock card service",
|
|
24
|
+
paymentServiceDefinitionId: "mock-card",
|
|
25
|
+
acceptedCurrencies: ["USD"],
|
|
26
|
+
acceptedCountries: ["US"],
|
|
27
|
+
fields: [{ key: "merchant_id", value: "test" }],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await expect(
|
|
31
|
+
gr4vy.payouts.create({
|
|
32
|
+
amount: 1299,
|
|
33
|
+
currency: "USD",
|
|
34
|
+
paymentServiceId: service.id,
|
|
35
|
+
paymentMethod: {
|
|
36
|
+
method: "card",
|
|
37
|
+
number: APPROVING_CARD.number,
|
|
38
|
+
expirationDate: APPROVING_CARD.expiration_date,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
).rejects.toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("fetching a missing payout is exercised at the request level", async () => {
|
|
45
|
+
await expect(
|
|
46
|
+
gr4vy.payouts.get("00000000-0000-0000-0000-000000000000")
|
|
47
|
+
).rejects.toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { ReportCreate } from "../../src/models/components";
|
|
4
|
+
import { uniqueId } from "../utils/fixtures";
|
|
5
|
+
import { setupMerchant } from "../utils/setup";
|
|
6
|
+
|
|
7
|
+
let gr4vy: Gr4vy;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
({ client: gr4vy } = await setupMerchant());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const reportCreate = (): ReportCreate => ({
|
|
14
|
+
name: uniqueId("report"),
|
|
15
|
+
schedule: "daily",
|
|
16
|
+
scheduleEnabled: true,
|
|
17
|
+
scheduleTimezone: "UTC",
|
|
18
|
+
spec: {
|
|
19
|
+
model: "transactions",
|
|
20
|
+
params: {
|
|
21
|
+
fields: ["id", "status"],
|
|
22
|
+
filters: { status: ["capture_succeeded"] },
|
|
23
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("Reports", () => {
|
|
29
|
+
test("create → get → put (partial) → list", async () => {
|
|
30
|
+
const created = await gr4vy.reports.create(reportCreate());
|
|
31
|
+
expect(created.id).toBeDefined();
|
|
32
|
+
|
|
33
|
+
const fetched = await gr4vy.reports.get(created.id);
|
|
34
|
+
expect(fetched.id).toBe(created.id);
|
|
35
|
+
|
|
36
|
+
// Partial update: toggle the schedule without resending name/spec.
|
|
37
|
+
const updated = await gr4vy.reports.put(
|
|
38
|
+
{ scheduleEnabled: false },
|
|
39
|
+
created.id
|
|
40
|
+
);
|
|
41
|
+
expect(updated.scheduleEnabled).toBe(false);
|
|
42
|
+
|
|
43
|
+
const page = await gr4vy.reports.list({});
|
|
44
|
+
expect(page).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("report executions are listable per-report and account-wide", async () => {
|
|
48
|
+
const created = await gr4vy.reports.create(reportCreate());
|
|
49
|
+
|
|
50
|
+
const perReport = await gr4vy.reports.executions.list(created.id);
|
|
51
|
+
expect(perReport).toBeDefined();
|
|
52
|
+
|
|
53
|
+
const accountWide = await gr4vy.reportExecutions.list({});
|
|
54
|
+
expect(accountWide).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// A freshly created report has no executions yet, so fetching one / generating
|
|
58
|
+
// a download URL is exercised at the request level against a non-existent
|
|
59
|
+
// execution id; the API is expected to reject it.
|
|
60
|
+
test("fetching a missing execution / url is exercised at the request level", async () => {
|
|
61
|
+
const created = await gr4vy.reports.create(reportCreate());
|
|
62
|
+
const bogus = "00000000-0000-0000-0000-000000000000";
|
|
63
|
+
|
|
64
|
+
await expect(gr4vy.reports.executions.get(bogus)).rejects.toThrow();
|
|
65
|
+
await expect(
|
|
66
|
+
gr4vy.reports.executions.url(created.id, bogus)
|
|
67
|
+
).rejects.toThrow();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import { ThreeDSecureScenarioCreate } from "../../src/models/components";
|
|
4
|
+
import { setupMerchant } from "../utils/setup";
|
|
5
|
+
|
|
6
|
+
let gr4vy: Gr4vy;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
({ client: gr4vy } = await setupMerchant());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const scenario = (): ThreeDSecureScenarioCreate => ({
|
|
13
|
+
conditions: { amount: 5000 },
|
|
14
|
+
outcome: { authentication: { transactionStatus: "Y" } },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("3DS Scenarios", () => {
|
|
18
|
+
test("create → list → update (partial) → delete", async () => {
|
|
19
|
+
const created = await gr4vy.threeDsScenarios.create(scenario());
|
|
20
|
+
expect(created.id).toBeDefined();
|
|
21
|
+
|
|
22
|
+
const page = await gr4vy.threeDsScenarios.list();
|
|
23
|
+
expect(page).toBeDefined();
|
|
24
|
+
|
|
25
|
+
// Partial update: replace only the outcome, leaving the conditions intact.
|
|
26
|
+
const updated = await gr4vy.threeDsScenarios.update(
|
|
27
|
+
{ outcome: { authentication: { transactionStatus: "N" } } },
|
|
28
|
+
created.id
|
|
29
|
+
);
|
|
30
|
+
expect(updated.id).toBe(created.id);
|
|
31
|
+
|
|
32
|
+
await gr4vy.threeDsScenarios.delete(created.id);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { Gr4vy } from "../../src";
|
|
3
|
+
import {
|
|
4
|
+
address,
|
|
5
|
+
billingDetails,
|
|
6
|
+
buyer,
|
|
7
|
+
cardPaymentMethod,
|
|
8
|
+
uniqueId,
|
|
9
|
+
} from "../utils/fixtures";
|
|
10
|
+
import { setupMerchant } from "../utils/setup";
|
|
11
|
+
|
|
12
|
+
let gr4vy: Gr4vy;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
({ client: gr4vy } = await setupMerchant());
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("Buyer lifecycle", () => {
|
|
19
|
+
test("create buyer, store a card against them, transact, and read it back", async () => {
|
|
20
|
+
// 1. Create a buyer.
|
|
21
|
+
const created = await gr4vy.buyers.create(buyer({ displayName: "Ada Lovelace" }));
|
|
22
|
+
expect(created.id).toBeDefined();
|
|
23
|
+
expect(created.displayName).toBe("Ada Lovelace");
|
|
24
|
+
|
|
25
|
+
// 2. Store a card payment method against the buyer.
|
|
26
|
+
const method = await gr4vy.paymentMethods.create({
|
|
27
|
+
...cardPaymentMethod(),
|
|
28
|
+
buyerId: created.id,
|
|
29
|
+
});
|
|
30
|
+
expect(method.id).toBeDefined();
|
|
31
|
+
|
|
32
|
+
// 3. The stored method shows up under the buyer's payment methods.
|
|
33
|
+
const buyerMethods = await gr4vy.buyers.paymentMethods.list({
|
|
34
|
+
buyerId: created.id,
|
|
35
|
+
});
|
|
36
|
+
expect(buyerMethods.items.some((m) => m.id === method.id)).toBe(true);
|
|
37
|
+
|
|
38
|
+
// 4. Add shipping details for the buyer.
|
|
39
|
+
const shipping = await gr4vy.buyers.shippingDetails.create(
|
|
40
|
+
{
|
|
41
|
+
firstName: "Ada",
|
|
42
|
+
lastName: "Lovelace",
|
|
43
|
+
address: address(),
|
|
44
|
+
},
|
|
45
|
+
created.id!
|
|
46
|
+
);
|
|
47
|
+
expect(shipping.id).toBeDefined();
|
|
48
|
+
|
|
49
|
+
// 5. Charge the stored method, referencing the buyer + shipping details.
|
|
50
|
+
const transaction = await gr4vy.transactions.create({
|
|
51
|
+
amount: 1299,
|
|
52
|
+
currency: "USD",
|
|
53
|
+
buyerId: created.id,
|
|
54
|
+
shippingDetailsId: shipping.id,
|
|
55
|
+
externalIdentifier: uniqueId("txn"),
|
|
56
|
+
paymentMethod: { method: "id", id: method.id, securityCode: "123" },
|
|
57
|
+
});
|
|
58
|
+
expect(transaction.status).toBe("authorization_succeeded");
|
|
59
|
+
|
|
60
|
+
// 6. Gift cards list for the buyer is reachable (empty is fine).
|
|
61
|
+
const giftCards = await gr4vy.buyers.giftCards.list({ buyerId: created.id });
|
|
62
|
+
expect(Array.isArray(giftCards.items)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("buyer can be created with billing details and deleted", async () => {
|
|
66
|
+
const created = await gr4vy.buyers.create(
|
|
67
|
+
buyer({ billingDetails: billingDetails({ firstName: "Grace" }) })
|
|
68
|
+
);
|
|
69
|
+
expect(created.billingDetails?.firstName).toBe("Grace");
|
|
70
|
+
|
|
71
|
+
await gr4vy.buyers.delete(created.id!);
|
|
72
|
+
await expect(() => gr4vy.buyers.get(created.id!)).rejects.toThrow();
|
|
73
|
+
});
|
|
74
|
+
});
|