SDKs & code examples
The portal’s SDKs & Codegen tab has samples for 5 languages × 3 HTTP clients. They’re a useful starting point, but several won’t work as published (see 8.7). The examples below are complete, minimal clients that avoid those problems. Each one:
- reads every value from configuration (3.2)
- builds URLs by plain string joining, so
/sandbox/payment/v1is never dropped - sends the exact field names, including
charginginformation,chargeMetaDataandterminalID - reads the status from
status, falling back to the names the SDK samples use - keeps the three first-run details (amount
format, refund
tranType, merchant values) in one place
| Language | Client | Section |
|---|---|---|
| PHP / Laravel | Laravel Http facade |
8.1 |
| JavaScript | Node.js 18+ fetch |
8.2 |
| Python | requests |
8.3 |
| Java | Spring RestClient (Spring Boot 3.2+) |
8.4 |
| C# | .NET 8 HttpClient |
8.5 |
The full, verbatim portal samples for all 15 clients are in
reference/ECOCASH-EIP-SDK-REFERENCE.md.
8.1 PHP / Laravel
config/services.php:
'ecocash' => [
'base_url' => env('EIP_BASE_URL', 'https://developers.ecocash.co.zw/sandbox/payment/v1'),
'username' => env('EIP_USERNAME'),
'password' => env('EIP_PASSWORD'),
'merchant_code' => env('EIP_MERCHANT_CODE'),
'merchant_pin' => env('EIP_MERCHANT_PIN'),
'merchant_number' => env('EIP_MERCHANT_NUMBER'),
'terminal_id' => env('EIP_TERMINAL_ID'),
'merchant_name' => env('EIP_MERCHANT_NAME'),
'super_merchant_name' => env('EIP_SUPER_MERCHANT_NAME'),
'channel' => env('EIP_CHANNEL', 'WEB'),
'location' => env('EIP_LOCATION', 'Harare'),
'refund_tran_type' => env('EIP_REFUND_TRAN_TYPE', 'REF'),
],
app/Services/EcoCash.php:
<?php
namespace App\Services;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class EcoCash
{
private function http(): PendingRequest
{
return Http::baseUrl(rtrim(config('services.ecocash.base_url'), '/'))
->withBasicAuth(config('services.ecocash.username'), config('services.ecocash.password'))
->acceptJson()
->asJson()
->timeout(30);
}
/** Fields every charge and refund carries. */
private function merchant(): array
{
$c = config('services.ecocash');
return [
'merchantCode' => $c['merchant_code'],
'merchantPin' => $c['merchant_pin'],
'merchantNumber' => $c['merchant_number'],
'countryCode' => 'ZW',
'terminalID' => $c['terminal_id'],
'location' => $c['location'],
'superMerchantName' => $c['super_merchant_name'],
'merchantName' => $c['merchant_name'],
];
}
private function amount(string $amount, string $currency, string $description): array
{
return [
'charginginformation' => [
'amount' => number_format((float) $amount, 2, '.', ''), // "2.00" - see section 4.5
'currency' => $currency,
'description' => $description,
],
'chargeMetaData' => ['channel' => config('services.ecocash.channel')],
];
}
/** Start a charge. Persist $correlator BEFORE calling this. */
public function charge(string $msisdn, string $amount, string $currency, string $reference,
string $correlator, string $notifyUrl = ''): array
{
$payload = [
'clientCorrelator' => $correlator,
'notifyUrl' => $notifyUrl,
'referenceCode' => $reference,
'tranType' => 'MER',
'endUserId' => $msisdn,
'remarks' => $reference,
'transactionOperationStatus' => 'Charged',
'paymentAmount' => $this->amount($amount, $currency, $reference),
] + $this->merchant();
return $this->http()->post('/transactions/amount/', $payload)->throw()->json();
}
public function lookup(string $msisdn, string $correlator): array
{
return $this->http()
->get('/'.rawurlencode($msisdn).'/transactions/amount/'.rawurlencode($correlator))
->throw()->json();
}
public function refund(string $msisdn, string $originalReference, string $amount,
string $currency, string $reference): array
{
$payload = [
'clientCorrelator' => (string) Str::uuid(), // a NEW correlator
'referenceCode' => $reference,
'tranType' => config('services.ecocash.refund_tran_type'),
'endUserId' => $msisdn,
'originalEcocashReference' => $originalReference,
'remarks' => $reference,
'paymentAmount' => $this->amount($amount, $currency, 'Refund'),
] + $this->merchant();
return $this->http()->post('/transactions/refund/', $payload)->throw()->json();
}
/** The API documents `status`; the portal's SDK samples read two other names. */
public static function status(array $body): ?string
{
return $body['status'] ?? $body['transactionStatus'] ?? $body['transactionOperationStatus'] ?? null;
}
/** The reference a refund needs as originalEcocashReference. */
public static function reference(array $body): ?string
{
return $body['ecocashReference'] ?? $body['transactionId'] ?? null;
}
}
Usage:
$eip = app(\App\Services\EcoCash::class);
$correlator = (string) \Illuminate\Support\Str::uuid();
// save $correlator on the order first, then:
$res = $eip->charge('263771234567', '10.00', 'USD', 'ORDER-1001', $correlator,
route('ecocash.callback'));
$status = \App\Services\EcoCash::status($eip->lookup('263771234567', $correlator)); // PENDING → SUCCESS
->throw() turns 4xx/5xx into an Illuminate\Http\Client\RequestException. Its
$e->response->json() holds statusCode and statusMessage.
8.2 Node.js
Node 18+ (built-in fetch), ES modules. ecocash.js:
import { randomUUID } from 'node:crypto';
const cfg = {
baseUrl: (process.env.EIP_BASE_URL ?? 'https://developers.ecocash.co.zw/sandbox/payment/v1').replace(/\/+$/, ''),
auth: 'Basic ' + Buffer.from(`${process.env.EIP_USERNAME}:${process.env.EIP_PASSWORD}`).toString('base64'),
refundTranType: process.env.EIP_REFUND_TRAN_TYPE ?? 'REF',
};
const merchant = () => ({
merchantCode: process.env.EIP_MERCHANT_CODE,
merchantPin: process.env.EIP_MERCHANT_PIN,
merchantNumber: process.env.EIP_MERCHANT_NUMBER,
countryCode: 'ZW',
terminalID: process.env.EIP_TERMINAL_ID,
location: process.env.EIP_LOCATION ?? 'Harare',
superMerchantName: process.env.EIP_SUPER_MERCHANT_NAME,
merchantName: process.env.EIP_MERCHANT_NAME,
});
const paymentAmount = (amount, currency, description) => ({
charginginformation: { amount: Number(amount).toFixed(2), currency, description }, // "2.00" - section 4.5
chargeMetaData: { channel: process.env.EIP_CHANNEL ?? 'WEB' },
});
async function eip(path, init = {}) {
const res = await fetch(cfg.baseUrl + path, {
...init,
headers: { Authorization: cfg.auth, 'Content-Type': 'application/json', Accept: 'application/json' },
signal: AbortSignal.timeout(30_000),
});
const body = await res.json().catch(() => ({}));
if (!res.ok) {
const err = new Error(`EIP ${res.status}: ${body.statusMessage ?? 'request failed'}`);
err.status = res.status;
err.body = body;
throw err;
}
return body;
}
/** Start a charge. Persist `correlator` BEFORE calling this. */
export function charge({ msisdn, amount, currency = 'USD', reference, correlator, notifyUrl = '' }) {
return eip('/transactions/amount/', {
method: 'POST',
body: JSON.stringify({
clientCorrelator: correlator,
notifyUrl,
referenceCode: reference,
tranType: 'MER',
endUserId: msisdn,
remarks: reference,
transactionOperationStatus: 'Charged',
paymentAmount: paymentAmount(amount, currency, reference),
...merchant(),
}),
});
}
export function lookup(msisdn, correlator) {
return eip(`/${encodeURIComponent(msisdn)}/transactions/amount/${encodeURIComponent(correlator)}`);
}
export function refund({ msisdn, originalReference, amount, currency = 'USD', reference }) {
return eip('/transactions/refund/', {
method: 'POST',
body: JSON.stringify({
clientCorrelator: randomUUID(), // a NEW correlator
referenceCode: reference,
tranType: cfg.refundTranType,
endUserId: msisdn,
originalEcocashReference: originalReference,
remarks: reference,
paymentAmount: paymentAmount(amount, currency, 'Refund'),
...merchant(),
}),
});
}
/** `status` is documented; the SDK samples read two other names. */
export const statusOf = (b) => b.status ?? b.transactionStatus ?? b.transactionOperationStatus ?? null;
export const referenceOf = (b) => b.ecocashReference ?? b.transactionId ?? null;
/** Poll every 3 s until the status is final or the timeout passes. */
export async function waitForResult(msisdn, correlator, timeoutMs = 90_000) {
const until = Date.now() + timeoutMs;
while (Date.now() < until) {
const s = statusOf(await lookup(msisdn, correlator));
if (s === 'SUCCESS' || s === 'FAILED') return s;
await new Promise((r) => setTimeout(r, 3_000));
}
return 'UNKNOWN'; // not final: flag for review, never assume failure
}
Usage:
import { randomUUID } from 'node:crypto';
import { charge, waitForResult } from './ecocash.js';
const correlator = randomUUID(); // save it on the order first
await charge({ msisdn: '263771234567', amount: '10.00', reference: 'ORDER-1001', correlator });
console.log(await waitForResult('263771234567', correlator)); // SUCCESS | FAILED | UNKNOWN
Keep this on your server. Never call the EIP API from a browser or mobile app: the credentials and merchant PIN would be exposed.
8.3 Python
pip install requests, then ecocash.py:
import os
import time
import uuid
import requests
BASE_URL = os.environ.get(
"EIP_BASE_URL", "https://developers.ecocash.co.zw/sandbox/payment/v1"
).rstrip("/")
REFUND_TRAN_TYPE = os.environ.get("EIP_REFUND_TRAN_TYPE", "REF")
session = requests.Session()
session.auth = (os.environ["EIP_USERNAME"], os.environ["EIP_PASSWORD"])
session.headers.update({"Content-Type": "application/json", "Accept": "application/json"})
class EcoCashError(Exception):
def __init__(self, status, body):
super().__init__(f"EIP {status}: {body.get('statusMessage', 'request failed')}")
self.status, self.body = status, body
def _merchant():
return {
"merchantCode": os.environ["EIP_MERCHANT_CODE"],
"merchantPin": os.environ["EIP_MERCHANT_PIN"],
"merchantNumber": os.environ["EIP_MERCHANT_NUMBER"],
"countryCode": "ZW",
"terminalID": os.environ["EIP_TERMINAL_ID"],
"location": os.environ.get("EIP_LOCATION", "Harare"),
"superMerchantName": os.environ["EIP_SUPER_MERCHANT_NAME"],
"merchantName": os.environ["EIP_MERCHANT_NAME"],
}
def _amount(amount, currency, description):
return {
"charginginformation": {
"amount": f"{float(amount):.2f}", # "2.00" - see section 4.5
"currency": currency,
"description": description,
},
"chargeMetaData": {"channel": os.environ.get("EIP_CHANNEL", "WEB")},
}
def _call(method, path, payload=None):
resp = session.request(method, BASE_URL + path, json=payload, timeout=30)
try:
body = resp.json()
except ValueError:
body = {}
if not resp.ok:
raise EcoCashError(resp.status_code, body)
return body
def charge(msisdn, amount, reference, correlator, currency="USD", notify_url=""):
"""Start a charge. Persist `correlator` BEFORE calling this."""
return _call("POST", "/transactions/amount/", {
"clientCorrelator": correlator,
"notifyUrl": notify_url,
"referenceCode": reference,
"tranType": "MER",
"endUserId": msisdn,
"remarks": reference,
"transactionOperationStatus": "Charged",
"paymentAmount": _amount(amount, currency, reference),
**_merchant(),
})
def lookup(msisdn, correlator):
q = requests.utils.quote
return _call("GET", f"/{q(msisdn, safe='')}/transactions/amount/{q(correlator, safe='')}")
def refund(msisdn, original_reference, amount, reference, currency="USD"):
return _call("POST", "/transactions/refund/", {
"clientCorrelator": str(uuid.uuid4()), # a NEW correlator
"referenceCode": reference,
"tranType": REFUND_TRAN_TYPE,
"endUserId": msisdn,
"originalEcocashReference": original_reference,
"remarks": reference,
"paymentAmount": _amount(amount, currency, "Refund"),
**_merchant(),
})
def status_of(body):
"""`status` is documented; the portal's SDK samples read two other names."""
return body.get("status") or body.get("transactionStatus") or body.get("transactionOperationStatus")
def reference_of(body):
return body.get("ecocashReference") or body.get("transactionId")
def wait_for_result(msisdn, correlator, timeout=90, every=3):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
status = status_of(lookup(msisdn, correlator))
if status in ("SUCCESS", "FAILED"):
return status
time.sleep(every)
return "UNKNOWN" # not final: flag for review, never assume failure
Usage:
import uuid
import ecocash
correlator = str(uuid.uuid4()) # save it on the order first
ecocash.charge("263771234567", "10.00", "ORDER-1001", correlator)
print(ecocash.wait_for_result("263771234567", correlator))
8.4 Java / Spring Boot
Spring Boot 3.2+ (spring-boot-starter-web). application.yml:
eip:
base-url: ${EIP_BASE_URL:https://developers.ecocash.co.zw/sandbox/payment/v1}
username: ${EIP_USERNAME}
password: ${EIP_PASSWORD}
merchant-code: ${EIP_MERCHANT_CODE}
merchant-pin: ${EIP_MERCHANT_PIN}
merchant-number: ${EIP_MERCHANT_NUMBER}
terminal-id: ${EIP_TERMINAL_ID}
merchant-name: ${EIP_MERCHANT_NAME}
super-merchant-name: ${EIP_SUPER_MERCHANT_NAME}
channel: ${EIP_CHANNEL:WEB}
location: ${EIP_LOCATION:Harare}
refund-tran-type: ${EIP_REFUND_TRAN_TYPE:REF}
EcoCashClient.java. It uses Maps so the unusual field names are sent exactly as written:
package com.example.ecocash;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class EcoCashClient {
private static final ParameterizedTypeReference<Map<String, Object>> JSON =
new ParameterizedTypeReference<>() {};
private final RestClient http;
private final Map<String, Object> merchant = new LinkedHashMap<>();
private final String channel;
private final String refundTranType;
public EcoCashClient(
@Value("${eip.base-url}") String baseUrl,
@Value("${eip.username}") String username,
@Value("${eip.password}") String password,
@Value("${eip.merchant-code}") String merchantCode,
@Value("${eip.merchant-pin}") String merchantPin,
@Value("${eip.merchant-number}") String merchantNumber,
@Value("${eip.terminal-id}") String terminalId,
@Value("${eip.merchant-name}") String merchantName,
@Value("${eip.super-merchant-name}") String superMerchantName,
@Value("${eip.location}") String location,
@Value("${eip.channel}") String channel,
@Value("${eip.refund-tran-type}") String refundTranType) {
this.http = RestClient.builder()
.baseUrl(baseUrl.replaceAll("/+$", ""))
.defaultHeaders(h -> {
h.setBasicAuth(username, password);
h.setContentType(MediaType.APPLICATION_JSON);
h.setAccept(java.util.List.of(MediaType.APPLICATION_JSON));
})
.build();
merchant.put("merchantCode", merchantCode);
merchant.put("merchantPin", merchantPin);
merchant.put("merchantNumber", merchantNumber);
merchant.put("countryCode", "ZW");
merchant.put("terminalID", terminalId);
merchant.put("location", location);
merchant.put("superMerchantName", superMerchantName);
merchant.put("merchantName", merchantName);
this.channel = channel;
this.refundTranType = refundTranType;
}
private Map<String, Object> paymentAmount(BigDecimal amount, String currency, String description) {
Map<String, Object> info = new LinkedHashMap<>();
info.put("amount", amount.setScale(2, RoundingMode.HALF_UP).toPlainString()); // "2.00" - section 4.5
info.put("currency", currency);
info.put("description", description);
return Map.of("charginginformation", info, "chargeMetaData", Map.of("channel", channel));
}
/** Start a charge. Persist the correlator BEFORE calling this. */
public Map<String, Object> charge(String msisdn, BigDecimal amount, String currency,
String reference, String correlator, String notifyUrl) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("clientCorrelator", correlator);
body.put("notifyUrl", notifyUrl == null ? "" : notifyUrl);
body.put("referenceCode", reference);
body.put("tranType", "MER");
body.put("endUserId", msisdn);
body.put("remarks", reference);
body.put("transactionOperationStatus", "Charged");
body.put("paymentAmount", paymentAmount(amount, currency, reference));
body.putAll(merchant);
return http.post().uri("/transactions/amount/").body(body).retrieve().body(JSON);
}
public Map<String, Object> lookup(String msisdn, String correlator) {
return http.get()
.uri("/{endUserId}/transactions/amount/{clientCorrelator}", msisdn, correlator)
.retrieve().body(JSON);
}
public Map<String, Object> refund(String msisdn, String originalReference, BigDecimal amount,
String currency, String reference) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("clientCorrelator", UUID.randomUUID().toString()); // a NEW correlator
body.put("referenceCode", reference);
body.put("tranType", refundTranType);
body.put("endUserId", msisdn);
body.put("originalEcocashReference", originalReference);
body.put("remarks", reference);
body.put("paymentAmount", paymentAmount(amount, currency, "Refund"));
body.putAll(merchant);
return http.post().uri("/transactions/refund/").body(body).retrieve().body(JSON);
}
/** `status` is documented; the portal's SDK samples read two other names. */
public static String statusOf(Map<String, Object> b) {
for (String k : new String[] {"status", "transactionStatus", "transactionOperationStatus"}) {
if (b != null && b.get(k) != null) return b.get(k).toString();
}
return null;
}
public static String referenceOf(Map<String, Object> b) {
Object v = b.get("ecocashReference") != null ? b.get("ecocashReference") : b.get("transactionId");
return v == null ? null : v.toString();
}
}
RestClient throws HttpClientErrorException (4xx) or HttpServerErrorException (5xx).
getResponseBodyAsString() holds statusCode and statusMessage.
8.5 C# / .NET
.NET 8, HttpClient with no extra packages. Note the trailing slash on the base address
and no leading slash on paths: that’s what keeps /sandbox/payment/v1 in the URL.
using System.Globalization;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Nodes;
public sealed class EcoCashClient
{
private readonly HttpClient _http;
private readonly Dictionary<string, string> _cfg;
public EcoCashClient(HttpClient http, Dictionary<string, string> cfg)
{
_cfg = cfg;
_http = http;
_http.BaseAddress = new Uri(cfg["EIP_BASE_URL"].TrimEnd('/') + "/");
var token = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{cfg["EIP_USERNAME"]}:{cfg["EIP_PASSWORD"]}"));
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", token);
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_http.Timeout = TimeSpan.FromSeconds(30);
}
private JsonObject Body(string correlator, string tranType, string msisdn, decimal amount,
string currency, string reference, string description)
{
return new JsonObject
{
["clientCorrelator"] = correlator,
["referenceCode"] = reference,
["tranType"] = tranType,
["endUserId"] = msisdn,
["remarks"] = reference,
["paymentAmount"] = new JsonObject
{
["charginginformation"] = new JsonObject
{
["amount"] = amount.ToString("0.00", CultureInfo.InvariantCulture), // "2.00" - section 4.5
["currency"] = currency,
["description"] = description,
},
["chargeMetaData"] = new JsonObject { ["channel"] = _cfg["EIP_CHANNEL"] },
},
["merchantCode"] = _cfg["EIP_MERCHANT_CODE"],
["merchantPin"] = _cfg["EIP_MERCHANT_PIN"],
["merchantNumber"] = _cfg["EIP_MERCHANT_NUMBER"],
["countryCode"] = "ZW",
["terminalID"] = _cfg["EIP_TERMINAL_ID"],
["location"] = _cfg["EIP_LOCATION"],
["superMerchantName"] = _cfg["EIP_SUPER_MERCHANT_NAME"],
["merchantName"] = _cfg["EIP_MERCHANT_NAME"],
};
}
/// <summary>Start a charge. Persist the correlator BEFORE calling this.</summary>
public Task<JsonObject> ChargeAsync(string msisdn, decimal amount, string currency,
string reference, string correlator, string notifyUrl = "")
{
var body = Body(correlator, "MER", msisdn, amount, currency, reference, reference);
body["notifyUrl"] = notifyUrl;
body["transactionOperationStatus"] = "Charged";
return SendAsync(HttpMethod.Post, "transactions/amount/", body);
}
public Task<JsonObject> LookupAsync(string msisdn, string correlator) =>
SendAsync(HttpMethod.Get,
$"{Uri.EscapeDataString(msisdn)}/transactions/amount/{Uri.EscapeDataString(correlator)}", null);
public Task<JsonObject> RefundAsync(string msisdn, string originalReference, decimal amount,
string currency, string reference)
{
var body = Body(Guid.NewGuid().ToString(), _cfg["EIP_REFUND_TRAN_TYPE"], msisdn,
amount, currency, reference, "Refund"); // a NEW correlator
body["originalEcocashReference"] = originalReference;
return SendAsync(HttpMethod.Post, "transactions/refund/", body);
}
private async Task<JsonObject> SendAsync(HttpMethod method, string path, JsonObject? body)
{
using var req = new HttpRequestMessage(method, path);
if (body is not null) req.Content = JsonContent.Create(body);
using var res = await _http.SendAsync(req);
var text = await res.Content.ReadAsStringAsync();
var json = (string.IsNullOrWhiteSpace(text) ? null : JsonNode.Parse(text) as JsonObject) ?? new JsonObject();
if (!res.IsSuccessStatusCode)
throw new HttpRequestException(
$"EIP {(int)res.StatusCode}: {json["statusMessage"]}", null, res.StatusCode);
return json;
}
/// <summary>`status` is documented; the SDK samples read two other names.</summary>
public static string? StatusOf(JsonObject b) =>
(b["status"] ?? b["transactionStatus"] ?? b["transactionOperationStatus"])?.ToString();
public static string? ReferenceOf(JsonObject b) =>
(b["ecocashReference"] ?? b["transactionId"])?.ToString();
}
Usage (e.g. in Program.cs, with the EIP_* values from configuration):
var cfg = new[] { "EIP_BASE_URL", "EIP_USERNAME", "EIP_PASSWORD", "EIP_MERCHANT_CODE",
"EIP_MERCHANT_PIN", "EIP_MERCHANT_NUMBER", "EIP_TERMINAL_ID", "EIP_MERCHANT_NAME",
"EIP_SUPER_MERCHANT_NAME", "EIP_CHANNEL", "EIP_LOCATION", "EIP_REFUND_TRAN_TYPE" }
.ToDictionary(k => k, k => Environment.GetEnvironmentVariable(k) ?? "");
var eip = new EcoCashClient(new HttpClient(), cfg);
var correlator = Guid.NewGuid().ToString(); // save it on the order first
await eip.ChargeAsync("263771234567", 10.00m, "USD", "ORDER-1001", correlator);
var status = EcoCashClient.StatusOf(await eip.LookupAsync("263771234567", correlator));
8.6 Polling pattern
Prefer callbacks (notifyUrl) in production. Poll when you have no callback, or to confirm
one:
- Wait about 3 seconds after the charge.
- Look it up every 3 seconds. Sandbox transactions usually resolve in 12–30 seconds.
- Stop at
SUCCESSorFAILED. - After about 90 seconds, stop polling and mark the payment unknown, not failed. Re-check it later in a background job before telling the customer anything final.
8.7 Pitfalls in the portal’s SDK samples
The portal’s SDKs & Codegen tab has 15 clients. If you copy one, fix these first:
| Pitfall | Affects | Fix |
|---|---|---|
The status field is read under two different names: transactionStatus (Java, PHP, JavaScript, C#) and transactionOperationStatus (Python). The API documents status. |
All 15 | Read status first and fall back to the others, as the examples above do |
The base path is dropped. A base URL with a path plus a request path starting with / loses /sandbox/payment/v1 in some clients. |
PHP Guzzle, PHP Symfony HttpClient, C# HttpClient, Python aiohttp (high risk); C# RestSharp (verify) |
Give the base URL a trailing / and drop the leading / from paths, or join strings yourself |
Refunds send tranType: "MER" plus an undocumented currencyCode |
All 15 refund samples | See 4.5 |
amount is a string ("5.00") |
All 15 | Fine. Keep it consistent (4.5). |
Hard-coded "REF-001" correlator |
Most samples | Generate a new unique value per attempt (only Java Feign and C# Refit do) |
C# HttpClient uses await using on a class that is only IDisposable |
C# HttpClient | Use using var, or implement IAsyncDisposable. It won’t compile as published. |
Refit auth: username/password are undeclared, using System;/using System.Text; are missing, and Refit sends Bearer by default |
C# Refit | Declare them, add the usings, and put [Headers("Authorization: Basic")] on the interface |
DTO types are never defined (PaymentRequest, RefundRequest, PaymentAmount, ChargingInfo, ChargeMetaData, TransactionResponse) |
Java, C# Refit/RestSharp | Write them yourself, with explicit JSON names (charginginformation, terminalID, chargeMetaData) |
btoa for Basic auth |
JS Axios, Fetch | Use Buffer.from(...).toString('base64') in Node.js |
Got’s prefixUrl throws if a path starts with / |
JS Got | Keep paths without a leading /, as the sample does |
This page is generated from section 8 of the README in README.md. Spotted something wrong? Open an issue.