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.
Files changed (219) hide show
  1. package/README.md +184 -0
  2. package/apps/admin/index.html +12 -0
  3. package/apps/admin/package.json +25 -0
  4. package/apps/admin/postcss.config.js +6 -0
  5. package/apps/admin/public/anime-girl.svg +84 -0
  6. package/apps/admin/public/bhooai-nexus-logo.png +0 -0
  7. package/apps/admin/public/bhooai-nexus-logo.svg +135 -0
  8. package/apps/admin/public/favicon.ico +0 -0
  9. package/apps/admin/src/main.tsx +10 -0
  10. package/apps/admin/src/vite-env.d.ts +18 -0
  11. package/apps/admin/tailwind.config.js +10 -0
  12. package/apps/admin/tsconfig.json +17 -0
  13. package/apps/admin/vite.config.ts +68 -0
  14. package/bin/nexus.js +52 -0
  15. package/package.json +108 -0
  16. package/packages/nexus-admin/package.json +32 -0
  17. package/packages/nexus-admin/src/App.tsx +4878 -0
  18. package/packages/nexus-admin/src/alertCenter.tsx +150 -0
  19. package/packages/nexus-admin/src/api.ts +601 -0
  20. package/packages/nexus-admin/src/assets/bhooai-nexus-logo.svg +25 -0
  21. package/packages/nexus-admin/src/index.css +3635 -0
  22. package/packages/nexus-admin/src/vite-env.d.ts +19 -0
  23. package/packages/nexus-ads/package.json +20 -0
  24. package/packages/nexus-ads/src/GaqlBuilder.ts +72 -0
  25. package/packages/nexus-ads/src/GoogleAdsClient.ts +121 -0
  26. package/packages/nexus-ads/src/fetchTransport.ts +10 -0
  27. package/packages/nexus-ads/src/index.ts +20 -0
  28. package/packages/nexus-ads/src/types.ts +41 -0
  29. package/packages/nexus-ai-client/package.json +18 -0
  30. package/packages/nexus-ai-client/src/client.ts +159 -0
  31. package/packages/nexus-ai-client/src/errors.ts +21 -0
  32. package/packages/nexus-ai-client/src/index.ts +4 -0
  33. package/packages/nexus-ai-client/src/sse.ts +53 -0
  34. package/packages/nexus-ai-client/src/types.ts +174 -0
  35. package/packages/nexus-auth/package.json +22 -0
  36. package/packages/nexus-auth/src/auth.ts +116 -0
  37. package/packages/nexus-auth/src/bcryptjs.d.ts +10 -0
  38. package/packages/nexus-auth/src/cors.ts +91 -0
  39. package/packages/nexus-auth/src/csrf.ts +158 -0
  40. package/packages/nexus-auth/src/headers.ts +55 -0
  41. package/packages/nexus-auth/src/index.ts +11 -0
  42. package/packages/nexus-auth/src/jwt.ts +65 -0
  43. package/packages/nexus-auth/src/middleware.ts +94 -0
  44. package/packages/nexus-auth/src/oauth.ts +199 -0
  45. package/packages/nexus-auth/src/password.ts +33 -0
  46. package/packages/nexus-auth/src/rateLimit.ts +59 -0
  47. package/packages/nexus-auth/src/rbac.ts +94 -0
  48. package/packages/nexus-auth/src/session.ts +91 -0
  49. package/packages/nexus-cache/package.json +21 -0
  50. package/packages/nexus-cache/src/Cache.ts +155 -0
  51. package/packages/nexus-cache/src/PubSub.ts +98 -0
  52. package/packages/nexus-cache/src/RateLimiter.ts +43 -0
  53. package/packages/nexus-cache/src/index.ts +9 -0
  54. package/packages/nexus-cache/src/types.ts +47 -0
  55. package/packages/nexus-cli/package.json +25 -0
  56. package/packages/nexus-cli/src/commands/cluster.ts +133 -0
  57. package/packages/nexus-cli/src/commands/dev.ts +200 -0
  58. package/packages/nexus-cli/src/commands/doctor.ts +199 -0
  59. package/packages/nexus-cli/src/commands/init.ts +1009 -0
  60. package/packages/nexus-cli/src/commands/node.ts +106 -0
  61. package/packages/nexus-cli/src/commands/pysetup.ts +142 -0
  62. package/packages/nexus-cli/src/commands/sync.ts +116 -0
  63. package/packages/nexus-cli/src/commands/uninstall.ts +287 -0
  64. package/packages/nexus-cli/src/config-sync.ts +384 -0
  65. package/packages/nexus-cli/src/dotenv.ts +39 -0
  66. package/packages/nexus-cli/src/index.ts +94 -0
  67. package/packages/nexus-cli/src/supervisor.ts +394 -0
  68. package/packages/nexus-cli/src/util.ts +132 -0
  69. package/packages/nexus-cli/src/wizard.ts +149 -0
  70. package/packages/nexus-cli/templates/Dockerfile +60 -0
  71. package/packages/nexus-cli/templates/README.md +69 -0
  72. package/packages/nexus-cli/templates/apps/admin/index.html +12 -0
  73. package/packages/nexus-cli/templates/apps/admin/package.json +24 -0
  74. package/packages/nexus-cli/templates/apps/admin/postcss.config.js +6 -0
  75. package/packages/nexus-cli/templates/apps/admin/src/main.tsx +10 -0
  76. package/packages/nexus-cli/templates/apps/admin/src/vite-env.d.ts +18 -0
  77. package/packages/nexus-cli/templates/apps/admin/tailwind.config.js +9 -0
  78. package/packages/nexus-cli/templates/apps/admin/tsconfig.json +17 -0
  79. package/packages/nexus-cli/templates/apps/admin/vite.config.ts +64 -0
  80. package/packages/nexus-cli/templates/apps/ai-server/main.py +43 -0
  81. package/packages/nexus-cli/templates/apps/ai-server/providers/__init__.py +3 -0
  82. package/packages/nexus-cli/templates/apps/ai-server/providers/base.py +111 -0
  83. package/packages/nexus-cli/templates/apps/ai-server/requirements.txt +3 -0
  84. package/packages/nexus-cli/templates/apps/ai-server/routers/__init__.py +3 -0
  85. package/packages/nexus-cli/templates/apps/ai-server/routers/chat.py +47 -0
  86. package/packages/nexus-cli/templates/apps/ai-server/routers/embeddings.py +30 -0
  87. package/packages/nexus-cli/templates/apps/ai-server/routers/lint.py +167 -0
  88. package/packages/nexus-cli/templates/apps/ai-server/routers/models.py +23 -0
  89. package/packages/nexus-cli/templates/apps/ai-server/routers/preflight.py +169 -0
  90. package/packages/nexus-cli/templates/apps/ai-server/settings.py +48 -0
  91. package/packages/nexus-cli/templates/apps/backend/package.json +34 -0
  92. package/packages/nexus-cli/templates/apps/backend/src/main.ts +375 -0
  93. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/adminRoutes.ts +732 -0
  94. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/clusterRoutes.ts +391 -0
  95. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/databaseRoutes.ts +161 -0
  96. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/lintProxy.ts +89 -0
  97. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/preflightProxy.ts +242 -0
  98. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/roleCatalog.ts +78 -0
  99. package/packages/nexus-cli/templates/apps/backend/src/modules/admin/schemaRoutes.ts +449 -0
  100. package/packages/nexus-cli/templates/apps/backend/src/modules/ai/aiProxy.ts +265 -0
  101. package/packages/nexus-cli/templates/apps/backend/src/modules/auth/authRoutes.ts +220 -0
  102. package/packages/nexus-cli/templates/apps/backend/src/modules/payments/paymentRoutes.ts +100 -0
  103. package/packages/nexus-cli/templates/apps/backend/src/modules/payments/paymentStore.ts +172 -0
  104. package/packages/nexus-cli/templates/apps/backend/src/modules/requests/requestLog.ts +175 -0
  105. package/packages/nexus-cli/templates/apps/backend/src/modules/users/userGraph.ts +83 -0
  106. package/packages/nexus-cli/templates/apps/backend/src/modules/users/userModel.ts +88 -0
  107. package/packages/nexus-cli/templates/apps/backend/src/plugins/CronScheduler.ts +69 -0
  108. package/packages/nexus-cli/templates/apps/backend/src/plugins/loadPlugins.ts +107 -0
  109. package/packages/nexus-cli/templates/apps/backend/tsconfig.json +14 -0
  110. package/packages/nexus-cli/templates/apps/frontend/index.html +12 -0
  111. package/packages/nexus-cli/templates/apps/frontend/package.json +19 -0
  112. package/packages/nexus-cli/templates/apps/frontend/src/main.tsx +64 -0
  113. package/packages/nexus-cli/templates/apps/frontend/vite.config.ts +72 -0
  114. package/packages/nexus-cli/templates/bin/nexus.js +35 -0
  115. package/packages/nexus-cli/templates/bin/serve-all.mjs +45 -0
  116. package/packages/nexus-cli/templates/dockerignore +15 -0
  117. package/packages/nexus-cli/templates/gitignore +12 -0
  118. package/packages/nexus-cli/templates/nexus.config.ts +69 -0
  119. package/packages/nexus-cli/templates/package.json +47 -0
  120. package/packages/nexus-cli/templates/tsconfig.json +17 -0
  121. package/packages/nexus-cli/templates/uploads/.gitkeep +0 -0
  122. package/packages/nexus-core/package.json +38 -0
  123. package/packages/nexus-core/src/config/ConfigLoader.ts +161 -0
  124. package/packages/nexus-core/src/config/defaults.ts +155 -0
  125. package/packages/nexus-core/src/config/env.ts +113 -0
  126. package/packages/nexus-core/src/config/index.ts +6 -0
  127. package/packages/nexus-core/src/config/merge.ts +28 -0
  128. package/packages/nexus-core/src/config/schema.ts +184 -0
  129. package/packages/nexus-core/src/config/types.ts +352 -0
  130. package/packages/nexus-core/src/di/Container.ts +98 -0
  131. package/packages/nexus-core/src/di/index.ts +1 -0
  132. package/packages/nexus-core/src/errors.ts +62 -0
  133. package/packages/nexus-core/src/http/Router.ts +149 -0
  134. package/packages/nexus-core/src/http/Server.ts +145 -0
  135. package/packages/nexus-core/src/http/bodyParser.ts +129 -0
  136. package/packages/nexus-core/src/http/context.ts +112 -0
  137. package/packages/nexus-core/src/http/index.ts +6 -0
  138. package/packages/nexus-core/src/http/static.ts +85 -0
  139. package/packages/nexus-core/src/http/uploads.ts +94 -0
  140. package/packages/nexus-core/src/index.ts +4 -0
  141. package/packages/nexus-crypto/package.json +18 -0
  142. package/packages/nexus-crypto/src/csr.ts +165 -0
  143. package/packages/nexus-crypto/src/der.ts +262 -0
  144. package/packages/nexus-crypto/src/https.ts +36 -0
  145. package/packages/nexus-crypto/src/index.ts +5 -0
  146. package/packages/nexus-crypto/src/keypair.ts +59 -0
  147. package/packages/nexus-crypto/src/x509.ts +156 -0
  148. package/packages/nexus-data/package.json +27 -0
  149. package/packages/nexus-data/src/connection/Connection.ts +57 -0
  150. package/packages/nexus-data/src/connection/ConnectionManager.ts +37 -0
  151. package/packages/nexus-data/src/errors.ts +17 -0
  152. package/packages/nexus-data/src/index.ts +66 -0
  153. package/packages/nexus-data/src/model/Document.ts +126 -0
  154. package/packages/nexus-data/src/model/Model.ts +189 -0
  155. package/packages/nexus-data/src/model/hydrate.ts +12 -0
  156. package/packages/nexus-data/src/populate/populate.ts +114 -0
  157. package/packages/nexus-data/src/projects.ts +185 -0
  158. package/packages/nexus-data/src/query/Query.ts +158 -0
  159. package/packages/nexus-data/src/schema/Schema.ts +173 -0
  160. package/packages/nexus-data/src/schema/SchemaType.ts +80 -0
  161. package/packages/nexus-data/src/schema/validators.ts +88 -0
  162. package/packages/nexus-email/package.json +22 -0
  163. package/packages/nexus-email/src/EmailService.ts +76 -0
  164. package/packages/nexus-email/src/TemplateEngine.ts +62 -0
  165. package/packages/nexus-email/src/index.ts +37 -0
  166. package/packages/nexus-email/src/providers.ts +63 -0
  167. package/packages/nexus-email/src/queue.ts +103 -0
  168. package/packages/nexus-email/src/types.ts +49 -0
  169. package/packages/nexus-graphql/package.json +24 -0
  170. package/packages/nexus-graphql/src/federation/composeSupergraph.ts +153 -0
  171. package/packages/nexus-graphql/src/federation/createFederatedGateway.ts +36 -0
  172. package/packages/nexus-graphql/src/federation/executor.ts +305 -0
  173. package/packages/nexus-graphql/src/federation/parseMetadata.ts +87 -0
  174. package/packages/nexus-graphql/src/gateway/createGateway.ts +77 -0
  175. package/packages/nexus-graphql/src/gateway/fieldResolver.ts +45 -0
  176. package/packages/nexus-graphql/src/gateway/graphqlHttpHandler.ts +90 -0
  177. package/packages/nexus-graphql/src/gateway/publicSchema.ts +26 -0
  178. package/packages/nexus-graphql/src/index.ts +14 -0
  179. package/packages/nexus-graphql/src/subgraph/defineSubgraph.ts +115 -0
  180. package/packages/nexus-graphql/src/subgraph/federationDirectives.ts +47 -0
  181. package/packages/nexus-graphql/src/subgraph/parseKeys.ts +25 -0
  182. package/packages/nexus-graphql/src/subscriptions/PubSub.ts +64 -0
  183. package/packages/nexus-graphql/src/subscriptions/SubscriptionServer.ts +203 -0
  184. package/packages/nexus-graphql/src/types.ts +77 -0
  185. package/packages/nexus-payments/package.json +20 -0
  186. package/packages/nexus-payments/src/errors.ts +15 -0
  187. package/packages/nexus-payments/src/http.ts +53 -0
  188. package/packages/nexus-payments/src/index.ts +106 -0
  189. package/packages/nexus-payments/src/providers/PayPalProvider.ts +192 -0
  190. package/packages/nexus-payments/src/providers/PayUProvider.ts +164 -0
  191. package/packages/nexus-payments/src/providers/PayoneerProvider.ts +122 -0
  192. package/packages/nexus-payments/src/providers/RazorpayProvider.ts +184 -0
  193. package/packages/nexus-payments/src/providers/SkrillProvider.ts +117 -0
  194. package/packages/nexus-payments/src/signature.ts +24 -0
  195. package/packages/nexus-payments/src/types.ts +150 -0
  196. package/packages/nexus-payments/src/webhooks.ts +58 -0
  197. package/packages/nexus-plugins/package.json +20 -0
  198. package/packages/nexus-plugins/src/HookBus.ts +42 -0
  199. package/packages/nexus-plugins/src/admin/adminExtensions.ts +53 -0
  200. package/packages/nexus-plugins/src/host.ts +105 -0
  201. package/packages/nexus-plugins/src/index.ts +8 -0
  202. package/packages/nexus-plugins/src/manifest.ts +73 -0
  203. package/packages/nexus-plugins/src/registry.ts +123 -0
  204. package/packages/nexus-plugins/src/runtime/TrustedPlugin.ts +134 -0
  205. package/packages/nexus-plugins/src/sandbox/SandboxedPlugin.ts +279 -0
  206. package/packages/nexus-plugins/src/sandbox/capabilityPolicy.ts +68 -0
  207. package/packages/nexus-plugins/src/sandbox/workerBootstrap.mjs +211 -0
  208. package/packages/nexus-realtime/package.json +26 -0
  209. package/packages/nexus-realtime/src/adapter/PubSubAdapter.ts +42 -0
  210. package/packages/nexus-realtime/src/adapter/RedisPubSubAdapter.ts +59 -0
  211. package/packages/nexus-realtime/src/index.ts +5 -0
  212. package/packages/nexus-realtime/src/mediasoup/MediasoupAdapter.ts +176 -0
  213. package/packages/nexus-realtime/src/server/RealtimeServer.ts +252 -0
  214. package/packages/nexus-realtime/src/types.ts +52 -0
  215. package/packages/nexus-telemetry/package.json +20 -0
  216. package/packages/nexus-telemetry/src/index.ts +3 -0
  217. package/packages/nexus-telemetry/src/logger/Logger.ts +178 -0
  218. package/packages/nexus-telemetry/src/metrics/Metrics.ts +123 -0
  219. 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
+ }