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

@ojire/payment-gateway-sdk

v1.0.1

Published

JavaScript SDK for Ojire Payment Gateway

Readme

OPG JavaScript SDK

JavaScript SDK for Ojire Payment Gateway (OPG) - enables secure payment collection via QR Code (QRIS), Virtual Accounts (VA), and Credit Cards (CC).

Features

  • 🔒 PCI-DSS Compliant: Credit card data handled through secure Hosted Fields (iFrames)
  • 🛡️ Dual-Key Authentication: Separate keys for frontend and backend operations
  • 💳 Multiple Payment Methods: QRIS, Virtual Accounts, Credit Cards
  • 🎨 Customizable UI: Modal or embedded, light/dark themes
  • 📱 Mobile Responsive: Works seamlessly on all devices
  • 🔄 Automatic Retries: Built-in retry logic with exponential backoff
  • 🎯 TypeScript Support: Full type definitions included
  • 📦 Multiple Formats: ES modules, CommonJS, and UMD bundles

Installation

NPM

npm install @ojire/opg-sdk

Yarn

yarn add @ojire/opg-sdk

CDN (UMD)

<!-- Production (minified) -->
<script src="https://cdn.ojire.com/opg-sdk/1.0.0/index.umd.min.js"></script>
<link rel="stylesheet" href="https://cdn.ojire.com/opg-sdk/1.0.0/index.umd.min.css">

<!-- Development -->
<script src="https://cdn.ojire.com/opg-sdk/1.0.0/index.umd.js"></script>
<link rel="stylesheet" href="https://cdn.ojire.com/opg-sdk/1.0.0/index.umd.css">

Quick Start

1. Create Payment Intent (Backend)

First, create a Payment Intent from your backend using your Secret Server Key:

// Backend (Node.js example)
const response = await fetch('https://api.ojire.com/v1/payment-intents', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer opg_live_sk_v1_xxxxx', // Secret Server Key
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: {
      value: '150000.00',
      currency: 'IDR'
    },
    order_id: 'ORDER-123',
    customer: {
      name: 'John Doe',
      email: '[email protected]',
      phone: '+6281234567890'
    },
    description: 'Order #123 - Product Purchase'
  })
});

const { payment_intent } = await response.json();
const clientSecret = payment_intent.client_secret;

// Send clientSecret to your frontend

2. Initialize SDK (Frontend)

import { OPGClient } from '@ojire/opg-sdk';

// Initialize with your Public Client Key
const opg = new OPGClient({
  publicKey: 'opg_test_pk_v1_xxxxx', // Public Client Key
  locale: 'en', // 'en' or 'id'
  theme: 'light' // 'light' or 'dark'
});

// Initialize and check connectivity
await opg.init();

3. Process Payment

Option A: Promise-based API

try {
  const result = await opg.pay(clientSecret, {
    modal: true, // Show as modal overlay
    onPending: (result) => {
      console.log('Payment pending:', result);
      // Display QR code or VA number to user
    }
  });
  
  console.log('Payment successful:', result);
  // Redirect to success page
  window.location.href = '/order/success';
  
} catch (error) {
  console.error('Payment failed:', error);
  // Handle error
}

Option B: Callback-based API

const session = await opg.mount({
  clientSecret: clientSecret,
  modal: true,
  
  onSuccess: (result) => {
    console.log('Payment successful:', result);
    // Update UI, redirect to success page
    window.location.href = '/order/success';
  },
  
  onPending: (result) => {
    console.log('Payment pending:', result);
    // Show QR code or VA instructions
  },
  
  onFailure: (error) => {
    console.error('Payment failed:', error);
    // Show error message to user
    alert(`Payment failed: ${error.message}`);
  },
  
  onClose: () => {
    console.log('Payment UI closed');
    // User closed the payment modal
  }
});

Option C: Embedded in Container

<div id="payment-container"></div>
const session = await opg.mount({
  clientSecret: clientSecret,
  container: '#payment-container', // or document.getElementById('payment-container')
  modal: false, // Embed in container instead of modal
  
  onSuccess: (result) => {
    console.log('Payment successful:', result);
  }
});

API Reference

OPGClient

Constructor

new OPGClient(config: OPGClientConfig | string)

Parameters:

  • config: Configuration object or public key string
    • publicKey (required): Your Public Client Key
    • locale (optional): UI language - 'en' or 'id' (default: 'en')
    • theme (optional): UI theme - 'light' or 'dark' (default: 'light')
    • baseUrl (optional): Custom API base URL (for testing)

Example:

// Simple initialization
const opg = new OPGClient('opg_test_pk_v1_xxxxx');

// With options
const opg = new OPGClient({
  publicKey: 'opg_test_pk_v1_xxxxx',
  locale: 'id',
  theme: 'dark'
});

Methods

init(): Promise<void>

Initialize the SDK and validate connectivity to OPG servers.

await opg.init();

Throws:

  • NetworkError: If connectivity check fails
pay(clientSecret: string, options?: PaymentOptions): Promise<PaymentResult>

Shorthand method to mount payment UI and wait for result (Promise-based).

const result = await opg.pay(clientSecret, {
  modal: true,
  idempotencyKey: 'unique-key-123', // Optional
  onPending: (result) => {
    // Handle pending state
  }
});

Returns: Promise<PaymentResult | PaymentActionRequired>

mount(options: PaymentOptions): Promise<PaymentSession>

Mount payment UI and return a PaymentSession for manual control.

const session = await opg.mount({
  clientSecret: clientSecret,
  container: '#payment-container',
  modal: false,
  onSuccess: (result) => { /* ... */ },
  onFailure: (error) => { /* ... */ }
});

Returns: Promise<PaymentSession>

destroy(): void

Cleanup and unmount the SDK.

opg.destroy();

Properties

version: string

SDK version number.

console.log(opg.version); // "1.0.0"

PaymentOptions

Configuration for payment UI and event handlers.

interface PaymentOptions {
  clientSecret: string;           // Required: Client secret from Payment Intent
  container?: string | HTMLElement; // DOM selector or element for embedded mode
  modal?: boolean;                 // Show as modal (default: true)
  idempotencyKey?: string;         // Optional: For safe retries
  onSuccess?: (result: PaymentResult) => void;
  onPending?: (result: PendingResult) => void;
  onFailure?: (error: PaymentError) => void;
  onClose?: () => void;
}

PaymentResult

Result object returned on successful payment.

interface PaymentResult {
  id: string;                    // Payment ID
  payment_intent_id: string;     // Payment Intent ID
  status: 'success';             // Terminal status
  amount: Amount;                // Payment amount
  payment_method: string;        // 'VA' | 'QRIS' | 'CREDIT_CARD'
  
  // VA-specific fields
  va_number?: string;
  va_bank_code?: string;
  
  // QRIS-specific fields
  qr_code?: string;              // Base64 encoded QR image
  
  // Credit Card-specific fields
  card_last4?: string;
  card_network?: string;
  auth_code?: string;
  
  created_at: string;
  updated_at: string;
}

PendingResult

Result object for pending payments (QR/VA).

interface PendingResult {
  id: string;
  payment_intent_id: string;
  status: 'pending';
  amount: Amount;
  payment_method: string;
  
  // Display data for user
  va_number?: string;
  qr_code?: string;
  expires_at?: string;
}

PaymentError

Error object with details.

interface PaymentError {
  message: string;
  code: string;
  details?: Record<string, any>;
}

PaymentSession

Active payment session with control methods.

class PaymentSession {
  readonly paymentIntentId: string;
  readonly status: PaymentStatus;
  
  // Confirm payment with selected method
  confirm(method: PaymentMethodSelection): Promise<PaymentResult>;
  
  // Get current status
  getStatus(): Promise<PaymentStatus>;
  
  // Start/stop polling for status updates
  startPolling(intervalMs?: number): void;
  stopPolling(): void;
  
  // Close the payment UI
  close(): void;
  
  // Event listeners
  on(event: PaymentEvent, handler: EventHandler): void;
  off(event: PaymentEvent, handler: EventHandler): void;
}

Event Handling

The SDK emits events at key points in the payment lifecycle:

Event Types

  • success: Payment completed successfully
  • pending: Payment requires user action (scan QR, transfer to VA)
  • failure: Payment failed
  • close: User closed the payment UI

Event Data

Each event includes:

{
  payment_intent_id: string;
  payment_id?: string;        // Available after confirmation
  status: PaymentStatus;
  // ... additional payment details
}

Example

const session = await opg.mount({ clientSecret });

session.on('success', (result) => {
  console.log('Payment ID:', result.payment_id);
  console.log('Amount:', result.amount);
  // Update your UI
});

session.on('pending', (result) => {
  if (result.payment_method === 'QRIS') {
    console.log('Show QR code:', result.qr_code);
  } else if (result.payment_method === 'VA') {
    console.log('VA Number:', result.va_number);
  }
});

session.on('failure', (error) => {
  console.error('Error code:', error.code);
  console.error('Message:', error.message);
});

Error Codes

Common error codes you may encounter:

| Code | Description | Action | |------|-------------|--------| | INVALID_PUBLIC_KEY | Public key format is invalid | Check your public key format | | CONNECTIVITY_ERROR | Cannot connect to OPG servers | Check network connection | | INVALID_CLIENT_SECRET | Client secret is invalid or expired | Create a new Payment Intent | | PAYMENT_EXPIRED | Payment Intent has expired | Create a new Payment Intent | | AMOUNT_MISMATCH | Payment amount doesn't match intent | Check for tampering | | PAYMENT_FAILED | Payment processing failed | Check payment details | | NETWORK_ERROR | Network request failed | Retry the operation | | VALIDATION_ERROR | Input validation failed | Check input data | | CARD_DECLINED | Card was declined | Try another card | | INSUFFICIENT_FUNDS | Insufficient funds | Use another payment method | | 3DS_REQUIRED | 3D Secure authentication required | Complete 3DS challenge |

Payment Methods

QRIS (QR Code)

const result = await opg.pay(clientSecret, {
  onPending: (result) => {
    // Display QR code to user
    const qrImage = document.getElementById('qr-image');
    qrImage.src = result.qr_code; // Base64 encoded image
    
    // Show expiry time
    console.log('Expires at:', result.expires_at);
  }
});

The SDK automatically polls for payment status after QR is displayed.

Virtual Account (VA)

const result = await opg.pay(clientSecret, {
  onPending: (result) => {
    // Display VA number to user
    console.log('Bank:', result.va_bank_code);
    console.log('VA Number:', result.va_number);
    console.log('Amount:', result.amount.value);
    
    // Add copy button
    navigator.clipboard.writeText(result.va_number);
  }
});

The SDK automatically polls for payment status after VA is displayed.

Credit Card

Credit card payments use Hosted Fields for PCI-DSS compliance:

import { HostedFields } from '@ojire/opg-sdk';

// Create hosted fields
const hostedFields = new HostedFields({
  cardNumber: { 
    container: '#card-number',
    placeholder: '1234 5678 9012 3456'
  },
  expiryDate: { 
    container: '#expiry-date',
    placeholder: 'MM/YY'
  },
  cvv: { 
    container: '#cvv',
    placeholder: '123'
  },
  styles: {
    base: {
      fontSize: '16px',
      color: '#333'
    },
    invalid: {
      color: '#e74c3c'
    }
  }
});

// Mount the fields
await hostedFields.mount();

// Listen for validation events
hostedFields.on('change', (event) => {
  console.log('Field:', event.field);
  console.log('Valid:', event.valid);
});

// Tokenize and pay
const { token } = await hostedFields.tokenize();

const result = await opg.pay(clientSecret, {
  // Token is automatically used for card payments
});

Security Best Practices

1. Never Expose Secret Server Key

DON'T use Secret Server Key in frontend:

// WRONG - Never do this!
const opg = new OPGClient('opg_live_sk_v1_xxxxx'); // Secret key!

DO use Public Client Key in frontend:

// CORRECT
const opg = new OPGClient('opg_live_pk_v1_xxxxx'); // Public key

2. Create Payment Intents Server-Side

Always create Payment Intents from your backend to lock the amount:

// Backend only
const intent = await createPaymentIntent({
  amount: calculateOrderTotal(order), // Server-side calculation
  order_id: order.id
});

// Send only client_secret to frontend
res.json({ clientSecret: intent.client_secret });

3. Verify Webhooks

Always verify webhook signatures:

// Backend webhook handler
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  const expectedSignature = hmac.digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-opg-signature'];
  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const { event_type, payment } = req.body;
  // Update order status in database
  
  res.status(200).send('OK');
});

4. Use HTTPS

Always serve your website over HTTPS in production.

5. Implement CSP Headers

Add Content Security Policy headers to prevent XSS:

Content-Security-Policy: frame-src https://api.ojire.com https://api-staging.ojire.com;

Testing

Test Mode

Use test keys for development:

const opg = new OPGClient('opg_test_pk_v1_xxxxx');

Test Cards

Use these test card numbers:

| Card Number | Network | Result | |-------------|---------|--------| | 4111111111111111 | Visa | Success | | 5555555555554444 | Mastercard | Success | | 378282246310005 | Amex | Success | | 4000000000000002 | Visa | Declined | | 4000000000009995 | Visa | Insufficient Funds |

Test VA Banks

Available test banks:

  • BCA: 014
  • BNI: 009
  • Mandiri: 008
  • BRI: 002

Test QRIS

In test mode, QRIS payments are automatically marked as successful after 5 seconds.

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+
  • Mobile browsers (iOS Safari 14+, Chrome Mobile 90+)

TypeScript Support

The SDK is written in TypeScript and includes full type definitions:

import { 
  OPGClient, 
  PaymentResult, 
  PaymentError,
  OPGClientConfig 
} from '@ojire/opg-sdk';

const config: OPGClientConfig = {
  publicKey: 'opg_test_pk_v1_xxxxx',
  locale: 'en',
  theme: 'light'
};

const opg = new OPGClient(config);

try {
  const result: PaymentResult = await opg.pay(clientSecret);
  console.log(result.payment_id);
} catch (error) {
  const paymentError = error as PaymentError;
  console.error(paymentError.code);
}

Migration Guide

From v0.x to v1.0

  1. Update initialization:
// Old
const opg = new OPG({ apiKey: 'xxx' });

// New
const opg = new OPGClient({ publicKey: 'opg_test_pk_v1_xxx' });
await opg.init();
  1. Update payment method:
// Old
opg.createPayment({ amount: 150000 });

// New
// Create Payment Intent from backend first
const result = await opg.pay(clientSecret);

Examples

See the /examples directory for complete examples:

Support

  • Documentation: https://docs.ojire.com
  • API Reference: https://api-docs.ojire.com
  • Support Email: [email protected]
  • GitHub Issues: https://github.com/ojire/opg-sdk/issues

Development

# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Build
npm run build

# Type check
npm run typecheck

# Lint
npm run lint

Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

License

MIT License - see LICENSE for details.

Changelog

See CHANGELOG.md for version history.


Made with ❤️ by Ojire