Skip to content

Backend SDKs

Maytes ships official server-to-server SDKs so you don't hand-roll HTTP or OAuth. They're generated from a single API definition, so every language exposes the same operations and stays in sync with the API.

Available for: TypeScript, Python, PHP, Go, Java, Ruby, .NET.

Why use the SDK

  • Authentication is automatic. Construct the client with your client_id / client_secret; the SDK fetches, caches, and refreshes the access token (including retrying once on a 401). You never call /oauth/token.
  • Typed requests and responses for every operation.
  • Correct number handling — amounts stay integers, timestamps parse to your language's date type.

The SDKs are for your backend only — keep your credentials server-side.

What the SDK covers

The SDK exposes the merchant operations:

OperationPurpose
CreateCheckoutCreate a checkout
GetCheckoutRead the order's current status
CaptureCheckoutCapture the payment
CancelCheckoutVoid an authorization
GetHealthLiveness probe

Not in the SDK

Refunds aren't an API integration — they're issued from the merchant portal. Webhook subscription management isn't in the SDK today — call those endpoints with raw HTTP and the same Bearer token, or use the portal. (Webhook deliveries to you don't involve the SDK at all — see verifying signatures.)

Construct the client

LanguageConstruct the client
TypeScriptcreateMaytesApiClient({ endpoint, clientId, clientSecret })
Pythoncreate_maytes_api_client(endpoint, client_id, client_secret)
PHP\MaytesApiClient\ClientFactory::create($endpoint, $clientId, $clientSecret)
GoCreateMaytesAPIClient(endpoint, clientID, clientSecret)
JavaClientFactory.create(endpoint, clientId, clientSecret)
RubyMaytesApiClient.create_maytes_api_client(endpoint:, client_id:, client_secret:)
.NETMaytesApiClient.Auth.ClientFactory.Create(endpoint, clientId, clientSecret)

Use https://api.maytes.co as the endpoint.

Example — create then capture

The same flow in each supported language: create the checkout, then capture once you learn it's authorized — via the checkout.authorized webhook in the recommended flow, or the return_url landing in the legacy flow. Capture is synchronous: when the call returns, the money has moved. To keep the snippets compact they use category: "other" line items — for richer carts (tickets with event_id etc.) see Create a checkout.

ts
import {
  createMaytesApiClient,
  CreateCheckoutCommand,
  CaptureCheckoutCommand,
  GetCheckoutCommand,
} from '@maytes/api-client';

// Construct the managed client once at startup. The SDK fetches and caches
// the OAuth token automatically and refreshes it on 401.
const maytes = createMaytesApiClient({
  endpoint: 'https://api.maytes.co',
  clientId: process.env.MAYTES_CLIENT_ID!,
  clientSecret: process.env.MAYTES_CLIENT_SECRET!,
});

// 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
//    Persist `checkout_uuid` against your own `merchant_order_id`; you'll need
//    it to capture once you learn the payment is authorized.
const created = await maytes.send(new CreateCheckoutCommand({
  merchant_order_id: 'order_1001',
  total_amount: 4500,                // minor units — 4500 = AUD 45.00
  currency: 'AUD',
  return_url: 'https://shop.example.com/thanks?order=order_1001',
  cancel_url: 'https://shop.example.com/cart',
  items: [{
    item_ref: 'GA-1', name: 'General Admission',
    quantity: 1, unit_price: 4500, currency: 'AUD', category: 'other',
  }],
}));
const uuid = created.data!.checkout_uuid;

// 2. ...customer pays in the Maytes window; your checkout.authorized
//    webhook fires (or, legacy flow, the customer lands on return_url)...

// 3. Capture. Synchronous — when this returns, the money has moved.
//    `expected_total_amount` and `currency` are optional assertions — Maytes
//    returns 422 if they don't match the authorized amount.
const captured = await maytes.send(new CaptureCheckoutCommand({
  checkoutUuid: uuid,
  expected_total_amount: 4500,
  currency: 'AUD',
}));
console.log(captured.data!.status); // "captured" — fulfil now

// 4. GetCheckout reads the current state whenever you need it later
//    (support tooling, reconciliation, refund progress).
const status = await maytes.send(new GetCheckoutCommand({ checkoutUuid: uuid }));
python
from maytes_api_client import create_maytes_api_client
from maytes_api_client.models.create_checkout_request_content import CreateCheckoutRequestContent
from maytes_api_client.models.capture_checkout_request_content import CaptureCheckoutRequestContent
from maytes_api_client.models.line_item import LineItem

# Construct the managed client once at startup. The SDK fetches and caches
# the OAuth token automatically and refreshes it on 401.
maytes = create_maytes_api_client(
    "https://api.maytes.co",
    client_id=MAYTES_CLIENT_ID,
    client_secret=MAYTES_CLIENT_SECRET,
)

# 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
#    Persist `checkout_uuid` against your own `merchant_order_id`.
created = maytes.create_checkout(CreateCheckoutRequestContent(
    merchant_order_id="order_1001",
    total_amount=4500,                # minor units — 4500 = AUD 45.00
    currency="AUD",
    return_url="https://shop.example.com/thanks?order=order_1001",
    cancel_url="https://shop.example.com/cart",
    items=[LineItem(
        item_ref="GA-1", name="General Admission",
        quantity=1, unit_price=4500, currency="AUD", category="other",
    )],
))
uuid = created.data.checkout_uuid

# 2. ...customer pays in the Maytes window; your checkout.authorized
#    webhook fires (or, legacy flow, the customer lands on return_url)...

# 3. Capture. Synchronous — when this returns, the money has moved.
#    `expected_total_amount` and `currency` are optional amount assertions.
captured = maytes.capture_checkout(uuid, CaptureCheckoutRequestContent(
    expected_total_amount=4500,
    currency="AUD",
))
print(captured.data.status)  # "captured" — fulfil now

# 4. get_checkout reads the current state whenever you need it later.
status = maytes.get_checkout(uuid)
php
use MaytesApiClient\ClientFactory;
use MaytesApiClient\Model\CreateCheckoutRequestContent;
use MaytesApiClient\Model\CaptureCheckoutRequestContent;
use MaytesApiClient\Model\LineItem;

// Construct the managed client once at startup. The SDK fetches and caches
// the OAuth token automatically and refreshes it on 401.
$maytes = ClientFactory::create(
    'https://api.maytes.co',
    getenv('MAYTES_CLIENT_ID'),
    getenv('MAYTES_CLIENT_SECRET'),
);

// 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
//    Persist `checkout_uuid` against your own `merchant_order_id`.
$created = $maytes->createCheckout(new CreateCheckoutRequestContent([
    'merchant_order_id' => 'order_1001',
    'total_amount'      => 4500,             // minor units — 4500 = AUD 45.00
    'currency'          => 'AUD',
    'return_url'       => 'https://shop.example.com/thanks?order=order_1001',
    'cancel_url'        => 'https://shop.example.com/cart',
    'items'             => [new LineItem([
        'item_ref'   => 'GA-1',
        'name'       => 'General Admission',
        'quantity'   => 1,
        'unit_price' => 4500,
        'currency'   => 'AUD',
        'category'   => 'other',
    ])],
]));
$uuid = $created->getData()->getCheckoutUuid();

// 2. ...customer pays in the Maytes window; your checkout.authorized
//    webhook fires (or, legacy flow, the customer lands on return_url)...

// 3. Capture. Synchronous — when this returns, the money has moved.
//    `expected_total_amount` and `currency` are optional amount assertions.
$captured = $maytes->captureCheckout($uuid, new CaptureCheckoutRequestContent([
    'expected_total_amount' => 4500,
    'currency'              => 'AUD',
]));
echo $captured->getData()->getStatus(); // "captured" — fulfil now

// 4. getCheckout reads the current state whenever you need it later.
$status = $maytes->getCheckout($uuid);
go
import (
    "context"
    "fmt"
    maytes "github.com/maytes/api-client-go"
)

ctx := context.Background()

// Construct the managed client once at startup. The SDK fetches and caches
// the OAuth token automatically and refreshes it on 401.
api := maytes.CreateMaytesAPIClient("https://api.maytes.co", clientID, clientSecret)

// 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
//    Persist `checkout_uuid` against your own `merchant_order_id`.
createReq := *maytes.NewCreateCheckoutRequestContent(
    4500, "AUD",                       // minor units — 4500 = AUD 45.00
    "https://shop.example.com/thanks?order=order_1001",
    "https://shop.example.com/cart",
    []maytes.LineItem{
        *maytes.NewLineItem("GA-1", "General Admission", 1, 4500, "AUD", "other"),
    },
)
createReq.SetMerchantOrderId("order_1001")
created, _, err := api.DefaultAPI.CreateCheckout(ctx).
    CreateCheckoutRequestContent(createReq).Execute()
if err != nil { /* handle */ }
uuid := created.Data.GetCheckoutUuid()

// 2. ...customer pays in the Maytes window; your checkout.authorized
//    webhook fires (or, legacy flow, the customer lands on return_url)...

// 3. Capture. Synchronous — when this returns, the money has moved.
//    `expected_total_amount` and `currency` are optional amount assertions.
captureReq := *maytes.NewCaptureCheckoutRequestContent()
captureReq.SetExpectedTotalAmount(4500)
captureReq.SetCurrency("AUD")
captured, _, err := api.DefaultAPI.CaptureCheckout(ctx, uuid).
    CaptureCheckoutRequestContent(captureReq).Execute()
if err != nil { /* handle */ }
fmt.Println(captured.Data.GetStatus()) // "captured" — fulfil now

// 4. GetCheckout reads the current state whenever you need it later.
status, _, _ := api.DefaultAPI.GetCheckout(ctx, uuid).Execute()
_ = status
java
import co.maytes.api.DefaultApi;
import co.maytes.api.auth.ClientFactory;
import co.maytes.api.model.*;
import java.util.List;

// Construct the managed client once at startup. The SDK fetches and caches
// the OAuth token automatically and refreshes it on 401.
DefaultApi maytes = ClientFactory.create(
    "https://api.maytes.co",
    System.getenv("MAYTES_CLIENT_ID"),
    System.getenv("MAYTES_CLIENT_SECRET"));

// 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
//    Persist `checkout_uuid` against your own `merchant_order_id`.
CreateCheckoutResponseContent created = maytes.createCheckout(
    new CreateCheckoutRequestContent()
        .merchantOrderId("order_1001")
        .totalAmount(4500)             // minor units — 4500 = AUD 45.00
        .currency("AUD")
        .returnUrl("https://shop.example.com/thanks?order=order_1001")
        .cancelUrl("https://shop.example.com/cart")
        .items(List.of(new LineItem()
            .itemRef("GA-1").name("General Admission")
            .quantity(1).unitPrice(4500).currency("AUD").category("other"))));
String uuid = created.getData().getCheckoutUuid();

// 2. ...customer pays in the Maytes window; your checkout.authorized
//    webhook fires (or, legacy flow, the customer lands on return_url)...

// 3. Capture. Synchronous — when this returns, the money has moved.
//    `expectedTotalAmount` and `currency` are optional amount assertions.
CaptureCheckoutResponseContent captured = maytes.captureCheckout(uuid,
    new CaptureCheckoutRequestContent()
        .expectedTotalAmount(4500)
        .currency("AUD"));
System.out.println(captured.getData().getStatus()); // "captured" — fulfil now

// 4. getCheckout reads the current state whenever you need it later.
GetCheckoutResponseContent status = maytes.getCheckout(uuid);
ruby
require 'maytes_api_client'

# Construct the managed client once at startup. The SDK fetches and caches
# the OAuth token automatically and refreshes it on 401.
maytes = MaytesApiClient.create_maytes_api_client(
  endpoint: 'https://api.maytes.co',
  client_id: ENV['MAYTES_CLIENT_ID'],
  client_secret: ENV['MAYTES_CLIENT_SECRET'],
)

# 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
#    Persist `checkout_uuid` against your own `merchant_order_id`.
created = maytes.create_checkout(
  MaytesApiClient::CreateCheckoutRequestContent.new(
    merchant_order_id: 'order_1001',
    total_amount: 4500,                # minor units — 4500 = AUD 45.00
    currency: 'AUD',
    return_url: 'https://shop.example.com/thanks?order=order_1001',
    cancel_url: 'https://shop.example.com/cart',
    items: [MaytesApiClient::LineItem.new(
      item_ref: 'GA-1', name: 'General Admission',
      quantity: 1, unit_price: 4500, currency: 'AUD', category: 'other',
    )],
  ),
)
uuid = created.data.checkout_uuid

# 2. ...customer pays in the Maytes window; your checkout.authorized
#    webhook fires (or, legacy flow, the customer lands on return_url)...

# 3. Capture. Synchronous — when this returns, the money has moved.
#    All capture fields are optional in the Smithy model, so the Ruby
#    generator passes the body as a keyword arg.
captured = maytes.capture_checkout(uuid,
  capture_checkout_request_content: MaytesApiClient::CaptureCheckoutRequestContent.new(
    expected_total_amount: 4500, currency: 'AUD',
  ),
)
puts captured.data.status # "captured" — fulfil now

# 4. get_checkout reads the current state whenever you need it later.
status = maytes.get_checkout(uuid)
csharp
using MaytesApiClient.Auth;
using MaytesApiClient.Model;

// Construct the managed client once at startup. The SDK fetches and caches
// the OAuth token automatically and refreshes it on 401.
var maytes = ClientFactory.Create(
    "https://api.maytes.co",
    Environment.GetEnvironmentVariable("MAYTES_CLIENT_ID"),
    Environment.GetEnvironmentVariable("MAYTES_CLIENT_SECRET"));

// 1. Create the checkout — call this when the customer clicks "Pay with Maytes".
//    Persist `checkout_uuid` against your own `merchant_order_id`.
var created = await maytes.CreateCheckoutAsync(new CreateCheckoutRequestContent(
    totalAmount: 4500,                 // minor units — 4500 = AUD 45.00
    currency: "AUD",
    returnUrl: "https://shop.example.com/thanks?order=order_1001",
    cancelUrl: "https://shop.example.com/cart",
    items: new List<LineItem>
    {
        new LineItem(itemRef: "GA-1", name: "General Admission",
                     quantity: 1, unitPrice: 4500, currency: "AUD", category: "other"),
    })
{
    MerchantOrderId = "order_1001",
});
var uuid = created.Data.CheckoutUuid;

// 2. ...customer pays in the Maytes window; your checkout.authorized
//    webhook fires (or, legacy flow, the customer lands on return_url)...

// 3. Capture. Synchronous — when this returns, the money has moved.
//    `ExpectedTotalAmount` and `Currency` are optional amount assertions.
var captured = await maytes.CaptureCheckoutAsync(uuid, new CaptureCheckoutRequestContent
{
    ExpectedTotalAmount = 4500,
    Currency = "AUD",
});
Console.WriteLine(captured.Data.Status); // "captured" — fulfil now

// 4. GetCheckout reads the current state whenever you need it later.
var status = await maytes.GetCheckoutAsync(uuid);

Authentication, the SDK way

You never call the token endpoint. The SDK mints a token on the first call, caches it, and on a 401 refreshes once and retries the request transparently. See Authentication for the underlying flow.

Next steps