demo-integration

star 1.4k

Embeds prism into any app and enables to connect to any payment processor

juspay By juspay schedule Updated 4/14/2026

name: demo-integration description: > Embeds prism into any app and enables to connect to any payment processor license: Apache-2.0

Hyperswitch Prism — AI Integration Guide (llms.txt)

You are an AI assistant helping a developer integrate payment processors using the Hyperswitch Prism SDK. Follow this document exactly. Do NOT deviate from the prescribed flow.


PHASE 0 — GATHER REQUIREMENTS (MANDATORY)

Before writing ANY code, you MUST ask the developer two questions. Do not skip this step. Do not assume answers.

Question 1: Payment Processor Selection

Ask the developer @AskUserTool:

Which payment processor(s) do you want to integrate through Prism?

  1. Stripe
  2. Adyen
  3. Other (specify — Prism supports 81+ processors including PayPal, Cybersource, Braintree, Mollie, etc.)

You may select multiple processors.

Wait for their answer. Record their selection(s) as SELECTED_PROCESSORS.

Question 2: PCI Compliance Strategy

Ask the developer @AskUserTool:

How will you handle PCI compliance?

  A. Let the payment processor manage PCI compliance (Non-PCI / Tokenized flow)
     → Card details are collected on the frontend using the processor's own SDK (e.g., Stripe Elements, Adyen Drop-in).
     → Your server never sees raw card numbers. You only handle opaque tokens.
     → Best for: most web/mobile apps, SaaS platforms, marketplaces.

  B. I will self-manage PCI compliance (PCI / Direct flow)
     → Your server directly handles raw card numbers.
     → Requires PCI DSS certification and a SAQ-D or equivalent audit.
     → Best for: payment facilitators, large enterprises with existing PCI scope.

Choose one option only: A or B.

Wait for their answer. Record their selection as PCI_STRATEGY.


PHASE 1 — DETECT LANGUAGE (AUTOMATIC)

Do NOT ask the developer which language to use. Detect it automatically from the repository.

Detection Rules (apply in order)

Signal Language
package.json exists in repo root Node.js (TypeScript)
requirements.txt OR pyproject.toml OR setup.py OR Pipfile exists Python
pom.xml OR build.gradle OR build.gradle.kts exists Java/Kotlin
Multiple signals present Prefer the one closest to the project's entry point / main source directory
No signal found Ask the developer: "I couldn't detect your project language. Which SDK should I use: Node.js, Python, or Java?"

Record the result as SDK_LANGUAGE.

Install the SDK

Once language is detected, install the appropriate SDK:

Node.js (v18+):

npm install hyperswitch-prism

Python (3.9+):

pip install hyperswitch-prism

Java/Kotlin (JDK 17+):

<dependency>
  <groupId>io.hyperswitch</groupId>
  <artifactId>prism</artifactId>
  <version>0.0.1</version>
</dependency>

PHASE 2 — FETCH LIVE DOCUMENTATION

Before writing integration code, you MUST fetch the latest Prism reference. This is non-negotiable. The upstream docs contain connector-specific required fields, working examples, and rules that change between releases.

Step 2.1 — Fetch the master LLM context

Fetch: https://raw.githubusercontent.com/juspay/hyperswitch-prism/main/llm/llm.txt

Read this document fully. It contains:

  • Field Probe tool usage (discovers required fields per connector)
  • Connector-specific examples (copy-paste ready code)
  • Integration rules and status code reference
  • Error handling patterns

Step 2.2 — Fetch the generated API reference

Fetch: https://raw.githubusercontent.com/juspay/hyperswitch-prism/main/docs-generated/llms.txt

This contains the master index of all 81+ connectors with links to per-connector documentation.

Step 2.3 — Run Field Probe for each selected processor

For EACH processor in SELECTED_PROCESSORS, run the field probe to discover required fields:

npx hyperswitch-prism probe --connector <PROCESSOR_NAME> --flow authorize --payment-method card
npx hyperswitch-prism probe --connector <PROCESSOR_NAME> --all-flows

Why this matters: TypeScript types alone do NOT show which fields are required. Each connector has different required fields. Skipping this causes IntegrationError: MISSING_REQUIRED_FIELD at runtime.


PHASE 3 — IMPLEMENT BASED ON PCI STRATEGY

Use the developer's PCI_STRATEGY answer to determine which integration pattern to follow.


STRATEGY A: Non-PCI / Tokenized Flow

This is the 3-step tokenization pattern. The developer's server never handles raw card data.

How it works:

STEP 1 (Backend)  → Create a client authentication token via Prism
STEP 2 (Frontend) → Use processor's frontend SDK to collect card & tokenize
STEP 3 (Backend)  → Authorize payment using the opaque token via Prism

Step A.1 — Backend: Create Client Authentication Token

Node.js:

import { MerchantAuthenticationClient, PaymentClient, types } from 'hyperswitch-prism';

// Configure for each processor
// Stripe:
const stripeConfig: types.ConnectorConfig = {
  connectorConfig: {
    stripe: { apiKey: { value: process.env.STRIPE_SECRET_KEY! } }
  }
};

// Adyen:
const adyenConfig: types.ConnectorConfig = {
  connectorConfig: {
    adyen: {
      apiKey: { value: process.env.ADYEN_API_KEY! },
      merchantAccount: { value: process.env.ADYEN_MERCHANT_ACCOUNT! }
    }
  }
};

// Create client auth token (returns client secret for frontend)
async function createPaymentSession(processorConfig: types.ConnectorConfig, amount: number, currency: types.Currency) {
  const authClient = new MerchantAuthenticationClient(processorConfig);

  const response = await authClient.createClientAuthenticationToken({
    merchantClientSessionId: `session_${Date.now()}`,
    payment: {
      amount: { minorAmount: amount, currency }
    },
    testMode: true
  });

  // Extract processor-specific client secret
  const clientSecret =
    response.sessionData?.connectorSpecific?.stripe?.clientSecret?.value
    || response.sessionData?.connectorSpecific?.adyen?.clientSecret?.value;

  return { clientSecret, sessionData: response.sessionData };
}

Python:

from payments import MerchantAuthenticationClient, SecretString
from payments.generated import sdk_config_pb2, payment_pb2
import os

# Stripe config
stripe_cfg = sdk_config_pb2.ConnectorConfig()
stripe_cfg.connector_config.CopyFrom(payment_pb2.ConnectorSpecificConfig(
    stripe=payment_pb2.StripeConfig(
        api_key=SecretString(value=os.environ["STRIPE_SECRET_KEY"])
    )
))

# Create client auth token
auth_client = MerchantAuthenticationClient(stripe_cfg)
response = auth_client.create_client_authentication_token(
    payment_pb2.CreateClientAuthenticationTokenRequest(
        merchant_client_session_id=f"session_{int(time.time())}",
        payment=payment_pb2.PaymentInfo(
            amount=payment_pb2.MinorUnit(minor_amount=1000, currency=payment_pb2.Currency.USD)
        ),
        test_mode=True
    )
)

Step A.2 — Frontend: Tokenize Card Details

This step happens in the browser. Use the processor's own frontend SDK.

Stripe (using Stripe.js + Elements):

<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
  const elements = stripe.elements({ clientSecret: clientSecretFromBackend });
  const paymentElement = elements.create('payment');
  paymentElement.mount('#payment-element');

  // On form submit:
  const { error, paymentIntent } = await stripe.confirmPayment({
    elements,
    confirmParams: { return_url: window.location.origin + '/payment-complete' },
    redirect: 'if_required'
  });
  // paymentIntent.payment_method is the token (pm_xxx) to send to your backend
</script>

Adyen (using Adyen Web Drop-in):

<script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.59.0/adyen.js"></script>
<link rel="stylesheet" href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.59.0/adyen.css"/>
<script>
  const checkout = await AdyenCheckout({
    clientKey: 'test_YOUR_CLIENT_KEY',
    environment: 'test',
    session: { id: sessionIdFromBackend, sessionData: sessionDataFromBackend },
    onPaymentCompleted: (result) => {
      // result contains the token to send to your backend
      sendToBackend(result);
    }
  });
  checkout.create('dropin').mount('#dropin-container');
</script>

Step A.3 — Backend: Authorize Payment with Token

Node.js:

// SAME code structure works for ALL processors — only config changes
async function authorizeWithToken(
  processorConfig: types.ConnectorConfig,
  token: string,
  amount: number,
  currency: types.Currency
) {
  const client = new PaymentClient(processorConfig);

  const auth = await client.tokenAuthorize({
    merchantTransactionId: `txn_${Date.now()}`,
    merchantOrderId: `order_${Date.now()}`,
    amount: { minorAmount: amount, currency },
    connectorToken: { value: token }, // pm_xxx (Stripe) or Adyen session token
    address: { billingAddress: {} },
    captureMethod: types.CaptureMethod.MANUAL, // or AUTOMATIC for immediate capture
    testMode: true
  });

  // CRITICAL: status is a NUMBER, not a string
  if (auth.status === types.PaymentStatus.AUTHORIZED) {       // === 6
    console.log('Payment authorized:', auth.connectorTransactionId);
  } else if (auth.status === types.PaymentStatus.CHARGED) {   // === 8 (if AUTOMATIC capture)
    console.log('Payment charged:', auth.connectorTransactionId);
  }

  return auth;
}

Python:

client = PaymentClient(processor_cfg)
result = client.token_authorize(
    payment_pb2.TokenAuthorizeRequest(
        merchant_transaction_id=f"txn_{int(time.time())}",
        merchant_order_id=f"order_{int(time.time())}",
        amount=payment_pb2.MinorUnit(minor_amount=amount, currency=currency),
        connector_token=SecretString(value=token),
        capture_method=payment_pb2.CaptureMethod.MANUAL,
        test_mode=True,
    )
)

STRATEGY B: PCI / Direct Flow

The developer's server handles raw card numbers directly. Requires PCI DSS compliance.

How it works:

STEP 1 (Backend) → Collect card details on your server
STEP 2 (Backend) → Send card details directly to Prism's authorize() method

Step B.1 — Authorize with Raw Card Data

Node.js — Stripe:

import { PaymentClient, types, IntegrationError, ConnectorError, NetworkError } from 'hyperswitch-prism';

const client = new PaymentClient({
  connectorConfig: {
    stripe: { apiKey: { value: process.env.STRIPE_API_KEY! } }
  }
});

const authResult = await client.authorize({
  merchantTransactionId: 'txn_001',
  amount: { minorAmount: 1000, currency: types.Currency.USD },
  captureMethod: types.CaptureMethod.MANUAL,
  paymentMethod: {
    card: {
      cardNumber: { value: '4111111111111111' },
      cardExpMonth: { value: '12' },
      cardExpYear: { value: '2027' },
      cardCvc: { value: '123' },
      cardHolderName: { value: 'Jane Doe' }
    }
  },
  address: { billingAddress: {} },
  authType: types.AuthenticationType.NO_THREE_DS,
  testMode: true
});

// CRITICAL: status is a NUMBER, not a string
if (authResult.status === types.PaymentStatus.AUTHORIZED) { // === 6
  console.log('Authorized:', authResult.connectorTransactionId);
}

Node.js — Adyen (requires browserInfo):

const client = new PaymentClient({
  connectorConfig: {
    adyen: {
      apiKey: { value: process.env.ADYEN_API_KEY! },
      merchantAccount: { value: process.env.ADYEN_MERCHANT_ACCOUNT! }
    }
  }
});

const authResult = await client.authorize({
  merchantTransactionId: 'txn_adyen_001',
  amount: { minorAmount: 1000, currency: types.Currency.EUR },
  captureMethod: types.CaptureMethod.MANUAL,
  paymentMethod: {
    card: {
      cardNumber: { value: '4111111111111111' },
      cardExpMonth: { value: '03' },
      cardExpYear: { value: '2030' },
      cardCvc: { value: '737' },
      cardHolderName: { value: 'Jane Doe' }
    }
  },
  // ⚠️ REQUIRED for Adyen — omitting this throws IntegrationError
  browserInfo: {
    colorDepth: 24,
    screenHeight: 900,
    screenWidth: 1440,
    javaEnabled: false,
    javaScriptEnabled: true,
    language: 'en-US',
    timeZoneOffsetMinutes: 0,
    acceptHeader: 'text/html,*/*;q=0.8',
    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
  },
  address: { billingAddress: {} },
  authType: types.AuthenticationType.NO_THREE_DS,
  testMode: true
});

Python — Stripe:

from payments import PaymentClient, SecretString
from payments.generated import sdk_config_pb2, payment_pb2
import os

cfg = sdk_config_pb2.ConnectorConfig()
cfg.connector_config.CopyFrom(payment_pb2.ConnectorSpecificConfig(
    stripe=payment_pb2.StripeConfig(
        api_key=SecretString(value=os.environ["STRIPE_API_KEY"])
    )
))
client = PaymentClient(cfg)

request = payment_pb2.PaymentServiceAuthorizeRequest(
    merchant_transaction_id="txn_001",
    amount=payment_pb2.MinorUnit(minor_amount=1000, currency=payment_pb2.Currency.USD),
    capture_method=payment_pb2.CaptureMethod.AUTOMATIC,
    payment_method=payment_pb2.PaymentMethodData(
        card=payment_pb2.Card(
            card_number=SecretString(value="4111111111111111"),
            card_exp_month=SecretString(value="12"),
            card_exp_year=SecretString(value="2027"),
            card_cvc=SecretString(value="123"),
        )
    ),
    test_mode=True,
)

result = client.authorize(request)
# result.status == payment_pb2.CHARGED (8) for AUTOMATIC capture

PHASE 4 — IMPLEMENT CAPTURE, REFUND, VOID (Both Strategies)

After authorization succeeds, implement these follow-up operations. This code is IDENTICAL regardless of PCI strategy — both tokenized and direct flows produce the same connectorTransactionId.

Capture (for MANUAL capture method)

const captureResult = await client.capture({
  merchantCaptureId: `cap_${Date.now()}`,
  connectorTransactionId: authResult.connectorTransactionId!,
  amountToCapture: { minorAmount: 1000, currency: types.Currency.USD },
  testMode: true
});
// captureResult.status === types.PaymentStatus.CHARGED (8)

Refund

const refundResult = await client.refund({
  merchantRefundId: `ref_${Date.now()}`,
  connectorTransactionId: authResult.connectorTransactionId!,
  refundAmount: { minorAmount: 500, currency: types.Currency.USD },
  reason: 'RETURN',
  // ⚠️ Adyen: reason MUST be enum: OTHER|RETURN|DUPLICATE|FRAUD|CUSTOMER_REQUEST
  // Stripe: accepts free-text
  testMode: true
});
// ⚠️ Use RefundStatus, NOT PaymentStatus
// refundResult.status === types.RefundStatus.REFUND_SUCCESS (4)

Void (cancel before capture)

const voidResult = await client.void({
  merchantVoidId: `void_${Date.now()}`,
  connectorTransactionId: authResult.connectorTransactionId!,
  cancellationReason: 'Customer cancelled',
  testMode: true
});
// voidResult.status === types.PaymentStatus.VOIDED (11)

PHASE 5 — ERROR HANDLING (MANDATORY)

Wrap ALL Prism calls in this error handling pattern:

import { IntegrationError, ConnectorError, NetworkError, types } from 'hyperswitch-prism';

async function safePaymentCall<T>(operation: () => Promise<T>): Promise<T | null> {
  try {
    return await operation();
  } catch (error) {
    if (error instanceof IntegrationError) {
      // Bad config, missing required field, serialization error
      // DO NOT retry. Fix the request structure.
      console.error('[IntegrationError]', error.errorCode, error.message);
    } else if (error instanceof ConnectorError) {
      // Response transformation failed
      // DO NOT retry automatically. Log and investigate.
      console.error('[ConnectorError]', error.errorCode, error.message);
    } else if (error instanceof NetworkError) {
      // Timeout, DNS failure, connection refused
      // SAFE to retry with exponential backoff
      console.error('[NetworkError]', error.message);
    }
    return null;
  }
}

// Usage:
const auth = await safePaymentCall(() => client.authorize(request));
if (auth && auth.status === types.PaymentStatus.AUTHORIZED) {
  // proceed to capture
}

PHASE 6 — SUMMARIZE IMPLEMENTATION

After all code is written, provide the developer with a summary in this exact format:

## Implementation Summary

### What was integrated
- Processor(s): [list SELECTED_PROCESSORS]
- PCI strategy: [A: Non-PCI Tokenized / B: PCI Direct]
- SDK language: [SDK_LANGUAGE]
- SDK version: hyperswitch-prism@latest

### Files created or modified
- [list each file path and what it does]

### Environment variables required
- [list every env var needed, per processor]

### How to test

1. Set environment variables:
   [list the exact export commands with placeholder values]

2. Install dependencies:
   [npm install / pip install / maven command]

3. Start the server:
   [exact command]

4. Test the payment flow:
   [For Non-PCI: explain how to open the frontend, fill the form, and submit]
   [For PCI: provide a curl command to hit the authorize endpoint]

5. Verify in processor dashboard:
   - Stripe: https://dashboard.stripe.com/test/payments
   - Adyen: https://ca-test.adyen.com/
   - PayPal: https://www.sandbox.paypal.com/

### Test card numbers
| Processor | Card Number          | Expiry | CVC | Expected Result |
|-----------|----------------------|--------|-----|-----------------|
| Stripe    | 4242 4242 4242 4242  | 12/27  | 123 | Success         |
| Stripe    | 4000 0000 0000 0002  | 12/27  | 123 | Decline         |
| Adyen     | 4111 1111 1111 1111  | 03/30  | 737 | Success         |
| Adyen     | 5500 0000 0000 0004  | 03/30  | 737 | Decline         |
| PayPal    | 4111 1111 1111 1111  | 12/27  | 123 | Success         |

CRITICAL RULES — NEVER VIOLATE THESE

Rule 1: Status codes are NUMBERS, not strings

// ❌ WRONG — always evaluates to false
if (response.status === 'CHARGED') { }
if (response.status === 'AUTHORIZED') { }

// ✅ CORRECT
if (response.status === 8) { }
if (response.status === types.PaymentStatus.CHARGED) { }   // === 8
if (response.status === types.PaymentStatus.AUTHORIZED) { } // === 6

Rule 2: PaymentStatus vs RefundStatus — value 4 means DIFFERENT things

// PaymentStatus 4 = AUTHENTICATION_PENDING (authorize/capture/void)
// RefundStatus 4  = REFUND_SUCCESS (refund only)

// ✅ CORRECT
if (auth.status === types.PaymentStatus.AUTHORIZED) { }       // authorize flow
if (refund.status === types.RefundStatus.REFUND_SUCCESS) { }   // refund flow

Rule 3: Always run Field Probe before writing code

npx hyperswitch-prism probe --connector <name> --flow authorize --payment-method card

TypeScript types alone do NOT reveal which fields are required per connector.

Rule 4: Connector config formats vary

// Stripe — apiKey only
{ stripe: { apiKey: { value: '...' } } }

// Adyen — apiKey + merchantAccount
{ adyen: { apiKey: { value: '...' }, merchantAccount: { value: '...' } } }

// PayPal — clientId + clientSecret
{ paypal: { clientId: { value: '...' }, clientSecret: { value: '...' } } }

Rule 5: Adyen ALWAYS requires browserInfo for card payments

Omitting browserInfo for Adyen throws IntegrationError: MISSING_REQUIRED_FIELD: browser_info.

Rule 6: Guard optional fields

const txId = authResult.connectorTransactionId ?? '';
const errMsg = response.error?.message ?? 'Unknown error';

STATUS CODE REFERENCE

PaymentStatus (authorize, capture, void)

Code Enum Name Meaning
0 UNSPECIFIED Unknown
1 STARTED Initiated
4 AUTHENTICATION_PENDING 3DS redirect needed
5 AUTHENTICATION_SUCCESSFUL 3DS passed
6 AUTHORIZED Funds held
7 AUTHORIZATION_FAILED Declined
8 CHARGED Captured
11 VOIDED Cancelled
20 PENDING Async processing
21 FAILURE Soft decline

RefundStatus (refund only)

Code Enum Name Meaning
1 REFUND_FAILURE Failed
2 REFUND_MANUAL_REVIEW Under review
3 REFUND_PENDING Processing
4 REFUND_SUCCESS Completed

SERVICE CLIENTS REFERENCE

Client Methods
PaymentClient authorize, tokenAuthorize, capture, refund, void, get, sync
MerchantAuthenticationClient createServerAuthenticationToken, createClientAuthenticationToken
CustomerClient create
PaymentMethodClient tokenize
PaymentMethodAuthenticationClient preAuthenticate, authenticate, postAuthenticate
RecurringPaymentClient setup, charge, revoke
EventClient handleEvent
RefundClient get, createRefund, updateRefund
DisputeClient accept, defend, submitEvidence, get

ADDITIONAL RESOURCES

Install via CLI
npx skills add https://github.com/juspay/hyperswitch-prism --skill demo-integration
Repository Details
star Stars 1,430
call_split Forks 276
navigation Branch main
article Path SKILL.md
More from Creator