> For the complete documentation index, see [llms.txt](https://plexo.gitbook.io/rest-api/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://plexo.gitbook.io/rest-api/operations/testing.md).

# Testing

Test your integration with Plexo environments, test cards, and realistic payment scenarios before going live.

## Overview

Use this guide for environment selection, test credentials, cards, and validation scenarios. For API contract details, use the API reference and domain guides.

**What this guide covers:**

* Using test environments
* Test cards for all processors and scenarios
* Testing payment flows
* Testing callbacks and asynchronous flows
* Common testing scenarios
* Test data best practices

## Test Environments

Plexo provides separate environments for testing and production. For complete environment documentation including use cases, configuration, and troubleshooting, see the [API Environments Guide](/rest-api/getting-started/environments.md).

{% hint style="success" %}
**🧪 Recommended for Development:** Use the testing environment for all integration development and testing. It's safe, free, and mirrors production behavior without financial risk.
{% endhint %}

### Testing Environment

**Base URL:** `https://api.testing.plexo.com.uy`

**Dashboard:** `https://dashboard.testing.plexo.com.uy`

**Characteristics:**

* ✅ Safe for testing - no real money
* ✅ Full API functionality
* ✅ Real processor integrations (test mode)
* ✅ Separate test credentials
* ✅ Can test all payment methods
* ⚠️ Test cards only - real cards will decline
* ⚠️ Data may be reset periodically

### Production Environment

**Base URL:** `https://api.plexo.com.uy`

**Dashboard:** `https://dashboard.plexo.com`

**Characteristics:**

* 💰 Real money transactions
* 🔒 Production credentials required
* ⚠️ All transactions are final
* ⚠️ Real cards only

{% hint style="danger" %}
**🚀 Production Environment:** All transactions in production are **real and will result in actual money transfers**. Never use test credentials in production or production credentials in testing! Always verify which environment you're targeting. See the [API Environments Guide](/rest-api/getting-started/environments.md) for environment selection best practices.
{% endhint %}

## Test Credentials

### Obtaining Test Credentials

1. **Contact Plexo Support:**
   * Email: <soporte@plexo.com.uy>
   * Request: "Testing environment credentials"
   * Provide: Company name, integration type, expected go-live date
2. **Receive Credentials:**

   ```
   Testing Environment:
   Client ID: test_12345
   API Key: sk_test_abcdef123456
   Merchant ID: 67890
   ```
3. **Configure Your Application:**

   ```javascript
   const config = {
     environment: process.env.PLEXO_ENV || 'testing',
     credentials: {
       testing: {
         baseUrl: 'https://api.testing.plexo.com.uy',
         clientId: 'test_12345',
         apiKey: 'sk_test_abcdef123456',
         merchantId: {{TEST_MERCHANT_ID}}
       },
       production: {
         baseUrl: 'https://api.plexo.com.uy',
         clientId: process.env.PLEXO_CLIENT_ID,
         apiKey: process.env.PLEXO_API_KEY,
         merchantId: parseInt(process.env.PLEXO_MERCHANT_ID)
       }
     }
   };

   const currentConfig = config.credentials[config.environment];
   ```

## Test Cards

Plexo supports comprehensive test cards for all scenarios.

### Successful Test Cards

These cards simulate successful payments:

| Card Number        | Brand           | CVV          | Expiry      | Description        |
| ------------------ | --------------- | ------------ | ----------- | ------------------ |
| `4444333322221111` | VISA            | Any 3 digits | Future date | Successful payment |
| `5555444433332222` | MASTERCARD      | Any 3 digits | Future date | Successful payment |
| `4507990000004905` | VISA (UY)       | Any 3 digits | Future date | Uruguayan issuer   |
| `5588220000000006` | MASTERCARD (UY) | Any 3 digits | Future date | Uruguayan issuer   |

**Example:**

```javascript
const successfulPayment = {
  merchantId: {{TEST_MERCHANT_ID}},
  referenceId: 'test-success-1001',
  invoiceNumber: 'TEST-1001',
  amount: { total: 10000, currency: 'UYU' },
  capture: { method: 'automatic' },
  paymentMethod: {
    source: 'card',
    card: {
      number: '4444333322221111',
      expMonth: 12,
      expYear: 2028,
      cvc: '123',
      cardholder: {
        firstName: 'Test',
        lastName: 'User',
        email: 'test.user@example.com'
      }
    }
  }
};
// Result: Payment captured successfully
```

### Failure Scenario Cards

Test different decline reasons:

| Card Number        | Brand | Scenario           | Error Code           |
| ------------------ | ----- | ------------------ | -------------------- |
| `4000000000000002` | VISA  | Decline (generic)  | `card_declined`      |
| `4000000000000010` | VISA  | Insufficient funds | `insufficient_funds` |
| `4000000000000028` | VISA  | Lost card          | `lost_card`          |
| `4000000000000036` | VISA  | Stolen card        | `stolen_card`        |
| `4000000000000044` | VISA  | Expired card       | `expired_card`       |
| `4000000000000051` | VISA  | Invalid CVV        | `invalid_cvv`        |
| `4000000000000069` | VISA  | Processing error   | `processing_error`   |
| `4000000000000077` | VISA  | Suspected fraud    | `fraud_detected`     |

**Example:**

```javascript
const declinedPayment = {
  merchantId: {{TEST_MERCHANT_ID}},
  referenceId: 'test-decline-1001',
  invoiceNumber: 'TEST-DECLINE-1001',
  amount: { total: 10000, currency: 'UYU' },
  capture: { method: 'automatic' },
  paymentMethod: {
    source: 'card',
    card: {
      number: '4000000000000010',
      expMonth: 12,
      expYear: 2028,
      cvc: '123',
      cardholder: {
        firstName: 'Test',
        lastName: 'User'
      }
    }
  }
};
// Result: Payment declined with insufficient_funds error
```

### 3DS Test Cards

Test 3D Secure authentication flows:

| Card Number        | Brand      | 3DS Flow            | Result                          |
| ------------------ | ---------- | ------------------- | ------------------------------- |
| `4000000000003055` | VISA       | Frictionless        | Authenticated successfully      |
| `4000000000003063` | VISA       | Challenge (success) | User authenticates successfully |
| `4000000000003071` | VISA       | Challenge (failure) | User fails authentication       |
| `5200000000003002` | MASTERCARD | Frictionless        | Authenticated successfully      |
| `5200000000003010` | MASTERCARD | Challenge (success) | User authenticates successfully |

**Testing 3DS Challenge Flow:**

```javascript
const threeDSPayment = {
  merchantId: {{TEST_MERCHANT_ID}},
  referenceId: 'test-3ds-1001',
  invoiceNumber: 'TEST-3DS-1001',
  amount: { total: 10000, currency: 'UYU' },
  capture: { method: 'automatic' },
  paymentMethod: {
    source: 'card',
    card: {
      number: '4000000000003063',
      expMonth: 12,
      expYear: 2028,
      cvc: '123',
      cardholder: {
        firstName: 'Test',
        lastName: 'User',
        email: 'test.user@example.com'
      }
    }
  },
  browserDetails: {
    ipAddress: '203.0.113.10',
    userAgent: 'Mozilla/5.0'
  },
  callbackUrl: 'https://yoursite.com/callbacks/payments'
};

// Result: Depending on merchant configuration, the response may include
// payment.actions with an approval_url redirect.
```

### PIX Test Data (Brazil)

For testing PIX payments in Brazil:

**Test PIX Key:**

```
PIX Key Type: CPF
PIX Key: 123.456.789-00 (test CPF)
```

**Test PIX QR Code:**

```
Use the Mock processor to generate test PIX QR codes
that simulate successful payments after 30 seconds.
```

## Mock Processor

The Mock processor simulates payment processing without connecting to real payment gateways.

### Enabling Mock Processor

In the **Dashboard (Testing):**

1. Go to **Configuration → Payment Processors**
2. Select **Mock Processor**
3. Enable for your merchant
4. Configure behavior:
   * Success rate: 90% (configurable)
   * Average latency: 1000ms
   * Random failures: Enabled

### Mock Processor Behavior

**Successful Payments:**

* All amounts ending in `00` (e.g., 1000, 2500, 10000)
* Returns synthetic transaction ID: `mock_txn_abc123`

**Failed Payments:**

* Amount `666` → Decline (generic)
* Amount `111` → Insufficient funds
* Amount `222` → Invalid card
* Amount `333` → Expired card
* Amount `444` → Processing error

**Example:**

```javascript
// Will succeed
const success = await createPayment({
  merchantId: {{TEST_MERCHANT_ID}},
  amount: 10000,  // Ends in 00
  currency: \"UYU\",
  capture: true,
  paymentMethod: \"MOCK\"
});

// Will decline with insufficient_funds
const failure = await createPayment({
  merchantId: {{TEST_MERCHANT_ID}},
  amount: 111,  // Magic number for insufficient funds
  currency: \"UYU\",
  capture: true,
  paymentMethod: \"MOCK\"
});
```

## Testing Payment Flows

### Test: Simple Payment

```javascript
async function testSimplePayment() {
  const payment = {
    merchantId: {{TEST_MERCHANT_ID}},
    amount: 10000,
    currency: "UYU",
    description: \"Test simple payment\",
    capture: true,
    card: {
      number: \"4444333322221111\",
      expMonth: 12,
      expYear: 2025,
      cvv: \"123\",
      holder: \"Test User\"
    }
  };

  const result = await createPayment(payment);

  assert(result.status === 'captured', 'Payment should be captured');
  assert(result.amount === 10000, 'Amount should match');
  assert(result.paymentId, 'Should have payment ID');

  console.log('✅ Simple payment test passed');
}
```

### Test: Pre-Authorization + Capture

```javascript
async function testPreAuthCapture() {
  // 1. Pre-authorize
  const preAuth = await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    amount: 15000,
    currency: "UYU",
    capture: false,  // Don't capture yet
    card: {
      number: \"4444333322221111\",
      expMonth: 12,
      expYear: 2025,
      cvv: \"123\",
      holder: \"Test User\"
    }
  });

  assert(preAuth.status === 'authorized', 'Should be authorized only');

  // 2. Capture (full amount)
  const capture = await capturePayment({
    paymentId: preAuth.paymentId,
    amount: 15000
  });

  assert(capture.status === 'captured', 'Should be captured');

  // 3. Verify payment status
  const payment = await getPayment(preAuth.paymentId);
  assert(payment.status === 'captured', 'Final status should be captured');

  console.log('✅ Pre-auth + capture test passed');
}
```

### Test: Refund

```javascript
async function testRefund() {
  // 1. Create and capture payment
  const payment = await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    amount: 20000,
    currency: "UYU",
    capture: true,
    card: {
      number: \"4444333322221111\",
      expMonth: 12,
      expYear: 2025,
      cvv: \"123\",
      holder: \"Test User\"
    }
  });

  assert(payment.status === 'captured');

  // 2. Full refund
  const refund = await createRefund({
    paymentId: payment.paymentId,
    amount: 20000,
    reason: \"Customer requested refund\"
  });

  assert(refund.status === 'refunded');
  assert(refund.amount === 20000);

  // 3. Verify payment status
  const updatedPayment = await getPayment(payment.paymentId);
  assert(updatedPayment.status === 'refunded');

  console.log('✅ Refund test passed');
}
```

### Test: Tokenization + Recurring Payment

```javascript
async function testTokenizationRecurring() {
  // 1. Create customer
  const customer = await createCustomer({
    email: "test@example.com",
    firstName: \"Test\",
    lastName: \"User\"
  });

  // 2. First payment + tokenization request
  await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    customerId: customer.id,
    referenceId: 'tokenize-first-payment-1001',
    invoiceNumber: 'TOKENIZE-1001',
    amount: { total: 10000, currency: 'UYU' },
    capture: { method: 'automatic' },
    paymentMethod: {
      source: 'card',
      card: {
        number: '4444333322221111',
        expMonth: 12,
        expYear: 2028,
        cvc: '123',
        cardholder: {
          firstName: 'Test',
          lastName: 'User',
          email: 'test@example.com'
        },
        tokenizationSettings: {
          type: 'store'
        }
      }
    }
  });

  const instruments = await listCustomerPaymentInstruments(customer.id);
  assert(instruments.length > 0);
  const token = instruments[0].token;

  // 3. Second payment with token
  const secondPayment = await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    customerId: customer.id,
    referenceId: 'token-reuse-1001',
    invoiceNumber: 'TOKEN-REUSE-1001',
    amount: { total: 10000, currency: 'UYU' },
    capture: { method: 'automatic' },
    paymentMethod: {
      source: 'token',
      paymentInstrument: {
        token
      }
    }
  });

  assert(secondPayment.status === 'captured');

  console.log('✅ Tokenization + recurring test passed');
}
```

## Testing Callbacks

### Local Callback Testing

Use **ngrok** to expose your local server:

```bash
# 1. Install ngrok
npm install -g ngrok

# 2. Start your local server
npm start  # Running on http://localhost:3000

# 3. Expose with ngrok
ngrok http 3000

# 4. Use the public ngrok URL as your callback destination
# Example: https://abc123.ngrok.io/callbacks/plexo
```

### Test Callback Endpoint

```javascript
const express = require('express');

app.post('/callbacks/plexo', express.json(), (req, res) => {
  const payload = req.body;

  // Persist or enqueue the callback body, then acknowledge quickly.
  console.log('Received callback payload');
  console.log(payload);

  res.status(200).send('OK');
});
```

### Trigger Test Callbacks

Create a test payment or session with your callback URL:

1. Expose your local endpoint with ngrok
2. Set `callbackUrl` on `POST /v1/payments`, or set `settings.callbacks.*` on `POST /v1/sessions`
3. Complete the test payment or hosted session flow
4. Inspect the callback your endpoint receives

Check your logs for the callback:

```
Received callback payload
✅ Payment pay_test_123 captured
```

## Common Testing Scenarios

### Scenario 1: Payment Failure Handling

```javascript
async function testPaymentFailures() {
  const failureScenarios = [
    { amount: 111, expectedError: 'insufficient_funds' },
    { amount: 222, expectedError: 'invalid_card' },
    { amount: 333, expectedError: 'expired_card' }
  ];

  for (const scenario of failureScenarios) {
    try {
      await createPayment({
        merchantId: {{TEST_MERCHANT_ID}},
        amount: scenario.amount,
        currency: "UYU",
        capture: true,
        paymentMethod: \"MOCK\"
      });

      throw new Error('Payment should have failed');
    } catch (error) {
      assert(
        error.code === scenario.expectedError,
        `Expected ${scenario.expectedError}, got ${error.code}`
      );
      console.log(`✅ ${scenario.expectedError} handled correctly`);
    }
  }
}
```

### Scenario 2: Idempotency

```javascript
async function testIdempotency() {
  const idempotencyKey = `test-${Date.now()}`;

  // Send same request twice
  const payment1 = await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    amount: 10000,
    currency: "UYU",
    capture: true,
    referenceId: idempotencyKey,
    card: {
      number: \"4444333322221111\",
      expMonth: 12,
      expYear: 2025,
      cvv: \"123\",
      holder: \"Test User\"
    }
  });

  const payment2 = await createPayment({
    merchantId: {{TEST_MERCHANT_ID}},
    amount: 10000,
    currency: "UYU",
    capture: true,
    referenceId: idempotencyKey,  // Same key
    card: {
      number: \"4444333322221111\",
      expMonth: 12,
      expYear: 2025,
      cvv: \"123\",
      holder: \"Test User\"
    }
  });

  // Should return same payment
  assert(payment1.paymentId === payment2.paymentId);
  console.log('✅ Idempotency working correctly');
}
```

### Scenario 3: Concurrent Requests

```javascript
async function testConcurrentPayments() {
  const payments = [];

  // Create 10 payments concurrently
  for (let i = 0; i < 10; i++) {
    payments.push(
      createPayment({
        merchantId: {{TEST_MERCHANT_ID}},
        amount: 10000,
        currency: "UYU",
        referenceId: `concurrent-test-${i}`,
        capture: true,
        card: {
          number: \"4444333322221111\",
          expMonth: 12,
          expYear: 2025,
          cvv: \"123\",
          holder: \"Test User\"
        }
      })
    );
  }

  const results = await Promise.allSettled(payments);

  const successful = results.filter(r => r.status === 'fulfilled').length;
  const failed = results.filter(r => r.status === 'rejected').length;

  console.log(`✅ ${successful} payments succeeded, ${failed} failed`);
}
```

## Test Data Best Practices

### 1. Use Consistent Test Data

```javascript
// Create reusable test fixtures
const TEST_CARDS = {
  visa: {
    number: \"4444333322221111\",
    expMonth: 12,
    expYear: 2025,
    cvv: \"123\",
    holder: \"Test User\"
  },
  mastercard: {
    number: \"5555444433332222\",
    expMonth: 12,
    expYear: 2025,
    cvv: \"123\",
    holder: \"Test User\"
  }
};

const TEST_CUSTOMERS = {
  individual: {
    email: \"individual@test.com\",
    firstName: \"John\",
    lastName: \"Doe\",
    documentType: \"CI\",
    documentNumber: \"12345678\"
  }
};
```

### 2. Clean Up Test Data

```javascript
async function cleanupTestData() {
  // Delete test customers created during tests
  const customers = await getCustomers({ email: \"*@test.com\" });

  for (const customer of customers) {
    await deleteCustomer(customer.customerId);
  }

  console.log(`Cleaned up ${customers.length} test customers`);
}

// Run after test suite
afterAll(cleanupTestData);
```

### 3. Use Descriptive References

```javascript
// ✅ Good: Clear test purpose
const payment = await createPayment({
  referenceId: \"test-refund-scenario-001\",
  description: \"Test: Full refund of successful payment\"
});

// ❌ Bad: Generic reference
const payment = await createPayment({
  referenceId: \"test123\"
});
```

## Automated Testing

### Jest Test Suite Example

```javascript
const { PlexoClient } = require('./plexo-client');

describe('Plexo Payment Processing', () => {
  let client;

  beforeAll(() => {
    client = new PlexoClient({
      baseUrl: 'https://api.testing.plexo.com.uy',
      clientId: process.env.PLEXO_TEST_CLIENT_ID,
      apiKey: process.env.PLEXO_TEST_API_KEY,
      merchantId: parseInt(process.env.PLEXO_TEST_MERCHANT_ID)
    });
  });

  describe('Simple Payments', () => {
    it('should process successful payment', async () => {
      const payment = await client.createPayment({
        amount: 10000,
        currency: \"UYU\",
        capture: true,
        card: {
          number: \"4444333322221111\",
          expMonth: 12,
          expYear: 2025,
          cvv: \"123\",
          holder: \"Test User\"
        }
      });

      expect(payment.status).toBe('captured');
      expect(payment.amount).toBe(10000);
    });

    it('should handle declined payment', async () => {
      await expect(
        client.createPayment({
          amount: 10000,
          currency: \"UYU\",
          capture: true,
          card: {
            number: \"4000000000000002\",  // Decline card
            expMonth: 12,
            expYear: 2025,
            cvv: \"123\",
            holder: \"Test User\"
          }
        })
      ).rejects.toThrow('card_declined');
    });
  });

  describe('Tokenization', () => {
    it('should tokenize card and charge token', async () => {
      const customer = await client.createCustomer({
        email: \"tokenization-test@example.com\",
        firstName: \"Test\",
        lastName: \"User\"
      });

      const payment1 = await client.createPayment({
        customerId: customer.id,
        referenceId: 'tokenization-test-1001',
        invoiceNumber: 'TOKENIZATION-TEST-1001',
        amount: { total: 10000, currency: 'UYU' },
        capture: { method: 'automatic' },
        paymentMethod: {
          source: 'card',
          card: {
            number: '4444333322221111',
            expMonth: 12,
            expYear: 2028,
            cvc: '123',
            cardholder: {
              firstName: 'Test',
              lastName: 'User',
              email: 'tokenization-test@example.com'
            },
            tokenizationSettings: {
              type: 'store'
            }
          }
        }
      });

      expect(payment1.status).toBeDefined();

      const instruments = await client.listCustomerPaymentInstruments(customer.id);
      expect(instruments[0]?.token).toBeDefined();

      const payment2 = await client.createPayment({
        customerId: customer.id,
        referenceId: 'token-charge-1001',
        invoiceNumber: 'TOKEN-CHARGE-1001',
        amount: { total: 10000, currency: 'UYU' },
        capture: { method: 'automatic' },
        paymentMethod: {
          source: 'token',
          paymentInstrument: {
            token: instruments[0].token,
          }
        }
      });

      expect(payment2.status).toBe('captured');
    });
  });
});
```

## Going Live Checklist

Before switching to production:

* [ ] All tests passing in testing environment
* [ ] Callback endpoint tested end-to-end
* [ ] Error handling tested for all scenarios
* [ ] Idempotency keys implemented
* [ ] 3DS authentication tested (if enabled)
* [ ] Tokenization working correctly
* [ ] Refund process tested
* [ ] Customer support can view payments in Dashboard
* [ ] Monitoring and alerts configured
* [ ] PCI compliance requirements met
* [ ] Security review completed
* [ ] Production credentials obtained
* [ ] DNS/certificates configured for production
* [ ] Load testing completed

## Related Resources

* [Supported Test Cards](/rest-api/reference/test-cards.md)
* [Error Codes Reference](/rest-api/reference/error-codes.md)
* [Callbacks Guide](/rest-api/operations/callbacks.md)
* [Security & PCI Compliance](/rest-api/core-concepts/security.md)
* [Payment Processing Guide](/rest-api/payments/payment-processing.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://plexo.gitbook.io/rest-api/operations/testing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
