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, use webhooks to receive updates as they happen.
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
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.") 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: WebhookPayloadDataimport 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: 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/" } }}