better-auth-mercadopago 0.1.1
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 +127 -0
- package/SECURITY.md +469 -0
- package/dist/client.d.mts +4 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +334 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +309 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +1277 -0
- package/dist/index.d.ts +1277 -0
- package/dist/index.js +1677 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1648 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Mercado Pago Plugin for Better Auth
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
A robust and type-safe Mercado Pago plugin for [Better Auth](https://better-auth.com). seamless integration for one-time payments, subscriptions, and webhook handling.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 💳 **One-time Payments**: Easy API to create payments.
|
|
13
|
+
- 🔄 **Subscriptions**: Full support for recurring payments (PreApproval).
|
|
14
|
+
- 🔗 **Automatic Linking**: Robustly links recurring payments to subscriptions using `external_reference`.
|
|
15
|
+
- 🪝 **Webhook Handling**: Built-in, secure webhook processing for payment updates.
|
|
16
|
+
- 🛡️ **Type Safe**: Fully typed requests and responses for a great developer experience.
|
|
17
|
+
- 👥 **Customer Management**: Automatically manages Mercado Pago customers for your users.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @better-auth/mercadopago
|
|
23
|
+
# or
|
|
24
|
+
npm install @better-auth/mercadopago
|
|
25
|
+
# or
|
|
26
|
+
yarn add @better-auth/mercadopago
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Configure the Plugin
|
|
32
|
+
|
|
33
|
+
Add the plugin to your Better Auth configuration. You need your Mercado Pago Access Token.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { betterAuth } from "better-auth";
|
|
37
|
+
import { mercadoPagoPlugin } from "@better-auth/mercadopago";
|
|
38
|
+
|
|
39
|
+
export const auth = betterAuth({
|
|
40
|
+
// ... other config
|
|
41
|
+
plugins: [
|
|
42
|
+
mercadoPagoPlugin({
|
|
43
|
+
accessToken: process.env.MP_ACCESS_TOKEN!,
|
|
44
|
+
onSubscriptionUpdate: async ({ subscription, status, reason, mpPreapproval }) => {
|
|
45
|
+
// Handle subscription status changes (e.g., update DB)
|
|
46
|
+
console.log(`Subscription ${subscription.id} is now ${status}`);
|
|
47
|
+
},
|
|
48
|
+
onPaymentUpdate: async ({ payment, status, mpPayment }) => {
|
|
49
|
+
// Handle one-time payment updates
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Client-Side Usage
|
|
57
|
+
|
|
58
|
+
The plugin exposes client-side methods to create payments and subscriptions.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { createAuthClient } from "better-auth/client";
|
|
62
|
+
import { mercadoPagoClient } from "@better-auth/mercadopago/client";
|
|
63
|
+
|
|
64
|
+
const authClient = createAuthClient({
|
|
65
|
+
plugins: [mercadoPagoClient()]
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Create a Subscription
|
|
69
|
+
async function subscribe() {
|
|
70
|
+
const { data, error } = await authClient.mercadoPago.createSubscription({
|
|
71
|
+
reason: "Pro Plan",
|
|
72
|
+
autoRecurring: {
|
|
73
|
+
frequency: 1,
|
|
74
|
+
frequencyType: "months",
|
|
75
|
+
transactionAmount: 10,
|
|
76
|
+
currencyId: "ARS"
|
|
77
|
+
},
|
|
78
|
+
backUrl: "https://your-app.com/success"
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (data) {
|
|
82
|
+
window.location.href = data.init_point; // Redirect to Mercado Pago
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage Guide
|
|
88
|
+
|
|
89
|
+
### Subscriptions
|
|
90
|
+
|
|
91
|
+
To create a subscription, you use `createSubscription`. The plugin handles the complexity of:
|
|
92
|
+
1. Creating a PreApproval Plan.
|
|
93
|
+
2. Creating a PreApproval (Subscription) linked to that plan.
|
|
94
|
+
3. Returning the `init_point` for user redirection.
|
|
95
|
+
|
|
96
|
+
### Webhooks
|
|
97
|
+
|
|
98
|
+
The plugin automatically exposes a webhook endpoint at `/api/auth/mercado-pago/webhook`.
|
|
99
|
+
You must configure this URL in your Mercado Pago Dashboard (or use ngrok for local dev).
|
|
100
|
+
|
|
101
|
+
**Events Handled:**
|
|
102
|
+
- `subscription_authorized_payment`: recurring payments.
|
|
103
|
+
- `payment`: one-time payments.
|
|
104
|
+
- `preapproval`: subscription status updates.
|
|
105
|
+
|
|
106
|
+
## API Reference
|
|
107
|
+
|
|
108
|
+
### `mercadoPagoPlugin(options)`
|
|
109
|
+
|
|
110
|
+
**Options:**
|
|
111
|
+
- `accessToken` (required): Your Mercado Pago Production or Sandbox Access Token.
|
|
112
|
+
- `onSubscriptionUpdate`: Callback when a subscription changes status.
|
|
113
|
+
- `onPaymentUpdate`: Callback when a payment changes status.
|
|
114
|
+
- `onSubscriptionPayment`: Callback when a recurring payment is received.
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions are welcome!
|
|
119
|
+
|
|
120
|
+
1. Clone the repo
|
|
121
|
+
2. Install dependencies: `pnpm install`
|
|
122
|
+
3. Run tests: `pnpm test`
|
|
123
|
+
4. Create a changeset for your changes: `pnpm changeset`
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# Security Guide
|
|
2
|
+
|
|
3
|
+
This document outlines the security measures implemented in the Mercado Pago Better Auth plugin.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Webhook Security](#webhook-security)
|
|
8
|
+
2. [Rate Limiting](#rate-limiting)
|
|
9
|
+
3. [Input Validation](#input-validation)
|
|
10
|
+
4. [Idempotency](#idempotency)
|
|
11
|
+
5. [Error Handling](#error-handling)
|
|
12
|
+
6. [Attack Prevention](#attack-prevention)
|
|
13
|
+
7. [Best Practices](#best-practices)
|
|
14
|
+
|
|
15
|
+
## Webhook Security
|
|
16
|
+
|
|
17
|
+
### Signature Verification
|
|
18
|
+
|
|
19
|
+
All webhooks are verified using HMAC SHA256 signatures to ensure they come from Mercado Pago.
|
|
20
|
+
|
|
21
|
+
**Configuration:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
mercadoPago({
|
|
25
|
+
webhookSecret: process.env.MERCADO_PAGO_WEBHOOK_SECRET!,
|
|
26
|
+
// ...
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**How it works:**
|
|
31
|
+
|
|
32
|
+
1. Mercado Pago sends a signature in the `x-signature` header
|
|
33
|
+
2. Format: `ts=1234567890,v1=hash`
|
|
34
|
+
3. The plugin verifies using: `HMAC-SHA256(secret, "id:DATA_ID;request-id:REQUEST_ID;ts:TIMESTAMP;")`
|
|
35
|
+
4. Invalid signatures are rejected with 401
|
|
36
|
+
|
|
37
|
+
**Get your webhook secret:**
|
|
38
|
+
- Go to https://www.mercadopago.com/developers/panel/notifications/webhooks
|
|
39
|
+
- Click on your webhook
|
|
40
|
+
- Copy the "Secret" value
|
|
41
|
+
|
|
42
|
+
### Webhook Topics Validation
|
|
43
|
+
|
|
44
|
+
Only valid webhook topics are processed:
|
|
45
|
+
|
|
46
|
+
- `payment`
|
|
47
|
+
- `merchant_order`
|
|
48
|
+
- `subscription_preapproval`
|
|
49
|
+
- `subscription_preapproval_plan`
|
|
50
|
+
- `subscription_authorized_payment`
|
|
51
|
+
|
|
52
|
+
Invalid topics are logged and ignored.
|
|
53
|
+
|
|
54
|
+
### Idempotency Protection
|
|
55
|
+
|
|
56
|
+
Prevents duplicate webhook processing:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Each webhook is processed only once
|
|
60
|
+
const webhookId = `webhook:${notification.id}:${notification.type}`;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Webhooks are cached for 24 hours to prevent reprocessing.
|
|
64
|
+
|
|
65
|
+
## Rate Limiting
|
|
66
|
+
|
|
67
|
+
### Per-User Limits
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Payment creation: 10 per minute per user
|
|
71
|
+
const rateLimitKey = `payment:create:${userId}`;
|
|
72
|
+
rateLimiter.check(rateLimitKey, 10, 60 * 1000);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Global Limits
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Webhooks: 1000 per minute globally
|
|
79
|
+
rateLimiter.check("webhook:global", 1000, 60 * 1000);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Production Recommendations
|
|
83
|
+
|
|
84
|
+
For production, replace the in-memory rate limiter with Redis:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import Redis from "ioredis";
|
|
88
|
+
|
|
89
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
90
|
+
|
|
91
|
+
class RedisRateLimiter {
|
|
92
|
+
async check(key: string, max: number, windowMs: number): Promise<boolean> {
|
|
93
|
+
const count = await redis.incr(key);
|
|
94
|
+
|
|
95
|
+
if (count === 1) {
|
|
96
|
+
await redis.pexpire(key, windowMs);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return count <= max;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Input Validation
|
|
105
|
+
|
|
106
|
+
### Amount Validation
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Prevents negative or excessive amounts
|
|
110
|
+
ValidationRules.amount(amount); // 0 < amount <= 999,999,999
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Currency Validation
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Only accepts valid currencies
|
|
117
|
+
ValidationRules.currency("ARS"); // true
|
|
118
|
+
ValidationRules.currency("INVALID"); // false
|
|
119
|
+
|
|
120
|
+
// Supported: ARS, BRL, CLP, MXN, COP, PEN, UYU, USD
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### URL Validation
|
|
124
|
+
|
|
125
|
+
Prevents open redirect vulnerabilities:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
validateCallbackUrl(
|
|
129
|
+
"https://myapp.com/callback",
|
|
130
|
+
["myapp.com", "*.myapp.com"]
|
|
131
|
+
); // true
|
|
132
|
+
|
|
133
|
+
validateCallbackUrl(
|
|
134
|
+
"https://evil.com/phishing",
|
|
135
|
+
["myapp.com"]
|
|
136
|
+
); // false
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Configuration:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
mercadoPago({
|
|
143
|
+
trustedOrigins: [
|
|
144
|
+
"https://myapp.com",
|
|
145
|
+
"https://*.myapp.com", // Wildcard subdomains
|
|
146
|
+
],
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Metadata Sanitization
|
|
151
|
+
|
|
152
|
+
Prevents prototype pollution and XSS:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const sanitized = sanitizeMetadata({
|
|
156
|
+
orderId: "123",
|
|
157
|
+
__proto__: { isAdmin: true }, // ❌ Removed
|
|
158
|
+
userInput: "<script>alert('xss')</script>", // Kept but limited to 5000 chars
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Idempotency
|
|
163
|
+
|
|
164
|
+
### Payment Creation
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const { data } = await authClient.mercadoPago.createPayment({
|
|
168
|
+
items: [/* ... */],
|
|
169
|
+
idempotencyKey: "unique-key-123", // Same key = same result
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Rules:**
|
|
174
|
+
|
|
175
|
+
- Key format: UUID v4 or alphanumeric (8-64 chars)
|
|
176
|
+
- Cached for 24 hours
|
|
177
|
+
- Same key = returns cached response (no duplicate payments)
|
|
178
|
+
|
|
179
|
+
**Example:**
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// First request
|
|
183
|
+
const result1 = await createPayment({
|
|
184
|
+
idempotencyKey: "abc-123",
|
|
185
|
+
items: [{ title: "Product", quantity: 1, unitPrice: 100 }]
|
|
186
|
+
});
|
|
187
|
+
// Creates new payment
|
|
188
|
+
|
|
189
|
+
// Second request (network retry, user double-click, etc.)
|
|
190
|
+
const result2 = await createPayment({
|
|
191
|
+
idempotencyKey: "abc-123",
|
|
192
|
+
items: [{ title: "Product", quantity: 1, unitPrice: 100 }]
|
|
193
|
+
});
|
|
194
|
+
// Returns cached result, no duplicate payment ✅
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Error Handling
|
|
198
|
+
|
|
199
|
+
### Graceful Degradation
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
try {
|
|
203
|
+
await mpAPI.createPayment();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
handleMercadoPagoError(error);
|
|
206
|
+
// Converts MP errors to Better Auth APIError format
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Error Types
|
|
211
|
+
|
|
212
|
+
| Status | Better Auth Type | Use Case |
|
|
213
|
+
|--------|-----------------|----------|
|
|
214
|
+
| 400 | BAD_REQUEST | Invalid input |
|
|
215
|
+
| 401 | UNAUTHORIZED | Invalid credentials |
|
|
216
|
+
| 403 | FORBIDDEN | Not allowed |
|
|
217
|
+
| 404 | NOT_FOUND | Resource missing |
|
|
218
|
+
| 429 | TOO_MANY_REQUESTS | Rate limited |
|
|
219
|
+
| 500 | INTERNAL_SERVER_ERROR | Server error |
|
|
220
|
+
|
|
221
|
+
### Webhook Error Handling
|
|
222
|
+
|
|
223
|
+
Webhooks return 200 even on processing errors to prevent infinite retries:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
try {
|
|
227
|
+
await processWebhook(notification);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
logger.error("Webhook processing failed", { error });
|
|
230
|
+
// Still return 200 to acknowledge receipt
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return ctx.json({ received: true });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Attack Prevention
|
|
237
|
+
|
|
238
|
+
### SQL Injection
|
|
239
|
+
|
|
240
|
+
✅ **Protected** - Uses parameterized queries via Better Auth adapter:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// ✅ Safe
|
|
244
|
+
await ctx.context.adapter.findOne({
|
|
245
|
+
model: "mercadoPagoPayment",
|
|
246
|
+
where: [{ field: "id", value: userInput }] // Parameterized
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ❌ Never do this
|
|
250
|
+
await db.raw(`SELECT * FROM payments WHERE id = '${userInput}'`);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### XSS (Cross-Site Scripting)
|
|
254
|
+
|
|
255
|
+
✅ **Protected** - Metadata is sanitized and limited to 5000 chars:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const sanitized = sanitizeMetadata(userInput);
|
|
259
|
+
// Scripts, iframes, etc. are stored but limited
|
|
260
|
+
// Rendering layer must still escape HTML
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### CSRF (Cross-Site Request Forgery)
|
|
264
|
+
|
|
265
|
+
✅ **Protected** - Better Auth handles CSRF tokens automatically:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Better Auth adds CSRF protection to all POST endpoints
|
|
269
|
+
// No additional configuration needed
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Prototype Pollution
|
|
273
|
+
|
|
274
|
+
✅ **Protected** - Dangerous keys are filtered:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
sanitizeMetadata({
|
|
278
|
+
__proto__: { isAdmin: true }, // ❌ Removed
|
|
279
|
+
constructor: { ... }, // ❌ Removed
|
|
280
|
+
prototype: { ... }, // ❌ Removed
|
|
281
|
+
normalKey: "value", // ✅ Kept
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Timing Attacks
|
|
286
|
+
|
|
287
|
+
✅ **Protected** - Uses constant-time comparison for signatures:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// ❌ Vulnerable
|
|
291
|
+
if (signature === expectedSignature) { }
|
|
292
|
+
|
|
293
|
+
// ✅ Safe
|
|
294
|
+
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Payment Amount Manipulation
|
|
298
|
+
|
|
299
|
+
✅ **Protected** - Validates amounts haven't been tampered:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// In webhook
|
|
303
|
+
if (!validatePaymentAmount(storedAmount, mpPayment.amount)) {
|
|
304
|
+
throw new Error("Amount mismatch - possible tampering");
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Replay Attacks
|
|
309
|
+
|
|
310
|
+
✅ **Protected** - Webhooks are deduplicated:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const webhookId = `webhook:${id}:${type}`;
|
|
314
|
+
if (alreadyProcessed(webhookId)) {
|
|
315
|
+
return; // Ignore duplicate
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Best Practices
|
|
320
|
+
|
|
321
|
+
### 1. Always Use HTTPS in Production
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// In production, validate URLs are HTTPS
|
|
325
|
+
if (process.env.NODE_ENV === "production" && !url.startsWith("https://")) {
|
|
326
|
+
throw new Error("URLs must use HTTPS in production");
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 2. Set Strict Trusted Origins
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
mercadoPago({
|
|
334
|
+
trustedOrigins: [
|
|
335
|
+
"https://myapp.com", // ✅ Specific domain
|
|
336
|
+
"https://*.myapp.com", // ✅ Wildcard subdomains
|
|
337
|
+
// ❌ Don't use wildcards like "*" or "*.com"
|
|
338
|
+
],
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 3. Use Environment Variables
|
|
343
|
+
|
|
344
|
+
```env
|
|
345
|
+
# ✅ Good
|
|
346
|
+
MERCADO_PAGO_ACCESS_TOKEN=APP_USR-xxx
|
|
347
|
+
MERCADO_PAGO_WEBHOOK_SECRET=xxx
|
|
348
|
+
APP_URL=https://myapp.com
|
|
349
|
+
|
|
350
|
+
# ❌ Bad - never hardcode secrets
|
|
351
|
+
const accessToken = "APP_USR-123456...";
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### 4. Monitor Webhook Failures
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
mercadoPago({
|
|
358
|
+
onPaymentUpdate: async ({ payment, status }) => {
|
|
359
|
+
if (status === "rejected") {
|
|
360
|
+
// Alert your team
|
|
361
|
+
await alerting.notify("Payment rejected", { payment });
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 5. Implement Proper Logging
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Log security events
|
|
371
|
+
logger.warn("Invalid webhook signature", {
|
|
372
|
+
xSignature,
|
|
373
|
+
xRequestId,
|
|
374
|
+
ip: request.ip,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Don't log sensitive data
|
|
378
|
+
// ❌ logger.info("Payment", { cardNumber: "..." });
|
|
379
|
+
// ✅ logger.info("Payment", { paymentId: "..." });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 6. Use Redis for Production
|
|
383
|
+
|
|
384
|
+
Replace in-memory stores with Redis:
|
|
385
|
+
|
|
386
|
+
- Rate limiting
|
|
387
|
+
- Idempotency cache
|
|
388
|
+
- Webhook deduplication
|
|
389
|
+
|
|
390
|
+
### 7. Regular Security Audits
|
|
391
|
+
|
|
392
|
+
- Review logs for suspicious patterns
|
|
393
|
+
- Update dependencies regularly
|
|
394
|
+
- Test webhook signature validation
|
|
395
|
+
- Validate rate limits are working
|
|
396
|
+
|
|
397
|
+
### 8. Implement Monitoring
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Track failed webhooks
|
|
401
|
+
if (webhookProcessingFailed) {
|
|
402
|
+
metrics.increment("webhook.failed", {
|
|
403
|
+
type: notification.type,
|
|
404
|
+
error: error.message,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 9. Handle PCI Compliance
|
|
410
|
+
|
|
411
|
+
⚠️ **Never store card data:**
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// ❌ Never do this
|
|
415
|
+
const cardData = {
|
|
416
|
+
number: "4111111111111111",
|
|
417
|
+
cvv: "123",
|
|
418
|
+
expiry: "12/25"
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// ✅ Let Mercado Pago handle it
|
|
422
|
+
// Users enter card details directly in MP's hosted checkout
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 10. Database Security
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Use row-level security
|
|
429
|
+
CREATE POLICY user_payments ON mercadoPagoPayment
|
|
430
|
+
FOR SELECT
|
|
431
|
+
USING (userId = current_user_id());
|
|
432
|
+
|
|
433
|
+
// Encrypt sensitive fields
|
|
434
|
+
encryptedMetadata = encrypt(metadata, encryptionKey);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Security Checklist
|
|
438
|
+
|
|
439
|
+
Before deploying to production:
|
|
440
|
+
|
|
441
|
+
- [ ] Webhook secret configured
|
|
442
|
+
- [ ] HTTPS enforced
|
|
443
|
+
- [ ] Trusted origins set
|
|
444
|
+
- [ ] Rate limiting configured (Redis recommended)
|
|
445
|
+
- [ ] Error logging implemented
|
|
446
|
+
- [ ] Monitoring and alerts set up
|
|
447
|
+
- [ ] Never log sensitive data (cards, tokens)
|
|
448
|
+
- [ ] Database has proper indexes
|
|
449
|
+
- [ ] Row-level security enabled (if using Postgres)
|
|
450
|
+
- [ ] Regular dependency updates scheduled
|
|
451
|
+
- [ ] Incident response plan documented
|
|
452
|
+
|
|
453
|
+
## Reporting Security Issues
|
|
454
|
+
|
|
455
|
+
If you discover a security vulnerability:
|
|
456
|
+
|
|
457
|
+
1. **DO NOT** open a public GitHub issue
|
|
458
|
+
2. Email security@yourcompany.com
|
|
459
|
+
3. Include:
|
|
460
|
+
- Description of the vulnerability
|
|
461
|
+
- Steps to reproduce
|
|
462
|
+
- Potential impact
|
|
463
|
+
- Suggested fix (if any)
|
|
464
|
+
|
|
465
|
+
We'll respond within 48 hours.
|
|
466
|
+
|
|
467
|
+
## License
|
|
468
|
+
|
|
469
|
+
MIT
|
package/dist/client.d.ts
ADDED