@easypayment/medusa-paypal 0.6.3 → 0.6.5
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/.medusa/server/src/admin/index.js +7 -10
- package/.medusa/server/src/admin/index.mjs +7 -10
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.js +1 -11
- package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.js +0 -9
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.js +162 -115
- package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.js +46 -24
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.js +97 -43
- package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -1
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js +20 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +0 -42
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +0 -8
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +6 -114
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.d.ts +0 -2
- package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.js +0 -9
- package/.medusa/server/src/modules/paypal/types/config.js.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +21 -17
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +195 -99
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/README.md +156 -152
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +48 -52
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +0 -23
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +56 -65
- package/src/api/store/paypal/capture-order/route.ts +266 -276
- package/src/api/store/paypal/create-order/route.ts +0 -9
- package/src/api/store/paypal/webhook/route.ts +325 -246
- package/src/api/store/paypal-complete/route.ts +105 -76
- package/src/jobs/paypal-webhook-retry.ts +149 -85
- package/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.ts +17 -0
- package/src/modules/paypal/payment-provider/service.ts +1079 -1121
- package/src/modules/paypal/service.ts +6 -127
- package/src/modules/paypal/types/config.ts +33 -47
- package/src/modules/paypal/webhook-processor.ts +377 -215
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +0 -3
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +0 -1
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +0 -9
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts +0 -7
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.js +0 -109
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +0 -4
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.js +0 -47
- package/.medusa/server/src/modules/paypal/utils/crypto.js.map +0 -1
- package/src/api/admin/paypal/rotate-credentials/route.ts +0 -8
- package/src/jobs/paypal-reconcile.ts +0 -113
- package/src/modules/paypal/utils/crypto.ts +0 -51
|
@@ -1,76 +1,105 @@
|
|
|
1
|
-
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
-
import type { IPaymentModuleService } from "@medusajs/framework/types"
|
|
3
|
-
import { Modules } from "@medusajs/framework/utils"
|
|
4
|
-
import type PayPalModuleService from "../../../modules/paypal/service"
|
|
5
|
-
|
|
6
|
-
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
7
|
-
const { cart_id } = req.body as { cart_id: string }
|
|
8
|
-
|
|
9
|
-
if (!cart_id) {
|
|
10
|
-
return res.status(400).json({ error: "cart_id is required" })
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
await (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type { IPaymentModuleService } from "@medusajs/framework/types"
|
|
3
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
4
|
+
import type PayPalModuleService from "../../../modules/paypal/service"
|
|
5
|
+
|
|
6
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
7
|
+
const { cart_id } = req.body as { cart_id: string }
|
|
8
|
+
|
|
9
|
+
if (!cart_id) {
|
|
10
|
+
return res.status(400).json({ error: "cart_id is required" })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const query = req.scope.resolve("query")
|
|
15
|
+
const { data: carts } = await query.graph({
|
|
16
|
+
entity: "cart",
|
|
17
|
+
fields: [
|
|
18
|
+
"id",
|
|
19
|
+
"payment_collection.payment_sessions.id",
|
|
20
|
+
"payment_collection.payment_sessions.data",
|
|
21
|
+
"payment_collection.payment_sessions.status",
|
|
22
|
+
"payment_collection.payment_sessions.provider_id",
|
|
23
|
+
"payment_collection.payment_sessions.created_at",
|
|
24
|
+
"payment_collection.payment_sessions.amount",
|
|
25
|
+
"payment_collection.payment_sessions.currency_code",
|
|
26
|
+
],
|
|
27
|
+
filters: { id: cart_id },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const sessions = carts?.[0]?.payment_collection?.payment_sessions || []
|
|
31
|
+
const session = sessions
|
|
32
|
+
.filter((s: any) => String(s.provider_id || "").includes("paypal"))
|
|
33
|
+
.sort(
|
|
34
|
+
(a: any, b: any) =>
|
|
35
|
+
new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
|
|
36
|
+
)[0]
|
|
37
|
+
|
|
38
|
+
if (!session) {
|
|
39
|
+
return res.status(400).json({ error: "No PayPal payment session found for cart" })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If already authorized or captured, nothing to do
|
|
43
|
+
const currentStatus = String(session.status || "")
|
|
44
|
+
if (currentStatus === "authorized" || currentStatus === "captured") {
|
|
45
|
+
console.info("[paypal-complete] session already in terminal status:", currentStatus)
|
|
46
|
+
return res.json({ success: true, session_id: session.id, status: currentStatus })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const paymentModule = req.scope.resolve(Modules.PAYMENT) as IPaymentModuleService
|
|
50
|
+
|
|
51
|
+
// Read LIVE session data from DB — gets the latest state written by capture-order
|
|
52
|
+
// (avoids overwriting authorization_id / capture_id that capture-order just saved)
|
|
53
|
+
const [liveSession] = await paymentModule.listPaymentSessions(
|
|
54
|
+
{ id: [session.id] },
|
|
55
|
+
{ take: 1 }
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const liveData = (liveSession?.data || {}) as Record<string, any>
|
|
59
|
+
|
|
60
|
+
// Determine payment action from settings
|
|
61
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
62
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
63
|
+
const settingsData =
|
|
64
|
+
settings && typeof settings === "object" && "data" in settings
|
|
65
|
+
? ((settings as { data?: Record<string, any> }).data ?? {})
|
|
66
|
+
: {}
|
|
67
|
+
const additionalSettings = (settingsData.additional_settings || {}) as Record<string, any>
|
|
68
|
+
const paymentAction =
|
|
69
|
+
typeof additionalSettings.paymentAction === "string"
|
|
70
|
+
? additionalSettings.paymentAction
|
|
71
|
+
: "capture"
|
|
72
|
+
|
|
73
|
+
const timestampKey = paymentAction === "authorize" ? "authorized_at" : "captured_at"
|
|
74
|
+
|
|
75
|
+
// Only write timestamp if not already present — avoids overwriting capture-order's data
|
|
76
|
+
if (!liveData[timestampKey]) {
|
|
77
|
+
await (paymentModule as any).updatePaymentSession({
|
|
78
|
+
id: session.id,
|
|
79
|
+
data: {
|
|
80
|
+
...liveData,
|
|
81
|
+
[timestampKey]: new Date().toISOString(),
|
|
82
|
+
},
|
|
83
|
+
amount: liveSession?.amount ?? session.amount,
|
|
84
|
+
currency_code: liveSession?.currency_code ?? session.currency_code,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Trigger Medusa's payment state machine — the only correct way to move
|
|
89
|
+
// a session from "pending" → "authorized".
|
|
90
|
+
// Calls the provider's authorizePayment() which checks for
|
|
91
|
+
// authorization_id / authorized_at in data and returns status: "authorized".
|
|
92
|
+
try {
|
|
93
|
+
await (paymentModule as any).authorizePaymentSession(session.id, {})
|
|
94
|
+
console.info("[paypal-complete] authorizePaymentSession succeeded for session", session.id)
|
|
95
|
+
} catch (e: any) {
|
|
96
|
+
// Throws if session is already authorized/captured — that's expected and fine
|
|
97
|
+
console.warn("[paypal-complete] authorizePaymentSession non-fatal:", e?.message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return res.json({ success: true, session_id: session.id })
|
|
101
|
+
} catch (e: any) {
|
|
102
|
+
console.error("[paypal-complete] error:", e?.message || e)
|
|
103
|
+
return res.status(500).json({ error: e?.message || "Internal error" })
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,85 +1,149 @@
|
|
|
1
|
-
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
-
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
-
import {
|
|
4
|
-
computeNextRetryAt,
|
|
5
|
-
isAllowedEventType,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export default async function paypalWebhookRetry(container: MedusaContainer) {
|
|
12
|
-
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
13
|
-
const now = Date.now()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
computeNextRetryAt,
|
|
5
|
+
isAllowedEventType,
|
|
6
|
+
isRetryableError,
|
|
7
|
+
MAX_WEBHOOK_ATTEMPTS,
|
|
8
|
+
processPayPalWebhookEvent,
|
|
9
|
+
} from "../modules/paypal/webhook-processor"
|
|
10
|
+
|
|
11
|
+
export default async function paypalWebhookRetry(container: MedusaContainer) {
|
|
12
|
+
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
|
|
15
|
+
const candidates = await paypal.listPayPalWebhookEvents({ status: "failed" })
|
|
16
|
+
if (!candidates?.length) return
|
|
17
|
+
|
|
18
|
+
console.info(
|
|
19
|
+
`[PayPal] webhook-retry: evaluating ${candidates.length} failed event(s)`
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
for (const event of candidates) {
|
|
23
|
+
const nextRetryAt = event?.next_retry_at
|
|
24
|
+
? new Date(event.next_retry_at).getTime()
|
|
25
|
+
: null
|
|
26
|
+
if (!nextRetryAt || nextRetryAt > now) continue
|
|
27
|
+
|
|
28
|
+
const attemptCount = Number(event.attempt_count || 0)
|
|
29
|
+
|
|
30
|
+
if (attemptCount >= MAX_WEBHOOK_ATTEMPTS) {
|
|
31
|
+
await paypal
|
|
32
|
+
.updateWebhookEventRecord({
|
|
33
|
+
id: event.id,
|
|
34
|
+
status: "dead_letter",
|
|
35
|
+
next_retry_at: null,
|
|
36
|
+
last_error: `Exceeded max attempts (${MAX_WEBHOOK_ATTEMPTS})`,
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {})
|
|
39
|
+
console.warn("[PayPal] webhook-retry: dead-lettered (max attempts)", {
|
|
40
|
+
id: event.id,
|
|
41
|
+
event_type: event.event_type,
|
|
42
|
+
attempts: attemptCount,
|
|
43
|
+
})
|
|
44
|
+
await paypal.recordMetric("webhook_dead_letter").catch(() => {})
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await paypal
|
|
49
|
+
.updateWebhookEventRecord({
|
|
50
|
+
id: event.id,
|
|
51
|
+
status: "processing",
|
|
52
|
+
attempt_count: attemptCount + 1,
|
|
53
|
+
next_retry_at: null,
|
|
54
|
+
last_error: null,
|
|
55
|
+
})
|
|
56
|
+
.catch(() => {})
|
|
57
|
+
|
|
58
|
+
const eventType = String(event.event_type || "")
|
|
59
|
+
|
|
60
|
+
if (!isAllowedEventType(eventType)) {
|
|
61
|
+
await paypal
|
|
62
|
+
.updateWebhookEventRecord({
|
|
63
|
+
id: event.id,
|
|
64
|
+
status: "ignored",
|
|
65
|
+
processed_at: new Date(),
|
|
66
|
+
})
|
|
67
|
+
.catch(() => {})
|
|
68
|
+
console.info("[PayPal] webhook-retry: ignored unsupported event type", {
|
|
69
|
+
id: event.id,
|
|
70
|
+
event_type: eventType,
|
|
71
|
+
})
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const payload = (event.payload || {}) as Record<string, any>
|
|
77
|
+
const processed = await processPayPalWebhookEvent(container, { eventType, payload })
|
|
78
|
+
|
|
79
|
+
await paypal
|
|
80
|
+
.updateWebhookEventRecord({
|
|
81
|
+
id: event.id,
|
|
82
|
+
status: "processed",
|
|
83
|
+
processed_at: new Date(),
|
|
84
|
+
resource_id:
|
|
85
|
+
processed.refundId || processed.captureId || processed.orderId || null,
|
|
86
|
+
})
|
|
87
|
+
.catch(() => {})
|
|
88
|
+
|
|
89
|
+
console.info("[PayPal] webhook-retry: processed successfully", {
|
|
90
|
+
id: event.id,
|
|
91
|
+
event_type: eventType,
|
|
92
|
+
attempt: attemptCount + 1,
|
|
93
|
+
order_id: processed.orderId,
|
|
94
|
+
capture_id: processed.captureId,
|
|
95
|
+
cart_id: processed.cartId,
|
|
96
|
+
session_updated: processed.sessionUpdated,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await paypal.recordMetric("webhook_retry_success").catch(() => {})
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
const retryable = isRetryableError(error)
|
|
102
|
+
const nextAttempt = attemptCount + 1
|
|
103
|
+
|
|
104
|
+
if (!retryable || nextAttempt >= MAX_WEBHOOK_ATTEMPTS) {
|
|
105
|
+
await paypal
|
|
106
|
+
.updateWebhookEventRecord({
|
|
107
|
+
id: event.id,
|
|
108
|
+
status: "dead_letter",
|
|
109
|
+
attempt_count: nextAttempt,
|
|
110
|
+
next_retry_at: null,
|
|
111
|
+
last_error: error?.message || String(error),
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {})
|
|
114
|
+
console.error("[PayPal] webhook-retry: dead-lettered after error", {
|
|
115
|
+
id: event.id,
|
|
116
|
+
event_type: eventType,
|
|
117
|
+
attempt: nextAttempt,
|
|
118
|
+
retryable,
|
|
119
|
+
error: error?.message,
|
|
120
|
+
})
|
|
121
|
+
await paypal.recordMetric("webhook_dead_letter").catch(() => {})
|
|
122
|
+
} else {
|
|
123
|
+
const nextRetry = computeNextRetryAt(nextAttempt)
|
|
124
|
+
await paypal
|
|
125
|
+
.updateWebhookEventRecord({
|
|
126
|
+
id: event.id,
|
|
127
|
+
status: "failed",
|
|
128
|
+
attempt_count: nextAttempt,
|
|
129
|
+
next_retry_at: nextRetry,
|
|
130
|
+
last_error: error?.message || String(error),
|
|
131
|
+
})
|
|
132
|
+
.catch(() => {})
|
|
133
|
+
console.warn("[PayPal] webhook-retry: scheduled retry", {
|
|
134
|
+
id: event.id,
|
|
135
|
+
event_type: eventType,
|
|
136
|
+
attempt: nextAttempt,
|
|
137
|
+
next_retry_at: nextRetry?.toISOString(),
|
|
138
|
+
error: error?.message,
|
|
139
|
+
})
|
|
140
|
+
await paypal.recordMetric("webhook_retry_failed").catch(() => {})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const config = {
|
|
147
|
+
name: "paypal-webhook-retry",
|
|
148
|
+
schedule: "*/10 * * * *",
|
|
149
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Migration } from "@medusajs/framework/mikro-orm/migrations"
|
|
2
|
+
|
|
3
|
+
export class Migration20270201000000 extends Migration {
|
|
4
|
+
async up(): Promise<void> {
|
|
5
|
+
this.addSql(`
|
|
6
|
+
CREATE INDEX IF NOT EXISTS "idx_paypal_webhook_event_status_retry"
|
|
7
|
+
ON "paypal_webhook_event" ("status", "next_retry_at")
|
|
8
|
+
WHERE "status" = 'failed';
|
|
9
|
+
`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async down(): Promise<void> {
|
|
13
|
+
this.addSql(`
|
|
14
|
+
DROP INDEX IF EXISTS "idx_paypal_webhook_event_status_retry";
|
|
15
|
+
`)
|
|
16
|
+
}
|
|
17
|
+
}
|