npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qazuor/qzpay-mercadopago

v1.1.1

Published

MercadoPago payment adapter for QZPay billing library

Downloads

159

Readme

@qazuor/qzpay-mercadopago

MercadoPago payment provider adapter for the QZPay billing library.

Installation

pnpm add @qazuor/qzpay-mercadopago mercadopago

Features

  • Payment Processing: Create, capture, cancel payments
  • Subscriptions: Create and manage recurring payments (preapprovals)
  • 3D Secure Support: Secure card payments with challenge flow
  • Refunds: Full and partial refunds
  • Webhooks/IPN: Event handling and HMAC signature verification
  • Error Handling: Comprehensive error mapping with QZPayMercadoPagoError
  • Multiple Payment Methods: Credit/debit cards, PIX, bank transfers

Quick Start

Basic Setup

import { createQZPayMercadoPagoAdapter } from '@qazuor/qzpay-mercadopago';

const mpAdapter = createQZPayMercadoPagoAdapter({
  accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN!,
  webhookSecret: process.env.MERCADOPAGO_WEBHOOK_SECRET, // Optional
  timeout: 5000 // Optional, default 5000ms
});

With QZPayBilling

import { QZPayBilling } from '@qazuor/qzpay-core';
import { createDrizzleAdapter } from '@qazuor/qzpay-drizzle';

const billing = new QZPayBilling({
  storage: createDrizzleAdapter({ db }),
  paymentAdapter: mpAdapter
});

Usage

Payment Processing

First-time Payment (with token)

// Create a payment with a token (generated on frontend)
const payment = await mpAdapter.payments.create('customer_mp_id', {
  amount: 10000, // $100.00 in cents
  currency: 'ARS',
  token: 'card_token_from_frontend', // From MercadoPago.js
  paymentMethodId: 'visa', // or 'master', 'pix', etc.
  saveCard: true // Optional: save card for future use
});

// Retrieve payment status
const status = await mpAdapter.payments.retrieve(payment.id);

// Capture an authorized payment
await mpAdapter.payments.capture(payment.id);

// Refund a payment (partial or full)
await mpAdapter.payments.refund({ amount: 5000 }, payment.id);

// Cancel a payment
await mpAdapter.payments.cancel(payment.id);

Recurring Payment (with saved card)

// Charge a previously saved card
// The adapter automatically generates a new token from the card_id
const payment = await mpAdapter.payments.create('customer_mp_id', {
  amount: 10000, // $100.00 in cents
  currency: 'ARS',
  cardId: 'saved_card_id_123', // Previously saved card
  paymentMethodId: 'visa', // Optional
  installments: 1 // Optional
});

Checkout Sessions

// Create a checkout session with optional notification URL
const checkout = await mpAdapter.checkout.create(
  {
    mode: 'payment',
    successUrl: 'https://example.com/success',
    cancelUrl: 'https://example.com/cancel',
    lineItems: [{ priceId: 'price_1', quantity: 1, description: 'Product' }],
    notificationUrl: 'https://example.com/webhooks/mercadopago', // Optional webhook URL
    customerEmail: '[email protected]', // Optional
    expiresInMinutes: 30 // Optional expiration
  },
  ['price_1'] // Provider price IDs
);

console.log('Checkout URL:', checkout.url);

// Retrieve checkout session
const session = await mpAdapter.checkout.retrieve(checkout.id);

// Expire a checkout session
await mpAdapter.checkout.expire(checkout.id);

Notification URL: When provided, MercadoPago will send IPN (Instant Payment Notification) events to this URL for payment status updates. This is useful for tracking payment completion server-side.

Subscriptions (Preapprovals)

// Create a subscription
const subscription = await mpAdapter.subscriptions.create('customer_mp_id', {
  priceId: 'price_plan_basic', // Your plan identifier
  trialDays: 14 // Optional trial period
});

// Retrieve subscription
const sub = await mpAdapter.subscriptions.retrieve(subscription.id);

// Update subscription
await mpAdapter.subscriptions.update(subscription.id, {
  priceId: 'price_plan_premium' // Upgrade/downgrade
});

// Cancel subscription
await mpAdapter.subscriptions.cancel(subscription.id);

3D Secure

import {
  isMP3DSRequired,
  extractMP3DSResult,
  extractMP3DSPaymentInfo
} from '@qazuor/qzpay-mercadopago';

// Handle 3DS in webhook
function handlePaymentWebhook(event: QZPayWebhookEvent) {
  const info = extractMP3DSPaymentInfo(event);

  if (info.requires3DS) {
    // Redirect user to challenge
    const challengeUrl = info.challengeUrl;
    return { action: 'redirect', url: challengeUrl };
  }

  if (info.threeDSecure?.status === 'authenticated') {
    // Payment authenticated successfully
    return { action: 'complete' };
  }

  if (info.threeDSecure?.status === 'failed') {
    // Authentication failed
    return { action: 'failed', reason: '3DS authentication failed' };
  }
}

Webhook/IPN Handling

import { createQZPayMercadoPagoAdapter } from '@qazuor/qzpay-mercadopago';

const mpAdapter = createQZPayMercadoPagoAdapter({
  accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN!,
  webhookSecret: process.env.MERCADOPAGO_WEBHOOK_SECRET!
});

// Verify and parse webhook
app.post('/webhooks/mercadopago', async (c) => {
  const payload = await c.req.text();
  const signature = c.req.header('x-signature');
  const requestId = c.req.header('x-request-id');

  const event = await mpAdapter.webhooks.verifyAndParse(payload, signature, requestId);

  if (!event) {
    return c.json({ error: 'Invalid signature' }, 401);
  }

  switch (event.type) {
    case 'payment.created':
    case 'payment.updated':
      // Handle payment event
      break;
    case 'subscription.created':
    case 'subscription.updated':
      // Handle subscription event
      break;
  }

  return c.json({ received: true });
});

Saved Card Service

Unified service for saving and managing payment cards with a consistent API.

import { createSavedCardService } from '@qazuor/qzpay-mercadopago';

// Create the service
const cardService = createSavedCardService({
  provider: 'mercadopago',
  mercadopagoAccessToken: process.env.MERCADOPAGO_ACCESS_TOKEN!,
  getProviderCustomerId: async (customerId) => {
    // Resolve your local customer ID to MercadoPago customer ID
    const customer = await db.customers.findById(customerId);
    return customer.mercadopagoCustomerId;
  },
});

// Save a card from token (created on frontend with MercadoPago.js)
const card = await cardService.save({
  customerId: 'local_cus_123',
  token: 'card_token_xxx', // From MercadoPago.createCardToken()
  setAsDefault: true, // Note: App must track this separately
});

console.log(`Card saved: ${card.brand} ending in ${card.last4}`);
console.log(`First 6 digits: ${card.firstSixDigits}`); // Available in MercadoPago

// List all saved cards
const cards = await cardService.list('local_cus_123');
cards.forEach((card) => {
  console.log(`${card.brand} ${card.firstSixDigits}****${card.last4}`);
});

// Remove a card
await cardService.remove('local_cus_123', 'card_id_xxx');

// Note: setDefault() throws an error for MercadoPago
// You must track the default card in your application database

Frontend Integration

// Create card token on frontend using MercadoPago.js
const mp = new MercadoPago('PUBLIC_KEY');
const cardForm = mp.cardForm({
  amount: '100.5',
  iframe: true,
  form: {
    id: 'form-checkout',
    cardNumber: {
      id: 'form-checkout__cardNumber',
      placeholder: 'Card number',
    },
    expirationDate: {
      id: 'form-checkout__expirationDate',
      placeholder: 'MM/YY',
    },
    securityCode: {
      id: 'form-checkout__securityCode',
      placeholder: 'CVV',
    },
    cardholderName: {
      id: 'form-checkout__cardholderName',
      placeholder: 'Cardholder name',
    },
    issuer: {
      id: 'form-checkout__issuer',
      placeholder: 'Issuer',
    },
    installments: {
      id: 'form-checkout__installments',
      placeholder: 'Installments',
    },
    identificationType: {
      id: 'form-checkout__identificationType',
      placeholder: 'Document type',
    },
    identificationNumber: {
      id: 'form-checkout__identificationNumber',
      placeholder: 'Document number',
    },
  },
  callbacks: {
    onFormMounted: error => {
      if (error) return console.warn('Form mounted handling error: ', error);
      console.log('Form mounted');
    },
    onSubmit: event => {
      event.preventDefault();
      const { token } = cardForm.getCardFormData();

      // Send token to backend
      fetch('/api/cards/save', {
        method: 'POST',
        body: JSON.stringify({
          customerId: 'local_cus_123',
          token: token,
          setAsDefault: true,
        }),
      });
    },
  },
});

SavedCard Type

interface SavedCard {
  id: string;                    // Card ID
  customerId: string;            // Your local customer ID
  providerCustomerId: string;    // MercadoPago customer ID
  provider: 'mercadopago';
  last4: string;                 // Last 4 digits
  brand: string;                 // visa, master, amex, etc.
  expMonth: number;              // 1-12
  expYear: number;               // 4 digits
  isDefault: boolean;            // Always false (track in your DB)
  cardholderName?: string;       // Cardholder name
  firstSixDigits?: string;       // First 6 digits (MercadoPago specific)
  createdAt: Date;               // When the card was saved
}

Important Notes on Default Payment Method

MercadoPago doesn't have a native concept of default payment method at the API level. This means:

  1. The isDefault field will always be false when listing cards
  2. The setAsDefault() method will throw an error
  3. Your application must track the default card ID in your database

Example Database Schema:

interface Customer {
  id: string;
  email: string;
  mercadopagoCustomerId: string;
  defaultMercadoPagoCardId?: string; // Track this yourself
}

When Creating Payments:

// Get customer's default card from your database
const customer = await db.customers.findById(customerId);

if (!customer.defaultMercadoPagoCardId) {
  throw new Error('Customer has no default payment method');
}

// Create payment with explicit card ID
// The adapter will automatically generate a new token from the saved card
const payment = await mpAdapter.payments.create(
  customer.mercadopagoCustomerId,
  {
    amount: 1000,
    currency: 'ARS',
    cardId: customer.defaultMercadoPagoCardId, // Use your tracked card ID
  }
);

Card on File Payment Flow

When using saved cards for recurring payments, QZPay automatically handles token generation:

// 1. Customer saves a card (first time)
const savedCard = await mpAdapter.customers.saveCard(
  customer.mercadopagoCustomerId,
  'card_token_from_frontend'
);

// Store card ID in your database
await db.update(customers)
  .set({ defaultMercadoPagoCardId: savedCard.id })
  .where(eq(customers.id, customerId));

// 2. Later, charge the saved card (recurring payment)
// The adapter automatically:
// - Generates a new card token from the saved card_id using CardToken API
// - Uses that token to create the payment
const payment = await mpAdapter.payments.create(
  customer.mercadopagoCustomerId,
  {
    amount: 2999, // $29.99 in cents
    currency: 'ARS',
    cardId: savedCard.id, // Saved card ID
    paymentMethodId: 'visa', // Optional
    installments: 1 // Optional, default 1
  }
);

// 3. The payment is processed with the saved card
console.log(`Payment ${payment.id} - Status: ${payment.status}`);

Technical Details:

Under the hood, when you provide cardId, the adapter:

  1. Calls the MercadoPago CardToken API with the card_id
  2. Receives a new single-use token
  3. Uses that token to create the payment
  4. Stores the original card_id in payment metadata for tracking

This is the recommended MercadoPago flow for Card on File payments.

Configuration

interface QZPayMercadoPagoConfig {
  /** MercadoPago access token (required) */
  accessToken: string;

  /** Webhook secret for signature verification (optional) */
  webhookSecret?: string;

  /** Request timeout in ms (default: 5000) */
  timeout?: number;

  /** Integrator ID for attribution (optional) */
  integratorId?: string;

  /** Platform ID for marketplace (optional) */
  platformId?: string;
}

API Reference

Adapters

| Adapter | Description | |---------|-------------| | mpAdapter.customers | Customer management | | mpAdapter.subscriptions | Subscription/preapproval management | | mpAdapter.payments | Payment operations | | mpAdapter.checkout | Checkout preferences | | mpAdapter.prices | Price management (in-memory) | | mpAdapter.webhooks | Webhook/IPN handling |


Limitations vs Stripe

MercadoPago has some functional differences compared to Stripe that you should be aware of when designing your integration:

Customer Metadata

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Custom metadata on customers | ✅ Full support | ⚠️ Limited | | Arbitrary key-value pairs | ✅ Up to 50 keys | ❌ Not supported | | Searchable metadata | ✅ Yes | ❌ No |

Workaround: Store extended customer data in your own database linked by provider_customer_id.

Saved Cards

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Save card | ✅ PaymentMethod | ✅ Card token | | List saved cards | ✅ Yes | ✅ Yes | | Remove card | ✅ Yes | ✅ Yes | | Default payment method | ✅ Native API support | ❌ Not supported | | First 6 digits | ❌ No | ✅ Available |

Workaround for default card: Track the default card ID in your application database. See the "Saved Card Service" section for implementation details.

Split Payments (Marketplace)

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Split payments | ✅ Stripe Connect | ⚠️ v2 (prepared) | | Application fees | ✅ Yes | ⚠️ v2 (prepared) | | Direct charges | ✅ Yes | ⚠️ v2 (prepared) | | Destination charges | ✅ Yes | ⚠️ v2 (prepared) |

Note: Split payments are prepared in the adapter but not yet implemented. Use the MercadoPago API directly for now:

// For split payments, use MercadoPago SDK directly
// See: https://www.mercadopago.com.ar/developers/en/docs/split-payments

Subscription Features

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Trial periods | ✅ Native | ✅ Via preapproval | | Proration | ✅ Automatic | ❌ Manual handling | | Pause/resume | ✅ Native | ⚠️ Via status update | | Multiple items | ✅ Yes | ❌ One price per subscription | | Quantity changes | ✅ Yes | ❌ Not supported | | Usage-based billing | ✅ Metered | ❌ Not supported |

Workaround for proration: Calculate prorated amounts manually and apply as credits or adjustments.

Webhook/IPN Differences

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Signature algorithm | HMAC-SHA256 | HMAC-SHA256 | | Timestamp validation | ✅ 5 min tolerance | ⚠️ Manual | | Retry mechanism | ✅ Automatic | ✅ Automatic | | Event types | ~100+ types | ~20 types | | Test mode events | ✅ Yes | ✅ Yes |

Payment Methods

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Credit cards | ✅ Global | ✅ Regional (LATAM) | | Debit cards | ✅ Yes | ✅ Yes | | Bank transfers | ✅ ACH, SEPA | ✅ Regional | | PIX (Brazil) | ❌ No | ✅ Yes | | Boleto (Brazil) | ❌ No | ✅ Yes | | PSE (Colombia) | ❌ No | ✅ Yes | | OXXO (Mexico) | ❌ No | ✅ Yes |

Currency Support

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Multi-currency | ✅ 135+ currencies | ⚠️ Regional currencies | | Currency conversion | ✅ Automatic | ❌ Local only |

Supported MercadoPago currencies: ARS, BRL, CLP, COP, MXN, PEN, UYU, VES

API Rate Limits

| Provider | Limits | |----------|--------| | Stripe | 100 req/sec per key (live), 25 req/sec (test) | | MercadoPago | Not publicly documented, be conservative |

Refund Policies

| Feature | Stripe | MercadoPago | |---------|--------|-------------| | Partial refunds | ✅ Yes | ✅ Yes | | Multiple refunds | ✅ Yes | ✅ Yes | | Refund window | 90 days | Varies by payment method | | Refund to different card | ❌ No | ❌ No |

Migration from Stripe

If you're considering migrating from Stripe to MercadoPago (or supporting both), here are key considerations:

1. Customer Migration

// Stripe customer
const stripeCustomer = await stripe.customers.retrieve('cus_xxx');

// Create in MercadoPago (without metadata)
const mpCustomer = await mpAdapter.customers.create({
  email: stripeCustomer.email,
  name: stripeCustomer.name,
  externalId: `migrated_${stripeCustomer.id}`
});

// Store mapping in your database
await db.insert(customerMappings).values({
  stripeId: stripeCustomer.id,
  mercadoPagoId: mpCustomer,
  metadata: stripeCustomer.metadata // Store locally
});

2. Subscription Migration

// Subscriptions need to be recreated
// MercadoPago preapprovals are different from Stripe subscriptions

const subscription = await mpAdapter.subscriptions.create(mpCustomerId, {
  priceId: yourPriceMapping[stripeSubscription.items[0].price.id]
});

// Handle existing billing cycle manually

3. Webhook Mapping

| Stripe Event | MercadoPago Event | |--------------|-------------------| | payment_intent.succeeded | payment.updated (status=approved) | | payment_intent.payment_failed | payment.updated (status=rejected) | | customer.subscription.created | subscription_preapproval.created | | customer.subscription.updated | subscription_preapproval.updated | | invoice.paid | subscription_authorized_payment.created |

Best Practices

1. Always Store Provider IDs

// Store both internal and provider IDs
const customer = await billing.customers.create({
  email: '[email protected]',
  externalId: 'your_user_123'
});

// customer.providerCustomerId contains MercadoPago ID
await db.update(users)
  .set({ mpCustomerId: customer.providerCustomerId })
  .where(eq(users.id, 'your_user_123'));

2. Handle 3DS Properly

// Always check for 3DS requirement in payment flow
const payment = await mpAdapter.payments.create(customerId, options);

if (payment.status === 'pending' && payment.statusDetail === 'pending_challenge') {
  // Return challenge URL to frontend
  return { status: 'requires_action', challengeUrl: payment.initPoint };
}

3. Implement Idempotency

// Use external_reference for idempotency
const payment = await mpAdapter.payments.create(customerId, {
  amount: 10000,
  currency: 'ARS',
  metadata: {
    orderId: 'order_123',
    idempotencyKey: `payment_${orderId}_${Date.now()}`
  }
});

Error Handling

All MercadoPago adapters now include comprehensive error handling:

import { QZPayMercadoPagoError, QZPayErrorCode } from '@qazuor/qzpay-mercadopago';

try {
  const payment = await mpAdapter.payments.create(customerId, {
    amount: 10000,
    currency: 'ARS',
    paymentMethodId: 'visa'
  });
} catch (error) {
  if (error instanceof QZPayMercadoPagoError) {
    console.log('Error code:', error.code);
    console.log('Error message:', error.message);
    console.log('Status detail:', error.statusDetail);
    console.log('Original error:', error.originalError);

    switch (error.code) {
      case QZPayErrorCode.CARD_DECLINED:
        // Handle card declined
        // Check error.statusDetail for specific reason
        break;
      case QZPayErrorCode.INSUFFICIENT_FUNDS:
        // Handle insufficient funds
        break;
      case QZPayErrorCode.INVALID_CARD:
        // Handle invalid card details
        break;
      case QZPayErrorCode.AUTHENTICATION_ERROR:
        // Handle API authentication issues
        break;
      case QZPayErrorCode.RATE_LIMIT_ERROR:
        // Implement backoff strategy
        break;
      case QZPayErrorCode.RESOURCE_NOT_FOUND:
        // Handle not found errors
        break;
      default:
        // Handle other provider errors
    }
  }
}

Error Mapping

The adapter maps MercadoPago errors to standardized QZPay error codes:

// MercadoPago status_detail -> QZPayErrorCode
const errorMap = {
  'cc_rejected_insufficient_amount': 'insufficient_funds',
  'cc_rejected_bad_filled_card_number': 'invalid_card',
  'cc_rejected_bad_filled_security_code': 'invalid_card',
  'cc_rejected_blacklist': 'card_declined',
  'cc_rejected_high_risk': 'card_declined',
  'cc_rejected_duplicated_payment': 'duplicate_transaction',
  // ... and many more
};

// All adapters wrap operations with error mapping
try {
  return await operation();
} catch (error) {
  throw mapMercadoPagoError(error, 'CustomerAdapter.create');
}

Fixed Issues

  1. Payer Email in Subscriptions: Now properly fetches and includes customer email when creating preapprovals.
  2. Checkout Prices: Fixed unit_price and currency_id extraction from price objects.
  3. Try/Catch Coverage: All adapter methods now have proper error handling.

Common Error Codes

| Code | Description | Action | |------|-------------|--------| | cc_rejected_insufficient_amount | Insufficient funds | Ask for different card | | cc_rejected_bad_filled_security_code | Invalid CVV | Re-enter card details | | cc_rejected_card_expired | Card expired | Use different card | | cc_rejected_blacklist | Card blocked | Contact issuer | | cc_rejected_other_reason | Generic rejection | Try different payment method |

v2 Features (Coming Soon)

  • Split Payments: Full marketplace support
  • Application Fees: Platform commission handling
  • Multi-Seller: Advanced marketplace features
  • Enhanced Webhooks: More event types

License

MIT