Example implementation
When building a custom API integration with iwocaPay, your customer will follow a straightforward flow:
- Redirect the customer to iwocaPay.
- Reconcile the order with your back end.
You’ll only need to implement two or three key functions to handle these steps. For reconciliation, you can either:
- Use webhooks to receive updates as they happen (recommended), or:
- Poll for updates on the order (not-recommended).
Below are example integrations in Python, PHP, and TypeScript. They are meant as a guide to help you get started; they are not intended to be used without adaptation and extension in a production environment. In particular, consider:
- Integration with your existing database or back-end systems
- Authentication or user-specific business logic
- Error handling and logging mechanisms
- Deployment in a secure and scalable environment
Customer flow
This is an example customer flow between your checkout and the iwocaPay checkout.
Directorysrc
Directoryconfig
- env.py
Directoryservices
- iwoca_pay.py
Directorywebhooks
- order_webhook_handler.py
Directorymodels
- iwoca_pay.py
Directoryutils
- http_client.py
- main.py
- .env
IWOCAPAY_SUPPLIER_ACCESS_TOKEN=access_tokenIWOCAPAY_SUPPLIER_UUID=uuidIWOCAPAY_API_TOKEN=api_tokenIWOCAPAY_API_URL=https://stage.iwoca-dev.co.uk/api/lending/edge/
from pydantic import BaseSettings
class Settings(BaseSettings): IWOCAPAY_API_URL: str IWOCAPAY_SUPPLIER_ACCESS_TOKEN: str IWOCAPAY_SUPPLIER_UUID: str
class Config: env_file = ".env"
settings = Settings()
if not (settings.IWOCAPAY_API_URL and settings.IWOCAPAY_SUPPLIER_ACCESS_TOKEN and settings.IWOCAPAY_SUPPLIER_UUID): raise ValueError("Environment variables are not properly configured.")
from src.utils.http_client import http_clientfrom src.models.iwoca_pay import CreateOrderRequest, CreateOrderResponse, GetOrderStatusResponse
class IwocaPayService: def __init__(self, supplier_uuid: str): self.supplier_uuid = supplier_uuid
def create_order(self, request: CreateOrderRequest) -> CreateOrderResponse: endpoint = f"/ecommerce/seller/{self.supplier_uuid}/order/" response = http_client.post(endpoint, {"data": request.dict()}) return CreateOrderResponse(**response)
def get_order_status(self, order_id: str) -> GetOrderStatusResponse: endpoint = f"/ecommerce/order/{order_id}/" response = http_client.get(endpoint) return GetOrderStatusResponse(**response)
from fastapi import APIRouter, Requestfrom src.models.iwoca_pay import WebhookPayload
router = APIRouter()
@router.post("/webhook")async def handle_order_webhook(request: Request): payload = WebhookPayload(**(await request.json()))
# Process the webhook data (e.g., update order status in the database) print("Received webhook:", payload)
# Example: Handle specific statuses if payload.data["status"] == "CREATED": print(f"Order {payload.data['order_id']} has been created.") else: print(f"Unhandled order status: {payload.data['status']}")
return {"message": "Webhook processed"}
from pydantic import BaseModel, Fieldfrom typing import List, Optional
class CreateOrderRequest(BaseModel): amount: float = Field(..., description="The total amount for the order.") reference: str = Field(..., description="A unique reference for the order.") allowed_payment_terms: Optional[List[str]] = Field( None, description="Optional list of allowed payment terms, e.g., ['PAY_LATER']" )
class PricingDetails(BaseModel): representative_interest: float = Field(..., description="Representative interest rate.") promotions: List[dict] = Field(..., description="List of promotions, if any.")
class CreateOrderResponseData(BaseModel): id: str = Field(..., description="Unique identifier for the order.") amount: float = Field(..., description="The total amount for the order.") reference: str = Field(..., description="A unique reference for the order.") order_url: str = Field(..., description="The URL for the order checkout.") status: str = Field(..., description="The current status of the order.") allowed_payment_terms: List[str] = Field( ..., description="List of allowed payment terms returned by iwocaPay." )
class CreateOrderResponse(BaseModel): data: CreateOrderResponseData
class GetOrderStatusResponseData(BaseModel): id: str = Field(..., description="Unique identifier for the order.") amount: float = Field(..., description="The total amount for the order.") reference: str = Field(..., description="A unique reference for the order.") redirect_url: str = Field(..., description="URL to redirect the user to after checkout.") pricing: PricingDetails = Field(..., description="Details about pricing and promotions.") pay_link_id: str = Field(..., description="Unique ID for the payment link.") seller_name: str = Field(..., description="Name of the seller.") status: str = Field(..., description="The current status of the order.") allowed_payment_terms: List[str] = Field( ..., description="List of allowed payment terms for the order." )
class GetOrderStatusResponse(BaseModel): data: GetOrderStatusResponseData
class WebhookPayloadData(BaseModel): order_id: int = Field(..., description="Unique identifier for the order.") pay_link_id: str = Field(..., description="Unique ID for the payment link.") amount: float = Field(..., description="The total amount for the order.") reference: str = Field(..., description="A unique reference for the order.") status: str = Field(..., description="The current status of the order.")
class WebhookPayload(BaseModel): data: WebhookPayloadData
import requestsfrom src.config.env import settings
class HttpClient: def __init__(self, base_url: str, token: str): self.base_url = base_url self.headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json", }
def post(self, endpoint: str, data: dict) -> dict: response = requests.post(f"{self.base_url}{endpoint}", json=data, headers=self.headers) response.raise_for_status() return response.json()
def get(self, endpoint: str) -> dict: response = requests.get(f"{self.base_url}{endpoint}", headers=self.headers) response.raise_for_status() return response.json()
http_client = HttpClient(settings.IWOCAPAY_API_URL, settings.IWOCAPAY_SUPPLIER_ACCESS_TOKEN)
from fastapi import FastAPIfrom src.services.iwoca_pay import IwocaPayServicefrom src.models.iwoca_pay import CreateOrderRequestfrom src.webhooks.order_webhook_handler import router as webhook_routerfrom src.config.env import settings
app = FastAPI()iwoca_pay_service = IwocaPayService(supplier_uuid=settings.IWOCAPAY_SUPPLIER_UUID)
# Include webhook routesapp.include_router(webhook_router)
@app.post("/create-order")async def create_order(request: CreateOrderRequest): try: response = iwoca_pay_service.create_order(request) return response except Exception as e: return {"error": str(e)}
@app.get("/order-status/{order_id}")async def get_order_status(order_id: str): try: response = iwoca_pay_service.get_order_status(order_id) return response except Exception as e: return {"error": str(e)}
Directorysrc
Directoryconfig
- env.ts
Directoryservices
- iwocaPay.ts
Directorywebhooks
- orderWebhookHandler.ts
Directorytypes
- iwocaPay.d.ts
Directoryutils
- httpClient.ts
- index.ts
- .env
IWOCAPAY_SUPPLIER_ACCESS_TOKEN=access_tokenIWOCAPAY_SUPPLIER_UUID=uuidIWOCAPAY_API_TOKEN=api_tokenIWOCAPAY_API_URL=https://stage.iwoca-dev.co.uk/api/lending/edge/
import * as dotenv from "dotenv";
dotenv.config();
export const IWOCAPAY_CONFIG = { API_URL: process.env.IWOCAPAY_API_URL!, SUPPLIER_ACCESS_TOKEN: process.env.IWOCAPAY_SUPPLIER_ACCESS_TOKEN!, SUPPLIER_UUID: process.env.IWOCAPAY_SUPPLIER_UUID!,};
if (!IWOCAPAY_CONFIG.API_URL || !IWOCAPAY_CONFIG.SUPPLIER_ACCESS_TOKEN || !IWOCAPAY_CONFIG.SUPPLIER_UUID) { throw new Error("Environment variables are missing.");}
import { httpClient } from "../utils/httpClient.ts";import { CreateOrderRequest, CreateOrderResponse, GetOrderStatusResponse } from "../types/iwocapay";
export class IwocaPayService { private supplierUuid = process.env.IWOCAPAY_SUPPLIER_UUID!;
async createOrder(request: CreateOrderRequest): Promise<CreateOrderResponse> { const response = await httpClient.post<CreateOrderResponse>( `/ecommerce/seller/${this.supplierUuid}/order/`, { data: request } ); return response.data; }
async getOrderStatus(orderId: string): Promise<GetOrderStatusResponse> { const response = await httpClient.get<GetOrderStatusResponse>(`/ecommerce/order/${orderId}/`); return response.data; }}
type AllowedPaymentTerms = "PAY_NOW" | "PAY_LATER";
export interface CreateOrderRequest { amount: number; reference: string; allowed_payment_terms?: AllowedPaymentTerms[];}
export interface CreateOrderResponse { data: { id: string; amount: number; reference: string; order_url: string; status: string; allowed_payment_terms: AllowedPaymentTerms[]; };}
export interface GetOrderStatusResponse { data: { id: string; amount: number; reference: string; redirect_url: string; pricing: { representative_interest: number; promotions: any[]; }; pay_link_id: string; seller_name: string; status: string; allowed_payment_terms: AllowedPaymentTerms[]; };}
export interface WebhookPayload { data: { order_id: number; pay_link_id: string; amount: number; reference: string; status: string; };}
import axios, { AxiosInstance } from "axios";import { IWOCAPAY_CONFIG } from "../config/env.ts";
export const httpClient: AxiosInstance = axios.create({ baseURL: IWOCAPAY_CONFIG.API_URL, headers: { Authorization: `Bearer ${IWOCAPAY_CONFIG.SUPPLIER_ACCESS_TOKEN}`, "Content-Type": "application/json", },});
import { Request, Response } from "express";import { WebhookPayload } from "../types/iwocapay";
export const handleOrderWebhook = async (req: Request, res: Response): Promise<void> => { const payload: WebhookPayload = req.body;
// Process the webhook data (e.g., update order status in the database) console.log("Received webhook:", payload);
// Example: Handle specific statuses switch (payload.data.status) { case "CREATED": console.log(`Order ${payload.data.order_id} has been created.`); break; // Handle other statuses as needed default: console.log(`Unhandled order status: ${payload.data.status}`); }
res.status(200).send("Webhook processed");};
import express from "express";import bodyParser from "body-parser";import { IwocaPayService } from "./services/iwocaPay";import { handleOrderWebhook } from "./webhooks/orderWebhookHandler";
const app = express();const iwocaPayService = new IwocaPayService();
app.use(bodyParser.json());
// Route for creating an orderapp.post("/create-order", async (req, res) => { try { const { amount, reference, allowed_payment_terms } = req.body; const response = await iwocaPayService.createOrder({ amount, reference, allowed_payment_terms }); res.json(response); } catch (error) { console.error("Error creating order:", error); res.status(500).send("Error creating order"); }});
// Route for polling order statusapp.get("/order-status/:orderId", async (req, res) => { try { const { orderId } = req.params; const response = await iwocaPayService.getOrderStatus(orderId); res.json(response); } catch (error) { console.error("Error getting order status:", error); res.status(500).send("Error getting order status"); }});
// Webhook routeapp.post("/webhook", handleOrderWebhook);
// Start the serverconst PORT = process.env.PORT || 3000;app.listen(PORT, () => { console.log(`Server running on port ${PORT}`);});
Directorysrc
DirectoryConfig
- Env.php
DirectoryModels
- CreateOrderRequest.php
- CreateOrderResponse.php
- GetOrderStatusResponse.php
- WebhookPayload.php
DirectoryServices
- IwocaPayService.php
DirectoryControllers
- WebhookController.php
DirectoryUtils
- HttpClient.php
Directorypublic
- index.php
- .env
- composer.json
IWOCAPAY_SUPPLIER_ACCESS_TOKEN=access_tokenIWOCAPAY_SUPPLIER_UUID=uuidIWOCAPAY_API_TOKEN=api_tokenIWOCAPAY_API_URL=https://stage.iwoca-dev.co.uk/api/lending/edge/
<?php
namespace App\Config;
use Dotenv\Dotenv;
class Env{ private static ?Env $instance = null; private array $env;
private function __construct() { $dotenv = Dotenv::createImmutable(__DIR__ . '/../../'); $dotenv->load(); $this->env = $_ENV; }
public static function getInstance(): Env { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; }
public function get(string $key, $default = null) { return $this->env[$key] ?? $default; }}
<?php
namespace App\Controllers;
use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;
class WebhookController{ public function handle(Request $request): Response { $data = json_decode($request->getContent(), true);
if (!$data || !isset($data['data'])) { return new Response('Invalid payload', 400); }
// Process webhook payload $payload = $data['data']; // Example: Update database or trigger business logic file_put_contents('php://stdout', json_encode($payload) . "\n");
return new Response('Webhook processed', 200); }}
<?php
namespace App\Models;
class CreateOrderRequest{ public float $amount; public string $reference; public ?array $allowedPaymentTerms;
public function __construct(float $amount, string $reference, ?array $allowedPaymentTerms = null) { $this->amount = $amount; $this->reference = $reference; $this->allowedPaymentTerms = $allowedPaymentTerms; }
public function toArray(): array { return [ 'amount' => $this->amount, 'reference' => $this->reference, 'allowed_payment_terms' => $this->allowedPaymentTerms, ]; }}
<?php
namespace App\Models;
class CreateOrderRequest{ public float $amount; public string $reference; public ?array $allowedPaymentTerms;
public function __construct(float $amount, string $reference, ?array $allowedPaymentTerms = null) { $this->amount = $amount; $this->reference = $reference; $this->allowedPaymentTerms = $allowedPaymentTerms; }
public function toArray(): array { return [ 'amount' => $this->amount, 'reference' => $this->reference, 'allowed_payment_terms' => $this->allowedPaymentTerms, ]; }}
<?php
require __DIR__ . '/../vendor/autoload.php';
use App\Controllers\WebhookController;use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();$path = $request->getPathInfo();
if ($path === '/webhook' && $request->getMethod() === 'POST') { $controller = new WebhookController(); $response = $controller->handle($request); $response->send(); exit;}
// Default route for unhandled pathshttp_response_code(404);echo 'Not Found';
<?php
namespace App\Services;
use App\Models\CreateOrderRequest;use App\Models\CreateOrderResponse;use App\Utils\HttpClient;
class IwocaPayService{ private string $supplierUuid; private HttpClient $httpClient;
public function __construct(string $supplierUuid) { $this->supplierUuid = $supplierUuid; $this->httpClient = new HttpClient(); }
public function createOrder(CreateOrderRequest $request): CreateOrderResponse { $endpoint = "/ecommerce/seller/{$this->supplierUuid}/order/"; $response = $this->httpClient->post($endpoint, ['data' => $request->toArray()]); return new CreateOrderResponse($response['data']); }
public function getOrderStatus(string $orderId): array { $endpoint = "/ecommerce/order/{$orderId}/"; return $this->httpClient->get($endpoint); }}
<?php
namespace App\Utils;
use GuzzleHttp\Client;use GuzzleHttp\Exception\RequestException;use App\Config\Env;
class HttpClient{ private Client $client;
public function __construct() { $baseUrl = Env::getInstance()->get('IWOCAPAY_API_URL'); $token = Env::getInstance()->get('IWOCAPAY_SUPPLIER_ACCESS_TOKEN');
$this->client = new Client([ 'base_uri' => $baseUrl, 'headers' => [ 'Authorization' => "Bearer $token", 'Content-Type' => 'application/json', ], ]); }
public function post(string $endpoint, array $data): array { try { $response = $this->client->post($endpoint, ['json' => $data]); return json_decode($response->getBody()->getContents(), true); } catch (RequestException $e) { throw new \Exception($e->getMessage()); } }
public function get(string $endpoint): array { try { $response = $this->client->get($endpoint); return json_decode($response->getBody()->getContents(), true); } catch (RequestException $e) { throw new \Exception($e->getMessage()); } }}
{ "require": { "vlucas/phpdotenv": "^5.5", "guzzlehttp/guzzle": "^7.0", "symfony/http-foundation": "^6.0" }, "autoload": { "psr-4": { "App\\": "src/" } }}