@paykit-sdk/medusajs 1.0.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 ADDED
@@ -0,0 +1,66 @@
1
+ # @paykit-sdk/medusajs
2
+
3
+ Universal payment provider adapter for Medusa v2+ using PayKit. Compatible with any PayKit provider.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @paykit-sdk/medusajs @paykit-sdk/core
9
+ ```
10
+
11
+ ## Install your provider
12
+
13
+ ```bash
14
+ npm install @paykit-sdk/stripe
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ Configure in `medusa-config.ts`:
20
+
21
+ ```typescript
22
+ import { defineConfig } from '@medusajs/framework/utils';
23
+ import { createStripe } from '@paykit-sdk/stripe';
24
+
25
+ export default defineConfig({
26
+ modules: [
27
+ {
28
+ resolve: '@paykit-sdk/medusa-adapter',
29
+ options: {
30
+ provider: createStripe({ apiKey: process.env.STRIPE_API_KEY }),
31
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
32
+ debug: process.env.NODE_ENV === 'development',
33
+ },
34
+ },
35
+ ],
36
+ });
37
+ ```
38
+
39
+ ## Environment Variables
40
+
41
+ ```env
42
+ STRIPE_API_KEY=sk_test_...
43
+ STRIPE_WEBHOOK_SECRET=whsec_...
44
+ ```
45
+
46
+ ## Webhook Setup
47
+
48
+ Configure your payment provider to send webhooks to:
49
+
50
+ ```
51
+ https://your-store.com/hooks/payment/pp_paykit
52
+ ```
53
+
54
+ Select payment-related events based on the PayKit provider documentation.
55
+
56
+ ## Configuration Options
57
+
58
+ | Option | Type | Required | Description |
59
+ | ------------- | -------------- | -------- | ---------------------------- |
60
+ | provider | PayKitProvider | Yes | PayKit provider instance |
61
+ | webhookSecret | string | Yes | Webhook secret from provider |
62
+ | debug | boolean | No | Enable debug logging |
63
+
64
+ ## License
65
+
66
+ ISC
@@ -0,0 +1,10 @@
1
+ import * as _medusajs_types from '@medusajs/types';
2
+ export { PaykitMedusaJSAdapter, PaykitMedusaJSAdapterOptions } from './providers/paykit-provider.mjs';
3
+ import '@medusajs/framework/types';
4
+ import '@medusajs/framework/utils';
5
+ import '@paykit-sdk/core';
6
+ import 'zod';
7
+
8
+ declare const _default: _medusajs_types.ModuleProviderExports;
9
+
10
+ export { _default as default };
@@ -0,0 +1,10 @@
1
+ import * as _medusajs_types from '@medusajs/types';
2
+ export { PaykitMedusaJSAdapter, PaykitMedusaJSAdapterOptions } from './providers/paykit-provider.js';
3
+ import '@medusajs/framework/types';
4
+ import '@medusajs/framework/utils';
5
+ import '@paykit-sdk/core';
6
+ import 'zod';
7
+
8
+ declare const _default: _medusajs_types.ModuleProviderExports;
9
+
10
+ export { _default as default };
package/dist/index.js ADDED
@@ -0,0 +1,311 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var utils = require('@medusajs/framework/utils');
6
+ var core = require('@paykit-sdk/core');
7
+ var zod = require('zod');
8
+
9
+ // src/index.ts
10
+ var medusaStatus$InboundSchema = (status) => {
11
+ switch (status) {
12
+ case "pending":
13
+ case "processing":
14
+ return utils.PaymentSessionStatus.PENDING;
15
+ case "requires_action":
16
+ return utils.PaymentSessionStatus.REQUIRES_MORE;
17
+ case "requires_capture":
18
+ return utils.PaymentSessionStatus.AUTHORIZED;
19
+ case "succeeded":
20
+ return utils.PaymentSessionStatus.CAPTURED;
21
+ case "canceled":
22
+ return utils.PaymentSessionStatus.CANCELED;
23
+ case "failed":
24
+ return utils.PaymentSessionStatus.ERROR;
25
+ default:
26
+ return utils.PaymentSessionStatus.PENDING;
27
+ }
28
+ };
29
+
30
+ // src/providers/paykit-provider.ts
31
+ var optionsSchema = zod.z.object({
32
+ /**
33
+ * The underlying PayKit provider instance (Stripe, PayPal, etc.)
34
+ * This is required and must be a valid PayKit provider instance
35
+ */
36
+ provider: core.providerSchema,
37
+ /**
38
+ * The webhook secret for the provider
39
+ * This is required and must be a valid webhook secret
40
+ */
41
+ webhookSecret: zod.z.string(),
42
+ /**
43
+ * Whether to enable debug mode
44
+ * If enabled, the adapter will log debug information to the console
45
+ */
46
+ debug: zod.z.boolean().optional()
47
+ });
48
+ var PaykitMedusaJSAdapter = class extends utils.AbstractPaymentProvider {
49
+ /**
50
+ * The unique identifier for this payment provider
51
+ * Will be stored as `pp_paykit_{id}` in Medusa
52
+ */
53
+ static identifier = "paykit";
54
+ paykit;
55
+ provider;
56
+ options;
57
+ static validateOptions(options) {
58
+ const { error } = optionsSchema.safeParse(options);
59
+ if (error) {
60
+ throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, error.message);
61
+ }
62
+ return;
63
+ }
64
+ /**
65
+ * Constructor receives Medusa's container and provider options
66
+ *
67
+ * @param cradle - Medusa's dependency injection container
68
+ * @param options - PayKit provider configuration
69
+ */
70
+ constructor(cradle, options) {
71
+ super(cradle, options);
72
+ this.options = options;
73
+ this.provider = options.provider;
74
+ this.paykit = new core.PayKit(this.provider);
75
+ if (this.options.debug) {
76
+ console.info(`[PayKit] Initialized with provider: ${this.provider.providerName}`);
77
+ }
78
+ }
79
+ initiatePayment = async ({
80
+ context,
81
+ amount,
82
+ currency_code,
83
+ data
84
+ }) => {
85
+ if (this.options.debug) {
86
+ console.info("[PayKit] Initiating payment", { context, amount, currency_code, data });
87
+ }
88
+ const intent = {
89
+ status: "pending",
90
+ amount: Number(amount),
91
+ currency: currency_code,
92
+ metadata: { ...data?.metadata ?? {}, session_id: data?.session_id ?? "" },
93
+ provider_metadata: data?.provider_metadata ?? void 0
94
+ };
95
+ let customer;
96
+ if (context?.account_holder?.data?.id) {
97
+ customer = context.account_holder.data.id;
98
+ } else if (data?.email) {
99
+ customer = { email: data.email };
100
+ }
101
+ if (!customer) {
102
+ throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Required: customer ID (account_holder) or email (data)");
103
+ }
104
+ if (typeof customer === "object" && "email" in customer) {
105
+ await core.tryCatchAsync(
106
+ this.paykit.customers.create({
107
+ email: customer.email,
108
+ phone: data?.phone ?? "",
109
+ name: data?.name ? data.name : customer.email.split("@")[0],
110
+ metadata: { PAYKIT_METADATA_KEY: JSON.stringify({ source: "medusa" }) }
111
+ })
112
+ );
113
+ } else {
114
+ customer = customer;
115
+ }
116
+ intent.customer = customer;
117
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(
118
+ this.paykit.payments.create(intent)
119
+ );
120
+ if (paymentIntentError)
121
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
122
+ return { id: paymentIntentResult.id, status: medusaStatus$InboundSchema(paymentIntentResult.status) };
123
+ };
124
+ capturePayment = async (input) => {
125
+ if (this.options.debug) {
126
+ console.info("[PayKit] Capturing payment", input);
127
+ }
128
+ const { id, amount } = core.validateRequiredKeys(
129
+ ["id", "amount"],
130
+ input?.data ?? {},
131
+ "Missing required payment ID"
132
+ );
133
+ if (!id) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
134
+ if (!amount) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment amount");
135
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(
136
+ this.paykit.payments.capture(id, { amount: Number(amount) })
137
+ );
138
+ if (paymentIntentError)
139
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
140
+ return { data: paymentIntentResult };
141
+ };
142
+ authorizePayment = async (input) => {
143
+ if (this.options.debug) {
144
+ console.info("[PayKit] Authorizing payment", input);
145
+ }
146
+ return this.getPaymentStatus(input);
147
+ };
148
+ cancelPayment = async (input) => {
149
+ if (this.options.debug) {
150
+ console.info("[PayKit] Canceling payment", input);
151
+ }
152
+ const { id } = core.validateRequiredKeys(
153
+ ["id"],
154
+ input?.data ?? {},
155
+ "Missing required payment ID"
156
+ );
157
+ if (!id) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
158
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(this.paykit.payments.cancel(id));
159
+ if (paymentIntentError)
160
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
161
+ return { data: paymentIntentResult };
162
+ };
163
+ deletePayment = async (input) => {
164
+ return this.cancelPayment(input);
165
+ };
166
+ getPaymentStatus = async (input) => {
167
+ const { id } = core.validateRequiredKeys(
168
+ ["id"],
169
+ input?.data ?? {},
170
+ "Missing required payment ID"
171
+ );
172
+ if (!id) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
173
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(this.paykit.payments.retrieve(id));
174
+ if (paymentIntentError)
175
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
176
+ if (!paymentIntentResult) throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, "Payment not found");
177
+ return {
178
+ status: medusaStatus$InboundSchema(paymentIntentResult.status),
179
+ data: paymentIntentResult
180
+ };
181
+ };
182
+ refundPayment = async (input) => {
183
+ if (this.options.debug) {
184
+ console.info("[PayKit] Refunding payment", input);
185
+ }
186
+ const { id: paymentId } = core.validateRequiredKeys(
187
+ ["id"],
188
+ input?.data ?? {},
189
+ "Missing required payment ID"
190
+ );
191
+ if (!paymentId) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
192
+ const [refundResult, refundError] = await core.tryCatchAsync(
193
+ this.paykit.refunds.create({
194
+ payment_id: paymentId,
195
+ amount: Number(input.amount),
196
+ reason: null,
197
+ metadata: input.data?.metadata ? input.data.metadata : null,
198
+ provider_metadata: input.data?.provider_metadata ? input.data.provider_metadata : void 0
199
+ })
200
+ );
201
+ if (refundError) throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, refundError.message);
202
+ return { data: refundResult };
203
+ };
204
+ retrievePayment = async (input) => {
205
+ if (this.options.debug) {
206
+ console.info("[PayKit] Retrieving payment", input);
207
+ }
208
+ const { id } = core.validateRequiredKeys(
209
+ ["id"],
210
+ input?.data ?? {},
211
+ "Missing required payment ID"
212
+ );
213
+ if (!id) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
214
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(this.paykit.payments.retrieve(id));
215
+ if (paymentIntentError)
216
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
217
+ return { data: paymentIntentResult };
218
+ };
219
+ updatePayment = async (input) => {
220
+ if (this.options.debug) {
221
+ console.info("[PayKit] Updating payment", input);
222
+ }
223
+ const { amount, currency_code } = core.validateRequiredKeys(
224
+ ["amount", "currency_code"],
225
+ input,
226
+ "Missing required payment ID"
227
+ );
228
+ if (!amount || !currency_code)
229
+ throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required amount or currency code");
230
+ const { id: paymentId } = core.validateRequiredKeys(
231
+ ["id"],
232
+ input.data,
233
+ "Missing required payment ID"
234
+ );
235
+ if (!paymentId) throw new utils.MedusaError(utils.MedusaError.Types.INVALID_DATA, "Missing required payment ID");
236
+ const [paymentIntentResult, paymentIntentError] = await core.tryCatchAsync(
237
+ this.paykit.payments.update(paymentId, {
238
+ amount: Number(amount),
239
+ currency: currency_code,
240
+ metadata: input.data?.metadata ? input.data.metadata : {},
241
+ provider_metadata: input.data?.provider_metadata ? input.data.provider_metadata : void 0
242
+ })
243
+ );
244
+ if (paymentIntentError)
245
+ throw new utils.MedusaError(utils.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, paymentIntentError.message);
246
+ return { data: paymentIntentResult };
247
+ };
248
+ getWebhookActionAndData = async (payload) => {
249
+ if (this.options.debug) {
250
+ console.info("[PayKit] Getting webhook action and data", payload);
251
+ }
252
+ const { rawData, headers } = payload;
253
+ const fullUrl = (() => {
254
+ if (headers["origin"]) {
255
+ return headers["origin"];
256
+ }
257
+ if (headers["x-forwarded-host"]) {
258
+ const protocol = headers["x-forwarded-proto"] || "https";
259
+ const host = headers["x-forwarded-host"];
260
+ const path = headers["x-forwarded-path"] || "";
261
+ return `${protocol}://${host}${path}`;
262
+ }
263
+ if (headers["host"]) {
264
+ const protocol = headers["x-forwarded-proto"] || (String(headers["host"]).includes("localhost") ? "http" : "https");
265
+ const host = headers["host"];
266
+ const path = headers["x-original-url"] || headers["x-forwarded-path"] || "";
267
+ return `${protocol}://${host}${path}`;
268
+ }
269
+ return "";
270
+ })();
271
+ const webhook = this.paykit.webhooks.setup({ webhookSecret: this.options.webhookSecret }).on("payment.created", async (event) => {
272
+ return {
273
+ action: utils.PaymentActions.PENDING,
274
+ data: { session_id: event.data?.metadata?.session_id, amount: event.data?.amount }
275
+ };
276
+ }).on("payment.updated", async (event) => {
277
+ const statusActionMap = {
278
+ pending: utils.PaymentActions.PENDING,
279
+ processing: utils.PaymentActions.PENDING,
280
+ requires_action: utils.PaymentActions.REQUIRES_MORE,
281
+ requires_capture: utils.PaymentActions.AUTHORIZED,
282
+ succeeded: utils.PaymentActions.SUCCESSFUL,
283
+ failed: utils.PaymentActions.FAILED,
284
+ canceled: utils.PaymentActions.CANCELED
285
+ };
286
+ return {
287
+ action: event.data?.status ? statusActionMap[event.data.status] : utils.PaymentActions.PENDING,
288
+ data: { session_id: event.data?.metadata?.session_id, amount: event.data?.amount }
289
+ };
290
+ }).on("payment.canceled", async (event) => {
291
+ return {
292
+ action: utils.PaymentActions.CANCELED,
293
+ data: { session_id: event.data?.metadata?.session_id, amount: event.data?.amount }
294
+ };
295
+ });
296
+ const webhookEvents = await webhook.handle({
297
+ body: rawData,
298
+ headers,
299
+ fullUrl
300
+ });
301
+ return webhookEvents;
302
+ };
303
+ };
304
+
305
+ // src/index.ts
306
+ var index_default = utils.ModuleProvider(utils.Modules.PAYMENT, {
307
+ services: [PaykitMedusaJSAdapter]
308
+ });
309
+
310
+ exports.PaykitMedusaJSAdapter = PaykitMedusaJSAdapter;
311
+ exports.default = index_default;