Skip to main content
This guide covers the implementation of the four required routes for a VTEX payment connector: authorize, cancel, settle, and refund. You’ll learn about request/response formats, response helpers, and asynchronous payment flows.

Overview of payment routes

Every payment connector must implement four core methods:

authorize

Validates and authorizes a payment transaction

cancel

Cancels an authorized payment before settlement

settle

Captures/settles an authorized payment

refund

Refunds a settled payment

Authorization route

The authorization route is the most complex route, handling payment validation and supporting multiple payment flows.

Basic authorization implementation

1

Import required types and helpers

import {
  AuthorizationRequest,
  AuthorizationResponse,
  Authorizations,
  PaymentProvider,
} from '@vtex/payment-provider'
2

Implement the authorize method

export default class TestSuiteApprover extends PaymentProvider {
  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // Validate the payment request
    // Communicate with your payment gateway
    // Return an appropriate response
    
    return Authorizations.approve(authorization, {
      authorizationId: 'unique-auth-id',
      nsu: 'unique-nsu',
      tid: 'transaction-id',
    })
  }
}

Authorization response types

The @vtex/payment-provider package provides helper methods for different authorization responses:
import { Authorizations } from '@vtex/payment-provider'

// Approve the authorization immediately
Authorizations.approve(request, {
  authorizationId: 'unique-auth-id',
  nsu: 'network-sequence-number',
  tid: 'transaction-id',
})

Handling different payment methods

Use type guards to handle different payment method types:
import {
  isCardAuthorization,
  isBankInvoiceAuthorization,
  isTokenizedCard,
} from '@vtex/payment-provider'

export default class TestSuiteApprover extends PaymentProvider {
  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // Handle bank invoice payments
    if (isBankInvoiceAuthorization(authorization)) {
      return this.handleBankInvoice(authorization)
    }
    
    // Handle card payments
    if (isCardAuthorization(authorization)) {
      const { card } = authorization
      
      if (isTokenizedCard(card)) {
        // Handle tokenized card
        return this.handleTokenizedCard(authorization, card)
      } else {
        // Handle regular card
        return this.handleRegularCard(authorization, card)
      }
    }
    
    // Default handling
    return Authorizations.deny(authorization, { tid: 'unknown-method' })
  }
}

Asynchronous authorization flow

For payments that require async processing, use the callback mechanism:
import { VBase } from '@vtex/api'

export default class TestSuiteApprover extends PaymentProvider {
  private async saveAndRetry(
    req: AuthorizationRequest,
    resp: AuthorizationResponse
  ) {
    // Persist the response
    await this.context.clients.vbase.saveJSON(
      'authorizations',
      resp.paymentId,
      resp
    )
    
    // Send callback to VTEX
    this.callback(req, resp)
  }

  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // Return pending response immediately
    const pendingResponse = Authorizations.pending(authorization, {
      delayToCancel: 1000,
      tid: 'pending-transaction',
    })
    
    // Process asynchronously and callback later
    this.processPaymentAsync(authorization).then(finalResponse => {
      this.saveAndRetry(authorization, finalResponse)
    })
    
    return pendingResponse
  }
}
Ensure the outbound-access policy is configured in your manifest.json to enable callbacks to VTEX.

Cancellation route

The cancellation route handles canceling authorized but not yet settled payments.

Basic cancellation implementation

import {
  CancellationRequest,
  CancellationResponse,
  Cancellations,
} from '@vtex/payment-provider'

export default class TestSuiteApprover extends PaymentProvider {
  public async cancel(
    cancellation: CancellationRequest
  ): Promise<CancellationResponse> {
    // Communicate with your payment gateway to cancel the transaction
    // Return appropriate response
    
    return Cancellations.approve(cancellation, {
      cancellationId: 'unique-cancellation-id',
    })
  }
}

Cancellation response helpers

import { Cancellations } from '@vtex/payment-provider'

Cancellations.approve(request, {
  cancellationId: 'unique-cancellation-id',
})

Settlement route

The settlement route captures/settles an authorized payment.

Basic settlement implementation

import {
  SettlementRequest,
  SettlementResponse,
  Settlements,
} from '@vtex/payment-provider'

export default class TestSuiteApprover extends PaymentProvider {
  public async settle(
    settlement: SettlementRequest
  ): Promise<SettlementResponse> {
    // Communicate with your payment gateway to settle the transaction
    // Return appropriate response
    
    return Settlements.approve(settlement, {
      settleId: 'unique-settlement-id',
    })
  }
}

Settlement response helpers

import { Settlements } from '@vtex/payment-provider'

Settlements.approve(request, {
  settleId: 'unique-settlement-id',
})

Refund route

The refund route handles refunding a settled payment.

Basic refund implementation

import {
  RefundRequest,
  RefundResponse,
  Refunds,
} from '@vtex/payment-provider'

export default class TestSuiteApprover extends PaymentProvider {
  public async refund(refund: RefundRequest): Promise<RefundResponse> {
    // Communicate with your payment gateway to refund the transaction
    // Return appropriate response
    
    return Refunds.approve(refund, {
      refundId: 'unique-refund-id',
    })
  }
}

Refund response helpers

import { Refunds } from '@vtex/payment-provider'

Refunds.approve(request, {
  refundId: 'unique-refund-id',
})

Complete example

Here’s a complete example from the TestSuiteApprover connector:
node/connector.ts
import {
  AuthorizationRequest,
  AuthorizationResponse,
  CancellationRequest,
  CancellationResponse,
  Cancellations,
  PaymentProvider,
  RefundRequest,
  RefundResponse,
  Refunds,
  SettlementRequest,
  SettlementResponse,
  Settlements,
} from '@vtex/payment-provider'
import { VBase } from '@vtex/api'

import { randomString } from './utils'
import { executeAuthorization } from './flow'

const authorizationsBucket = 'authorizations'

const persistAuthorizationResponse = async (
  vbase: VBase,
  resp: AuthorizationResponse
) => vbase.saveJSON(authorizationsBucket, resp.paymentId, resp)

const getPersistedAuthorizationResponse = async (
  vbase: VBase,
  req: AuthorizationRequest
) =>
  vbase.getJSON<AuthorizationResponse | undefined>(
    authorizationsBucket,
    req.paymentId,
    true
  )

export default class TestSuiteApprover extends PaymentProvider {
  private async saveAndRetry(
    req: AuthorizationRequest,
    resp: AuthorizationResponse
  ) {
    await persistAuthorizationResponse(this.context.clients.vbase, resp)
    this.callback(req, resp)
  }

  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    if (this.isTestSuite) {
      const persistedResponse = await getPersistedAuthorizationResponse(
        this.context.clients.vbase,
        authorization
      )

      if (persistedResponse !== undefined && persistedResponse !== null) {
        return persistedResponse
      }

      return executeAuthorization(authorization, response =>
        this.saveAndRetry(authorization, response)
      )
    }

    throw new Error('Not implemented')
  }

  public async cancel(
    cancellation: CancellationRequest
  ): Promise<CancellationResponse> {
    if (this.isTestSuite) {
      return Cancellations.approve(cancellation, {
        cancellationId: randomString(),
      })
    }

    throw new Error('Not implemented')
  }

  public async refund(refund: RefundRequest): Promise<RefundResponse> {
    if (this.isTestSuite) {
      return Refunds.deny(refund)
    }

    throw new Error('Not implemented')
  }

  public async settle(
    settlement: SettlementRequest
  ): Promise<SettlementResponse> {
    if (this.isTestSuite) {
      return Settlements.deny(settlement)
    }

    throw new Error('Not implemented')
  }

  public inbound: undefined
}
Always implement proper error handling and validation in production code. The example above uses simplified logic for demonstration purposes.

Next steps

Configuration

Configure your connector with manifest and service settings

Payment methods

Configure supported payment methods

Build docs developers (and LLMs) love