Skip to main content
This guide will walk you through creating a basic payment connector using the VTEX Payment Provider Framework. You’ll have a working connector that can authorize, cancel, and settle payments.

Clone the example repository

The fastest way to start is by cloning the official example repository, which includes all the base configuration:
git clone https://github.com/vtex-apps/payment-provider-example.git
cd payment-provider-example
The repository structure includes:
payment-provider-example/
├── node/
│   ├── connector.ts        # Payment connector implementation
│   ├── index.ts           # Service initialization
│   ├── flow.ts            # Payment flow logic
│   ├── utils.ts           # Utility functions
│   └── package.json       # Node dependencies
├── paymentProvider/
│   └── configuration.json # Payment methods configuration
└── manifest.json          # VTEX IO app manifest

Install dependencies

Navigate to the node folder and install the required packages:
cd node
yarn install
The @vtex/payment-provider SDK should be listed in your dependencies at version ^1.4.0 or higher.

Configure your manifest

Update manifest.json with your connector details:
manifest.json
{
  "name": "payment-provider-example",
  "vendor": "vtex",
  "version": "1.0.0",
  "title": "My Payment Connector",
  "description": "Custom payment connector for MyGateway",
  "builders": {
    "paymentProvider": "1.x",
    "node": "6.x"
  },
  "policies": [
    {
      "name": "vbase-read-write"
    },
    {
      "name": "colossus-fire-event"
    },
    {
      "name": "outbound-access",
      "attrs": {
        "host": "heimdall.vtexpayments.com.br",
        "path": "/api/payment-provider/callback/*"
      }
    }
  ]
}
1

Update vendor

Change vendor to your VTEX account name or organization identifier
2

Set app name

Change name to a unique identifier for your connector (e.g., payment-provider-mygateway)
3

Configure builders

The paymentProvider builder exposes protocol routes and the node builder runs your service

Define payment methods

Configure which payment methods your connector supports in paymentProvider/configuration.json:
paymentProvider/configuration.json
{
  "name": "MyConnector",
  "paymentMethods": [
    {
      "name": "Visa",
      "allowsSplit": "onCapture"
    },
    {
      "name": "Mastercard",
      "allowsSplit": "onCapture"
    },
    {
      "name": "American Express",
      "allowsSplit": "onCapture"
    }
  ],
  "customFields": [
    {
      "name": "API Key",
      "type": "text"
    },
    {
      "name": "Secret Token",
      "type": "password"
    }
  ]
}
Payment method options:
  • name: The payment method identifier (Visa, Mastercard, BankInvoice, etc.)
  • allowsSplit: When split payments are allowed ("onCapture", "onAuthorize", or "disabled")
Custom fields allow merchants to configure API credentials when setting up your connector in VTEX Admin.

Implement your connector

Create your payment connector by extending the PaymentProvider class:
node/connector.ts
import {
  AuthorizationRequest,
  AuthorizationResponse,
  CancellationRequest,
  CancellationResponse,
  Cancellations,
  PaymentProvider,
  RefundRequest,
  RefundResponse,
  Refunds,
  SettlementRequest,
  SettlementResponse,
  Settlements,
  Authorizations,
} from '@vtex/payment-provider'
import { randomString } from './utils'

export default class MyPaymentConnector extends PaymentProvider {
  // Authorize a payment
  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // Call your payment gateway to authorize the transaction
    // For this example, we'll approve all payments
    
    return Authorizations.approve(authorization, {
      authorizationId: randomString(),
      nsu: randomString(),
      tid: randomString(),
    })
  }

  // Cancel a payment
  public async cancel(
    cancellation: CancellationRequest
  ): Promise<CancellationResponse> {
    // Call your payment gateway to cancel the transaction
    
    return Cancellations.approve(cancellation, {
      cancellationId: randomString(),
    })
  }

  // Refund a payment
  public async refund(refund: RefundRequest): Promise<RefundResponse> {
    // Call your payment gateway to refund the transaction
    
    return Refunds.deny(refund)
  }

  // Settle (capture) a payment
  public async settle(
    settlement: SettlementRequest
  ): Promise<SettlementResponse> {
    // Call your payment gateway to capture the authorized amount
    
    return Settlements.approve(settlement, {
      settleId: randomString(),
    })
  }

  // Inbound webhook handler (optional)
  public inbound: undefined
}
Each method receives a typed request object and must return a typed response. The framework provides helper functions like Authorizations.approve() and Cancellations.deny() to construct valid responses.

Initialize the service

Create the payment provider service in node/index.ts:
node/index.ts
import { PaymentProviderService } from '@vtex/payment-provider'
import MyPaymentConnector from './connector'

export default new PaymentProviderService({
  connector: MyPaymentConnector,
})
That’s it! The PaymentProviderService automatically sets up all required routes:
  • GET /manifest - Connector manifest and capabilities
  • GET /payment-methods - Supported payment methods
  • POST /payments - Payment authorization
  • POST /payments/:paymentId/cancellations - Payment cancellation
  • POST /payments/:paymentId/settlements - Payment settlement (capture)
  • POST /payments/:paymentId/refunds - Payment refund
  • POST /payments/:paymentId/inbound/hooks - Inbound webhooks
Now you can test your connector locally using VTEX IO:
1

Login to VTEX

vtex login {account}
2

Create a development workspace

vtex use dev-workspace
3

Link your app

vtex link
Your connector is now running in your development workspace!

Working with card payments

When processing card payments, you’ll receive card data in a secure format. Here’s how to handle different card types:
node/connector.ts
import {
  isCardAuthorization,
  isBankInvoiceAuthorization,
  isTokenizedCard,
  AuthorizationRequest,
  AuthorizationResponse,
  Authorizations,
} from '@vtex/payment-provider'

export default class MyPaymentConnector extends PaymentProvider {
  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // Check if this is a card payment
    if (isCardAuthorization(authorization)) {
      const { card } = authorization
      
      // Check if card is tokenized or has raw data
      if (isTokenizedCard(card)) {
        // Use card.token for tokenized cards
        const cardToken = card.token
      } else {
        // Use card.number, card.csc for non-tokenized cards
        // These will be encrypted tokens when using Secure Proxy
        const cardNumber = card.numberToken
        const cvv = card.cscToken
      }
    }
    
    // Check if this is a bank invoice payment
    if (isBankInvoiceAuthorization(authorization)) {
      // Return a payment URL for the customer
      return Authorizations.pendingBankInvoice(authorization, {
        paymentUrl: 'https://payment-gateway.com/invoice/12345',
        delayToCancel: 600000, // 10 minutes
        tid: randomString(),
      })
    }
    
    return Authorizations.approve(authorization, {
      authorizationId: randomString(),
      nsu: randomString(),
      tid: randomString(),
    })
  }
}

Handle async payments

For payments that are processed asynchronously (like bank invoices or redirect flows), use the retry mechanism:
node/connector.ts
export default class MyPaymentConnector extends PaymentProvider {
  public async authorize(
    authorization: AuthorizationRequest
  ): Promise<AuthorizationResponse> {
    // For async payments, return pending status initially
    const response = Authorizations.pending(authorization, {
      delayToCancel: 300000, // 5 minutes
      tid: randomString(),
    })
    
    // Later, when payment is confirmed, use callback to notify VTEX
    this.callback(authorization, Authorizations.approve(authorization, {
      authorizationId: 'gateway-auth-id',
      nsu: randomString(),
      tid: randomString(),
    }))
    
    return response
  }
}
The callback() method tells VTEX Gateway to retry the authorization request. Your connector must return a consistent response (approved or denied) when called again with the same paymentId.

Next steps

Configuration options

Learn about advanced configuration options like OAuth, Secure Proxy, and split payments

Testing your connector

Run the VTEX test suite to validate your implementation

Secure Proxy usage

Process card payments without PCI certification

Deploy to production

Publish your connector to the VTEX App Store

Common patterns

Access merchant configuration

Access the custom fields configured by the merchant:
const apiKey = this.context.vtex.appKey
const apiToken = this.context.vtex.appToken

Persist data with VBase

Store transaction data for later retrieval:
import { VBase } from '@vtex/api'

const vbase = this.context.clients.vbase

// Save data
await vbase.saveJSON('authorizations', paymentId, authorizationResponse)

// Retrieve data
const data = await vbase.getJSON<AuthorizationResponse>('authorizations', paymentId)

Handle different response types

// Approve a payment
Authorizations.approve(request, { authorizationId, nsu, tid })

// Deny a payment
Authorizations.deny(request, { tid })

// Return pending status
Authorizations.pending(request, { delayToCancel, tid })

// Redirect to external page
Authorizations.redirect(request, { redirectUrl, delayToCancel, tid })
You now have a fully functional payment connector! The example approves all payments, but you can replace the logic with actual API calls to your payment gateway.

Build docs developers (and LLMs) love