Payments
Step-by-step guide to create a payment request, collect consent, and confirm settlement.
Goal: Collect a one-time bank payment from a payer and confirm when funds are settled.
Estimated time: 20 minutes
Prerequisites
- Sandbox credentials (
appId,apiKey,tenant) - A registered creditor bank account
- TypeScript SDK,
npm install @spare-technologies/spare-api
SDK availability
The TypeScript snippets use the published @spare-technologies/spare-api package (v1.0.0, UAE). The Python, Java, C#, and Go SDKs are still under review; for those, run this flow over the REST API and the in-browser Try-it until they ship.
Authenticate
Create a client with automatic token management:
curl -X POST https://api.sandbox.tryspare.ae/auth/api-keys/sessions \
-H "x-tenant: UAE" \
-H "app-id: $SPARE_APP_ID" \
-H "x-api-key: $SPARE_API_KEY"import { Configuration, SpareApiClient } from "@spare-technologies/spare-api";
const client = new SpareApiClient({
authProvider: new Configuration({
appId: process.env.SPARE_APP_ID!,
apiKey: process.env.SPARE_API_KEY!,
tenant: "UAE",
environment: "sandbox",
}),
});import os
from spare_api import Configuration, SpareApiClient
client = SpareApiClient(
auth_provider=Configuration(
app_id=os.environ["SPARE_APP_ID"],
api_key=os.environ["SPARE_API_KEY"],
tenant="UAE",
environment="sandbox",
)
)Configuration config = Configuration.builder()
.appId(System.getenv("SPARE_APP_ID"))
.apiKey(System.getenv("SPARE_API_KEY"))
.tenant("UAE")
.environment("sandbox")
.build();
SpareApiClient client = SpareApiClient.fromConfiguration(config);var client = new SpareApiClient(new Configuration(new ConfigurationOptions
{
AppId = Environment.GetEnvironmentVariable("SPARE_APP_ID")!,
ApiKey = Environment.GetEnvironmentVariable("SPARE_API_KEY")!,
Tenant = "UAE",
Environment = "sandbox",
}));config, err := spareapi.NewConfiguration(spareapi.ConfigurationOptions{
AppID: os.Getenv("SPARE_APP_ID"),
APIKey: os.Getenv("SPARE_API_KEY"),
Tenant: "UAE",
Environment: "sandbox",
})
if err != nil {
log.Fatal(err)
}
client := spareapi.NewClientFromConfiguration(config)Create a payment request
Define amount, currency, and where the payer returns after bank authorization:
curl -X POST https://api.sandbox.tryspare.ae/payment-requests \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-H "Content-Type: application/json" \
-d '{
"type": "SingleInstantPayment",
"creditorType": "MERCHANT",
"creditorReference": "Order #9876",
"merchantReference": "order-9876",
"purpose": "GDDS",
"creditorAccount": {
"schemeName": "IBAN",
"identification": "AE070331234567890123456",
"name": "Acme Trading LLC"
},
"instructions": {
"amount": { "amount": "250.00", "currency": "AED" }
},
"successRedirectUrl": "https://yourapp.com/checkout/complete"
}'const request = await client.paymentRequests.create({
amount: 250_00,
currency: "AED",
description: "Order #9876",
redirectUrl: "https://yourapp.com/checkout/complete",
});request = client.payment_requests.create(
amount=250_00,
currency="AED",
description="Order #9876",
redirect_url="https://yourapp.com/checkout/complete",
)var request = client.paymentRequests().create(CreatePaymentRequestRequest.builder()
.amount(250_00)
.currency("AED")
.description("Order #9876")
.redirectUrl("https://yourapp.com/checkout/complete")
.build());var request = await client.PaymentRequests.CreateAsync(new CreatePaymentRequestRequest
{
Amount = 250_00,
Currency = "AED",
Description = "Order #9876",
RedirectUrl = "https://yourapp.com/checkout/complete",
});request, err := client.PaymentRequests.Create(ctx, &spareapi.CreatePaymentRequestRequest{
Amount: 250_00,
Currency: "AED",
Description: "Order #9876",
RedirectURL: "https://yourapp.com/checkout/complete",
})
if err != nil {
log.Fatal(err)
}Create consent and redirect the payer
List providers, create consent from the payment request, then redirect the browser:
const providers = await client.providers.list();
const consent = await client.paymentConsents.createFromPaymentRequest({
paymentRequestId: request.data.id,
provider: {
providerId: providers.data[0].id,
providerCode: providers.data[0].code,
},
});
// Redirect: consent.data.authorizationUrlproviders = client.providers.list()
consent = client.payment_consents.create_from_payment_request(
payment_request_id=request.data.id,
provider_id=providers.data[0].id,
provider_code=providers.data[0].code,
)
# Redirect: consent.data.authorization_urlvar providers = client.providers().list();
var consent = client.paymentConsents().createFromPaymentRequest(CreateFromPaymentRequestRequest.builder()
.paymentRequestId(request.getData().getId())
.provider(Provider.builder()
.providerId(providers.getData().get(0).getId())
.providerCode(providers.getData().get(0).getCode())
.build())
.build());
// Redirect: consent.getData().getAuthorizationUrl()var providers = await client.Providers.ListAsync();
var consent = await client.PaymentConsents.CreateFromPaymentRequestAsync(new CreateFromPaymentRequestRequest
{
PaymentRequestId = request.Data.Id,
Provider = new Provider
{
ProviderId = providers.Data[0].Id,
ProviderCode = providers.Data[0].Code,
},
});
// Redirect: consent.Data.AuthorizationUrlproviders, err := client.Providers.List(ctx)
if err != nil {
log.Fatal(err)
}
consent, err := client.PaymentConsents.CreateFromPaymentRequest(ctx, &spareapi.CreateFromPaymentRequestRequest{
PaymentRequestId: request.Data.Id,
Provider: &spareapi.Provider{
ProviderId: providers.Data[0].Id,
ProviderCode: providers.Data[0].Code,
},
})
if err != nil {
log.Fatal(err)
}
// Redirect: consent.Data.AuthorizationUrlHandle the callback
When the payer returns to your redirectUrl, poll consent status or wait for a webhook:
curl https://api.sandbox.tryspare.ae/consent/payment/CONSENT_ID \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-tenant: UAE"const status = await client.paymentConsents.get({
consentId: consent.data.id,
});
if (status.data.status === "Authorised") {
// Fulfill the order; settlement follows asynchronously
}status = client.payment_consents.get(consent_id=consent.data.id)
if status.data.status == "Authorised":
# Fulfill the order; settlement follows asynchronously
passvar status = client.paymentConsents().get(consent.getData().getId());
if (status.getData().getStatus().equals("Authorised")) {
// Fulfill the order; settlement follows asynchronously
}var status = await client.PaymentConsents.GetAsync(consent.Data.Id);
if (status.Data.Status == "Authorised")
{
// Fulfill the order; settlement follows asynchronously
}status, err := client.PaymentConsents.Get(ctx, consent.Data.Id)
if err != nil {
log.Fatal(err)
}
if status.Data.Status == "Authorised" {
// Fulfill the order; settlement follows asynchronously
}Confirm settlement
List payments linked to the payment request:
curl "https://api.sandbox.tryspare.ae/payments?externalReference=order-9876" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-tenant: UAE"const payments = await client.payments.list({
paymentRequestId: request.data.id,
});payments = client.payments.list(payment_request_id=request.data.id)var payments = client.payments().list(ListPaymentsRequest.builder()
.paymentRequestId(request.getData().getId())
.build());var payments = await client.Payments.ListAsync(new ListPaymentsRequest
{
PaymentRequestId = request.Data.Id,
});payments, err := client.Payments.List(ctx, &spareapi.ListPaymentsRequest{
PaymentRequestId: request.Data.Id,
})
if err != nil {
log.Fatal(err)
}Key takeaways
- A payment request is your intent; a consent is payer authorization; a payment is the settled record.
- Always redirect payers to
authorizationUrl, never skip bank authorization in production. - Use webhooks in production instead of polling consent status indefinitely.