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

ngx-mp-payments

v1.0.2

Published

Angular library for Mercado Pago integration — Checkout Bricks & Checkout Pro

Readme


📋 Descripción General

ngx-mp-payments es una librería Angular moderna y reutilizable que simplifica la integración con Mercado Pago en aplicaciones Angular 21+.

¿Qué hace esta librería?

| Funcionalidad | Descripción | |---|---| | 🧱 Checkout Bricks | Formularios de pago embebidos (tarjeta, efectivo, transferencia) directamente en tu app | | 🛒 Checkout Pro | Botón que redirige al checkout hospedado de Mercado Pago | | 📊 Status Screen | Pantalla de resultado de pago con el look oficial de MP | | 🔌 Servicios inyectables | API programática para control total del flujo de pago |

¿Por qué usar esta librería?

  • Standalone Components — Sin NgModule, tree-shakeable por defecto
  • Angular Signals — Estado reactivo con la API moderna de Angular 21
  • Tipado completo — Interfaces TypeScript para todos los modelos de MP
  • Carga lazy del SDK — El script de MP se carga solo cuando se necesita
  • Cleanup automático — Los bricks se destruyen al desmontar el componente
  • Zone-aware — Correcta detección de cambios con NgZone
  • SSR-compatible — Usa DOCUMENT token en vez de window directamente
  • Backend-agnóstica — Funciona con cualquier backend (Node, .NET, Java, etc.)

⚙️ Requisitos Previos

| Requisito | Versión mínima | |---|---| | Angular | ^21.0.0 | | Node.js | 18.x o superior | | npm | 9.x o superior | | Cuenta en Mercado Pago | Crear cuenta |

También necesitas un backend que se comunique con la API de Mercado Pago usando el Access Token (secreto). La librería incluye un ejemplo completo de backend en Node.js.


📦 Instalación

npm install ngx-mp-payments

Nota: La librería carga el SDK de Mercado Pago automáticamente desde el CDN oficial (https://sdk.mercadopago.com/js/v2). No necesitas instalar ningún otro paquete adicional en el frontend.

Peer Dependencies

La librería requiere Angular 21 como peer dependency:

{
  "peerDependencies": {
    "@angular/common": "^21.0.0",
    "@angular/core": "^21.0.0"
  }
}

🔧 Configuración

Paso 1: Obtener Credenciales

  1. Ve al panel de desarrolladores de Mercado Pago
  2. Crea una aplicación o selecciona una existente
  3. Copia tu Public Key (para frontend) y Access Token (para backend)

⚠️ Importante sobre las credenciales:

| Credencial | Uso | ¿Dónde va? | |---|---|---| | Public Key | Identifica tu app ante MP | ✅ Frontend (segura) | | Access Token | Permite operar (crear cobros, reembolsos, etc.) | 🔒 SOLO Backend |

El Access Token es equivalente a una contraseña. NUNCA lo incluyas en código frontend.

Paso 2: Configurar la librería en Angular

Abre tu archivo app.config.ts y agrega el provider:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideMercadoPago } from 'ngx-mp-payments';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),  // Requerido para el servicio de preferencias

    // ── Configuración de Mercado Pago ──
    provideMercadoPago({
      publicKey: 'TEST-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
      locale: 'es-AR',
      advancedFraudPrevention: true,
    }),
  ],
};

Opciones de Configuración

| Propiedad | Tipo | Requerida | Default | Descripción | |---|---|---|---|---| | publicKey | string | ✅ | — | Public Key de tu cuenta de Mercado Pago | | locale | MercadoPagoLocale | ❌ | 'es-AR' | Idioma y país del checkout | | advancedFraudPrevention | boolean | ❌ | false | Activa device fingerprint para prevención de fraude |

Locales Soportados

| Locale | País | |---|---| | 'es-AR' | 🇦🇷 Argentina | | 'es-MX' | 🇲🇽 México | | 'es-CO' | 🇨🇴 Colombia | | 'es-CL' | 🇨🇱 Chile | | 'es-PE' | 🇵🇪 Perú | | 'es-UY' | 🇺🇾 Uruguay | | 'es-VE' | 🇻🇪 Venezuela | | 'pt-BR' | 🇧🇷 Brasil | | 'en-US' | 🇺🇸 Estados Unidos |


🚀 Uso Básico

Checkout Bricks (Pago embebido)

El Payment Brick renderiza un formulario de pago completo dentro de tu aplicación. Soporta tarjeta de crédito, débito, efectivo, transferencia y más — sin redirigir al usuario.

import { Component, signal } from '@angular/core';
import {
  MpCheckoutBrickComponent,
  PaymentResult,
  BrickError,
} from 'ngx-mp-payments';

@Component({
  selector: 'app-checkout',
  standalone: true,
  imports: [MpCheckoutBrickComponent],
  template: `
    <mp-checkout-brick
      type="payment"
      [amount]="1500"
      processPaymentUrl="https://api.miapp.com/payments/process"
      (paymentSuccess)="onSuccess($event)"
      (paymentError)="onError($event)"
      (brickReady)="onReady()"
    />

    @if (result()) {
      <p>Pago {{ result()?.status }} - ID: {{ result()?.id }}</p>
    }
  `,
})
export class CheckoutPage {
  readonly result = signal<PaymentResult | null>(null);

  onSuccess(result: PaymentResult): void {
    console.log('Pago exitoso:', result);
    this.result.set(result);
  }

  onError(error: BrickError): void {
    console.error('Error:', error.message);
  }

  onReady(): void {
    console.log('Brick listo para interacción');
  }
}

Resultado visual: El componente muestra un spinner de carga mientras se inicializa y luego renderiza el formulario oficial de Mercado Pago con todos los medios de pago habilitados para tu país.


Checkout Pro (Botón de pago con redirección)

El Checkout Pro muestra un botón oficial de Mercado Pago. Al hacer clic, redirige al usuario al checkout hospedado de MP donde puede pagar con cualquier medio disponible.

Flujo:

  1. Tu app crea una preferencia en el backend
  2. El backend retorna un preferenceId
  3. El componente renderiza el botón con ese ID
  4. Al hacer clic, el usuario va al checkout de MP
import { Component, inject, OnInit, signal } from '@angular/core';
import {
  MpPaymentButtonComponent,
  MercadoPagoPreferenceService,
  PreferenceResponse,
  BrickError,
} from 'ngx-mp-payments';

@Component({
  selector: 'app-checkout-pro',
  standalone: true,
  imports: [MpPaymentButtonComponent],
  template: `
    @if (loading()) {
      <p>Creando preferencia...</p>
    }

    @if (preferenceId()) {
      <mp-payment-button
        [preferenceId]="preferenceId()!"
        (buttonReady)="onReady()"
        (buttonError)="onError($event)"
      />
    }
  `,
})
export class CheckoutProPage implements OnInit {
  private readonly preferenceService = inject(MercadoPagoPreferenceService);

  readonly preferenceId = signal<string | null>(null);
  readonly loading = signal(false);

  ngOnInit(): void {
    this.loading.set(true);

    this.preferenceService
      .createPreference('http://localhost:3000/api/payments/preference', {
        title: 'Producto Premium',
        quantity: 1,
        unitPrice: 2500.00,
        currencyId: 'ARS',
      })
      .subscribe({
        next: (res: PreferenceResponse) => {
          this.preferenceId.set(res.preferenceId);
          this.loading.set(false);
        },
        error: (err) => {
          console.error('Error creando preferencia:', err);
          this.loading.set(false);
        },
      });
  }

  onReady(): void {
    console.log('Botón de Checkout Pro listo');
  }

  onError(error: BrickError): void {
    console.error('Error:', error.message);
  }
}

Status Screen (Resultado del pago)

El Status Screen Brick muestra el resultado de un pago con la interfaz oficial de Mercado Pago (aprobado, pendiente, rechazado).

import { Component, input } from '@angular/core';
import { MpStatusScreenComponent, BrickError } from 'ngx-mp-payments';

@Component({
  selector: 'app-payment-result',
  standalone: true,
  imports: [MpStatusScreenComponent],
  template: `
    <h2>Resultado de tu pago</h2>

    <mp-status-screen
      [paymentId]="paymentId()"
      (screenReady)="onReady()"
      (screenError)="onError($event)"
    />
  `,
})
export class PaymentResultPage {
  readonly paymentId = input.required<string>();

  onReady(): void {
    console.log('Pantalla de estado lista');
  }

  onError(error: BrickError): void {
    console.error('Error:', error.message);
  }
}

🔬 Uso Avanzado

Personalización visual de Bricks

Puedes personalizar la apariencia y los métodos de pago permitidos:

import { BrickCustomization } from 'ngx-mp-payments';

const customization: BrickCustomization = {
  visual: {
    style: {
      theme: 'dark',               // 'default' | 'dark' | 'flat' | 'bootstrap'
      customVariables: {
        formBackgroundColor: '#1a1a2e',
        baseColor: '#00b4d8',
      },
    },
    hideFormTitle: false,
    hidePaymentButton: false,
  },
  paymentMethods: {
    creditCard: 'all',
    debitCard: 'all',
    ticket: 'all',                  // Efectivo (Rapipago, Pago Fácil, etc.)
    bankTransfer: 'all',
    mercadoPago: 'all',             // Saldo en cuenta MP
    onboarding_credits: 'all',      // Mercado Crédito
    wallet_purchase: 'all',         // Compra con dinero en cuenta MP
    maxInstallments: 12,
    minInstallments: 1,
  },
};

Pásalo al componente:

<mp-checkout-brick
  type="payment"
  [amount]="1500"
  [customization]="customization"
  processPaymentUrl="https://api.miapp.com/payments/process"
  (paymentSuccess)="onSuccess($event)"
/>

Uso directo de servicios (sin componentes)

Si necesitas control total, puedes usar los servicios directamente:

import { Component, inject, signal } from '@angular/core';
import {
  MercadoPagoPaymentService,
  MercadoPagoSdkService,
  BrickConfig,
  PaymentResult,
} from 'ngx-mp-payments';

@Component({
  selector: 'app-custom-checkout',
  standalone: true,
  template: `
    <div id="custom-brick-container"></div>

    @if (paymentService.isLoading()) {
      <p>Procesando...</p>
    }

    @if (paymentService.error()) {
      <p class="error">{{ paymentService.error()?.message }}</p>
    }

    @if (paymentService.lastResult()) {
      <p>Pago {{ paymentService.lastResult()?.status }}</p>
    }
  `,
})
export class CustomCheckoutPage {
  readonly paymentService = inject(MercadoPagoPaymentService);

  async ngOnInit(): Promise<void> {
    // Renderizar Payment Brick manualmente
    await this.paymentService.renderPaymentBrick(
      {
        type: 'payment',
        amount: 3000,
        containerId: 'custom-brick-container',
        customization: {
          paymentMethods: { maxInstallments: 6 },
        },
        callbacks: {
          onReady: () => console.log('Brick montado'),
          onError: (err) => console.error('Error en brick:', err),
        },
      },
      'https://api.miapp.com/payments/process'
    );
  }

  ngOnDestroy(): void {
    // Limpiar brick al salir
    this.paymentService.destroyBrick('custom-brick-container');
  }
}

Renderizar Wallet Brick manualmente

// Renderizar botón de Checkout Pro sin usar el componente
await this.paymentService.renderWalletBrick(
  'wallet-container',       // ID del div contenedor
  'PREF-xxxx-xxxx',         // preferenceId del backend
  {
    onReady: () => console.log('Wallet listo'),
    onError: (err) => console.error(err),
  }
);

Múltiples Bricks en una página

Puedes renderizar varios bricks simultáneamente. Cada componente genera un ID único automáticamente:

<!-- Credit card only -->
<mp-checkout-brick
  type="cardPayment"
  [amount]="1500"
  processPaymentUrl="/api/payments/process"
  (paymentSuccess)="onCardPayment($event)"
/>

<!-- Wallet (Checkout Pro) -->
<mp-checkout-brick
  type="wallet"
  [amount]="1500"
  [preferenceId]="'PREF-xxxx'"
  (brickReady)="onWalletReady()"
/>

Consultar estado de un pago

import { inject } from '@angular/core';
import { MercadoPagoPreferenceService } from 'ngx-mp-payments';

export class PaymentStatusPage {
  private readonly preferenceService = inject(MercadoPagoPreferenceService);

  checkPayment(paymentId: string): void {
    this.preferenceService
      .getPaymentStatus('http://localhost:3000/api/payments/status', paymentId)
      .subscribe({
        next: (data) => console.log('Estado del pago:', data),
        error: (err) => console.error(err),
      });
  }
}

Resetear el flujo de pago

// Limpiar errores y resultados previos
this.paymentService.resetState();

// Destruir todos los bricks activos
this.paymentService.destroyAllBricks();

🖼️ Ejemplos Visuales

Flujo de Checkout Bricks

┌─────────────────────────────────────────────────────────────┐
│  Tu aplicación Angular                                      │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  <mp-checkout-brick type="payment" />                 │  │
│  │                                                       │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  💳 Formulario de pago Mercado Pago             │  │  │
│  │  │                                                 │  │  │
│  │  │  Número de tarjeta: [________________]          │  │  │
│  │  │  Vencimiento: [__/__]  CVV: [___]               │  │  │
│  │  │  Nombre: [____________________]                 │  │  │
│  │  │  Cuotas: [▼ 12 cuotas sin interés]              │  │  │
│  │  │                                                 │  │  │
│  │  │  [          💙 Pagar $1,500.00           ]      │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
│  También acepta: Efectivo · Transferencia · Saldo MP        │
└─────────────────────────────────────────────────────────────┘

Flujo de Checkout Pro

┌──────────────┐       ┌──────────────┐       ┌──────────────┐
│  Tu App      │       │  Backend     │       │  Mercado     │
│              │       │  (Node.js)   │       │  Pago API    │
│              │       │              │       │              │
│ 1. Crear     │──────▶│ 2. POST      │──────▶│ 3. Crear     │
│  preferencia │       │  /preference │       │  preferencia │
│              │◀──────│              │◀──────│              │
│ 4. Recibe    │       │ preferenceId │       │ preferenceId │
│  prefId      │       │              │       │              │
│              │       │              │       │              │
│ 5. Renderiza │       │              │       │              │
│ <mp-payment- │       │              │       │              │
│  button />   │       │              │       │              │
│              │       │              │       │              │
│ 6. Click ────│───────│──────────────│──────▶│ 7. Checkout  │
│              │       │              │       │  hospedado   │
│              │       │              │       │              │
│ 8. Redirect ◀│───────│──────────────│───────│ back_url     │
└──────────────┘       └──────────────┘       └──────────────┘

Arquitectura de la Librería

ngx-mp-payments
├── provideMercadoPago()          ← Configuración (app.config.ts)
│
├── Componentes (standalone)
│   ├── <mp-checkout-brick />     ← Payment / Card / Wallet Brick
│   ├── <mp-payment-button />     ← Botón de Checkout Pro
│   └── <mp-status-screen />      ← Pantalla de resultado
│
├── Servicios (inyectables)
│   ├── MercadoPagoSdkService     ← Carga el SDK desde CDN
│   ├── MercadoPagoPaymentService ← Facade para Bricks
│   └── MercadoPagoPreferenceService ← HTTP para preferencias
│
└── Modelos (TypeScript)
    ├── MercadoPagoConfig
    ├── BrickConfig / BrickType / BrickCustomization
    ├── PaymentResult / PaymentStatus
    ├── PreferenceRequest / PreferenceResponse
    └── BrickError / BrickCallbacks / CheckoutState

📖 API de la Librería

Función provideMercadoPago()

Provee la configuración de Mercado Pago a nivel de aplicación usando makeEnvironmentProviders.

function provideMercadoPago(config: MercadoPagoConfig): EnvironmentProviders;

Componentes

<mp-checkout-brick />

Renderiza un Checkout Brick de Mercado Pago (Payment, Card o Wallet).

| Input | Tipo | Requerido | Descripción | |---|---|---|---| | type | BrickType | ❌ | Tipo de brick: 'payment' (default), 'cardPayment', 'wallet' | | amount | number | ✅ | Monto total del pago | | processPaymentUrl | string | ⚠️ | URL del backend para procesar pagos. Requerido para payment y cardPayment | | preferenceId | string | ⚠️ | ID de la preferencia. Requerido para wallet | | customization | BrickCustomization | ❌ | Personalización visual y de métodos de pago |

| Output | Tipo | Descripción | |---|---|---| | paymentSuccess | PaymentResult | Emitido cuando el pago se procesa exitosamente | | paymentError | BrickError | Emitido cuando ocurre un error | | brickReady | void | Emitido cuando el brick está listo para interacción | | loadingChange | boolean | Emitido cuando cambia el estado de carga |

Selector: mp-checkout-brick

<mp-checkout-brick
  type="payment"
  [amount]="1500"
  processPaymentUrl="/api/payments/process"
  [customization]="customization"
  (paymentSuccess)="onSuccess($event)"
  (paymentError)="onError($event)"
  (brickReady)="onReady()"
  (loadingChange)="onLoading($event)"
/>

<mp-payment-button />

Renderiza el botón oficial de Checkout Pro (Wallet Brick).

| Input | Tipo | Requerido | Descripción | |---|---|---|---| | preferenceId | string | ✅ | ID de la preferencia creada en backend |

| Output | Tipo | Descripción | |---|---|---| | buttonReady | void | Emitido cuando el botón está renderizado | | buttonError | BrickError | Emitido cuando ocurre un error | | loadingChange | boolean | Emitido cuando cambia el estado de carga |

Selector: mp-payment-button

<mp-payment-button
  [preferenceId]="preferenceId()"
  (buttonReady)="onReady()"
  (buttonError)="onError($event)"
/>

<mp-status-screen />

Muestra el resultado de un pago con la interfaz oficial de Mercado Pago.

| Input | Tipo | Requerido | Descripción | |---|---|---|---| | paymentId | string | ✅ | ID del pago a consultar |

| Output | Tipo | Descripción | |---|---|---| | screenReady | void | Emitido cuando la pantalla está lista | | screenError | BrickError | Emitido cuando ocurre un error |

Selector: mp-status-screen

<mp-status-screen
  [paymentId]="paymentId"
  (screenReady)="onReady()"
  (screenError)="onError($event)"
/>

Servicios

MercadoPagoSdkService

Carga el SDK de Mercado Pago desde el CDN e inicializa la instancia.

| Método / Propiedad | Retorno | Descripción | |---|---|---| | load() | Promise<MercadoPagoInstance> | Carga el SDK y retorna la instancia. Singleton: solo carga una vez | | getInstance() | MercadoPagoInstance \| null | Retorna la instancia si ya fue cargada | | isLoaded | Signal<boolean> | Signal reactivo: true cuando el SDK está listo | | loadError | Signal<string \| null> | Signal reactivo: mensaje de error si falló la carga |

const mp = await this.sdkService.load();
const bricks = mp.bricks();

MercadoPagoPaymentService

Servicio facade para renderizar y gestionar Bricks.

| Método | Parámetros | Retorno | Descripción | |---|---|---|---| | renderPaymentBrick() | config: BrickConfig, processPaymentUrl: string | Promise<void> | Renderiza un Payment/Card Brick | | renderWalletBrick() | containerId: string, preferenceId: string, callbacks?: {...} | Promise<void> | Renderiza un Wallet Brick | | destroyBrick() | containerId: string | void | Destruye un brick específico | | destroyAllBricks() | — | void | Destruye todos los bricks activos | | resetState() | — | void | Resetea el estado interno |

| Signal | Tipo | Descripción | |---|---|---| | state | Signal<CheckoutState> | Estado completo del checkout | | isLoading | Signal<boolean> | true durante la carga/procesamiento | | error | Signal<BrickError \| null> | Error actual | | lastResult | Signal<PaymentResult \| null> | Resultado del último pago |


MercadoPagoPreferenceService

Servicio HTTP para crear preferencias de pago en tu backend.

| Método | Parámetros | Retorno | Descripción | |---|---|---|---| | createPreference() | backendUrl: string, request: PreferenceRequest | Observable<PreferenceResponse> | Crea una preferencia de pago | | getPaymentStatus() | backendUrl: string, paymentId: string | Observable<Record<string, unknown>> | Consulta el estado de un pago |

this.preferenceService
  .createPreference('/api/payments/preference', {
    title: 'Producto',
    quantity: 1,
    unitPrice: 999.99,
  })
  .subscribe((res) => console.log(res.preferenceId));

Interfaces y Tipos

MercadoPagoConfig

interface MercadoPagoConfig {
  publicKey: string;                          // Public Key de MP
  locale?: MercadoPagoLocale;                 // 'es-AR' | 'pt-BR' | etc.
  advancedFraudPrevention?: boolean;          // Device fingerprint
}

BrickConfig

interface BrickConfig {
  type: BrickType;                            // 'payment' | 'cardPayment' | 'statusScreen' | 'wallet'
  amount: number;                             // Monto total
  containerId: string;                        // ID del div contenedor
  customization?: BrickCustomization;         // Visual y métodos de pago
  callbacks?: BrickCallbacks;                 // Eventos del brick
}

BrickCustomization

interface BrickCustomization {
  visual?: {
    style?: {
      theme?: 'default' | 'dark' | 'flat' | 'bootstrap';
      customVariables?: Record<string, string>;
    };
    hideFormTitle?: boolean;
    hidePaymentButton?: boolean;
  };
  paymentMethods?: {
    creditCard?: 'all' | string | string[];
    debitCard?: 'all' | string | string[];
    ticket?: 'all' | string | string[];
    bankTransfer?: 'all' | string | string[];
    mercadoPago?: 'all' | string | string[];
    onboarding_credits?: 'all' | string | string[];
    wallet_purchase?: 'all' | string | string[];
    maxInstallments?: number;
    minInstallments?: number;
  };
}

BrickCallbacks

interface BrickCallbacks {
  onSubmit?: (formData: Record<string, unknown>) => Promise<void>;
  onError?: (error: BrickError) => void;
  onReady?: () => void;
  onBinChange?: (bin: string) => void;
}

PaymentResult

interface PaymentResult {
  id: string;                                 // ID del pago en MP
  status: PaymentStatus;                      // 'approved' | 'pending' | 'rejected' | ...
  statusDetail: string;                       // Detalle del estado
  externalReference?: string;                 // Referencia de tu sistema
  paymentMethodId?: string;                   // Tipo de pago
  paymentTypeId?: string;                     // Medio de pago
  raw?: Record<string, unknown>;              // Datos crudos de MP
}

PaymentStatus

type PaymentStatus =
  | 'approved'          // Pago aprobado
  | 'pending'           // Pago pendiente (efectivo, transferencia)
  | 'authorized'        // Autorizado (preautorización)
  | 'in_process'        // En proceso de revisión
  | 'in_mediation'      // En disputa/mediación
  | 'rejected'          // Rechazado
  | 'cancelled'         // Cancelado
  | 'refunded'          // Devuelto/reembolsado
  | 'charged_back'      // Contracargo

PreferenceRequest

interface PreferenceRequest {
  title: string;                              // Título del item
  description?: string;                       // Descripción
  quantity: number;                           // Cantidad
  unitPrice: number;                          // Precio unitario
  currencyId?: string;                        // 'ARS' | 'BRL' | 'MXN' | etc.
  externalReference?: string;                 // Referencia de tu sistema
  pictureUrl?: string;                        // URL de imagen
  metadata?: Record<string, unknown>;         // Datos adicionales
}

PreferenceResponse

interface PreferenceResponse {
  preferenceId: string;                       // ID de la preferencia
  initPoint?: string;                         // URL del checkout
  sandboxInitPoint?: string;                  // URL del sandbox
}

BrickError

interface BrickError {
  type: string;                               // Tipo de error
  message: string;                            // Mensaje descriptivo
  cause: string;                              // Causa/origen del error
  stack?: string;                             // Stack trace (solo en dev)
}

CheckoutState

interface CheckoutState {
  sdkLoaded: boolean;                         // SDK cargado y listo
  loading: boolean;                           // Procesando pago
  error: BrickError | null;                   // Error actual
  lastPaymentResult: PaymentResult | null;    // Último resultado
}

📁 Estructura del Proyecto

projects/ngx-mercadopago/
├── src/
│   ├── public-api.ts                         # API pública (exports)
│   └── lib/
│       ├── models/
│       │   ├── mercadopago.models.ts         # Interfaces y tipos
│       │   └── index.ts                      # Barrel export
│       │
│       ├── config/
│       │   ├── mercadopago.token.ts          # InjectionToken
│       │   ├── provide-mercadopago.ts        # provideMercadoPago()
│       │   └── index.ts                      # Barrel export
│       │
│       ├── services/
│       │   ├── mercadopago-sdk.service.ts    # Carga del SDK (CDN)
│       │   ├── mercadopago-payment.service.ts # Facade para Bricks
│       │   ├── mercadopago-preference.service.ts # HTTP para preferencias
│       │   └── index.ts                      # Barrel export
│       │
│       └── components/
│           ├── checkout-brick/
│           │   └── mp-checkout-brick.component.ts
│           ├── payment-button/
│           │   └── mp-payment-button.component.ts
│           ├── status-screen/
│           │   └── mp-status-screen.component.ts
│           └── index.ts                      # Barrel export
│
├── package.json                              # Metadata del paquete npm
├── ng-package.json                           # Configuración ng-packagr
├── tsconfig.lib.json                         # TypeScript config
└── README.md                                 # Este archivo

🖥️ Backend de Ejemplo

La librería incluye un backend de ejemplo en Node.js/Express listo para usar:

examples/backend/
├── server.js          # Servidor Express completo
├── package.json       # Dependencias
└── .env.example       # Variables de entorno

Instalación del Backend

cd examples/backend
npm install

Configuración

Crea un archivo .env:

MP_ACCESS_TOKEN=TEST-xxxx-your-access-token
FRONTEND_URL=http://localhost:4200
PORT=3000

Ejecución

node server.js
# o con nodemon para desarrollo:
npx nodemon server.js

Endpoints disponibles

| Método | Endpoint | Descripción | |---|---|---| | POST | /api/payments/preference | Crea una preferencia de pago | | POST | /api/payments/process | Procesa un pago con datos del brick | | GET | /api/payments/status/:id | Consulta estado de un pago | | POST | /api/webhooks/mercadopago | Recibe notificaciones de MP |

Ejemplo de request

curl -X POST http://localhost:3000/api/payments/preference \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Producto Demo",
    "quantity": 1,
    "unitPrice": 1500,
    "currencyId": "ARS"
  }'

Respuesta:

{
  "preferenceId": "1234567890-xxxx-xxxx",
  "initPoint": "https://www.mercadopago.com.ar/checkout/v1/redirect?pref_id=...",
  "sandboxInitPoint": "https://sandbox.mercadopago.com.ar/checkout/v1/redirect?pref_id=..."
}

❗ Errores Comunes y Soluciones

1. "publicKey es requerida en la configuración"

Causa: No se configuró provideMercadoPago() en app.config.ts.

Solución:

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideMercadoPago({
      publicKey: 'TU-PUBLIC-KEY-AQUÍ', // ← No olvidar
    }),
  ],
};

2. "No se pudo cargar el SDK de Mercado Pago desde el CDN"

Causa: Problema de red, bloqueador de anuncios, o proxy corporativo.

Soluciones:

  • Verificar conectividad a https://sdk.mercadopago.com/js/v2
  • Desactivar bloqueadores de anuncios (algunos bloquean scripts de pago)
  • Si estás detrás de un proxy, verificar que el dominio sdk.mercadopago.com esté permitido
  • Verificar la consola del navegador para más detalles

3. "processPaymentUrl es requerido para bricks de tipo payment/cardPayment"

Causa: Usaste <mp-checkout-brick type="payment"> sin pasar processPaymentUrl.

Solución:

<mp-checkout-brick
  type="payment"
  [amount]="1500"
  processPaymentUrl="/api/payments/process"
  (paymentSuccess)="onSuccess($event)"
/>

4. "preferenceId es requerido para bricks de tipo wallet"

Causa: Usaste type="wallet" o <mp-payment-button> sin un preferenceId.

Solución: Crear la preferencia en el backend primero y luego pasar el ID:

this.preferenceService
  .createPreference('/api/payments/preference', { ... })
  .subscribe((res) => this.preferenceId.set(res.preferenceId));

5. El brick no se renderiza (div vacío)

Posibles causas y soluciones:

| Causa | Solución | |---|---| | Public Key inválida | Verificar en el panel de MP | | Public Key de test con datos de producción | Usar credenciales del mismo entorno | | CSP bloqueando scripts | Agregar sdk.mercadopago.com a script-src | | Contenedor no visible | Verificar que el div tenga dimensiones |


6. Error CORS al crear preferencia

Causa: Tu backend no tiene CORS configurado para el dominio del frontend.

Solución en Express:

const cors = require('cors');
app.use(cors({
  origin: 'http://localhost:4200', // Tu dominio frontend
}));

7. El pago queda siempre en "pending"

Causa: Estás en modo sandbox. Los pagos de prueba siempre quedan pendientes a menos que uses las tarjetas de prueba de MP.

Solución: Usar las tarjetas de prueba oficiales:

  • Aprobado: 5031 7557 3453 0604 (Mastercard)
  • Rechazado: 5031 7557 3453 0604 con CVV 123 y nombre APRO/OTHE

✅ Buenas Prácticas

Seguridad

  • 🔒 NUNCA incluyas el Access Token en código frontend
  • 🔒 Valida montos y datos en el backend antes de crear preferencias
  • 🔒 Implementa externalReference para vincular pagos con órdenes de tu sistema
  • 🔒 Activa advancedFraudPrevention: true en producción
  • 🔒 Usa HTTPS en producción

Rendimiento

  • ⚡ El SDK se carga lazy (solo cuando un componente lo necesita)
  • ⚡ Usa destroyBrick() o destroyAllBricks() en ngOnDestroy si usas servicios directamente
  • ⚡ Los componentes hacen cleanup automático vía DestroyRef
  • ⚡ El SDK se ejecuta fuera de NgZone para minimizar detección de cambios

UX

  • 🎨 Los componentes incluyen spinner de carga por defecto
  • 🎨 Escucha loadingChange para sincronizar el loading con tu UI
  • 🎨 Usa paymentError / buttonError / screenError para informar al usuario
  • 🎨 Considera mostrar el PaymentResult.status con mensajes amigables

Testing

  • 🧪 Todos los servicios son inyectables y fáciles de mockear
  • 🧪 Los componentes usan input() / output() de Angular 21, compatibles con ComponentFixture
  • 🧪 La librería incluye tests unitarios escritos con Vitest 4.x

🧪 Testing

La librería usa Vitest como test runner (default en Angular 21).

Ejecutar tests

# Desde la raíz del workspace
npm run test

# O en modo CI (sin watch)
npm run test:ci

# O directamente con Angular CLI
npx ng test ngx-mp-payments

Cobertura de tests

| Suite | Tests | |---|---| | MercadoPagoSdkService | 11 | | MercadoPagoPaymentService | 11 | | MercadoPagoPreferenceService | 6 | | MpCheckoutBrickComponent | 7 | | MpPaymentButtonComponent | 6 | | Total | 41 |

Mockear servicios en tus tests

import { vi, describe, it, expect } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { MercadoPagoPaymentService } from 'ngx-mp-payments';

describe('MyComponent', () => {
  const mockPaymentService = {
    renderPaymentBrick: vi.fn().mockResolvedValue(undefined),
    destroyBrick: vi.fn(),
    isLoading: vi.fn().mockReturnValue(false),
    error: vi.fn().mockReturnValue(null),
    lastResult: vi.fn().mockReturnValue(null),
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: MercadoPagoPaymentService, useValue: mockPaymentService },
      ],
    });
  });

  it('should render brick on init', async () => {
    // ...
    expect(mockPaymentService.renderPaymentBrick).toHaveBeenCalled();
  });
});

� Ejemplo Completo de Uso en Código

A continuación se muestra un ejemplo completo paso a paso de cómo integrar ngx-mp-payments en un proyecto Angular real, desde la configuración hasta el resultado del pago.

Paso 1: Configuración global (app.config.ts)

import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideMercadoPago } from 'ngx-mp-payments';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideHttpClient(),

    // ── Configuración de Mercado Pago ──
    provideMercadoPago({
      publicKey: 'TEST-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // Tu Public Key
      locale: 'es-AR',
      advancedFraudPrevention: true,
    }),
  ],
};

Paso 2: Rutas con lazy loading (app.routes.ts)

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./pages/home/home.page').then((m) => m.HomePage),
  },
  {
    path: 'checkout-bricks',
    loadComponent: () =>
      import('./pages/checkout-bricks/checkout-bricks.page').then(
        (m) => m.CheckoutBricksPage
      ),
  },
  {
    path: 'checkout-pro',
    loadComponent: () =>
      import('./pages/checkout-pro/checkout-pro.page').then(
        (m) => m.CheckoutProPage
      ),
  },
  {
    path: 'payment/success',
    loadComponent: () =>
      import('./pages/payment-result/payment-result.page').then(
        (m) => m.PaymentResultPage
      ),
    data: { status: 'success' },
  },
  {
    path: 'payment/failure',
    loadComponent: () =>
      import('./pages/payment-result/payment-result.page').then(
        (m) => m.PaymentResultPage
      ),
    data: { status: 'failure' },
  },
  {
    path: 'payment/pending',
    loadComponent: () =>
      import('./pages/payment-result/payment-result.page').then(
        (m) => m.PaymentResultPage
      ),
    data: { status: 'pending' },
  },
  { path: '**', redirectTo: '' },
];

Paso 3: Componente raíz con navegación (app.ts)

import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, RouterLink],
  template: `
    <nav class="navbar">
      <a routerLink="/" class="logo">ngx-mp-payments Demo</a>
      <div class="nav-links">
        <a routerLink="/checkout-bricks">Checkout Bricks</a>
        <a routerLink="/checkout-pro">Checkout Pro</a>
      </div>
    </nav>
    <main class="container">
      <router-outlet />
    </main>
  `,
  styles: `
    .navbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 1rem 2rem;
      background: #009ee3;
      color: white;
    }
    .logo {
      font-weight: 700;
      font-size: 1.2rem;
      color: white;
      text-decoration: none;
    }
    .nav-links { display: flex; gap: 1.5rem; }
    .nav-links a { color: white; text-decoration: none; font-weight: 500; }
    .nav-links a:hover { text-decoration: underline; }
    .container { max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
  `,
})
export class App {}

Paso 4: Página de Checkout Bricks (pago embebido)

Este es el ejemplo más completo — un formulario de pago renderizado directamente en tu aplicación:

// pages/checkout-bricks/checkout-bricks.page.ts
import { Component, inject, signal } from '@angular/core';
import {
  MpCheckoutBrickComponent,
  MercadoPagoPaymentService,
  BrickError,
  PaymentResult,
} from 'ngx-mp-payments';

@Component({
  selector: 'app-checkout-bricks',
  standalone: true,
  imports: [MpCheckoutBrickComponent],
  template: `
    <h1>Checkout Bricks</h1>
    <p>Formulario de pago embebido con Payment Brick de Mercado Pago.</p>

    <div class="product-card">
      <h2>Producto Demo</h2>
      <p class="price">$ {{ amount() }}</p>
      <p>Producto de prueba para demostrar la integración.</p>
    </div>

    <!-- ── PAYMENT BRICK ── -->
    <div class="brick-wrapper">
      <mp-checkout-brick
        type="payment"
        [amount]="amount()"
        [processPaymentUrl]="processUrl"
        [customization]="brickCustomization"
        (paymentSuccess)="onPaymentSuccess($event)"
        (paymentError)="onPaymentError($event)"
        (brickReady)="onBrickReady()"
        (loadingChange)="onLoadingChange($event)"
      />
    </div>

    <!-- ── RESULTADO ── -->
    @if (paymentResult()) {
      <div class="result" [class.success]="paymentResult()?.status === 'approved'">
        <h3>Resultado del pago</h3>
        <p><strong>ID:</strong> {{ paymentResult()?.id }}</p>
        <p><strong>Estado:</strong> {{ paymentResult()?.status }}</p>
        <p><strong>Detalle:</strong> {{ paymentResult()?.statusDetail }}</p>
      </div>
    }

    @if (errorMsg()) {
      <div class="error">
        <h3>Error</h3>
        <p>{{ errorMsg() }}</p>
      </div>
    }
  `,
  styles: `
    .product-card {
      border: 1px solid #e0e0e0;
      border-radius: 12px;
      padding: 1.5rem;
      margin-bottom: 2rem;
    }
    .price { font-size: 2rem; font-weight: 700; color: #009ee3; }
    .brick-wrapper { margin: 2rem 0; }
    .result {
      padding: 1rem 1.5rem;
      border-radius: 8px;
      background: #fff3cd;
      border: 1px solid #ffc107;
      margin-top: 1rem;
    }
    .result.success { background: #d4edda; border-color: #28a745; }
    .error {
      padding: 1rem 1.5rem;
      border-radius: 8px;
      background: #f8d7da;
      border: 1px solid #dc3545;
      margin-top: 1rem;
    }
  `,
})
export class CheckoutBricksPage {
  private readonly paymentService = inject(MercadoPagoPaymentService);

  /** Monto del producto demo */
  readonly amount = signal(1500);

  /** URL del backend para procesar pagos */
  readonly processUrl = 'http://localhost:3000/api/payments/process';

  /** Personalización del brick */
  readonly brickCustomization = {
    visual: {
      style: { theme: 'default' as const },
    },
    paymentMethods: {
      maxInstallments: 12,
    },
  };

  /** Estado reactivo */
  readonly paymentResult = signal<PaymentResult | null>(null);
  readonly errorMsg = signal<string | null>(null);

  onPaymentSuccess(result: PaymentResult): void {
    console.log('Pago exitoso:', result);
    this.paymentResult.set(result);
    this.errorMsg.set(null);
  }

  onPaymentError(error: BrickError): void {
    console.error('Error en pago:', error);
    this.errorMsg.set(error.message);
  }

  onBrickReady(): void {
    console.log('Brick listo para interacción');
  }

  onLoadingChange(loading: boolean): void {
    console.log('Loading:', loading);
  }
}

Paso 5: Página de Checkout Pro (botón con redirección)

Muestra cómo crear una preferencia en el backend y renderizar el botón de pago:

// pages/checkout-pro/checkout-pro.page.ts
import { Component, inject, OnInit, signal } from '@angular/core';
import {
  MpPaymentButtonComponent,
  MercadoPagoPreferenceService,
  BrickError,
  PreferenceResponse,
} from 'ngx-mp-payments';

@Component({
  selector: 'app-checkout-pro',
  standalone: true,
  imports: [MpPaymentButtonComponent],
  template: `
    <h1>Checkout Pro</h1>
    <p>Botón de pago que redirige al checkout hospedado de Mercado Pago.</p>

    <div class="product-card">
      <h2>Producto Demo</h2>
      <p class="price">$ 2500.00</p>
      <p>Suscripción Premium - 1 mes</p>
    </div>

    @if (loading()) {
      <p>Creando preferencia de pago...</p>
    }

    @if (errorMsg()) {
      <div class="error">
        <p>{{ errorMsg() }}</p>
      </div>
    }

    <!-- ── WALLET BRICK (CHECKOUT PRO) ── -->
    @if (preferenceId()) {
      <div class="wallet-wrapper">
        <mp-payment-button
          [preferenceId]="preferenceId()!"
          (buttonReady)="onButtonReady()"
          (buttonError)="onButtonError($event)"
        />
      </div>
    }

    <section class="info">
      <h3>¿Cómo funciona?</h3>
      <ol>
        <li>La app crea una <strong>preferencia</strong> en el backend.</li>
        <li>El backend usa el <strong>Access Token</strong> para comunicarse con la API de MP.</li>
        <li>La API retorna un <strong>preferenceId</strong>.</li>
        <li>El frontend renderiza el botón de pago con ese ID.</li>
        <li>Al hacer clic, el usuario va al checkout seguro de MP.</li>
        <li>Después del pago, MP redirige al usuario de vuelta a tu app.</li>
      </ol>
    </section>
  `,
  styles: `
    .product-card {
      border: 1px solid #e0e0e0;
      border-radius: 12px;
      padding: 1.5rem;
      margin-bottom: 2rem;
    }
    .price { font-size: 2rem; font-weight: 700; color: #009ee3; }
    .wallet-wrapper { margin: 2rem 0; }
    .error {
      padding: 1rem 1.5rem;
      border-radius: 8px;
      background: #f8d7da;
      border: 1px solid #dc3545;
      margin: 1rem 0;
    }
    .info {
      margin-top: 2rem;
      padding: 1.5rem;
      background: #f5f5f5;
      border-radius: 8px;
    }
  `,
})
export class CheckoutProPage implements OnInit {
  private readonly preferenceService = inject(MercadoPagoPreferenceService);

  readonly preferenceId = signal<string | null>(null);
  readonly loading = signal(false);
  readonly errorMsg = signal<string | null>(null);

  ngOnInit(): void {
    this.createPreference();
  }

  private createPreference(): void {
    this.loading.set(true);
    this.errorMsg.set(null);

    this.preferenceService
      .createPreference('http://localhost:3000/api/payments/preference', {
        title: 'Suscripción Premium',
        description: 'Acceso premium por 1 mes',
        quantity: 1,
        unitPrice: 2500.0,
        currencyId: 'ARS',
        externalReference: `order-demo-${Date.now()}`,
      })
      .subscribe({
        next: (response: PreferenceResponse) => {
          console.log('Preferencia creada:', response);
          this.preferenceId.set(response.preferenceId);
          this.loading.set(false);
        },
        error: (err) => {
          console.error('Error creando preferencia:', err);
          this.errorMsg.set(
            'No se pudo crear la preferencia. ¿Está corriendo el backend en localhost:3000?'
          );
          this.loading.set(false);
        },
      });
  }

  onButtonReady(): void {
    console.log('Botón de Checkout Pro listo');
  }

  onButtonError(error: BrickError): void {
    console.error('Error en Wallet Brick:', error);
    this.errorMsg.set(error.message);
  }
}

Paso 6: Página de resultado de pago

Se muestra después de que Mercado Pago redirige al usuario de vuelta a tu app (Checkout Pro):

// pages/payment-result/payment-result.page.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';

@Component({
  selector: 'app-payment-result',
  standalone: true,
  imports: [RouterLink],
  template: `
    <div class="result-card" [class]="status">
      @switch (status) {
        @case ('success') {
          <h1>✅ Pago Aprobado</h1>
          <p>Tu pago fue procesado exitosamente.</p>
        }
        @case ('failure') {
          <h1>❌ Pago Rechazado</h1>
          <p>El pago no pudo ser procesado. Intenta nuevamente.</p>
        }
        @case ('pending') {
          <h1>⏳ Pago Pendiente</h1>
          <p>Tu pago está siendo procesado. Te notificaremos cuando se confirme.</p>
        }
      }

      <a routerLink="/" class="btn">Volver al inicio</a>
    </div>
  `,
  styles: `
    .result-card {
      text-align: center;
      padding: 3rem 2rem;
      border-radius: 12px;
      margin: 2rem 0;
    }
    .result-card h1 { font-size: 2rem; }
    .success { background: #d4edda; border: 1px solid #28a745; }
    .failure { background: #f8d7da; border: 1px solid #dc3545; }
    .pending { background: #fff3cd; border: 1px solid #ffc107; }
    .btn {
      display: inline-block;
      margin-top: 1.5rem;
      padding: 0.6rem 1.5rem;
      background: #009ee3;
      color: white;
      border-radius: 8px;
      text-decoration: none;
      font-weight: 500;
    }
  `,
})
export class PaymentResultPage {
  private readonly route = inject(ActivatedRoute);
  readonly status: string = this.route.snapshot.data['status'] || 'pending';
}

Paso 7: Backend en Node.js/Express

Este servidor procesa los pagos de forma segura usando el Access Token (que nunca llega al frontend):

// examples/backend/server.js
const express = require('express');
const cors = require('cors');
const { MercadoPagoConfig, Preference, Payment } = require('mercadopago');

const app = express();
const PORT = process.env.PORT || 3000;

// ── Configuración de MP (Access Token SOLO en backend) ──
const ACCESS_TOKEN = process.env.MP_ACCESS_TOKEN || 'TEST-xxxx-your-access-token-here';

const mpClient = new MercadoPagoConfig({ accessToken: ACCESS_TOKEN });
const preferenceClient = new Preference(mpClient);
const paymentClient = new Payment(mpClient);

app.use(express.json());
app.use(cors({
  origin: process.env.FRONTEND_URL || 'http://localhost:4200',
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

// ── Crear preferencia (para Checkout Pro) ──
app.post('/api/payments/preference', async (req, res) => {
  try {
    const { title, description, quantity, unitPrice, currencyId, externalReference } = req.body;

    if (!title || !quantity || !unitPrice) {
      return res.status(400).json({ error: 'Campos requeridos: title, quantity, unitPrice' });
    }

    const preference = await preferenceClient.create({
      body: {
        items: [{
          title,
          description: description || '',
          quantity: Number(quantity),
          unit_price: Number(unitPrice),
          currency_id: currencyId || 'ARS',
        }],
        back_urls: {
          success: `${process.env.FRONTEND_URL || 'http://localhost:4200'}/payment/success`,
          failure: `${process.env.FRONTEND_URL || 'http://localhost:4200'}/payment/failure`,
          pending: `${process.env.FRONTEND_URL || 'http://localhost:4200'}/payment/pending`,
        },
        auto_return: 'approved',
        external_reference: externalReference || `order-${Date.now()}`,
        notification_url: `${process.env.BACKEND_URL || 'https://tu-backend.com'}/api/webhooks/mercadopago`,
      },
    });

res.json({
      preferenceId: preference.id,
      initPoint: preference.init_point,
      sandboxInitPoint: preference.sandbox_init_point,
    });
  } catch (error) {
    console.error('Error creando preferencia:', error);
    res.status(500).json({ error: 'Error al crear la preferencia de pago' });
  }
});

// ── Procesar pago (para Payment Brick) ──
app.post('/api/payments/process', async (req, res) => {
  try {
    const { token, issuer_id, payment_method_id, transaction_amount, installments, payer } = req.body;

    if (!token || !payment_method_id || !transaction_amount) {
      return res.status(400).json({ error: 'Datos de pago incompletos' });
    }

    const payment = await paymentClient.create({
      body: {
        token,
        issuer_id: issuer_id ? String(issuer_id) : undefined,
        payment_method_id,
        transaction_amount: Number(transaction_amount),
        installments: Number(installments) || 1,
        payer: { email: payer?.email, identification: payer?.identification },
      },
      requestOptions: {
        idempotencyKey: `payment-${Date.now()}`,
      },
    });

    res.json({
      id: String(payment.id),
      status: payment.status,
      statusDetail: payment.status_detail,
      paymentMethodId: payment.payment_method_id,
      paymentTypeId: payment.payment_type_id,
    });
  } catch (error) {
    console.error('Error procesando pago:', error);
    res.status(500).json({ error: 'Error al procesar el pago' });
  }
});

// ── Consultar estado de un pago ──
app.get('/api/payments/status/:paymentId', async (req, res) => {
  try {
    const payment = await paymentClient.get({ id: req.params.paymentId });
    res.json({
      id: String(payment.id),
      status: payment.status,
      statusDetail: payment.status_detail,
      externalReference: payment.external_reference,
    });
  } catch (error) {
    res.status(500).json({ error: 'Error al consultar el estado del pago' });
  }
});

// ── Webhook de Mercado Pago ──
app.post('/api/webhooks/mercadopago', async (req, res) => {
  res.sendStatus(200); // Responder rápido

  const { type, data } = req.body;
  if (type === 'payment' && data?.id) {
    const payment = await paymentClient.get({ id: data.id });
    console.log(`Webhook: Pago ${data.id} → ${payment.status}`);
    // TODO: Actualizar estado en tu DB, enviar email, etc.
  }
});

app.listen(PORT, () => {
  console.log(`✅ Backend corriendo en http://localhost:${PORT}`);
});

Paso 8: Ejecutar todo

# Terminal 1 — Backend
cd examples/backend
npm install
MP_ACCESS_TOKEN=TEST-tu-access-token node server.js

# Terminal 2 — Frontend Angular
npm start
# Abrir http://localhost:4200

Resumen del flujo completo

                     Tu Proyecto Angular
                     ===================

 app.config.ts ──► provideMercadoPago({ publicKey: '...' })
       │
 app.routes.ts ──► Lazy loading de páginas
       │
       ├── /checkout-bricks
       │     └── <mp-checkout-brick />
       │           │
       │           ├── Renderiza formulario de pago embebido
       │           ├── Usuario completa datos de tarjeta/efectivo
       │           ├── Brick envía datos al backend → POST /api/payments/process
       │           └── Backend procesa pago con Access Token → retorna PaymentResult
       │
       ├── /checkout-pro
       │     ├── MercadoPagoPreferenceService.createPreference()
       │     │     └── POST /api/payments/preference → { preferenceId }
       │     └── <mp-payment-button [preferenceId]="..." />
       │           └── Click → Redirige a checkout.mercadopago.com
       │
       └── /payment/success | /payment/failure | /payment/pending
             └── PaymentResultPage muestra resultado

�🔨 Desarrollo Local

Clonar y configurar

git clone https://github.com/JosemaCeballos/ngx-mp-payments.git
cd ngx-mp-payments

npm install

Compilar la librería

npm run build

Ejecutar la app demo

npm start

Build de producción

npm run build:prod

Publicar en npm

npm run publish:lib

🤝 Contribución

¡Las contribuciones son bienvenidas! Sigue estos pasos:

  1. Fork del repositorio
  2. Crea una rama para tu feature:
    git checkout -b feature/mi-feature
  3. Implementa los cambios siguiendo las convenciones del proyecto
  4. Agrega tests para cualquier funcionalidad nueva
  5. Verifica que todos los tests pasen:
    npm run test
  6. Compila la librería para verificar que no hay errores:
    npm run build
  7. Commit con mensajes descriptivos (preferiblemente Conventional Commits):
    git commit -m "feat: agregar soporte para Card Payment Brick"
  8. Push y abre un Pull Request

Lineamientos

  • Usar standalone components (sin NgModule)
  • Seguir las convenciones de Angular 21 (signals, input(), output(), inject())
  • Documentar funciones y clases públicas con JSDoc
  • Mantener compatibilidad con tree-shaking (sideEffects: false)
  • Escribir tests en Vitest

📄 Licencia

Este proyecto está bajo la licencia MIT. Ver LICENSE para más detalles.


🔗 Enlaces Útiles

| Recurso | URL | |---|---| | Documentación oficial de Mercado Pago | developers.mercadopago.com | | Panel de desarrolladores | Panel de apps | | Checkout Bricks (docs oficiales) | Checkout Bricks | | Checkout Pro (docs oficiales) | Checkout Pro | | Tarjetas de prueba | Test cards | | Angular 21 Signals | Angular Signals |