paybridge 0.9.0 → 0.11.0
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 +80 -0
- package/dist/cli/commands/drift-watch.d.ts +1 -0
- package/dist/cli/commands/drift-watch.js +118 -0
- package/dist/cli/commands/drift.d.ts +20 -0
- package/dist/cli/commands/drift.js +212 -0
- package/dist/cli/commands/reconcile.d.ts +1 -0
- package/dist/cli/commands/reconcile.js +341 -0
- package/dist/cli/drift-store.d.ts +14 -0
- package/dist/cli/drift-store.js +80 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/reconcile-types.d.ts +22 -0
- package/dist/cli/reconcile-types.js +2 -0
- package/dist/cli/reconcile.d.ts +12 -0
- package/dist/cli/reconcile.js +102 -0
- package/dist/cli/runners.js +49 -11
- package/dist/cli/utils.js +17 -0
- package/dist/drift-detector.d.ts +40 -0
- package/dist/drift-detector.js +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/providers/square.js +1 -1
- package/package.json +6 -3
package/dist/cli/utils.js
CHANGED
|
@@ -70,9 +70,21 @@ COMMANDS
|
|
|
70
70
|
webhook verify <p> Verify webhook signature (raw body from stdin)
|
|
71
71
|
webhook parse <p> Parse webhook event (raw body from stdin)
|
|
72
72
|
quote <p> [opts] Get a crypto on/off-ramp quote
|
|
73
|
+
drift-check [opts] Capture/compare provider response shapes (drift detection)
|
|
74
|
+
drift-watch [opts] Run drift-check on a loop (long-running monitor)
|
|
75
|
+
reconcile [opts] Reconcile your DB against provider state (detects missed webhooks)
|
|
73
76
|
help, -h, --help Print this help
|
|
74
77
|
version, -v Print version
|
|
75
78
|
|
|
79
|
+
DRIFT DETECTION
|
|
80
|
+
drift-check [provider] Check all/single provider for API drift
|
|
81
|
+
drift-check --capture Capture baseline snapshots (init mode)
|
|
82
|
+
drift-check --json Output JSON instead of human-readable
|
|
83
|
+
drift-check --baseline-dir <p> Custom baseline location (default: .paybridge/drift-baseline)
|
|
84
|
+
drift-check --webhook-url <url> POST drift report to URL on detection
|
|
85
|
+
drift-watch --interval <6h|1h> Run every interval (default: 6h)
|
|
86
|
+
drift-watch --once Alias for drift-check (don't loop)
|
|
87
|
+
|
|
76
88
|
PROVIDER ENV VARS
|
|
77
89
|
See SETUP.md or run 'paybridge test --all' for the full list.
|
|
78
90
|
|
|
@@ -82,6 +94,11 @@ EXAMPLES
|
|
|
82
94
|
STRIPE_API_KEY=sk_test_... paybridge test stripe
|
|
83
95
|
echo '{"id":"evt_x"}' | paybridge webhook parse paystack \\
|
|
84
96
|
--header x-paystack-signature=abc
|
|
97
|
+
paybridge drift-check --capture
|
|
98
|
+
paybridge drift-check stripe
|
|
99
|
+
paybridge drift-watch --interval 1h --webhook-url https://hooks.slack.com/...
|
|
100
|
+
paybridge reconcile --input expected.jsonl
|
|
101
|
+
psql -t -c "SELECT provider, reference, status FROM payments" | paybridge reconcile
|
|
85
102
|
|
|
86
103
|
Docs: https://github.com/kobie3717/paybridge
|
|
87
104
|
`.trim();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface ResponseShape {
|
|
2
|
+
keys: string[];
|
|
3
|
+
types: Record<string, 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'>;
|
|
4
|
+
status?: string;
|
|
5
|
+
capturedAt: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderBaseline {
|
|
8
|
+
providerName: string;
|
|
9
|
+
operation: string;
|
|
10
|
+
shape: ResponseShape;
|
|
11
|
+
libVersion: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DriftReport {
|
|
14
|
+
providerName: string;
|
|
15
|
+
driftDetected: boolean;
|
|
16
|
+
addedKeys: string[];
|
|
17
|
+
removedKeys: string[];
|
|
18
|
+
typeChanges: Array<{
|
|
19
|
+
key: string;
|
|
20
|
+
oldType: string;
|
|
21
|
+
newType: string;
|
|
22
|
+
}>;
|
|
23
|
+
statusChanged?: {
|
|
24
|
+
old: string;
|
|
25
|
+
new: string;
|
|
26
|
+
};
|
|
27
|
+
baselineCapturedAt: string;
|
|
28
|
+
newCapturedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function captureShape(response: unknown): ResponseShape;
|
|
31
|
+
export declare function compareShapes(baseline: ResponseShape, current: ResponseShape): {
|
|
32
|
+
addedKeys: string[];
|
|
33
|
+
removedKeys: string[];
|
|
34
|
+
typeChanges: Array<{
|
|
35
|
+
key: string;
|
|
36
|
+
oldType: string;
|
|
37
|
+
newType: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
export declare function diffBaseline(baseline: ProviderBaseline, currentShape: ResponseShape, providerName: string): DriftReport;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureShape = captureShape;
|
|
4
|
+
exports.compareShapes = compareShapes;
|
|
5
|
+
exports.diffBaseline = diffBaseline;
|
|
6
|
+
function getType(value) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return 'null';
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return 'array';
|
|
11
|
+
return typeof value;
|
|
12
|
+
}
|
|
13
|
+
function flattenKeys(obj, prefix = '') {
|
|
14
|
+
const result = [];
|
|
15
|
+
if (obj === null || obj === undefined) {
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(obj)) {
|
|
19
|
+
if (obj.length === 0) {
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
for (const item of obj) {
|
|
23
|
+
const nested = flattenKeys(item, `${prefix}[*]`);
|
|
24
|
+
result.push(...nested);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else if (typeof obj === 'object') {
|
|
28
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
29
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
30
|
+
const valueType = getType(value);
|
|
31
|
+
if (valueType === 'object' || valueType === 'array') {
|
|
32
|
+
const nested = flattenKeys(value, path);
|
|
33
|
+
result.push(...nested);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
result.push({ path, type: valueType });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function deduplicateKeys(entries) {
|
|
43
|
+
const seen = new Map();
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (!seen.has(entry.path)) {
|
|
46
|
+
seen.set(entry.path, entry.type);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return Array.from(seen.entries()).map(([path, type]) => ({ path, type }));
|
|
50
|
+
}
|
|
51
|
+
function captureShape(response) {
|
|
52
|
+
const entries = flattenKeys(response);
|
|
53
|
+
const deduplicated = deduplicateKeys(entries);
|
|
54
|
+
const sorted = deduplicated.sort((a, b) => a.path.localeCompare(b.path));
|
|
55
|
+
const keys = sorted.map((e) => e.path);
|
|
56
|
+
const types = {};
|
|
57
|
+
for (const entry of sorted) {
|
|
58
|
+
types[entry.path] = entry.type;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
keys,
|
|
62
|
+
types,
|
|
63
|
+
capturedAt: new Date().toISOString(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function compareShapes(baseline, current) {
|
|
67
|
+
const baselineSet = new Set(baseline.keys);
|
|
68
|
+
const currentSet = new Set(current.keys);
|
|
69
|
+
const addedKeys = current.keys.filter((k) => !baselineSet.has(k));
|
|
70
|
+
const removedKeys = baseline.keys.filter((k) => !currentSet.has(k));
|
|
71
|
+
const typeChanges = [];
|
|
72
|
+
for (const key of current.keys) {
|
|
73
|
+
if (baselineSet.has(key)) {
|
|
74
|
+
const oldType = baseline.types[key];
|
|
75
|
+
const newType = current.types[key];
|
|
76
|
+
if (oldType !== newType) {
|
|
77
|
+
typeChanges.push({ key, oldType, newType });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { addedKeys, removedKeys, typeChanges };
|
|
82
|
+
}
|
|
83
|
+
function diffBaseline(baseline, currentShape, providerName) {
|
|
84
|
+
const diff = compareShapes(baseline.shape, currentShape);
|
|
85
|
+
let statusChanged;
|
|
86
|
+
if (baseline.shape.status && currentShape.status && baseline.shape.status !== currentShape.status) {
|
|
87
|
+
statusChanged = { old: baseline.shape.status, new: currentShape.status };
|
|
88
|
+
}
|
|
89
|
+
const driftDetected = diff.addedKeys.length > 0 ||
|
|
90
|
+
diff.removedKeys.length > 0 ||
|
|
91
|
+
diff.typeChanges.length > 0 ||
|
|
92
|
+
!!statusChanged;
|
|
93
|
+
return {
|
|
94
|
+
providerName,
|
|
95
|
+
driftDetected,
|
|
96
|
+
addedKeys: diff.addedKeys,
|
|
97
|
+
removedKeys: diff.removedKeys,
|
|
98
|
+
typeChanges: diff.typeChanges,
|
|
99
|
+
statusChanged,
|
|
100
|
+
baselineCapturedAt: baseline.shape.capturedAt,
|
|
101
|
+
newCapturedAt: currentShape.capturedAt,
|
|
102
|
+
};
|
|
103
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export * from './tracer';
|
|
|
22
22
|
export { createRedisCircuitBreakerStore, type RedisLike, type RedisStoreOptions } from './stores/redis';
|
|
23
23
|
export { createRedisIdempotencyStore, type RedisIdempotencyStoreOptions } from './stores/redis-idempotency';
|
|
24
24
|
export { createRedisLedgerStore, type RedisLedgerStoreOptions } from './stores/redis-ledger';
|
|
25
|
+
export { runReconcile, type ReconcileDeps } from './cli/reconcile';
|
|
26
|
+
export * from './cli/reconcile-types';
|
|
25
27
|
export declare class PayBridge {
|
|
26
28
|
readonly provider: PaymentProvider;
|
|
27
29
|
constructor(config: PayBridgeConfig);
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
20
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.PayBridge = exports.createRedisLedgerStore = exports.createRedisIdempotencyStore = exports.createRedisCircuitBreakerStore = void 0;
|
|
23
|
+
exports.PayBridge = exports.runReconcile = exports.createRedisLedgerStore = exports.createRedisIdempotencyStore = exports.createRedisCircuitBreakerStore = void 0;
|
|
24
24
|
const softycomp_1 = require("./providers/softycomp");
|
|
25
25
|
const yoco_1 = require("./providers/yoco");
|
|
26
26
|
const ozow_1 = require("./providers/ozow");
|
|
@@ -54,6 +54,9 @@ var redis_idempotency_1 = require("./stores/redis-idempotency");
|
|
|
54
54
|
Object.defineProperty(exports, "createRedisIdempotencyStore", { enumerable: true, get: function () { return redis_idempotency_1.createRedisIdempotencyStore; } });
|
|
55
55
|
var redis_ledger_1 = require("./stores/redis-ledger");
|
|
56
56
|
Object.defineProperty(exports, "createRedisLedgerStore", { enumerable: true, get: function () { return redis_ledger_1.createRedisLedgerStore; } });
|
|
57
|
+
var reconcile_1 = require("./cli/reconcile");
|
|
58
|
+
Object.defineProperty(exports, "runReconcile", { enumerable: true, get: function () { return reconcile_1.runReconcile; } });
|
|
59
|
+
__exportStar(require("./cli/reconcile-types"), exports);
|
|
57
60
|
class PayBridge {
|
|
58
61
|
constructor(config) {
|
|
59
62
|
this.provider = this.createProvider(config);
|
package/dist/providers/square.js
CHANGED
|
@@ -91,7 +91,7 @@ class SquareProvider extends base_1.PaymentProvider {
|
|
|
91
91
|
buyer_email: params.customer.email,
|
|
92
92
|
},
|
|
93
93
|
};
|
|
94
|
-
const response = await this.apiRequest('POST', '/checkout/payment-links', requestBody);
|
|
94
|
+
const response = await this.apiRequest('POST', '/online-checkout/payment-links', requestBody);
|
|
95
95
|
const link = response.payment_link;
|
|
96
96
|
return {
|
|
97
97
|
id: link.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paybridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "One API for fiat + crypto payments. Multi-provider routing, automatic failover, MoonPay on/off-ramp. SA-first, global-ready.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"cli": "node dist/cli/index.js",
|
|
15
15
|
"prepack": "npm run clean && npm run build:cli",
|
|
16
16
|
"prepublishOnly": "npm run clean && npm run build:cli",
|
|
17
|
-
"test": "tsc && tsc --project tsconfig.test.json &&
|
|
17
|
+
"test": "tsc && tsc --project tsconfig.test.json && find dist-test -name '*.test.js' -exec node --test {} +",
|
|
18
18
|
"test:e2e:moonpay": "tsx tests/e2e/moonpay-sandbox.ts",
|
|
19
19
|
"test:e2e:yellowcard": "tsx tests/e2e/yellowcard-sandbox.ts",
|
|
20
20
|
"test:e2e:sandbox": "tsx tests/e2e/sandbox-validate.ts"
|
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
"ramp",
|
|
47
47
|
"east-africa",
|
|
48
48
|
"cli",
|
|
49
|
-
"command-line"
|
|
49
|
+
"command-line",
|
|
50
|
+
"drift-detection",
|
|
51
|
+
"monitoring",
|
|
52
|
+
"api-validation"
|
|
50
53
|
],
|
|
51
54
|
"author": "Kobie Wentzel",
|
|
52
55
|
"license": "MIT",
|