# Note: You need to encrypt the payload first using a script/tool
# See the Encryption Guide for JWE token creation
# 1. Create encrypted JWE token (using external tool/script)
ENCRYPTED_TOKEN="eyJhbGciOiJBMjU2S1ciL..."
# 2. Make API request
curl -X POST https://qrph.sbx.moduluslabs.io/v1/pay/qr \
-u sk_YOUR_SECRET_KEY: \
-H "Content-Type: application/json" \
-d "{\"Token\": \"$ENCRYPTED_TOKEN\"}"
# 3. Response will be an encrypted JWE token that needs decryption
const axios = require('axios');
const jose = require('jose');
const crypto = require('crypto');
const SECRET_KEY = process.env.MODULUS_SECRET_KEY;
const ENCRYPTION_KEY = process.env.MODULUS_ENCRYPTION_KEY;
async function createDynamicQR(amount, reference) {
// 1. Prepare the payload
const payload = {
activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
currency: 'PHP',
amount: amount.toFixed(2), // Convert to string with 2 decimals
merchantReferenceNumber: reference || crypto.randomUUID()
};
// 2. Encrypt payload into JWE token
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);
// 3. Make API request
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' }
}
);
// 4. Decrypt response
const { plaintext } = await jose.compactDecrypt(
response.data.Token,
key
);
const result = JSON.parse(new TextDecoder().decode(plaintext));
console.log('Transaction ID:', result.id);
console.log('QR Code (Base64):', result.qrBody.substring(0, 50) + '...');
return result;
}
// Usage
createDynamicQR(500.00, 'ORDER-2025-001')
.then(result => {
// Save QR code to file
const fs = require('fs');
const buffer = Buffer.from(result.qrBody, 'base64');
fs.writeFileSync('qr-code.png', buffer);
console.log('QR code saved to qr-code.png');
})
.catch(error => {
console.error('Error:', error.response?.data || error.message);
});
import os
import base64
import json
import requests
from jose import jwe
from uuid import uuid4
SECRET_KEY = os.getenv('MODULUS_SECRET_KEY')
ENCRYPTION_KEY = os.getenv('MODULUS_ENCRYPTION_KEY')
def create_dynamic_qr(amount, reference=None):
# 1. Prepare the payload
payload = {
'activationCode': 'A9X4-B7P2-Q6Z8-M3L5',
'currency': 'PHP',
'amount': f'{amount:.2f}', # Convert to string with 2 decimals
'merchantReferenceNumber': reference or str(uuid4())
}
# 2. Encrypt payload into JWE token
jwe_token = jwe.encrypt(
json.dumps(payload),
ENCRYPTION_KEY,
algorithm='A256KW',
encryption='A256CBC-HS512'
)
# 3. Make API request
response = requests.post(
'https://qrph.sbx.moduluslabs.io/v1/pay/qr',
json={'Token': jwe_token},
auth=(SECRET_KEY, ''),
headers={'Content-Type': 'application/json'}
)
response.raise_for_status()
# 4. Decrypt response
decrypted = jwe.decrypt(response.json()['Token'], ENCRYPTION_KEY)
result = json.loads(decrypted)
print(f'Transaction ID: {result["id"]}')
print(f'QR Code (Base64): {result["qrBody"][:50]}...')
return result
# Usage
if __name__ == '__main__':
try:
result = create_dynamic_qr(500.00, 'ORDER-2025-001')
# Save QR code to file
image_data = base64.b64decode(result['qrBody'])
with open('qr-code.png', 'wb') as f:
f.write(image_data)
print('QR code saved to qr-code.png')
except Exception as e:
print(f'Error: {str(e)}')
<?php
require 'vendor/autoload.php';
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Algorithm\KeyEncryption\A256KW;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
$secretKey = getenv('MODULUS_SECRET_KEY');
$encryptionKey = getenv('MODULUS_ENCRYPTION_KEY');
function createDynamicQR($amount, $reference = null) {
global $secretKey, $encryptionKey;
// 1. Prepare the payload
$payload = [
'activationCode' => 'A9X4-B7P2-Q6Z8-M3L5',
'currency' => 'PHP',
'amount' => number_format($amount, 2, '.', ''),
'merchantReferenceNumber' => $reference ?? uniqid('ORDER-', true)
];
// 2. Encrypt payload (simplified - use proper JWE library)
// See encryption documentation for full implementation
$jweToken = encryptToJWE(json_encode($payload), $encryptionKey);
// 3. Make API request
$ch = curl_init('https://qrph.sbx.moduluslabs.io/v1/pay/qr');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['Token' => $jweToken]));
curl_setopt($ch, CURLOPT_USERPWD, $secretKey . ':');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API request failed with HTTP $httpCode");
}
// 4. Decrypt response
$responseData = json_decode($response, true);
$decrypted = decryptJWE($responseData['Token'], $encryptionKey);
$result = json_decode($decrypted, true);
echo "Transaction ID: {$result['id']}\n";
return $result;
}
// Usage
try {
$result = createDynamicQR(500.00, 'ORDER-2025-001');
// Save QR code to file
file_put_contents('qr-code.png', base64_decode($result['qrBody']));
echo "QR code saved to qr-code.png\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class CreateDynamicQRExample {
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
createDynamicQR(500.00, "ORDER-2025-001");
}
static void createDynamicQR(double amount, String reference) throws Exception {
String secretKey = System.getenv("MODULUS_SECRET_KEY");
String encryptionKey = System.getenv("MODULUS_ENCRYPTION_KEY");
if (secretKey == null || encryptionKey == null) {
System.out.println("Error: Missing environment variables");
System.out.println("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
// 1. Prepare the payload
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("activationCode", "A9X4-B7P2-Q6Z8-M3L5");
payload.put("currency", "PHP");
payload.put("amount", String.format("%.2f", amount));
payload.put("merchantReferenceNumber", reference != null ? reference : UUID.randomUUID().toString());
String payloadJson = mapper.writeValueAsString(payload);
System.out.println("Payload: " + payloadJson);
// 2. Decode encryption key from Base64URL and create JWE
byte[] keyBytes = Base64.getUrlDecoder().decode(encryptionKey);
OctetSequenceKey jwk = new OctetSequenceKey.Builder(keyBytes).build();
JWEHeader header = new JWEHeader(JWEAlgorithm.A256KW, EncryptionMethod.A256CBC_HS512);
JWEObject jwe = new JWEObject(header, new Payload(payloadJson));
jwe.encrypt(new AESEncrypter(jwk));
String jweToken = jwe.serialize();
System.out.println("JWE Token: " + jweToken.substring(0, 60) + "...");
// 3. Make API request with Basic Auth
HttpClient client = HttpClient.newHttpClient();
String auth = secretKey + ":";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
String requestBody = mapper.writeValueAsString(Map.of("Token", jweToken));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://qrph.sbx.moduluslabs.io/v1/pay/qr"))
.header("Authorization", "Basic " + encodedAuth)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
System.out.println("Sending request to API...");
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.out.println("Error: HTTP " + response.statusCode());
System.out.println("Details: " + response.body());
return;
}
// 4. Parse and decrypt response
Map<String, String> responseData = mapper.readValue(response.body(), Map.class);
String responseToken = responseData.get("Token");
JWEObject responseJwe = JWEObject.parse(responseToken);
responseJwe.decrypt(new AESDecrypter(jwk));
String decryptedJson = responseJwe.getPayload().toString();
Map<String, String> result = mapper.readValue(decryptedJson, Map.class);
System.out.println("✓ Transaction ID: " + result.get("id"));
String qrBody = result.get("qrBody");
System.out.println("✓ QR Code (Base64): " + qrBody.substring(0, Math.min(50, qrBody.length())) + "...");
// 5. Save QR code to file
byte[] imageData = Base64.getDecoder().decode(qrBody);
Files.write(Path.of("qr-code.png"), imageData);
System.out.println("✓ QR code saved to qr-code.png");
}
}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
)
type QRPayload struct {
ActivationCode string `json:"activationCode"`
Currency string `json:"currency"`
Amount string `json:"amount"`
MerchantReferenceNumber string `json:"merchantReferenceNumber"`
}
type TokenRequest struct {
Token string `json:"Token"`
}
type QRResponse struct {
ID string `json:"id"`
QRBody string `json:"qrBody"`
}
func main() {
if err := createDynamicQR(500.00, "ORDER-2025-001"); err != nil {
fmt.Printf("Error: %v\n", err)
}
}
func createDynamicQR(amount float64, reference string) error {
secretKey := os.Getenv("MODULUS_SECRET_KEY")
encryptionKey := os.Getenv("MODULUS_ENCRYPTION_KEY")
if secretKey == "" || encryptionKey == "" {
return fmt.Errorf("missing environment variables: MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY required")
}
// 1. Prepare the payload
payload := QRPayload{
ActivationCode: "A9X4-B7P2-Q6Z8-M3L5",
Currency: "PHP",
Amount: fmt.Sprintf("%.2f", amount),
MerchantReferenceNumber: reference,
}
payloadJSON, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal payload: %w", err)
}
fmt.Printf("Payload: %s\n", payloadJSON)
// 2. Decode encryption key from Base64URL
keyBytes, err := base64.RawURLEncoding.DecodeString(encryptionKey)
if err != nil {
return fmt.Errorf("failed to decode encryption key: %w", err)
}
fmt.Printf("Decoded key length: %d bytes (%d bits)\n", len(keyBytes), len(keyBytes)*8)
// 3. Encrypt payload into JWE token
encrypted, err := jwe.Encrypt(payloadJSON,
jwe.WithKey(jwa.A256KW, keyBytes),
jwe.WithContentEncryption(jwa.A256CBC_HS512),
)
if err != nil {
return fmt.Errorf("failed to encrypt payload: %w", err)
}
jweToken := string(encrypted)
fmt.Printf("JWE Token: %s...\n", jweToken[:60])
// 4. Make API request with Basic Auth
reqBody := TokenRequest{Token: jweToken}
jsonData, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", "https://qrph.sbx.moduluslabs.io/v1/pay/qr", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.SetBasicAuth(secretKey, "")
req.Header.Set("Content-Type", "application/json")
fmt.Println("Sending request to API...")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
// 5. Parse and decrypt response
var tokenResp TokenRequest
if err := json.Unmarshal(body, &tokenResp); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
decrypted, err := jwe.Decrypt([]byte(tokenResp.Token), jwe.WithKey(jwa.A256KW, keyBytes))
if err != nil {
return fmt.Errorf("failed to decrypt response: %w", err)
}
var result QRResponse
if err := json.Unmarshal(decrypted, &result); err != nil {
return fmt.Errorf("failed to parse decrypted response: %w", err)
}
fmt.Printf("✓ Transaction ID: %s\n", result.ID)
if len(result.QRBody) > 50 {
fmt.Printf("✓ QR Code (Base64): %s...\n", result.QRBody[:50])
}
// 6. Save QR code to file
imageData, err := base64.StdEncoding.DecodeString(result.QRBody)
if err != nil {
return fmt.Errorf("failed to decode QR image: %w", err)
}
if err := os.WriteFile("qr-code.png", imageData, 0644); err != nil {
return fmt.Errorf("failed to save QR code: %w", err)
}
fmt.Println("✓ QR code saved to qr-code.png")
return nil
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Jose;
namespace ModulusQRPH;
class Program
{
static async Task Main(string[] args)
{
await CreateDynamicQR(500.00m, "ORDER-2025-001");
}
/// <summary>
/// Decodes a Base64URL-encoded string to bytes.
/// This replicates what Node.js jose's importJWK does with the 'k' parameter.
/// </summary>
static byte[] Base64UrlDecode(string base64Url)
{
// Convert Base64URL to standard Base64
var base64 = base64Url
.Replace('-', '+') // Character 62
.Replace('_', '/'); // Character 63
// Add padding if necessary
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
static async Task CreateDynamicQR(decimal amount, string reference)
{
var secretKey = Environment.GetEnvironmentVariable("MODULUS_SECRET_KEY");
var encryptionKey = Environment.GetEnvironmentVariable("MODULUS_ENCRYPTION_KEY");
if (string.IsNullOrEmpty(secretKey) || string.IsNullOrEmpty(encryptionKey))
{
Console.WriteLine("Error: Missing environment variables");
Console.WriteLine("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
try
{
// 1. Prepare the payload
var payload = new
{
activationCode = "A9X4-B7P2-Q6Z8-M3L5",
currency = "PHP",
amount = amount.ToString("F2"),
merchantReferenceNumber = reference ?? Guid.NewGuid().ToString()
};
var payloadJson = JsonSerializer.Serialize(payload);
Console.WriteLine($"Payload: {payloadJson}");
// 2. Decode the encryption key from Base64URL format
// This replicates: importJWK({ kty: 'oct', k: encryptionKey }, algorithm)
var key = Base64UrlDecode(encryptionKey);
Console.WriteLine($"Decoded key length: {key.Length} bytes ({key.Length * 8} bits)");
// 3. Encrypt payload into JWE token
var jweToken = JWT.Encode(
payloadJson,
key,
JweAlgorithm.A256KW,
JweEncryption.A256CBC_HS512
);
Console.WriteLine($"JWE Token: {jweToken[..60]}...");
// 4. Make API request with Basic Auth
using var client = new HttpClient();
var authBytes = Encoding.ASCII.GetBytes($"{secretKey}:");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));
var requestBody = new { Token = jweToken };
var content = new StringContent(
JsonSerializer.Serialize(requestBody),
Encoding.UTF8,
"application/json"
);
Console.WriteLine("Sending request to API...");
var response = await client.PostAsync(
"https://qrph.sbx.moduluslabs.io/v1/pay/qr",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
return;
}
// 5. Parse and decrypt response
var responseData = JsonSerializer.Deserialize<TokenResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (responseData?.Token is null)
{
Console.WriteLine("Error: Invalid response - missing token");
return;
}
var decryptedJson = JWT.Decode(responseData.Token, key);
var result = JsonSerializer.Deserialize<QRResponse>(
decryptedJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (result is null)
{
Console.WriteLine("Error: Failed to deserialize QR response");
return;
}
Console.WriteLine($"✓ Transaction ID: {result.Id}");
Console.WriteLine($"✓ QR Code (Base64): {result.QrBody?[..Math.Min(50, result.QrBody?.Length ?? 0)]}...");
// 6. Save QR code to file
if (!string.IsNullOrEmpty(result.QrBody))
{
var imageData = Convert.FromBase64String(result.QrBody);
await File.WriteAllBytesAsync("qr-code.png", imageData);
Console.WriteLine("✓ QR code saved to qr-code.png");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.GetType().Name} - {e.Message}");
}
}
}
// Response models
public class TokenResponse
{
public string? Token { get; set; }
}
public class QRResponse
{
public string? Id { get; set; }
public string? QrBody { get; set; }
}
{
"Token": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..."
}{
"Token": "<string>"
}{
"code": "10000013",
"error": "Invalid API Key",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}{
"code": "10000015",
"error": "Insufficient permissions",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}{
"code": "10000001",
"error": "An unexpected error occurred. Please try again later.",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}QR API
Create Dynamic QR Ph
Generate a Dynamic QR Ph code for Consumer Scans Business transactions
POST
/
v1
/
pay
/
qr
# Note: You need to encrypt the payload first using a script/tool
# See the Encryption Guide for JWE token creation
# 1. Create encrypted JWE token (using external tool/script)
ENCRYPTED_TOKEN="eyJhbGciOiJBMjU2S1ciL..."
# 2. Make API request
curl -X POST https://qrph.sbx.moduluslabs.io/v1/pay/qr \
-u sk_YOUR_SECRET_KEY: \
-H "Content-Type: application/json" \
-d "{\"Token\": \"$ENCRYPTED_TOKEN\"}"
# 3. Response will be an encrypted JWE token that needs decryption
const axios = require('axios');
const jose = require('jose');
const crypto = require('crypto');
const SECRET_KEY = process.env.MODULUS_SECRET_KEY;
const ENCRYPTION_KEY = process.env.MODULUS_ENCRYPTION_KEY;
async function createDynamicQR(amount, reference) {
// 1. Prepare the payload
const payload = {
activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
currency: 'PHP',
amount: amount.toFixed(2), // Convert to string with 2 decimals
merchantReferenceNumber: reference || crypto.randomUUID()
};
// 2. Encrypt payload into JWE token
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);
// 3. Make API request
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' }
}
);
// 4. Decrypt response
const { plaintext } = await jose.compactDecrypt(
response.data.Token,
key
);
const result = JSON.parse(new TextDecoder().decode(plaintext));
console.log('Transaction ID:', result.id);
console.log('QR Code (Base64):', result.qrBody.substring(0, 50) + '...');
return result;
}
// Usage
createDynamicQR(500.00, 'ORDER-2025-001')
.then(result => {
// Save QR code to file
const fs = require('fs');
const buffer = Buffer.from(result.qrBody, 'base64');
fs.writeFileSync('qr-code.png', buffer);
console.log('QR code saved to qr-code.png');
})
.catch(error => {
console.error('Error:', error.response?.data || error.message);
});
import os
import base64
import json
import requests
from jose import jwe
from uuid import uuid4
SECRET_KEY = os.getenv('MODULUS_SECRET_KEY')
ENCRYPTION_KEY = os.getenv('MODULUS_ENCRYPTION_KEY')
def create_dynamic_qr(amount, reference=None):
# 1. Prepare the payload
payload = {
'activationCode': 'A9X4-B7P2-Q6Z8-M3L5',
'currency': 'PHP',
'amount': f'{amount:.2f}', # Convert to string with 2 decimals
'merchantReferenceNumber': reference or str(uuid4())
}
# 2. Encrypt payload into JWE token
jwe_token = jwe.encrypt(
json.dumps(payload),
ENCRYPTION_KEY,
algorithm='A256KW',
encryption='A256CBC-HS512'
)
# 3. Make API request
response = requests.post(
'https://qrph.sbx.moduluslabs.io/v1/pay/qr',
json={'Token': jwe_token},
auth=(SECRET_KEY, ''),
headers={'Content-Type': 'application/json'}
)
response.raise_for_status()
# 4. Decrypt response
decrypted = jwe.decrypt(response.json()['Token'], ENCRYPTION_KEY)
result = json.loads(decrypted)
print(f'Transaction ID: {result["id"]}')
print(f'QR Code (Base64): {result["qrBody"][:50]}...')
return result
# Usage
if __name__ == '__main__':
try:
result = create_dynamic_qr(500.00, 'ORDER-2025-001')
# Save QR code to file
image_data = base64.b64decode(result['qrBody'])
with open('qr-code.png', 'wb') as f:
f.write(image_data)
print('QR code saved to qr-code.png')
except Exception as e:
print(f'Error: {str(e)}')
<?php
require 'vendor/autoload.php';
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Algorithm\KeyEncryption\A256KW;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
$secretKey = getenv('MODULUS_SECRET_KEY');
$encryptionKey = getenv('MODULUS_ENCRYPTION_KEY');
function createDynamicQR($amount, $reference = null) {
global $secretKey, $encryptionKey;
// 1. Prepare the payload
$payload = [
'activationCode' => 'A9X4-B7P2-Q6Z8-M3L5',
'currency' => 'PHP',
'amount' => number_format($amount, 2, '.', ''),
'merchantReferenceNumber' => $reference ?? uniqid('ORDER-', true)
];
// 2. Encrypt payload (simplified - use proper JWE library)
// See encryption documentation for full implementation
$jweToken = encryptToJWE(json_encode($payload), $encryptionKey);
// 3. Make API request
$ch = curl_init('https://qrph.sbx.moduluslabs.io/v1/pay/qr');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['Token' => $jweToken]));
curl_setopt($ch, CURLOPT_USERPWD, $secretKey . ':');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API request failed with HTTP $httpCode");
}
// 4. Decrypt response
$responseData = json_decode($response, true);
$decrypted = decryptJWE($responseData['Token'], $encryptionKey);
$result = json_decode($decrypted, true);
echo "Transaction ID: {$result['id']}\n";
return $result;
}
// Usage
try {
$result = createDynamicQR(500.00, 'ORDER-2025-001');
// Save QR code to file
file_put_contents('qr-code.png', base64_decode($result['qrBody']));
echo "QR code saved to qr-code.png\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class CreateDynamicQRExample {
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
createDynamicQR(500.00, "ORDER-2025-001");
}
static void createDynamicQR(double amount, String reference) throws Exception {
String secretKey = System.getenv("MODULUS_SECRET_KEY");
String encryptionKey = System.getenv("MODULUS_ENCRYPTION_KEY");
if (secretKey == null || encryptionKey == null) {
System.out.println("Error: Missing environment variables");
System.out.println("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
// 1. Prepare the payload
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("activationCode", "A9X4-B7P2-Q6Z8-M3L5");
payload.put("currency", "PHP");
payload.put("amount", String.format("%.2f", amount));
payload.put("merchantReferenceNumber", reference != null ? reference : UUID.randomUUID().toString());
String payloadJson = mapper.writeValueAsString(payload);
System.out.println("Payload: " + payloadJson);
// 2. Decode encryption key from Base64URL and create JWE
byte[] keyBytes = Base64.getUrlDecoder().decode(encryptionKey);
OctetSequenceKey jwk = new OctetSequenceKey.Builder(keyBytes).build();
JWEHeader header = new JWEHeader(JWEAlgorithm.A256KW, EncryptionMethod.A256CBC_HS512);
JWEObject jwe = new JWEObject(header, new Payload(payloadJson));
jwe.encrypt(new AESEncrypter(jwk));
String jweToken = jwe.serialize();
System.out.println("JWE Token: " + jweToken.substring(0, 60) + "...");
// 3. Make API request with Basic Auth
HttpClient client = HttpClient.newHttpClient();
String auth = secretKey + ":";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
String requestBody = mapper.writeValueAsString(Map.of("Token", jweToken));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://qrph.sbx.moduluslabs.io/v1/pay/qr"))
.header("Authorization", "Basic " + encodedAuth)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
System.out.println("Sending request to API...");
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.out.println("Error: HTTP " + response.statusCode());
System.out.println("Details: " + response.body());
return;
}
// 4. Parse and decrypt response
Map<String, String> responseData = mapper.readValue(response.body(), Map.class);
String responseToken = responseData.get("Token");
JWEObject responseJwe = JWEObject.parse(responseToken);
responseJwe.decrypt(new AESDecrypter(jwk));
String decryptedJson = responseJwe.getPayload().toString();
Map<String, String> result = mapper.readValue(decryptedJson, Map.class);
System.out.println("✓ Transaction ID: " + result.get("id"));
String qrBody = result.get("qrBody");
System.out.println("✓ QR Code (Base64): " + qrBody.substring(0, Math.min(50, qrBody.length())) + "...");
// 5. Save QR code to file
byte[] imageData = Base64.getDecoder().decode(qrBody);
Files.write(Path.of("qr-code.png"), imageData);
System.out.println("✓ QR code saved to qr-code.png");
}
}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
)
type QRPayload struct {
ActivationCode string `json:"activationCode"`
Currency string `json:"currency"`
Amount string `json:"amount"`
MerchantReferenceNumber string `json:"merchantReferenceNumber"`
}
type TokenRequest struct {
Token string `json:"Token"`
}
type QRResponse struct {
ID string `json:"id"`
QRBody string `json:"qrBody"`
}
func main() {
if err := createDynamicQR(500.00, "ORDER-2025-001"); err != nil {
fmt.Printf("Error: %v\n", err)
}
}
func createDynamicQR(amount float64, reference string) error {
secretKey := os.Getenv("MODULUS_SECRET_KEY")
encryptionKey := os.Getenv("MODULUS_ENCRYPTION_KEY")
if secretKey == "" || encryptionKey == "" {
return fmt.Errorf("missing environment variables: MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY required")
}
// 1. Prepare the payload
payload := QRPayload{
ActivationCode: "A9X4-B7P2-Q6Z8-M3L5",
Currency: "PHP",
Amount: fmt.Sprintf("%.2f", amount),
MerchantReferenceNumber: reference,
}
payloadJSON, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal payload: %w", err)
}
fmt.Printf("Payload: %s\n", payloadJSON)
// 2. Decode encryption key from Base64URL
keyBytes, err := base64.RawURLEncoding.DecodeString(encryptionKey)
if err != nil {
return fmt.Errorf("failed to decode encryption key: %w", err)
}
fmt.Printf("Decoded key length: %d bytes (%d bits)\n", len(keyBytes), len(keyBytes)*8)
// 3. Encrypt payload into JWE token
encrypted, err := jwe.Encrypt(payloadJSON,
jwe.WithKey(jwa.A256KW, keyBytes),
jwe.WithContentEncryption(jwa.A256CBC_HS512),
)
if err != nil {
return fmt.Errorf("failed to encrypt payload: %w", err)
}
jweToken := string(encrypted)
fmt.Printf("JWE Token: %s...\n", jweToken[:60])
// 4. Make API request with Basic Auth
reqBody := TokenRequest{Token: jweToken}
jsonData, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", "https://qrph.sbx.moduluslabs.io/v1/pay/qr", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.SetBasicAuth(secretKey, "")
req.Header.Set("Content-Type", "application/json")
fmt.Println("Sending request to API...")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
// 5. Parse and decrypt response
var tokenResp TokenRequest
if err := json.Unmarshal(body, &tokenResp); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
decrypted, err := jwe.Decrypt([]byte(tokenResp.Token), jwe.WithKey(jwa.A256KW, keyBytes))
if err != nil {
return fmt.Errorf("failed to decrypt response: %w", err)
}
var result QRResponse
if err := json.Unmarshal(decrypted, &result); err != nil {
return fmt.Errorf("failed to parse decrypted response: %w", err)
}
fmt.Printf("✓ Transaction ID: %s\n", result.ID)
if len(result.QRBody) > 50 {
fmt.Printf("✓ QR Code (Base64): %s...\n", result.QRBody[:50])
}
// 6. Save QR code to file
imageData, err := base64.StdEncoding.DecodeString(result.QRBody)
if err != nil {
return fmt.Errorf("failed to decode QR image: %w", err)
}
if err := os.WriteFile("qr-code.png", imageData, 0644); err != nil {
return fmt.Errorf("failed to save QR code: %w", err)
}
fmt.Println("✓ QR code saved to qr-code.png")
return nil
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Jose;
namespace ModulusQRPH;
class Program
{
static async Task Main(string[] args)
{
await CreateDynamicQR(500.00m, "ORDER-2025-001");
}
/// <summary>
/// Decodes a Base64URL-encoded string to bytes.
/// This replicates what Node.js jose's importJWK does with the 'k' parameter.
/// </summary>
static byte[] Base64UrlDecode(string base64Url)
{
// Convert Base64URL to standard Base64
var base64 = base64Url
.Replace('-', '+') // Character 62
.Replace('_', '/'); // Character 63
// Add padding if necessary
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
static async Task CreateDynamicQR(decimal amount, string reference)
{
var secretKey = Environment.GetEnvironmentVariable("MODULUS_SECRET_KEY");
var encryptionKey = Environment.GetEnvironmentVariable("MODULUS_ENCRYPTION_KEY");
if (string.IsNullOrEmpty(secretKey) || string.IsNullOrEmpty(encryptionKey))
{
Console.WriteLine("Error: Missing environment variables");
Console.WriteLine("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
try
{
// 1. Prepare the payload
var payload = new
{
activationCode = "A9X4-B7P2-Q6Z8-M3L5",
currency = "PHP",
amount = amount.ToString("F2"),
merchantReferenceNumber = reference ?? Guid.NewGuid().ToString()
};
var payloadJson = JsonSerializer.Serialize(payload);
Console.WriteLine($"Payload: {payloadJson}");
// 2. Decode the encryption key from Base64URL format
// This replicates: importJWK({ kty: 'oct', k: encryptionKey }, algorithm)
var key = Base64UrlDecode(encryptionKey);
Console.WriteLine($"Decoded key length: {key.Length} bytes ({key.Length * 8} bits)");
// 3. Encrypt payload into JWE token
var jweToken = JWT.Encode(
payloadJson,
key,
JweAlgorithm.A256KW,
JweEncryption.A256CBC_HS512
);
Console.WriteLine($"JWE Token: {jweToken[..60]}...");
// 4. Make API request with Basic Auth
using var client = new HttpClient();
var authBytes = Encoding.ASCII.GetBytes($"{secretKey}:");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));
var requestBody = new { Token = jweToken };
var content = new StringContent(
JsonSerializer.Serialize(requestBody),
Encoding.UTF8,
"application/json"
);
Console.WriteLine("Sending request to API...");
var response = await client.PostAsync(
"https://qrph.sbx.moduluslabs.io/v1/pay/qr",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
return;
}
// 5. Parse and decrypt response
var responseData = JsonSerializer.Deserialize<TokenResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (responseData?.Token is null)
{
Console.WriteLine("Error: Invalid response - missing token");
return;
}
var decryptedJson = JWT.Decode(responseData.Token, key);
var result = JsonSerializer.Deserialize<QRResponse>(
decryptedJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (result is null)
{
Console.WriteLine("Error: Failed to deserialize QR response");
return;
}
Console.WriteLine($"✓ Transaction ID: {result.Id}");
Console.WriteLine($"✓ QR Code (Base64): {result.QrBody?[..Math.Min(50, result.QrBody?.Length ?? 0)]}...");
// 6. Save QR code to file
if (!string.IsNullOrEmpty(result.QrBody))
{
var imageData = Convert.FromBase64String(result.QrBody);
await File.WriteAllBytesAsync("qr-code.png", imageData);
Console.WriteLine("✓ QR code saved to qr-code.png");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.GetType().Name} - {e.Message}");
}
}
}
// Response models
public class TokenResponse
{
public string? Token { get; set; }
}
public class QRResponse
{
public string? Id { get; set; }
public string? QrBody { get; set; }
}
{
"Token": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..."
}{
"Token": "<string>"
}{
"code": "10000013",
"error": "Invalid API Key",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}{
"code": "10000015",
"error": "Insufficient permissions",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}{
"code": "10000001",
"error": "An unexpected error occurred. Please try again later.",
"referenceNumber": "097bf60a-bdab-40bf-b615-3c0eae693b86"
}How it works: Generate a unique QR code → Customer scans with banking app → Payment completed → Webhook notification sent
Authentication
This endpoint requires HTTP Basic Authentication using your Secret Key.Encrypted Payload Structure
TheToken field must contain a JWE-encrypted JSON payload with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
activationCode | string | Yes | Activation Code from Modulus Labs. Format: XXXX-XXXX-XXXX-XXXX |
currency | string | Yes | ISO 4217 currency code (3 chars). Currently only PHP supported |
amount | string | Yes | Payment amount with 2 decimals. Range: 1.00 to 99999.99 |
merchantReferenceNumber | string | Yes | Your unique reference (1-36 chars, alphanumeric and hyphens) |
activationCode
activationCode
Activation Code assigned to your sub-merchant account by Modulus Labs.
- Format:
XXXX-XXXX-XXXX-XXXX(19 characters including hyphens) - Example:
A9X4-B7P2-Q6Z8-M3L5
currency
currency
ISO 4217 currency code.
- Length: Exactly 3 characters
- Supported:
PHP - Example:
PHP
amount
amount
Payment amount in decimal format.
- Min:
1.00(₱1.00) - Max:
99999.99(₱99,999.99) - Format: String with exactly 2 decimal places
- Example:
"500.00","1234.56"
Pass amount as a string, not a number, to preserve decimal precision.
merchantReferenceNumber
merchantReferenceNumber
Your unique reference number for this transaction.
- Min Length: 1 character
- Max Length: 36 characters
- Allowed: Alphanumeric characters and hyphens
- Example:
"ORDER-12345","5e91bc6c-f7e2-4a39-ae0e-5e93985c94a4"
Use UUIDs for guaranteed uniqueness across transactions.
Example Payload (Before Encryption)
{
"activationCode": "A9X4-B7P2-Q6Z8-M3L5",
"currency": "PHP",
"amount": "500.00",
"merchantReferenceNumber": "ORDER-2025-001"
}
Decrypted Response
When you decrypt the responseToken, you’ll get:
| Field | Type | Description |
|---|---|---|
id | string | Unique transaction identifier generated by Modulus Labs |
qrBody | string | Base64-encoded PNG image of the QR code |
{
"id": "0ce32626-08cf-405c-adab-6d384a8871da",
"qrBody": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAMTElEQVR4nO..."
}
# Note: You need to encrypt the payload first using a script/tool
# See the Encryption Guide for JWE token creation
# 1. Create encrypted JWE token (using external tool/script)
ENCRYPTED_TOKEN="eyJhbGciOiJBMjU2S1ciL..."
# 2. Make API request
curl -X POST https://qrph.sbx.moduluslabs.io/v1/pay/qr \
-u sk_YOUR_SECRET_KEY: \
-H "Content-Type: application/json" \
-d "{\"Token\": \"$ENCRYPTED_TOKEN\"}"
# 3. Response will be an encrypted JWE token that needs decryption
const axios = require('axios');
const jose = require('jose');
const crypto = require('crypto');
const SECRET_KEY = process.env.MODULUS_SECRET_KEY;
const ENCRYPTION_KEY = process.env.MODULUS_ENCRYPTION_KEY;
async function createDynamicQR(amount, reference) {
// 1. Prepare the payload
const payload = {
activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
currency: 'PHP',
amount: amount.toFixed(2), // Convert to string with 2 decimals
merchantReferenceNumber: reference || crypto.randomUUID()
};
// 2. Encrypt payload into JWE token
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);
// 3. Make API request
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' }
}
);
// 4. Decrypt response
const { plaintext } = await jose.compactDecrypt(
response.data.Token,
key
);
const result = JSON.parse(new TextDecoder().decode(plaintext));
console.log('Transaction ID:', result.id);
console.log('QR Code (Base64):', result.qrBody.substring(0, 50) + '...');
return result;
}
// Usage
createDynamicQR(500.00, 'ORDER-2025-001')
.then(result => {
// Save QR code to file
const fs = require('fs');
const buffer = Buffer.from(result.qrBody, 'base64');
fs.writeFileSync('qr-code.png', buffer);
console.log('QR code saved to qr-code.png');
})
.catch(error => {
console.error('Error:', error.response?.data || error.message);
});
import os
import base64
import json
import requests
from jose import jwe
from uuid import uuid4
SECRET_KEY = os.getenv('MODULUS_SECRET_KEY')
ENCRYPTION_KEY = os.getenv('MODULUS_ENCRYPTION_KEY')
def create_dynamic_qr(amount, reference=None):
# 1. Prepare the payload
payload = {
'activationCode': 'A9X4-B7P2-Q6Z8-M3L5',
'currency': 'PHP',
'amount': f'{amount:.2f}', # Convert to string with 2 decimals
'merchantReferenceNumber': reference or str(uuid4())
}
# 2. Encrypt payload into JWE token
jwe_token = jwe.encrypt(
json.dumps(payload),
ENCRYPTION_KEY,
algorithm='A256KW',
encryption='A256CBC-HS512'
)
# 3. Make API request
response = requests.post(
'https://qrph.sbx.moduluslabs.io/v1/pay/qr',
json={'Token': jwe_token},
auth=(SECRET_KEY, ''),
headers={'Content-Type': 'application/json'}
)
response.raise_for_status()
# 4. Decrypt response
decrypted = jwe.decrypt(response.json()['Token'], ENCRYPTION_KEY)
result = json.loads(decrypted)
print(f'Transaction ID: {result["id"]}')
print(f'QR Code (Base64): {result["qrBody"][:50]}...')
return result
# Usage
if __name__ == '__main__':
try:
result = create_dynamic_qr(500.00, 'ORDER-2025-001')
# Save QR code to file
image_data = base64.b64decode(result['qrBody'])
with open('qr-code.png', 'wb') as f:
f.write(image_data)
print('QR code saved to qr-code.png')
except Exception as e:
print(f'Error: {str(e)}')
<?php
require 'vendor/autoload.php';
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Algorithm\KeyEncryption\A256KW;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
$secretKey = getenv('MODULUS_SECRET_KEY');
$encryptionKey = getenv('MODULUS_ENCRYPTION_KEY');
function createDynamicQR($amount, $reference = null) {
global $secretKey, $encryptionKey;
// 1. Prepare the payload
$payload = [
'activationCode' => 'A9X4-B7P2-Q6Z8-M3L5',
'currency' => 'PHP',
'amount' => number_format($amount, 2, '.', ''),
'merchantReferenceNumber' => $reference ?? uniqid('ORDER-', true)
];
// 2. Encrypt payload (simplified - use proper JWE library)
// See encryption documentation for full implementation
$jweToken = encryptToJWE(json_encode($payload), $encryptionKey);
// 3. Make API request
$ch = curl_init('https://qrph.sbx.moduluslabs.io/v1/pay/qr');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['Token' => $jweToken]));
curl_setopt($ch, CURLOPT_USERPWD, $secretKey . ':');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API request failed with HTTP $httpCode");
}
// 4. Decrypt response
$responseData = json_decode($response, true);
$decrypted = decryptJWE($responseData['Token'], $encryptionKey);
$result = json_decode($decrypted, true);
echo "Transaction ID: {$result['id']}\n";
return $result;
}
// Usage
try {
$result = createDynamicQR(500.00, 'ORDER-2025-001');
// Save QR code to file
file_put_contents('qr-code.png', base64_decode($result['qrBody']));
echo "QR code saved to qr-code.png\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class CreateDynamicQRExample {
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
createDynamicQR(500.00, "ORDER-2025-001");
}
static void createDynamicQR(double amount, String reference) throws Exception {
String secretKey = System.getenv("MODULUS_SECRET_KEY");
String encryptionKey = System.getenv("MODULUS_ENCRYPTION_KEY");
if (secretKey == null || encryptionKey == null) {
System.out.println("Error: Missing environment variables");
System.out.println("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
// 1. Prepare the payload
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("activationCode", "A9X4-B7P2-Q6Z8-M3L5");
payload.put("currency", "PHP");
payload.put("amount", String.format("%.2f", amount));
payload.put("merchantReferenceNumber", reference != null ? reference : UUID.randomUUID().toString());
String payloadJson = mapper.writeValueAsString(payload);
System.out.println("Payload: " + payloadJson);
// 2. Decode encryption key from Base64URL and create JWE
byte[] keyBytes = Base64.getUrlDecoder().decode(encryptionKey);
OctetSequenceKey jwk = new OctetSequenceKey.Builder(keyBytes).build();
JWEHeader header = new JWEHeader(JWEAlgorithm.A256KW, EncryptionMethod.A256CBC_HS512);
JWEObject jwe = new JWEObject(header, new Payload(payloadJson));
jwe.encrypt(new AESEncrypter(jwk));
String jweToken = jwe.serialize();
System.out.println("JWE Token: " + jweToken.substring(0, 60) + "...");
// 3. Make API request with Basic Auth
HttpClient client = HttpClient.newHttpClient();
String auth = secretKey + ":";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
String requestBody = mapper.writeValueAsString(Map.of("Token", jweToken));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://qrph.sbx.moduluslabs.io/v1/pay/qr"))
.header("Authorization", "Basic " + encodedAuth)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
System.out.println("Sending request to API...");
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.out.println("Error: HTTP " + response.statusCode());
System.out.println("Details: " + response.body());
return;
}
// 4. Parse and decrypt response
Map<String, String> responseData = mapper.readValue(response.body(), Map.class);
String responseToken = responseData.get("Token");
JWEObject responseJwe = JWEObject.parse(responseToken);
responseJwe.decrypt(new AESDecrypter(jwk));
String decryptedJson = responseJwe.getPayload().toString();
Map<String, String> result = mapper.readValue(decryptedJson, Map.class);
System.out.println("✓ Transaction ID: " + result.get("id"));
String qrBody = result.get("qrBody");
System.out.println("✓ QR Code (Base64): " + qrBody.substring(0, Math.min(50, qrBody.length())) + "...");
// 5. Save QR code to file
byte[] imageData = Base64.getDecoder().decode(qrBody);
Files.write(Path.of("qr-code.png"), imageData);
System.out.println("✓ QR code saved to qr-code.png");
}
}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
)
type QRPayload struct {
ActivationCode string `json:"activationCode"`
Currency string `json:"currency"`
Amount string `json:"amount"`
MerchantReferenceNumber string `json:"merchantReferenceNumber"`
}
type TokenRequest struct {
Token string `json:"Token"`
}
type QRResponse struct {
ID string `json:"id"`
QRBody string `json:"qrBody"`
}
func main() {
if err := createDynamicQR(500.00, "ORDER-2025-001"); err != nil {
fmt.Printf("Error: %v\n", err)
}
}
func createDynamicQR(amount float64, reference string) error {
secretKey := os.Getenv("MODULUS_SECRET_KEY")
encryptionKey := os.Getenv("MODULUS_ENCRYPTION_KEY")
if secretKey == "" || encryptionKey == "" {
return fmt.Errorf("missing environment variables: MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY required")
}
// 1. Prepare the payload
payload := QRPayload{
ActivationCode: "A9X4-B7P2-Q6Z8-M3L5",
Currency: "PHP",
Amount: fmt.Sprintf("%.2f", amount),
MerchantReferenceNumber: reference,
}
payloadJSON, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal payload: %w", err)
}
fmt.Printf("Payload: %s\n", payloadJSON)
// 2. Decode encryption key from Base64URL
keyBytes, err := base64.RawURLEncoding.DecodeString(encryptionKey)
if err != nil {
return fmt.Errorf("failed to decode encryption key: %w", err)
}
fmt.Printf("Decoded key length: %d bytes (%d bits)\n", len(keyBytes), len(keyBytes)*8)
// 3. Encrypt payload into JWE token
encrypted, err := jwe.Encrypt(payloadJSON,
jwe.WithKey(jwa.A256KW, keyBytes),
jwe.WithContentEncryption(jwa.A256CBC_HS512),
)
if err != nil {
return fmt.Errorf("failed to encrypt payload: %w", err)
}
jweToken := string(encrypted)
fmt.Printf("JWE Token: %s...\n", jweToken[:60])
// 4. Make API request with Basic Auth
reqBody := TokenRequest{Token: jweToken}
jsonData, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", "https://qrph.sbx.moduluslabs.io/v1/pay/qr", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.SetBasicAuth(secretKey, "")
req.Header.Set("Content-Type", "application/json")
fmt.Println("Sending request to API...")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
// 5. Parse and decrypt response
var tokenResp TokenRequest
if err := json.Unmarshal(body, &tokenResp); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
decrypted, err := jwe.Decrypt([]byte(tokenResp.Token), jwe.WithKey(jwa.A256KW, keyBytes))
if err != nil {
return fmt.Errorf("failed to decrypt response: %w", err)
}
var result QRResponse
if err := json.Unmarshal(decrypted, &result); err != nil {
return fmt.Errorf("failed to parse decrypted response: %w", err)
}
fmt.Printf("✓ Transaction ID: %s\n", result.ID)
if len(result.QRBody) > 50 {
fmt.Printf("✓ QR Code (Base64): %s...\n", result.QRBody[:50])
}
// 6. Save QR code to file
imageData, err := base64.StdEncoding.DecodeString(result.QRBody)
if err != nil {
return fmt.Errorf("failed to decode QR image: %w", err)
}
if err := os.WriteFile("qr-code.png", imageData, 0644); err != nil {
return fmt.Errorf("failed to save QR code: %w", err)
}
fmt.Println("✓ QR code saved to qr-code.png")
return nil
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Jose;
namespace ModulusQRPH;
class Program
{
static async Task Main(string[] args)
{
await CreateDynamicQR(500.00m, "ORDER-2025-001");
}
/// <summary>
/// Decodes a Base64URL-encoded string to bytes.
/// This replicates what Node.js jose's importJWK does with the 'k' parameter.
/// </summary>
static byte[] Base64UrlDecode(string base64Url)
{
// Convert Base64URL to standard Base64
var base64 = base64Url
.Replace('-', '+') // Character 62
.Replace('_', '/'); // Character 63
// Add padding if necessary
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
static async Task CreateDynamicQR(decimal amount, string reference)
{
var secretKey = Environment.GetEnvironmentVariable("MODULUS_SECRET_KEY");
var encryptionKey = Environment.GetEnvironmentVariable("MODULUS_ENCRYPTION_KEY");
if (string.IsNullOrEmpty(secretKey) || string.IsNullOrEmpty(encryptionKey))
{
Console.WriteLine("Error: Missing environment variables");
Console.WriteLine("Please set MODULUS_SECRET_KEY and MODULUS_ENCRYPTION_KEY");
return;
}
try
{
// 1. Prepare the payload
var payload = new
{
activationCode = "A9X4-B7P2-Q6Z8-M3L5",
currency = "PHP",
amount = amount.ToString("F2"),
merchantReferenceNumber = reference ?? Guid.NewGuid().ToString()
};
var payloadJson = JsonSerializer.Serialize(payload);
Console.WriteLine($"Payload: {payloadJson}");
// 2. Decode the encryption key from Base64URL format
// This replicates: importJWK({ kty: 'oct', k: encryptionKey }, algorithm)
var key = Base64UrlDecode(encryptionKey);
Console.WriteLine($"Decoded key length: {key.Length} bytes ({key.Length * 8} bits)");
// 3. Encrypt payload into JWE token
var jweToken = JWT.Encode(
payloadJson,
key,
JweAlgorithm.A256KW,
JweEncryption.A256CBC_HS512
);
Console.WriteLine($"JWE Token: {jweToken[..60]}...");
// 4. Make API request with Basic Auth
using var client = new HttpClient();
var authBytes = Encoding.ASCII.GetBytes($"{secretKey}:");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));
var requestBody = new { Token = jweToken };
var content = new StringContent(
JsonSerializer.Serialize(requestBody),
Encoding.UTF8,
"application/json"
);
Console.WriteLine("Sending request to API...");
var response = await client.PostAsync(
"https://qrph.sbx.moduluslabs.io/v1/pay/qr",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
return;
}
// 5. Parse and decrypt response
var responseData = JsonSerializer.Deserialize<TokenResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (responseData?.Token is null)
{
Console.WriteLine("Error: Invalid response - missing token");
return;
}
var decryptedJson = JWT.Decode(responseData.Token, key);
var result = JsonSerializer.Deserialize<QRResponse>(
decryptedJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
if (result is null)
{
Console.WriteLine("Error: Failed to deserialize QR response");
return;
}
Console.WriteLine($"✓ Transaction ID: {result.Id}");
Console.WriteLine($"✓ QR Code (Base64): {result.QrBody?[..Math.Min(50, result.QrBody?.Length ?? 0)]}...");
// 6. Save QR code to file
if (!string.IsNullOrEmpty(result.QrBody))
{
var imageData = Convert.FromBase64String(result.QrBody);
await File.WriteAllBytesAsync("qr-code.png", imageData);
Console.WriteLine("✓ QR code saved to qr-code.png");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.GetType().Name} - {e.Message}");
}
}
}
// Response models
public class TokenResponse
{
public string? Token { get; set; }
}
public class QRResponse
{
public string? Id { get; set; }
public string? QrBody { get; set; }
}
Using the QR Code
After receiving theqrBody, you need to convert it to an image that customers can scan:
Display in Web Application
<img src="data:image/png;base64,${qrBody}" alt="Scan to Pay" />
Save as File
const fs = require('fs');
function saveQRCode(qrBody, filename = 'qr-code.png') {
const buffer = Buffer.from(qrBody, 'base64');
fs.writeFileSync(filename, buffer);
}
import base64
def save_qr_code(qr_body, filename='qr-code.png'):
image_data = base64.b64decode(qr_body)
with open(filename, 'wb') as f:
f.write(image_data)
using System;
using System.IO;
void SaveQRCode(string qrBody, string filename = "qr-code.png")
{
var imageData = Convert.FromBase64String(qrBody);
File.WriteAllBytes(filename, imageData);
}
Best Practices
Use UUIDs
Use UUIDs for
merchantReferenceNumber to ensure uniquenessValidate Amount
Validate amount format and range before sending to API
Handle Errors
Implement proper error handling for all possible scenarios
Store Transaction ID
Save the returned
id for reconciliation and supportCommon Issues
JWE Token Decryption Fails
JWE Token Decryption Fails
Symptoms:
- Cannot decrypt response token
- “Invalid token” errors
- Verify you’re using the correct Encryption Key
- Check encryption algorithms match (A256KW, A256CBC-HS512)
- Ensure no whitespace in keys
Amount Validation Error
Amount Validation Error
Symptoms:
- API returns “Invalid amount” error
- Request rejected with 400 status
- Ensure amount is a string, not a number:
"500.00"not500 - Always include 2 decimal places:
"5.00"not"5" - Stay within min/max range (1.00 to 99999.99)
QR Code Won't Display
QR Code Won't Display
Symptoms:
- Base64 decode fails
- Invalid image format
- Ensure full Base64 string was received (not truncated)
- Check for proper Base64 padding
- Verify you’re decoding as PNG format
Next Steps
Testing Guide
Learn how to test QR code generation and payment simulation
Webhooks
Set up webhooks to receive payment notifications
Error Handling
Handle errors gracefully in your application
Go Live
Deploy your integration to production
Authorizations
HTTP Basic Authentication using your Secret Key as the username and an empty password
Body
application/json
JWE-encrypted token containing the payment payload. Algorithm: A256KW | Encryption: A256CBC-HS512. See the Encryption Guide for how to create this token.
Response
QR code generated successfully. Response contains an encrypted JWE token.
JWE-encrypted response token. When decrypted, contains the transaction ID and QR code image.
Was this page helpful?
⌘I