Skip to main content
This document describes common integration patterns and best practices for integrating with the Chargeworx payment platform.

API integration patterns

OAuth2 client credentials flow

The recommended authentication method for API access:
// 1. Request access token
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, 
    "https://api.chargeworx.com/connect/token");

request.Content = new FormUrlEncodedContent(new[]
{
    new KeyValuePair<string, string>("grant_type", "client_credentials"),
    new KeyValuePair<string, string>("client_id", "your_client_id"),
    new KeyValuePair<string, string>("client_secret", "your_client_secret"),
    new KeyValuePair<string, string>("scope", "PaymentScope")
});

var response = await client.SendAsync(request);
var token = await response.Content.ReadAsAsync<TokenResponse>();

// 2. Use token for API calls
client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", token.AccessToken);

var transactionResponse = await client.PostAsync(
    "/api/pay/{projectId}/transactions", 
    transactionContent);
Best practices:
  • Cache tokens until expiration
  • Implement token refresh logic
  • Use HTTPS for all requests
  • Store credentials securely

API key authentication

Alternative authentication for simpler integrations:
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

var response = await client.PostAsync(
    "/api/pay/{projectId}/transactions",
    transactionContent);
Best practices:
  • Rotate API keys regularly
  • Use different keys per environment
  • Monitor key usage
  • Revoke compromised keys immediately

Transaction processing patterns

Simple authorization

Basic credit card authorization:
var request = new
{
    merchantReference = "ORDER-12345",
    amount = 100.00,
    currency = "USD",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025",
    cvv = "123",
    billTo = new
    {
        firstName = "John",
        lastName = "Doe",
        street1 = "123 Main St",
        city = "San Francisco",
        state = "CA",
        postalCode = "94105",
        country = "US",
        email = "[email protected]"
    }
};

var response = await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/transactions",
    request);

var result = await response.Content.ReadAsAsync<TransactionResponse>();

Authorization with capture

Authorize and immediately capture:
var request = new
{
    merchantReference = "ORDER-12345",
    amount = 100.00,
    currency = "USD",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025",
    cvv = "123",
    captureImmediately = true,  // Capture right away
    billTo = new { /* ... */ }
};

Delayed capture

Authorize now, capture later:
// 1. Authorize
var authRequest = new
{
    merchantReference = "ORDER-12345",
    amount = 100.00,
    currency = "USD",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025",
    cvv = "123",
    captureImmediately = false  // Don't capture yet
};

var authResponse = await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/transactions",
    authRequest);

var authResult = await authResponse.Content.ReadAsAsync<TransactionResponse>();

// 2. Capture later (within 7 days)
var captureResponse = await client.PostAsync(
    $"/api/adm/transactions/{authResult.TransactionId}/capture",
    null);

Tokenized payments

Use saved payment info for recurring charges:
// 1. Save payment info
var saveRequest = new
{
    merchantReference = "CUSTOMER-001",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025",
    billTo = new { /* ... */ }
};

var saveResponse = await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/companyProjectCreditCardPaymentsInfo",
    saveRequest);

var paymentInfo = await saveResponse.Content
    .ReadAsAsync<PaymentInfoResponse>();

// 2. Charge using token
var chargeRequest = new
{
    merchantReference = "ORDER-12346",
    amount = 50.00,
    currency = "USD",
    paymentInfoId = paymentInfo.Id  // Use saved payment info
};

var chargeResponse = await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/transactions",
    chargeRequest);

Webhook integration

Receiving transaction notifications

Set up webhook endpoint to receive transaction updates:
[HttpPost]
[Route("webhooks/chargeworx/transactions")]
public async Task<IActionResult> ReceiveTransactionWebhook(
    [FromBody] TransactionWebhook webhook)
{
    // Verify webhook signature
    if (!VerifySignature(webhook))
        return Unauthorized();
    
    // Process webhook
    switch (webhook.EventType)
    {
        case "transaction.approved":
            await HandleApprovedTransaction(webhook.Transaction);
            break;
        case "transaction.declined":
            await HandleDeclinedTransaction(webhook.Transaction);
            break;
        case "chargeback.received":
            await HandleChargeback(webhook.Chargeback);
            break;
    }
    
    return Ok();
}

private bool VerifySignature(TransactionWebhook webhook)
{
    var signature = Request.Headers["X-Chargeworx-Signature"];
    var payload = JsonConvert.SerializeObject(webhook);
    var expectedSignature = ComputeHmacSha256(payload, _webhookSecret);
    
    return signature == expectedSignature;
}
Best practices:
  • Verify webhook signatures
  • Process webhooks asynchronously
  • Return 200 OK quickly
  • Implement idempotency
  • Log all webhook events

Batch processing patterns

Bulk transaction import

Import multiple transactions at once:
// 1. Prepare CSV file
var csv = new StringBuilder();
csv.AppendLine("MerchantReference,Amount,Currency,CardNumber,ExpirationMonth,ExpirationYear");
csv.AppendLine("ORDER-001,100.00,USD,4111111111111111,12,2025");
csv.AppendLine("ORDER-002,50.00,USD,5555555555554444,06,2026");

// 2. Upload file
var content = new MultipartFormDataContent();
content.Add(new StringContent(csv.ToString()), "file", "transactions.csv");

var response = await client.PostAsync(
    $"/api/adm/import",
    content);

var importResult = await response.Content.ReadAsAsync<ImportResponse>();

// 3. Poll for completion
while (importResult.Status == "Processing")
{
    await Task.Delay(5000);
    
    var statusResponse = await client.GetAsync(
        $"/api/adm/import/{importResult.BatchId}");
    
    importResult = await statusResponse.Content.ReadAsAsync<ImportResponse>();
}

Account updater integration

Keep card information current:
// 1. Enable account updater for project
var config = new
{
    accountUpdaterEnabled = true,
    updateFrequency = "Monthly"
};

await client.PutAsJsonAsync(
    $"/api/adm/companyProjects/{projectId}/settings",
    config);

// 2. Mark cards for update
var paymentInfoIds = new[] { guid1, guid2, guid3 };

await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/companyProjectCreditCardPaymentsInfo/setAccountUpdaterPriority",
    new { paymentInfoIds, priority = 1 });

// 3. Check update results
var results = await client.GetAsync(
    $"/api/pay/{projectId}/companyProjectCreditCardPaymentsInfo/getCreditCardUpdaterResult");

var updaterResults = await results.Content
    .ReadAsAsync<List<AccountUpdaterResult>>();

Error handling patterns

Retry logic

Implement exponential backoff for transient errors:
public async Task<TransactionResponse> ProcessTransactionWithRetry(
    TransactionRequest request)
{
    var maxRetries = 3;
    var delay = TimeSpan.FromSeconds(1);
    
    for (int i = 0; i < maxRetries; i++)
    {
        try
        {
            var response = await client.PostAsJsonAsync(
                $"/api/pay/{projectId}/transactions",
                request);
            
            if (response.IsSuccessStatusCode)
            {
                return await response.Content
                    .ReadAsAsync<TransactionResponse>();
            }
            
            // Don't retry client errors (4xx)
            if ((int)response.StatusCode >= 400 && 
                (int)response.StatusCode < 500)
            {
                throw new InvalidOperationException(
                    $"Client error: {response.StatusCode}");
            }
        }
        catch (HttpRequestException ex)
        {
            if (i == maxRetries - 1)
                throw;
            
            await Task.Delay(delay);
            delay = TimeSpan.FromSeconds(delay.TotalSeconds * 2);
        }
    }
    
    throw new Exception("Max retries exceeded");
}

Idempotency

Use idempotency keys to prevent duplicate transactions:
var idempotencyKey = Guid.NewGuid().ToString();

var request = new
{
    merchantReference = "ORDER-12345",
    amount = 100.00,
    currency = "USD",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025"
};

client.DefaultRequestHeaders.Add("X-Idempotency-Key", idempotencyKey);

var response = await client.PostAsJsonAsync(
    $"/api/pay/{projectId}/transactions",
    request);

// If request fails and is retried, same idempotency key
// will return the original transaction

Reporting integration

Transaction reports

Query transactions with filters:
var query = new
{
    startDate = DateTime.UtcNow.AddDays(-30),
    endDate = DateTime.UtcNow,
    status = "Approved",
    minAmount = 10.00,
    maxAmount = 1000.00
};

var response = await client.PostAsJsonAsync(
    $"/api/adm/reportTransactions/search",
    query);

var transactions = await response.Content
    .ReadAsAsync<List<ReportTransaction>>();

Export to CSV

Download transaction data:
var query = new
{
    startDate = DateTime.UtcNow.AddDays(-30),
    endDate = DateTime.UtcNow,
    format = "csv"
};

var response = await client.PostAsJsonAsync(
    $"/api/adm/reportTransactions/export",
    query);

var csvContent = await response.Content.ReadAsStringAsync();
File.WriteAllText("transactions.csv", csvContent);

Multi-tenancy patterns

Company and project isolation

Each API call is scoped to a specific project:
// Different projects have different configurations
var project1Id = Guid.Parse("...");
var project2Id = Guid.Parse("...");

// Transaction for project 1
await client.PostAsJsonAsync(
    $"/api/pay/{project1Id}/transactions",
    transaction1);

// Transaction for project 2 (different processor, settings)
await client.PostAsJsonAsync(
    $"/api/pay/{project2Id}/transactions",
    transaction2);

User access control

Users can belong to multiple companies and projects:
// Get user's companies
var companies = await client.GetAsync("/api/adm/companies");

// Get projects for a company
var projects = await client.GetAsync(
    $"/api/adm/companyProjects?companyId={companyId}");

// Switch context to different company
await client.PostAsJsonAsync(
    "/api/adm/users/switchCompany",
    new { companyId });

Testing patterns

Test mode

Use test credentials for development:
var testConfig = new
{
    apiBaseUrl = "https://test-api.chargeworx.com",
    clientId = "test_client_id",
    clientSecret = "test_client_secret"
};

// Test card numbers
var testCards = new[]
{
    "4111111111111111",  // Visa - Approved
    "5555555555554444",  // Mastercard - Approved
    "4000000000000002",  // Visa - Declined
};

Mocked responses

Configure mocked processor responses:
var request = new
{
    merchantReference = "TEST-001",
    amount = 100.00,
    currency = "USD",
    cardNumber = "4111111111111111",
    expirationMonth = "12",
    expirationYear = "2025",
    mockResponse = "Approved"  // Force specific response
};

Performance optimization

Connection pooling

Reuse HTTP connections:
// Use HttpClientFactory
services.AddHttpClient<IChargeworxClient, ChargeworxClient>(client =>
{
    client.BaseAddress = new Uri("https://api.chargeworx.com");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// Client is injected and reused
public class PaymentService
{
    private readonly IChargeworxClient _client;
    
    public PaymentService(IChargeworxClient client)
    {
        _client = client;
    }
}

Response caching

Cache frequently accessed data:
// Cache processor configuration
var cacheKey = $"processor:{projectId}";
var processor = await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
    entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
    
    var response = await client.GetAsync(
        $"/api/adm/projectPaymentProcessors?projectId={projectId}");
    
    return await response.Content
        .ReadAsAsync<ProcessorConfiguration>();
});

Batch operations

Process multiple items in a single request:
// Instead of multiple single requests
foreach (var transaction in transactions)
{
    await ProcessTransactionAsync(transaction);  // Slow
}

// Use batch endpoint
await client.PostAsJsonAsync(
    $"/api/adm/transactions/batch",
    transactions);  // Fast

Security best practices

Secure credential storage

Never hardcode credentials:
// Bad
var clientSecret = "hardcoded_secret";

// Good - use configuration
var clientSecret = _configuration["Chargeworx:ClientSecret"];

// Better - use secrets manager
var clientSecret = await _secretsManager.GetSecretAsync(
    "chargeworx-client-secret");

PCI compliance

Handle card data securely:
// Never log full card numbers
_logger.LogInformation("Processing card ending in {Last4}", 
    cardNumber.Substring(cardNumber.Length - 4));

// Use tokenization
var paymentInfo = await SavePaymentInfoAsync(cardNumber);
// Store only token, not card number
await StoreTokenAsync(paymentInfo.Id);

// Encrypt sensitive data
var encrypted = _encryptionService.Encrypt(cardNumber);

IP whitelisting

Restrict API access by IP:
// Configure allowed IPs in admin UI
var whitelist = new[]
{
    "192.168.1.100",
    "10.0.0.0/8"
};

await client.PostAsJsonAsync(
    $"/api/adm/whitelistIP",
    new { ipAddresses = whitelist });

Next steps