Integration Guides

Auto Payments (MIT)

Set up recurring payments and subscriptions with Merchant-Initiated Transactions

Auto payments allow you to initiate payments on behalf of your customers without requiring any action from them. This functionality is particularly useful for collecting recurring payments (subscriptions), automatic top-ups, or other automated billing processes where customer interaction is not needed.

What are MIT Payments?

MIT (Merchant-Initiated Transactions) are payments that you initiate without direct customer involvement, using previously stored payment credentials.

Common use cases:

  • Monthly subscriptions
  • Automatic renewals
  • Top-up when balance is low
  • Installment payments
  • Usage-based billing

Prerequisites

Before you can make auto payments, the following conditions must be met:

1. Prior Successful Payment

The customer must have completed a previous successful payment where:

  • allow_remember_credentials was set to true
  • Either request_unscheduled_mit or require_unscheduled_mit was set to true

2. Valid Stored Credential

You must have a valid credential token that:

  • Was generated from a previous successful payment
  • Uses a payment method that supports MIT
  • Is still active (not expired or deleted)

How It Works

mermaid
sequenceDiagram
    participant Customer
    participant YourServer as Your Server
    participant NjiaPay as NjiaPay API
    participant PSP as Payment Provider

    Note over Customer,PSP: Initial Setup (Interactive)
    Customer->>YourServer: Sign up for subscription
    YourServer->>NjiaPay: POST /api/intents (allow_remember_credentials: true)
    NjiaPay-->>YourServer: redirect_url
    YourServer-->>Customer: Redirect to checkout
    Customer->>NjiaPay: Complete payment
    NjiaPay-->>YourServer: Webhook: PaymentCredential event
    Note over YourServer: Store credential_token

    Note over Customer,PSP: Recurring Payment (Non-Interactive)
    YourServer->>NjiaPay: POST /api/intents/auto-attempt (use stored credential)
    NjiaPay->>PSP: Process payment
    PSP-->>NjiaPay: Payment result
    NjiaPay-->>YourServer: Webhook: StatusChange event
    YourServer-->>Customer: Send receipt email

Step 1: Initial Payment with Credential Storage

Create the first payment intent with MIT enabled:

example.js
const response = await fetch("https://app.infinic.com/api/intents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 10000,
    currency: "ZAR",
    reference_id: "subscription_init_" + customerId,
    purchaser_id: customerId,
    return_url: "https://yoursite.com/callback",
    allow_remember_credentials: true, // ← Enable credential storage
    request_unscheduled_mit: true, // ← Request MIT permission
  }),
});

Step 2: Obtain Credential Token

After the customer completes payment, you'll receive a PaymentCredential webhook event:

{
  "type": "PaymentCredential",
  "created": "2024-09-01T00:00:00Z",
  "content": {
    "intent_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_id": "subscription_init_customer_123",
    "purchaser_id": "customer_123",
    "credential_token": "cred_token_abc123xyz",
    "display_name": "Visa ****1234",
    "is_mit_compatible": true,
    "method": "card",
    "brand": "visa",
    "card_last4": "1234",
    "card_expiry": "2025-12"
  }
}

Store the credential_token securely in your database:

example.js
// Example: Store credential
await db.savePaymentCredential({
  customer_id: event.content.purchaser_id,
  credential_token: event.content.credential_token,
  display_name: event.content.display_name,
  card_last4: event.content.card_last4,
  expiry: event.content.card_expiry,
});

Step 3: Create Auto-Payment Attempts

When it's time to charge the customer (e.g., monthly renewal), create an auto-payment attempt:

example.js
// Retrieve stored credential
const credential = await db.getPaymentCredential(customerId);

// Create auto-payment attempt
const response = await fetch(
  "https://app.infinic.com/api/intents/auto-attempt",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount: 10000,
      currency: "ZAR",
      reference_id: "subscription_month_2_" + customerId,
      purchaser_id: customerId,
      credentials: [credential.credential_token], // ← Use stored token
    }),
  },
);

const result = await response.json();
console.log("Payment status:", result.intent_status);

Step 4: Handle Auto-Payment Results

Auto-payments complete immediately (success or failure). Monitor via webhooks:

example.js
app.post("/webhook", async (req, res) => {
  const event = req.body;

  if (event.type === "StatusChange") {
    const { status, reference_id, purchaser_id } = event.content;

    if (status === "success") {
      // Payment successful
      await extendSubscription(purchaser_id);
      await sendReceiptEmail(purchaser_id, reference_id);
    } else if (status === "failed") {
      // Payment failed
      await handleFailedPayment(purchaser_id, event.content.failure_reason);
      await notifyCustomerPaymentFailed(purchaser_id);
    }
  }

  res.status(200).send("OK");
});

Monitoring Payment Status

You can also check payment status via API:

example.js
const response = await fetch(
  `https://app.infinic.com/api/intents/${intent_id}?iauth=${intent_token}`,
  {
    headers: { Authorization: `Bearer ${API_KEY}` },
  },
);

const intent = await response.json();
console.log("Status:", intent.status);

Handling Failed Auto-Payments

When an auto-payment fails:

  1. Check failure reason from webhook
  2. Notify customer about the failure
  3. Retry logic (if appropriate):
    • Card expired → Request new card
    • Insufficient funds → Retry in a few days
    • Card declined → Request alternative payment method

Example retry logic:

example.js
async function handleFailedAutoPayment(customerId, failureReason) {
  if (failureReason === "CardExpired") {
    // Card expired - request customer update card
    await sendCardUpdateRequest(customerId);
  } else if (failureReason === "InsufficientFunds") {
    // Retry in 3 days
    await scheduleRetry(customerId, 3);
  } else {
    // Other failure - request new payment method
    await requestNewPaymentMethod(customerId);
  }
}

Error Scenarios

404: Credential Not Found

{
  "detail": "Credential not found"
}

Cause: The credential token doesn't exist or was deleted
Solution: Request customer to add a new payment method

403: Not Authorized

{
  "detail": "Not authorized to use this credential"
}

Cause: Credential belongs to a different merchant
Solution: Verify you're using the correct API key and credential

422: Not MIT Compatible

{
  "detail": "Credential cannot be used for MIT transactions"
}

Cause: The payment method doesn't support MIT
Solution: Request customer to use a different payment method

Best Practices

✅ DO

  • Store credential tokens securely (encrypted)
  • Notify customers before charging them
  • Handle failures gracefully with retry logic
  • Keep customer payment methods up to date
  • Send receipts after successful charges
  • Allow customers to update/remove payment methods
  • Set up monitoring for failed auto-payments

❌ DON'T

  • Charge customers without prior agreement
  • Ignore failed payment notifications
  • Store full card details (use tokens only)
  • Retry failed payments excessively
  • Forget to handle expired cards
  • Use credentials from interactive payments without MIT enabled

Webhooks

Handle PaymentCredential events

Create Auto Attempt API

Complete API reference

Payment Intents

Understand intents