> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get started with the Modulus Labs QR API in minutes

## Overview

This guide will walk you through creating your first Dynamic QR Ph payment in under 10 minutes. By the end, you'll have generated a QR code that customers can scan to make a payment.

<Note>
  This quickstart uses **Node.js**, but the same principles apply to any language. Check the [API Reference](/api-reference/qr/create) for examples in other languages.
</Note>

## Prerequisites

<Steps>
  <Step title="Get Your Credentials">
    Contact Modulus Labs to receive:

    * **Secret Key** (starts with `sk_`)
    * **Encryption Key**
    * **Activation Code** (format: `XXXX-XXXX-XXXX-XXXX`)
  </Step>

  <Step title="Install Node.js">
    Ensure you have Node.js 14+ installed

    ```bash theme={null}
    node --version
    # v18.0.0 or higher
    ```
  </Step>

  <Step title="Create a New Project">
    ```bash theme={null}
    mkdir qr-api-demo
    cd qr-api-demo
    npm init -y
    ```
  </Step>
</Steps>

## Step 1: Install Dependencies

Install the required packages:

```bash theme={null}
npm install axios jose dotenv
```

**Package Purposes:**

* `axios` - HTTP client for API requests
* `jose` - JWE token encryption/decryption
* `dotenv` - Manage environment variables securely

## Step 2: Set Up Environment Variables

Create a `.env` file in your project root:

```bash .env theme={null}
MODULUS_SECRET_KEY=sk_YOUR_SECRET_KEY_HERE
MODULUS_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE
MODULUS_ACTIVATION_CODE=XXXX-XXXX-XXXX-XXXX
```

<Warning>
  **Never commit your `.env` file to version control!**
  Add it to `.gitignore` immediately:

  ```bash theme={null}
  echo ".env" >> .gitignore
  ```

  **Note:**
  The `MODULUS_ENCRYPTION_KEY` must be a Base64-encoded key that decodes to 32 bytes to match the A256KW encryption key-length requirement. Any leading/trailing whitespace will cause encryption to fail.
</Warning>

## Step 3: Create Your First QR Code

Create `create-qr.js`:

```javascript create-qr.js theme={null}
require('dotenv').config();
const axios = require('axios');
const jose = require('jose');
const fs = require('fs');
const crypto = require('crypto');

const SECRET_KEY = process.env.MODULUS_SECRET_KEY;
const ENCRYPTION_KEY = process.env.MODULUS_ENCRYPTION_KEY;
const ACTIVATION_CODE = process.env.MODULUS_ACTIVATION_CODE;

async function createQRPayment(amount, reference) {
  console.log(' Creating Dynamic QR Ph...\n');

  try {
    // Step 1: Prepare payload
    console.log(' Step 1: Preparing payment data...');
    const payload = {
      activationCode: ACTIVATION_CODE,
      currency: 'PHP',
      amount: amount.toFixed(2),
      merchantReferenceNumber: reference || crypto.randomUUID()
    };
    console.log('Payload:', payload);

    // Step 2: Encrypt payload
    console.log('\n Step 2: Encrypting payload...');
    const key = Buffer.from(ENCRYPTION_KEY, 'base64');
    const jweToken = await new jose.CompactEncrypt(
      new TextEncoder().encode(JSON.stringify(payload))
    )
      .setProtectedHeader({ alg: 'A256KW', enc: 'A256CBC-HS512' })
      .encrypt(key);
    console.log('JWE Token (first 50 chars):', jweToken.substring(0, 50) + '...');

    // Step 3: Make API request
    console.log('\n Step 3: Calling API...');
    const response = await axios.post(
      'https://qrph.sbx.moduluslabs.io/v1/pay/qr',
      { Token: jweToken },
      {
        auth: { username: SECRET_KEY, password: '' },
        headers: { 'Content-Type': 'application/json' }
      }
    );
    console.log('API Response received');

    // Step 4: Decrypt response
    console.log('\n Step 4: Decrypting response...');
    const { plaintext } = await jose.compactDecrypt(
      response.data.Token,
      key
    );
    const result = JSON.parse(new TextDecoder().decode(plaintext));

    console.log('\n Success!');
    console.log('Transaction ID:', result.id);
    console.log('QR Body length:', result.qrBody.length, 'characters');

    // Step 5: Save QR code
    console.log('\n Step 5: Saving QR code...');
    const buffer = Buffer.from(result.qrBody, 'base64');
    const filename = `qr-${result.id}.png`;
    fs.writeFileSync(filename, buffer);
    console.log(`QR code saved as: ${filename}`);

    console.log('\n Done! Show this QR code to customers for payment.');

    return result;

  } catch (error) {
    console.error('\n Error creating QR code:');
    if (error.response) {
      console.error('Status:', error.response.status);
      console.error('Data:', error.response.data);

      // Try to decrypt error if it's a JWE token
      if (error.response.data.Token) {
        try {
          const key = Buffer.from(ENCRYPTION_KEY, 'base64');
          const { plaintext } = await jose.compactDecrypt(
            error.response.data.Token,
            key
          );
          const errorData = JSON.parse(new TextDecoder().decode(plaintext));
          console.error('Error Details:', errorData);
        } catch (decryptError) {
          console.error('Could not decrypt error response');
        }
      }
    } else {
      console.error('Error:', error.message);
    }
    throw error;
  }
}

// Run it!
const amount = 500.00; // ₱500.00
const reference = 'ORDER-' + Date.now();

createQRPayment(amount, reference)
  .then(() => process.exit(0))
  .catch(() => process.exit(1));
```

Run it:

```bash theme={null}
node create-qr.js
```

**Expected output:**

```
 Creating Dynamic QR Ph...

Step 1: Preparing payment data...
Payload: {
  activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
  currency: 'PHP',
  amount: '500.00',
  merchantReferenceNumber: 'ORDER-1706543210123'
}

Step 2: Encrypting payload...
JWE Token (first 50 chars): eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEy...

Step 3: Calling API...
API Response received

Step 4: Decrypting response...

Success!
Transaction ID: 0ce32626-08cf-405c-adab-6d384a8871da
QR Body length: 1234 characters

Step 5: Saving QR code...
QR code saved as: qr-0ce32626-08cf-405c-adab-6d384a8871da.png

Done! Show this QR code to customers for payment.
```

## Step 4: View Your QR Code

Open the generated PNG file:

```bash theme={null}
# macOS
open qr-*.png

# Linux
xdg-open qr-*.png

# Windows
start qr-*.png
```

You should see a QR code image that customers can scan to make a payment.

## Step 5: Test Payment (Optional)

To test the complete flow, follow the [Testing Guide](/docs/qr/testing) to simulate a payment and receive a webhook notification.

### Quick Test Summary:

<Steps>
  <Step title="Decode QR Code">
    Upload your QR image to [zxing.org/w/decode.jspx](https://zxing.org/w/decode.jspx) to get the raw QR value
  </Step>

  <Step title="Simulate Payment">
    Use the raw QR value with the Simulate Webhook API to trigger a payment event
  </Step>

  <Step title="Receive Webhook">
    Your registered webhook endpoint receives the transaction status
  </Step>
</Steps>

## Complete Example with Error Handling

Here's a production-ready example with proper error handling:

```javascript production-example.js theme={null}
require('dotenv').config();
const axios = require('axios');
const jose = require('jose');
const fs = require('fs');

class QRPaymentService {
  constructor(config) {
    this.secretKey = config.secretKey;
    this.encryptionKey = Buffer.from(config.encryptionKey, 'base64');
    this.activationCode = config.activationCode;
    this.baseURL = config.baseURL || 'https://qrph.sbx.moduluslabs.io';
  }

  async encrypt(data) {
    const jwe = await new jose.CompactEncrypt(
      new TextEncoder().encode(JSON.stringify(data))
    )
      .setProtectedHeader({ alg: 'A256KW', enc: 'A256CBC-HS512' })
      .encrypt(this.encryptionKey);
    return jwe;
  }

  async decrypt(jweToken) {
    const { plaintext } = await jose.compactDecrypt(jweToken, this.encryptionKey);
    return JSON.parse(new TextDecoder().decode(plaintext));
  }

  validateAmount(amount) {
    const num = parseFloat(amount);
    if (isNaN(num) || num < 1.00 || num > 99999.99) {
      throw new Error('Amount must be between 1.00 and 99999.99');
    }
    return num.toFixed(2);
  }

  async createQR(amount, merchantReferenceNumber) {
    try {
      // Validate inputs
      const validAmount = this.validateAmount(amount);

      if (!merchantReferenceNumber || merchantReferenceNumber.length > 36) {
        throw new Error('Invalid merchant reference number');
      }

      // Prepare and encrypt payload
      const payload = {
        activationCode: this.activationCode,
        currency: 'PHP',
        amount: validAmount,
        merchantReferenceNumber
      };

      const jweToken = await this.encrypt(payload);

      // Make API request
      const response = await axios.post(
        `${this.baseURL}/v1/pay/qr`,
        { Token: jweToken },
        {
          auth: { username: this.secretKey, password: '' },
          headers: { 'Content-Type': 'application/json' },
          timeout: 10000 // 10 second timeout
        }
      );

      // Decrypt and return result
      const result = await this.decrypt(response.data.Token);
      return {
        success: true,
        data: result
      };

    } catch (error) {
      // Handle errors
      if (error.response) {
        const { status, data } = error.response;

        // Try to decrypt error if it's a JWE token
        let errorDetails = data;
        if (data.Token) {
          try {
            errorDetails = await this.decrypt(data.Token);
          } catch (decryptError) {
            console.error('Could not decrypt error response');
          }
        }

        return {
          success: false,
          error: {
            status,
            code: errorDetails.code,
            message: errorDetails.error,
            referenceNumber: errorDetails.referenceNumber
          }
        };
      }

      return {
        success: false,
        error: {
          message: error.message
        }
      };
    }
  }

  saveQRCode(qrBody, filename) {
    const buffer = Buffer.from(qrBody, 'base64');
    fs.writeFileSync(filename, buffer);
    return filename;
  }
}

// Usage
async function main() {
  const service = new QRPaymentService({
    secretKey: process.env.MODULUS_SECRET_KEY,
    encryptionKey: process.env.MODULUS_ENCRYPTION_KEY,
    activationCode: process.env.MODULUS_ACTIVATION_CODE
  });

  const result = await service.createQR(500.00, `ORDER-${Date.now()}`);

  if (result.success) {
    console.log(' QR Code created successfully');
    console.log('Transaction ID:', result.data.id);

    const filename = service.saveQRCode(
      result.data.qrBody,
      `qr-${result.data.id}.png`
    );
    console.log('Saved to:', filename);
  } else {
    console.error('Failed to create QR code');
    console.error('Error:', result.error);
  }
}

main();
```

## What's Next?

Now that you've created your first QR code, here are some next steps:

<CardGroup cols={2}>
  <Card title="Set Up Webhooks" icon="webhook" href="/docs/webhooks">
    Receive real-time payment notifications
  </Card>

  <Card title="Test Payments" icon="flask" href="/docs/qr/testing">
    Simulate payments in sandbox environment
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/errors">
    Learn how to handle errors gracefully
  </Card>

  <Card title="Go to Production" icon="rocket" href="/docs/production">
    Deploy your integration to production
  </Card>
</CardGroup>

## Common Issues

<AccordionGroup>
  <Accordion title="Error: Invalid API Key">
    **Solution:**

    * Check your `.env` file has the correct `MODULUS_SECRET_KEY`
    * Ensure no extra spaces or quotes around the key
    * Verify you're using sandbox credentials for sandbox URL
  </Accordion>

  <Accordion title="Error: Cannot decrypt token">
    **Solution:**

    * Verify your `MODULUS_ENCRYPTION_KEY` is correct
    * Ensure the key hasn't been modified (no spaces, newlines)
    * Check you're Base64-decoding the key
  </Accordion>

  <Accordion title="Error: Amount validation failed">
    **Solution:**

    * Ensure amount is between 1.00 and 99999.99
    * Pass amount as a string with 2 decimal places: `"500.00"`
    * Use `.toFixed(2)` to format numbers correctly
  </Accordion>

  <Accordion title="QR code file is corrupted">
    **Solution:**

    * Ensure you're decoding the full Base64 string
    * Check the file was written in binary mode
    * Verify no encoding issues during Base64 decode
  </Accordion>
</AccordionGroup>

## Need Help?

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/qr/create">
    Complete API documentation
  </Card>

  <Card title="Code Examples" icon="code" href="/api-reference/qr/create#code-examples">
    Examples in multiple languages
  </Card>

  <Card title="Contact Support" icon="envelope" href="mailto:support@moduluslabs.io">
    Get help from our team
  </Card>

  <Card title="Join Community" icon="users" href="https://community.moduluslabs.io">
    Connect with other developers
  </Card>
</CardGroup>

<Tip>
  **You're all set!** You've successfully created your first Dynamic QR Ph payment. The QR code is ready to be scanned by customers using their banking apps.
</Tip>
