bhooai-nexus 0.1.2
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 +184 -0
- package/apps/admin/index.html +12 -0
- package/apps/admin/package.json +25 -0
- package/apps/admin/postcss.config.js +6 -0
- package/apps/admin/public/anime-girl.svg +84 -0
- package/apps/admin/public/bhooai-nexus-logo.png +0 -0
- package/apps/admin/public/bhooai-nexus-logo.svg +135 -0
- package/apps/admin/public/favicon.ico +0 -0
- package/apps/admin/src/main.tsx +10 -0
- package/apps/admin/src/vite-env.d.ts +18 -0
- package/apps/admin/tailwind.config.js +10 -0
- package/apps/admin/tsconfig.json +17 -0
- package/apps/admin/vite.config.ts +68 -0
- package/bin/nexus.js +52 -0
- package/package.json +108 -0
- package/packages/nexus-admin/package.json +32 -0
- package/packages/nexus-admin/src/App.tsx +4878 -0
- package/packages/nexus-admin/src/alertCenter.tsx +150 -0
- package/packages/nexus-admin/src/api.ts +601 -0
- package/packages/nexus-admin/src/assets/bhooai-nexus-logo.svg +25 -0
- package/packages/nexus-admin/src/index.css +3635 -0
- package/packages/nexus-admin/src/vite-env.d.ts +19 -0
- package/packages/nexus-ads/package.json +20 -0
- package/packages/nexus-ads/src/GaqlBuilder.ts +72 -0
- package/packages/nexus-ads/src/GoogleAdsClient.ts +121 -0
- package/packages/nexus-ads/src/fetchTransport.ts +10 -0
- package/packages/nexus-ads/src/index.ts +20 -0
- package/packages/nexus-ads/src/types.ts +41 -0
- package/packages/nexus-ai-client/package.json +18 -0
- package/packages/nexus-ai-client/src/client.ts +159 -0
- package/packages/nexus-ai-client/src/errors.ts +21 -0
- package/packages/nexus-ai-client/src/index.ts +4 -0
- package/packages/nexus-ai-client/src/sse.ts +53 -0
- package/packages/nexus-ai-client/src/types.ts +174 -0
- package/packages/nexus-auth/package.json +22 -0
- package/packages/nexus-auth/src/auth.ts +116 -0
- package/packages/nexus-auth/src/bcryptjs.d.ts +10 -0
- package/packages/nexus-auth/src/cors.ts +91 -0
- package/packages/nexus-auth/src/csrf.ts +158 -0
- package/packages/nexus-auth/src/headers.ts +55 -0
- package/packages/nexus-auth/src/index.ts +11 -0
- package/packages/nexus-auth/src/jwt.ts +65 -0
- package/packages/nexus-auth/src/middleware.ts +94 -0
- package/packages/nexus-auth/src/oauth.ts +199 -0
- package/packages/nexus-auth/src/password.ts +33 -0
- package/packages/nexus-auth/src/rateLimit.ts +59 -0
- package/packages/nexus-auth/src/rbac.ts +94 -0
- package/packages/nexus-auth/src/session.ts +91 -0
- package/packages/nexus-cache/package.json +21 -0
- package/packages/nexus-cache/src/Cache.ts +155 -0
- package/packages/nexus-cache/src/PubSub.ts +98 -0
- package/packages/nexus-cache/src/RateLimiter.ts +43 -0
- package/packages/nexus-cache/src/index.ts +9 -0
- package/packages/nexus-cache/src/types.ts +47 -0
- package/packages/nexus-cli/package.json +25 -0
- package/packages/nexus-cli/src/commands/cluster.ts +133 -0
- package/packages/nexus-cli/src/commands/dev.ts +200 -0
- package/packages/nexus-cli/src/commands/doctor.ts +199 -0
- package/packages/nexus-cli/src/commands/init.ts +1009 -0
- package/packages/nexus-cli/src/commands/node.ts +106 -0
- package/packages/nexus-cli/src/commands/pysetup.ts +142 -0
- package/packages/nexus-cli/src/commands/sync.ts +116 -0
- package/packages/nexus-cli/src/commands/uninstall.ts +287 -0
- package/packages/nexus-cli/src/config-sync.ts +384 -0
- package/packages/nexus-cli/src/dotenv.ts +39 -0
- package/packages/nexus-cli/src/index.ts +94 -0
- package/packages/nexus-cli/src/supervisor.ts +394 -0
- package/packages/nexus-cli/src/util.ts +132 -0
- package/packages/nexus-cli/src/wizard.ts +149 -0
- package/packages/nexus-cli/templates/Dockerfile +60 -0
- package/packages/nexus-cli/templates/README.md +69 -0
- package/packages/nexus-cli/templates/apps/admin/index.html +12 -0
- package/packages/nexus-cli/templates/apps/admin/package.json +24 -0
- package/packages/nexus-cli/templates/apps/admin/postcss.config.js +6 -0
- package/packages/nexus-cli/templates/apps/admin/src/main.tsx +10 -0
- package/packages/nexus-cli/templates/apps/admin/src/vite-env.d.ts +18 -0
- package/packages/nexus-cli/templates/apps/admin/tailwind.config.js +9 -0
- package/packages/nexus-cli/templates/apps/admin/tsconfig.json +17 -0
- package/packages/nexus-cli/templates/apps/admin/vite.config.ts +64 -0
- package/packages/nexus-cli/templates/apps/ai-server/main.py +43 -0
- package/packages/nexus-cli/templates/apps/ai-server/providers/__init__.py +3 -0
- package/packages/nexus-cli/templates/apps/ai-server/providers/base.py +111 -0
- package/packages/nexus-cli/templates/apps/ai-server/requirements.txt +3 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/__init__.py +3 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/chat.py +47 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/embeddings.py +30 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/lint.py +167 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/models.py +23 -0
- package/packages/nexus-cli/templates/apps/ai-server/routers/preflight.py +169 -0
- package/packages/nexus-cli/templates/apps/ai-server/settings.py +48 -0
- package/packages/nexus-cli/templates/apps/backend/package.json +34 -0
- package/packages/nexus-cli/templates/apps/backend/src/main.ts +375 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/adminRoutes.ts +732 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/clusterRoutes.ts +391 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/databaseRoutes.ts +161 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/lintProxy.ts +89 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/preflightProxy.ts +242 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/roleCatalog.ts +78 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/admin/schemaRoutes.ts +449 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/ai/aiProxy.ts +265 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/auth/authRoutes.ts +220 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/payments/paymentRoutes.ts +100 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/payments/paymentStore.ts +172 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/requests/requestLog.ts +175 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/users/userGraph.ts +83 -0
- package/packages/nexus-cli/templates/apps/backend/src/modules/users/userModel.ts +88 -0
- package/packages/nexus-cli/templates/apps/backend/src/plugins/CronScheduler.ts +69 -0
- package/packages/nexus-cli/templates/apps/backend/src/plugins/loadPlugins.ts +107 -0
- package/packages/nexus-cli/templates/apps/backend/tsconfig.json +14 -0
- package/packages/nexus-cli/templates/apps/frontend/index.html +12 -0
- package/packages/nexus-cli/templates/apps/frontend/package.json +19 -0
- package/packages/nexus-cli/templates/apps/frontend/src/main.tsx +64 -0
- package/packages/nexus-cli/templates/apps/frontend/vite.config.ts +72 -0
- package/packages/nexus-cli/templates/bin/nexus.js +35 -0
- package/packages/nexus-cli/templates/bin/serve-all.mjs +45 -0
- package/packages/nexus-cli/templates/dockerignore +15 -0
- package/packages/nexus-cli/templates/gitignore +12 -0
- package/packages/nexus-cli/templates/nexus.config.ts +69 -0
- package/packages/nexus-cli/templates/package.json +47 -0
- package/packages/nexus-cli/templates/tsconfig.json +17 -0
- package/packages/nexus-cli/templates/uploads/.gitkeep +0 -0
- package/packages/nexus-core/package.json +38 -0
- package/packages/nexus-core/src/config/ConfigLoader.ts +161 -0
- package/packages/nexus-core/src/config/defaults.ts +155 -0
- package/packages/nexus-core/src/config/env.ts +113 -0
- package/packages/nexus-core/src/config/index.ts +6 -0
- package/packages/nexus-core/src/config/merge.ts +28 -0
- package/packages/nexus-core/src/config/schema.ts +184 -0
- package/packages/nexus-core/src/config/types.ts +352 -0
- package/packages/nexus-core/src/di/Container.ts +98 -0
- package/packages/nexus-core/src/di/index.ts +1 -0
- package/packages/nexus-core/src/errors.ts +62 -0
- package/packages/nexus-core/src/http/Router.ts +149 -0
- package/packages/nexus-core/src/http/Server.ts +145 -0
- package/packages/nexus-core/src/http/bodyParser.ts +129 -0
- package/packages/nexus-core/src/http/context.ts +112 -0
- package/packages/nexus-core/src/http/index.ts +6 -0
- package/packages/nexus-core/src/http/static.ts +85 -0
- package/packages/nexus-core/src/http/uploads.ts +94 -0
- package/packages/nexus-core/src/index.ts +4 -0
- package/packages/nexus-crypto/package.json +18 -0
- package/packages/nexus-crypto/src/csr.ts +165 -0
- package/packages/nexus-crypto/src/der.ts +262 -0
- package/packages/nexus-crypto/src/https.ts +36 -0
- package/packages/nexus-crypto/src/index.ts +5 -0
- package/packages/nexus-crypto/src/keypair.ts +59 -0
- package/packages/nexus-crypto/src/x509.ts +156 -0
- package/packages/nexus-data/package.json +27 -0
- package/packages/nexus-data/src/connection/Connection.ts +57 -0
- package/packages/nexus-data/src/connection/ConnectionManager.ts +37 -0
- package/packages/nexus-data/src/errors.ts +17 -0
- package/packages/nexus-data/src/index.ts +66 -0
- package/packages/nexus-data/src/model/Document.ts +126 -0
- package/packages/nexus-data/src/model/Model.ts +189 -0
- package/packages/nexus-data/src/model/hydrate.ts +12 -0
- package/packages/nexus-data/src/populate/populate.ts +114 -0
- package/packages/nexus-data/src/projects.ts +185 -0
- package/packages/nexus-data/src/query/Query.ts +158 -0
- package/packages/nexus-data/src/schema/Schema.ts +173 -0
- package/packages/nexus-data/src/schema/SchemaType.ts +80 -0
- package/packages/nexus-data/src/schema/validators.ts +88 -0
- package/packages/nexus-email/package.json +22 -0
- package/packages/nexus-email/src/EmailService.ts +76 -0
- package/packages/nexus-email/src/TemplateEngine.ts +62 -0
- package/packages/nexus-email/src/index.ts +37 -0
- package/packages/nexus-email/src/providers.ts +63 -0
- package/packages/nexus-email/src/queue.ts +103 -0
- package/packages/nexus-email/src/types.ts +49 -0
- package/packages/nexus-graphql/package.json +24 -0
- package/packages/nexus-graphql/src/federation/composeSupergraph.ts +153 -0
- package/packages/nexus-graphql/src/federation/createFederatedGateway.ts +36 -0
- package/packages/nexus-graphql/src/federation/executor.ts +305 -0
- package/packages/nexus-graphql/src/federation/parseMetadata.ts +87 -0
- package/packages/nexus-graphql/src/gateway/createGateway.ts +77 -0
- package/packages/nexus-graphql/src/gateway/fieldResolver.ts +45 -0
- package/packages/nexus-graphql/src/gateway/graphqlHttpHandler.ts +90 -0
- package/packages/nexus-graphql/src/gateway/publicSchema.ts +26 -0
- package/packages/nexus-graphql/src/index.ts +14 -0
- package/packages/nexus-graphql/src/subgraph/defineSubgraph.ts +115 -0
- package/packages/nexus-graphql/src/subgraph/federationDirectives.ts +47 -0
- package/packages/nexus-graphql/src/subgraph/parseKeys.ts +25 -0
- package/packages/nexus-graphql/src/subscriptions/PubSub.ts +64 -0
- package/packages/nexus-graphql/src/subscriptions/SubscriptionServer.ts +203 -0
- package/packages/nexus-graphql/src/types.ts +77 -0
- package/packages/nexus-payments/package.json +20 -0
- package/packages/nexus-payments/src/errors.ts +15 -0
- package/packages/nexus-payments/src/http.ts +53 -0
- package/packages/nexus-payments/src/index.ts +106 -0
- package/packages/nexus-payments/src/providers/PayPalProvider.ts +192 -0
- package/packages/nexus-payments/src/providers/PayUProvider.ts +164 -0
- package/packages/nexus-payments/src/providers/PayoneerProvider.ts +122 -0
- package/packages/nexus-payments/src/providers/RazorpayProvider.ts +184 -0
- package/packages/nexus-payments/src/providers/SkrillProvider.ts +117 -0
- package/packages/nexus-payments/src/signature.ts +24 -0
- package/packages/nexus-payments/src/types.ts +150 -0
- package/packages/nexus-payments/src/webhooks.ts +58 -0
- package/packages/nexus-plugins/package.json +20 -0
- package/packages/nexus-plugins/src/HookBus.ts +42 -0
- package/packages/nexus-plugins/src/admin/adminExtensions.ts +53 -0
- package/packages/nexus-plugins/src/host.ts +105 -0
- package/packages/nexus-plugins/src/index.ts +8 -0
- package/packages/nexus-plugins/src/manifest.ts +73 -0
- package/packages/nexus-plugins/src/registry.ts +123 -0
- package/packages/nexus-plugins/src/runtime/TrustedPlugin.ts +134 -0
- package/packages/nexus-plugins/src/sandbox/SandboxedPlugin.ts +279 -0
- package/packages/nexus-plugins/src/sandbox/capabilityPolicy.ts +68 -0
- package/packages/nexus-plugins/src/sandbox/workerBootstrap.mjs +211 -0
- package/packages/nexus-realtime/package.json +26 -0
- package/packages/nexus-realtime/src/adapter/PubSubAdapter.ts +42 -0
- package/packages/nexus-realtime/src/adapter/RedisPubSubAdapter.ts +59 -0
- package/packages/nexus-realtime/src/index.ts +5 -0
- package/packages/nexus-realtime/src/mediasoup/MediasoupAdapter.ts +176 -0
- package/packages/nexus-realtime/src/server/RealtimeServer.ts +252 -0
- package/packages/nexus-realtime/src/types.ts +52 -0
- package/packages/nexus-telemetry/package.json +20 -0
- package/packages/nexus-telemetry/src/index.ts +3 -0
- package/packages/nexus-telemetry/src/logger/Logger.ts +178 -0
- package/packages/nexus-telemetry/src/metrics/Metrics.ts +123 -0
- package/packages/nexus-telemetry/src/trace.ts +28 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './http.js';
|
|
4
|
+
export * from './signature.js';
|
|
5
|
+
export * from './providers/RazorpayProvider.js';
|
|
6
|
+
export * from './providers/PayPalProvider.js';
|
|
7
|
+
export * from './providers/PayUProvider.js';
|
|
8
|
+
export * from './providers/SkrillProvider.js';
|
|
9
|
+
export * from './providers/PayoneerProvider.js';
|
|
10
|
+
export * from './webhooks.js';
|
|
11
|
+
|
|
12
|
+
import type { PaymentProvider, HttpTransport, ProviderConfig } from './types.js';
|
|
13
|
+
import { fetchTransport } from './http.js';
|
|
14
|
+
import { RazorpayProvider } from './providers/RazorpayProvider.js';
|
|
15
|
+
import { PayPalProvider } from './providers/PayPalProvider.js';
|
|
16
|
+
import { PayUProvider } from './providers/PayUProvider.js';
|
|
17
|
+
import { SkrillProvider } from './providers/SkrillProvider.js';
|
|
18
|
+
import { PayoneerProvider } from './providers/PayoneerProvider.js';
|
|
19
|
+
import { WebhookRouter } from './webhooks.js';
|
|
20
|
+
import type { WebhookEvent } from './types.js';
|
|
21
|
+
|
|
22
|
+
export interface PaymentsOptions {
|
|
23
|
+
/** Override the HTTP transport (tests inject a mock; default uses fetch). */
|
|
24
|
+
transport?: HttpTransport;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PaymentsService {
|
|
28
|
+
/** Enabled providers keyed by name (razorpay, paypal, payu, skrill, payoneer). */
|
|
29
|
+
providers: Map<string, PaymentProvider>;
|
|
30
|
+
/** Get a provider by name, or throw if not enabled. */
|
|
31
|
+
get(name: string): PaymentProvider;
|
|
32
|
+
/** Rebuild the enabled-provider map from (possibly updated) config in place,
|
|
33
|
+
* so admin toggles/keys take effect without a restart. */
|
|
34
|
+
refresh(config: Parameters<typeof createPayments>[0]): void;
|
|
35
|
+
/** Build a webhook router for `POST <webhookPath>`. */
|
|
36
|
+
webhookRouter(onEvent: (event: WebhookEvent) => Promise<void> | void): WebhookRouter;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the payments service from a `payments` config section. A provider
|
|
41
|
+
* activates when `enabled: true` OR all of its credential keys are set — so a
|
|
42
|
+
* user who adds keys (via config or `NEXUS_PAYMENTS_*` env) without remembering
|
|
43
|
+
* the `enabled` flag still gets a working provider. Explicit `enabled: false`
|
|
44
|
+
* with keys is the only way to keep a configured provider off.
|
|
45
|
+
*/
|
|
46
|
+
export function createPayments(
|
|
47
|
+
config: {
|
|
48
|
+
razorpay?: ProviderConfig;
|
|
49
|
+
paypal?: ProviderConfig;
|
|
50
|
+
payu?: ProviderConfig;
|
|
51
|
+
skrill?: ProviderConfig;
|
|
52
|
+
payoneer?: ProviderConfig;
|
|
53
|
+
},
|
|
54
|
+
opts: PaymentsOptions = {},
|
|
55
|
+
): PaymentsService {
|
|
56
|
+
const transport = opts.transport ?? fetchTransport;
|
|
57
|
+
const providers = new Map<string, PaymentProvider>();
|
|
58
|
+
|
|
59
|
+
const active = (cfg: ProviderConfig | undefined, keys: string[]): boolean => {
|
|
60
|
+
if (!cfg) return false;
|
|
61
|
+
const hasKeys = keys.every((k) => {
|
|
62
|
+
const v = (cfg as Record<string, unknown>)[k];
|
|
63
|
+
return typeof v === 'string' && v.length > 0;
|
|
64
|
+
});
|
|
65
|
+
if (!hasKeys) return false; // never instantiate without the required keys
|
|
66
|
+
// Keys present → provider works (matches "add keys and it activates"),
|
|
67
|
+
// unless explicitly disabled.
|
|
68
|
+
return cfg.enabled !== false;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const populate = (cfg: Parameters<typeof createPayments>[0]): void => {
|
|
72
|
+
providers.clear();
|
|
73
|
+
if (active(cfg.razorpay, ['keyId', 'keySecret'])) {
|
|
74
|
+
providers.set('razorpay', new RazorpayProvider(cfg.razorpay as any, transport));
|
|
75
|
+
}
|
|
76
|
+
if (active(cfg.paypal, ['clientId', 'clientSecret'])) {
|
|
77
|
+
providers.set('paypal', new PayPalProvider(cfg.paypal as any, transport));
|
|
78
|
+
}
|
|
79
|
+
if (active(cfg.payu, ['merchantKey', 'salt'])) {
|
|
80
|
+
providers.set('payu', new PayUProvider(cfg.payu as any, transport));
|
|
81
|
+
}
|
|
82
|
+
if (active(cfg.skrill, ['merchantEmail'])) {
|
|
83
|
+
providers.set('skrill', new SkrillProvider(cfg.skrill as any, transport));
|
|
84
|
+
}
|
|
85
|
+
if (active(cfg.payoneer, ['programId', 'apiKey'])) {
|
|
86
|
+
providers.set('payoneer', new PayoneerProvider(cfg.payoneer as any, transport));
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
populate(config);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
providers,
|
|
94
|
+
get(name: string): PaymentProvider {
|
|
95
|
+
const p = providers.get(name);
|
|
96
|
+
if (!p) throw new Error(`[nexus-payments] provider not enabled: ${name}`);
|
|
97
|
+
return p;
|
|
98
|
+
},
|
|
99
|
+
refresh(cfg) {
|
|
100
|
+
populate(cfg);
|
|
101
|
+
},
|
|
102
|
+
webhookRouter(onEvent) {
|
|
103
|
+
return new WebhookRouter(providers, onEvent);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PaymentProvider,
|
|
3
|
+
CreateOrderInput,
|
|
4
|
+
Order,
|
|
5
|
+
CaptureInput,
|
|
6
|
+
RefundInput,
|
|
7
|
+
RefundResult,
|
|
8
|
+
WebhookVerifyInput,
|
|
9
|
+
WebhookEvent,
|
|
10
|
+
HttpTransport,
|
|
11
|
+
OrderStatus,
|
|
12
|
+
ConnectionTest,
|
|
13
|
+
} from '../types.js';
|
|
14
|
+
import { jsonRequest, basicAuth } from '../http.js';
|
|
15
|
+
import { PaymentError } from '../errors.js';
|
|
16
|
+
|
|
17
|
+
interface PayPalConfig {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
sandbox: boolean;
|
|
20
|
+
clientId: string;
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
/** Optional webhook id (used by the verify-webhook-signature API). */
|
|
23
|
+
webhookId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function baseUrl(sandbox: boolean): string {
|
|
27
|
+
return sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapStatus(s: string): OrderStatus {
|
|
31
|
+
switch (s) {
|
|
32
|
+
case 'CREATED': return 'created';
|
|
33
|
+
case 'SAVED': case 'PAYER_ACTION_REQUIRED': return 'pending';
|
|
34
|
+
case 'APPROVED': return 'authorized';
|
|
35
|
+
case 'COMPLETED': return 'captured';
|
|
36
|
+
case 'VOIDED': return 'cancelled';
|
|
37
|
+
default: return 'pending';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Format a major-unit amount as PayPal's 2-decimal string. */
|
|
42
|
+
function fmt(amount: number): string {
|
|
43
|
+
return amount.toFixed(2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class PayPalProvider implements PaymentProvider {
|
|
47
|
+
readonly name = 'paypal';
|
|
48
|
+
private readonly cfg: PayPalConfig;
|
|
49
|
+
private readonly transport: HttpTransport;
|
|
50
|
+
private token: { value: string; expiresAt: number } | null = null;
|
|
51
|
+
|
|
52
|
+
constructor(cfg: PayPalConfig, transport: HttpTransport) {
|
|
53
|
+
this.cfg = cfg;
|
|
54
|
+
this.transport = transport;
|
|
55
|
+
if (!cfg.clientId || !cfg.clientSecret) throw new PaymentError('paypal clientId/clientSecret required', { code: 'CONFIG', provider: 'paypal' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private base(): string {
|
|
59
|
+
return baseUrl(this.cfg.sandbox);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** OAuth2 client-credentials token with a 5-minute safety margin on expiry. */
|
|
63
|
+
private async tokenValue(): Promise<string> {
|
|
64
|
+
if (this.token && Date.now() < this.token.expiresAt - 300_000) return this.token.value;
|
|
65
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
url: `${this.base()}/v1/oauth2/token`,
|
|
68
|
+
headers: { authorization: basicAuth(this.cfg.clientId, this.cfg.clientSecret), 'content-type': 'application/x-www-form-urlencoded' },
|
|
69
|
+
body: 'grant_type=client_credentials',
|
|
70
|
+
});
|
|
71
|
+
if (!res.access_token) throw new PaymentError('paypal token exchange failed', { code: 'AUTH', provider: 'paypal', raw: res });
|
|
72
|
+
this.token = { value: res.access_token, expiresAt: Date.now() + (res.expires_in ?? 3600) * 1000 };
|
|
73
|
+
return this.token.value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async authHeaders(): Promise<Record<string, string>> {
|
|
77
|
+
return { 'content-type': 'application/json', authorization: `Bearer ${await this.tokenValue()}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async createOrder(input: CreateOrderInput): Promise<Order> {
|
|
81
|
+
const body = {
|
|
82
|
+
intent: 'CAPTURE',
|
|
83
|
+
purchase_units: [{
|
|
84
|
+
reference_id: input.reference,
|
|
85
|
+
amount: { currency_code: input.currency, value: fmt(input.amount) },
|
|
86
|
+
...(input.description ? { description: input.description } : {}),
|
|
87
|
+
}],
|
|
88
|
+
...(input.returnUrl || input.cancelUrl ? { application_context: { return_url: input.returnUrl, cancel_url: input.cancelUrl } } : {}),
|
|
89
|
+
};
|
|
90
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
url: `${this.base()}/v2/checkout/orders`,
|
|
93
|
+
headers: await this.authHeaders(),
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
const approve = (res.links ?? []).find((l: { rel: string; href: string }) => l.rel === 'approve');
|
|
97
|
+
return {
|
|
98
|
+
id: res.id,
|
|
99
|
+
reference: input.reference,
|
|
100
|
+
status: mapStatus(res.status),
|
|
101
|
+
amount: input.amount,
|
|
102
|
+
currency: input.currency,
|
|
103
|
+
paymentUrl: approve?.href,
|
|
104
|
+
raw: res,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async capture(input: CaptureInput): Promise<Order> {
|
|
109
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
url: `${this.base()}/v2/checkout/orders/${input.orderId}/capture`,
|
|
112
|
+
headers: await this.authHeaders(),
|
|
113
|
+
body: '{}',
|
|
114
|
+
});
|
|
115
|
+
const unit = res.purchase_units?.[0];
|
|
116
|
+
const capture = unit?.payments?.captures?.[0];
|
|
117
|
+
return {
|
|
118
|
+
id: res.id,
|
|
119
|
+
reference: unit?.reference_id ?? '',
|
|
120
|
+
status: mapStatus(res.status),
|
|
121
|
+
amount: capture ? Number(capture.amount.value) : 0,
|
|
122
|
+
currency: capture?.amount?.currency_code ?? '',
|
|
123
|
+
paymentId: capture?.id,
|
|
124
|
+
raw: res,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async refund(input: RefundInput): Promise<RefundResult> {
|
|
129
|
+
const body = input.amount != null ? JSON.stringify({ amount: { value: fmt(input.amount), currency_code: 'USD' } }) : '{}';
|
|
130
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
url: `${this.base()}/v2/payments/captures/${input.paymentId}/refund`,
|
|
133
|
+
headers: await this.authHeaders(),
|
|
134
|
+
body,
|
|
135
|
+
});
|
|
136
|
+
return { id: res.id, status: res.status, amount: res.amount ? Number(res.amount.value) : 0, raw: res };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async getOrderStatus(orderId: string): Promise<Order> {
|
|
140
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
141
|
+
method: 'GET',
|
|
142
|
+
url: `${this.base()}/v2/checkout/orders/${orderId}`,
|
|
143
|
+
headers: await this.authHeaders(),
|
|
144
|
+
});
|
|
145
|
+
const unit = res.purchase_units?.[0];
|
|
146
|
+
return {
|
|
147
|
+
id: res.id,
|
|
148
|
+
reference: unit?.reference_id ?? '',
|
|
149
|
+
status: mapStatus(res.status),
|
|
150
|
+
amount: unit ? Number(unit.amount.value) : 0,
|
|
151
|
+
currency: unit?.amount?.currency_code ?? '',
|
|
152
|
+
raw: res,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Live probe: exchange client-credentials for an OAuth token (no order created). */
|
|
157
|
+
async testConnection(): Promise<ConnectionTest> {
|
|
158
|
+
await this.tokenValue();
|
|
159
|
+
return { ok: true, detail: `oauth2 authenticated (${this.cfg.sandbox ? 'sandbox' : 'live'})` };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* PayPal webhook verification uses the /v1/notifications/verify-webhook-signature
|
|
164
|
+
* API (PayPal signs with a rotating certificate — local HMAC isn't possible).
|
|
165
|
+
* Requires `webhookId` configured.
|
|
166
|
+
*/
|
|
167
|
+
async verifyWebhook(input: WebhookVerifyInput): Promise<WebhookEvent> {
|
|
168
|
+
if (!this.cfg.webhookId) return { verified: false, provider: this.name, data: 'no webhookId configured' };
|
|
169
|
+
const raw = typeof input.rawBody === 'string' ? input.rawBody : input.rawBody.toString();
|
|
170
|
+
const body = JSON.stringify({
|
|
171
|
+
auth_algo: input.headers['paypal-auth-algo'] ?? input.headers['PAYPAL-AUTH-ALGO'],
|
|
172
|
+
cert_url: input.headers['paypal-cert-url'] ?? input.headers['PAYPAL-CERT-URL'],
|
|
173
|
+
transmission_id: input.headers['paypal-transmission-id'] ?? input.headers['PAYPAL-TRANSMISSION-ID'],
|
|
174
|
+
transmission_sig: input.headers['paypal-transmission-sig'] ?? input.headers['PAYPAL-TRANSMISSION-SIG'],
|
|
175
|
+
transmission_time: input.headers['paypal-transmission-time'] ?? input.headers['PAYPAL-TRANSMISSION-TIME'],
|
|
176
|
+
webhook_id: this.cfg.webhookId,
|
|
177
|
+
webhook_event: safeParse(raw),
|
|
178
|
+
});
|
|
179
|
+
const res = await jsonRequest(this.transport, this.name, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
url: `${this.base()}/v1/notifications/verify-webhook-signature`,
|
|
182
|
+
headers: await this.authHeaders(),
|
|
183
|
+
body,
|
|
184
|
+
});
|
|
185
|
+
const verified = res.verification_status === 'SUCCESS';
|
|
186
|
+
return { verified, event: safeParse(raw)?.event_type, data: safeParse(raw), provider: this.name };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function safeParse(s: string): any {
|
|
191
|
+
try { return JSON.parse(s); } catch { return undefined; }
|
|
192
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PaymentProvider,
|
|
3
|
+
CreateOrderInput,
|
|
4
|
+
Order,
|
|
5
|
+
CaptureInput,
|
|
6
|
+
RefundInput,
|
|
7
|
+
RefundResult,
|
|
8
|
+
WebhookVerifyInput,
|
|
9
|
+
WebhookEvent,
|
|
10
|
+
HttpTransport,
|
|
11
|
+
OrderStatus,
|
|
12
|
+
} from '../types.js';
|
|
13
|
+
import { sha512Hex, safeEqual } from '../signature.js';
|
|
14
|
+
import { PaymentError } from '../errors.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* PayU India (happy-path). PayU uses a hash-based flow: the merchant computes a
|
|
18
|
+
* SHA-512 hash over the payment params + salt, POSTs to the hosted checkout, and
|
|
19
|
+
* verifies the response hash (the reverse sequence). No REST "create order";
|
|
20
|
+
* `createOrder` builds the params + hash and returns the checkout URL.
|
|
21
|
+
*/
|
|
22
|
+
interface PayUConfig {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
sandbox: boolean;
|
|
25
|
+
merchantKey: string;
|
|
26
|
+
salt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function checkoutUrl(sandbox: boolean): string {
|
|
30
|
+
return sandbox ? 'https://test.payu.in/_payment' : 'https://secure.payu.in/_payment';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Request hash = sha512(key|txnid|amount|productinfo|firstname|email|udf1..udf10|salt). */
|
|
34
|
+
export function payuRequestHash(
|
|
35
|
+
key: string,
|
|
36
|
+
salt: string,
|
|
37
|
+
p: { txnid: string; amount: string; productinfo: string; firstname: string; email: string; udf: string[] },
|
|
38
|
+
): string {
|
|
39
|
+
const fields = [key, p.txnid, p.amount, p.productinfo, p.firstname, p.email, ...p.udf, salt];
|
|
40
|
+
return sha512Hex(fields.join('|'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Response hash = sha512(salt|status|udf10..udf1|email|firstname|productinfo|amount|txnid|key). */
|
|
44
|
+
export function payuResponseHash(
|
|
45
|
+
key: string,
|
|
46
|
+
salt: string,
|
|
47
|
+
p: { status: string; txnid: string; amount: string; productinfo: string; firstname: string; email: string; udf: string[] },
|
|
48
|
+
): string {
|
|
49
|
+
const reversedUdf = [...p.udf].reverse();
|
|
50
|
+
const fields = [salt, p.status, ...reversedUdf, p.email, p.firstname, p.productinfo, p.amount, p.txnid, key];
|
|
51
|
+
return sha512Hex(fields.join('|'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function mapStatus(status: string): OrderStatus {
|
|
55
|
+
switch (status) {
|
|
56
|
+
case 'created': return 'created';
|
|
57
|
+
case 'pending': case 'in progress': return 'pending';
|
|
58
|
+
case 'captured': case 'success': return 'captured';
|
|
59
|
+
case 'failed': return 'failed';
|
|
60
|
+
case 'cancelled': return 'cancelled';
|
|
61
|
+
case 'refunded': return 'refunded';
|
|
62
|
+
default: return 'pending';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class PayUProvider implements PaymentProvider {
|
|
67
|
+
readonly name = 'payu';
|
|
68
|
+
private readonly cfg: PayUConfig;
|
|
69
|
+
private readonly transport: HttpTransport;
|
|
70
|
+
|
|
71
|
+
constructor(cfg: PayUConfig, transport: HttpTransport) {
|
|
72
|
+
this.cfg = cfg;
|
|
73
|
+
this.transport = transport;
|
|
74
|
+
if (!cfg.merchantKey || !cfg.salt) throw new PaymentError('payu merchantKey/salt required', { code: 'CONFIG', provider: 'payu' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async createOrder(input: CreateOrderInput): Promise<Order> {
|
|
78
|
+
const amount = input.amount.toFixed(2);
|
|
79
|
+
const firstname = input.customer?.name ?? 'Customer';
|
|
80
|
+
const email = input.customer?.email ?? '';
|
|
81
|
+
const productinfo = input.description ?? input.reference;
|
|
82
|
+
const udf = ['', '', '', '', '', '', '', '', '', '']; // udf1..udf10
|
|
83
|
+
const hash = payuRequestHash(this.cfg.merchantKey, this.cfg.salt, {
|
|
84
|
+
txnid: input.reference, amount, productinfo, firstname, email, udf,
|
|
85
|
+
});
|
|
86
|
+
const params = {
|
|
87
|
+
key: this.cfg.merchantKey,
|
|
88
|
+
txnid: input.reference,
|
|
89
|
+
amount,
|
|
90
|
+
productinfo,
|
|
91
|
+
firstname,
|
|
92
|
+
email,
|
|
93
|
+
udf1: '', udf2: '', udf3: '', udf4: '', udf5: '',
|
|
94
|
+
hash,
|
|
95
|
+
...(input.returnUrl ? { surl: input.returnUrl, furl: input.returnUrl } : {}),
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
id: input.reference,
|
|
99
|
+
reference: input.reference,
|
|
100
|
+
status: 'created',
|
|
101
|
+
amount: input.amount,
|
|
102
|
+
currency: input.currency,
|
|
103
|
+
// PayU expects a form POST to the checkout URL with `params` — return the
|
|
104
|
+
// endpoint as paymentUrl and the params in `raw` for the client to POST.
|
|
105
|
+
paymentUrl: checkoutUrl(this.cfg.sandbox),
|
|
106
|
+
raw: params,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** PayU has no server-side capture; payments auto-capture on the hosted page. */
|
|
111
|
+
async capture(input: CaptureInput): Promise<Order> {
|
|
112
|
+
return this.getOrderStatus(input.orderId);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** PayU refunds require the merchant panel / a separate API; v1 returns a stub. */
|
|
116
|
+
async refund(input: RefundInput): Promise<RefundResult> {
|
|
117
|
+
return { id: `refund-${input.paymentId}`, status: 'pending', amount: input.amount ?? 0, raw: { note: 'PayU refund via merchant panel (v1)' } };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getOrderStatus(orderId: string): Promise<Order> {
|
|
121
|
+
// Without a live REST credential, status is reconstructed from the webhook
|
|
122
|
+
// payload that updateOrder would have stored. v1 returns a placeholder.
|
|
123
|
+
return { id: orderId, reference: orderId, status: 'pending', amount: 0, currency: '', raw: { note: 'PayU status via webhook (v1)' } };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Verify a PayU webhook/redirect response. The body is form-encoded
|
|
128
|
+
* (key=value&...) with a `hash` field. We recompute the response hash and
|
|
129
|
+
* compare in constant time.
|
|
130
|
+
*/
|
|
131
|
+
async verifyWebhook(input: WebhookVerifyInput): Promise<WebhookEvent> {
|
|
132
|
+
const raw = typeof input.rawBody === 'string' ? input.rawBody : input.rawBody.toString();
|
|
133
|
+
const fields = parseForm(raw);
|
|
134
|
+
const providedHash = fields.hash;
|
|
135
|
+
if (!providedHash) return { verified: false, provider: this.name, data: 'no hash in payload' };
|
|
136
|
+
const udf = [fields.udf1, fields.udf2, fields.udf3, fields.udf4, fields.udf5, fields.udf6, fields.udf7, fields.udf8, fields.udf9, fields.udf10].map((v) => v ?? '');
|
|
137
|
+
const expected = payuResponseHash(this.cfg.merchantKey, this.cfg.salt, {
|
|
138
|
+
status: fields.status ?? '',
|
|
139
|
+
txnid: fields.txnid ?? '',
|
|
140
|
+
amount: fields.amount ?? '',
|
|
141
|
+
productinfo: fields.productinfo ?? '',
|
|
142
|
+
firstname: fields.firstname ?? '',
|
|
143
|
+
email: fields.email ?? '',
|
|
144
|
+
udf,
|
|
145
|
+
});
|
|
146
|
+
const verified = safeEqual(providedHash, expected);
|
|
147
|
+
return {
|
|
148
|
+
verified,
|
|
149
|
+
event: fields.status ? `payment.${fields.status}` : undefined,
|
|
150
|
+
data: fields,
|
|
151
|
+
provider: this.name,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseForm(body: string): Record<string, string> {
|
|
157
|
+
const out: Record<string, string> = {};
|
|
158
|
+
for (const pair of body.split('&')) {
|
|
159
|
+
if (!pair) continue;
|
|
160
|
+
const [k, ...rest] = pair.split('=');
|
|
161
|
+
out[decodeURIComponent(k!)] = decodeURIComponent(rest.join('='));
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PaymentProvider,
|
|
3
|
+
CreateOrderInput,
|
|
4
|
+
Order,
|
|
5
|
+
CaptureInput,
|
|
6
|
+
RefundInput,
|
|
7
|
+
RefundResult,
|
|
8
|
+
WebhookVerifyInput,
|
|
9
|
+
WebhookEvent,
|
|
10
|
+
HttpTransport,
|
|
11
|
+
OrderStatus,
|
|
12
|
+
} from '../types.js';
|
|
13
|
+
import { hmacSha256Hex, safeEqual } from '../signature.js';
|
|
14
|
+
import { PaymentError } from '../errors.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Payoneer (happy-path). Payoneer's API requires a registered partner program
|
|
18
|
+
* and has no public sandbox without provisioned credentials, so this adapter is
|
|
19
|
+
* structurally complete but verified by signature tests rather than live calls.
|
|
20
|
+
*
|
|
21
|
+
* The flow modeled here: the merchant builds a signed payment request (HMAC over
|
|
22
|
+
* the canonical params) and redirects to a Payoneer checkout URL; Payoneer posts
|
|
23
|
+
* a webhook signed with the same shared secret (HMAC-SHA256 over the raw body).
|
|
24
|
+
*/
|
|
25
|
+
interface PayoneerConfig {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
sandbox: boolean;
|
|
28
|
+
programId: string;
|
|
29
|
+
apiKey: string;
|
|
30
|
+
/** Shared secret for webhook HMAC verification. */
|
|
31
|
+
webhookSecret?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function checkoutUrl(sandbox: boolean, programId: string): string {
|
|
35
|
+
return sandbox
|
|
36
|
+
? `https://api.sandbox.payoneer.com/checkout/${programId}`
|
|
37
|
+
: `https://api.payoneer.com/checkout/${programId}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mapStatus(status: string): OrderStatus {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case 'initiated': return 'created';
|
|
43
|
+
case 'pending': return 'pending';
|
|
44
|
+
case 'authorized': return 'authorized';
|
|
45
|
+
case 'completed': case 'paid': return 'captured';
|
|
46
|
+
case 'failed': return 'failed';
|
|
47
|
+
case 'cancelled': return 'cancelled';
|
|
48
|
+
case 'refunded': return 'refunded';
|
|
49
|
+
default: return 'pending';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class PayoneerProvider implements PaymentProvider {
|
|
54
|
+
readonly name = 'payoneer';
|
|
55
|
+
private readonly cfg: PayoneerConfig;
|
|
56
|
+
private readonly transport: HttpTransport;
|
|
57
|
+
|
|
58
|
+
constructor(cfg: PayoneerConfig, transport: HttpTransport) {
|
|
59
|
+
this.cfg = cfg;
|
|
60
|
+
this.transport = transport;
|
|
61
|
+
if (!cfg.programId || !cfg.apiKey) throw new PaymentError('payoneer programId/apiKey required', { code: 'CONFIG', provider: 'payoneer' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createOrder(input: CreateOrderInput): Promise<Order> {
|
|
65
|
+
const params = new URLSearchParams();
|
|
66
|
+
params.set('program_id', this.cfg.programId);
|
|
67
|
+
params.set('reference', input.reference);
|
|
68
|
+
params.set('amount', input.amount.toFixed(2));
|
|
69
|
+
params.set('currency', input.currency);
|
|
70
|
+
if (input.customer?.email) params.set('payee_email', input.customer.email);
|
|
71
|
+
if (input.returnUrl) params.set('return_url', input.returnUrl);
|
|
72
|
+
// Canonical signature: HMAC-SHA256(apiKey, sorted query string).
|
|
73
|
+
const canonical = canonicalQuery(params);
|
|
74
|
+
const signature = hmacSha256Hex(this.cfg.apiKey, canonical);
|
|
75
|
+
params.set('signature', signature);
|
|
76
|
+
return {
|
|
77
|
+
id: input.reference,
|
|
78
|
+
reference: input.reference,
|
|
79
|
+
status: 'created',
|
|
80
|
+
amount: input.amount,
|
|
81
|
+
currency: input.currency,
|
|
82
|
+
paymentUrl: `${checkoutUrl(this.cfg.sandbox, this.cfg.programId)}?${params.toString()}`,
|
|
83
|
+
raw: Object.fromEntries(params),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async capture(input: CaptureInput): Promise<Order> {
|
|
88
|
+
return this.getOrderStatus(input.orderId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async refund(_input: RefundInput): Promise<RefundResult> {
|
|
92
|
+
return { id: `payoneer-refund`, status: 'pending', amount: 0, raw: { note: 'Payoneer refund via partner API (v1)' } };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getOrderStatus(orderId: string): Promise<Order> {
|
|
96
|
+
return { id: orderId, reference: orderId, status: 'pending', amount: 0, currency: '', raw: { note: 'Payoneer status via webhook (v1)' } };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async verifyWebhook(input: WebhookVerifyInput): Promise<WebhookEvent> {
|
|
100
|
+
const secret = this.cfg.webhookSecret;
|
|
101
|
+
if (!secret) return { verified: false, provider: this.name, data: 'no webhookSecret configured' };
|
|
102
|
+
const sig = input.headers['x-payoneer-signature'] ?? input.headers['X-Payoneer-Signature'];
|
|
103
|
+
const raw = typeof input.rawBody === 'string' ? input.rawBody : input.rawBody.toString();
|
|
104
|
+
const expected = hmacSha256Hex(secret, raw);
|
|
105
|
+
const verified = typeof sig === 'string' && safeEqual(sig, expected);
|
|
106
|
+
let event: string | undefined;
|
|
107
|
+
let data: unknown;
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(raw);
|
|
110
|
+
event = parsed.event ?? parsed.status;
|
|
111
|
+
data = parsed;
|
|
112
|
+
} catch {
|
|
113
|
+
data = raw;
|
|
114
|
+
}
|
|
115
|
+
return { verified, event, data, provider: this.name };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Sort params by key and encode as `k=v&k=v` (deterministic for signing). */
|
|
120
|
+
function canonicalQuery(params: URLSearchParams): string {
|
|
121
|
+
return [...params.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([k, v]) => `${k}=${v}`).join('&');
|
|
122
|
+
}
|