Selected work in payments security, compliance and platform architecture.
This page covers three products I’ve built, each as a short case study: the problem, the solution, how it works, and its impact.
Introduce a SCA solution for frictionless checkout for a major Nordic retail chain.
3-D Secure is mandatory for card payments within Europe, however for non-card payment methods, the authentication is not standardized and is done via multiple channels. This causes friction and abandonment during payment sessions.
I led the design and delivery of a FIDO2-based delegated authentication flow. The solution marries the transaction data, created by the merchant with the specific cryptographic proof, created on the customer’s device. The result is a SCA compatible cryptogram, which is time bound, transaction bound and can be validated by any stakeholder that takes part in the payment process.
The API handshake between the merchant app, the FIDO2 server, the e-wallet and the issuer:
sequenceDiagram
autonumber
actor C as Customer
participant MBE as Merchant server
participant MFE as Merchant App<br/>(device authenticator)
participant F as FIDO2 Server
participant PM as Payment Method Provider
rect rgba(128,128,128,0.08)
Note over C, F: Assume cryptographic keys were setup earlier during enrollment phase
C->>MFE: Select a payment method and click "Pay"
MFE->>MBE: Initiate payment
MBE->>F: POST /transactions (amount, currency, references)
F->>F: Validate the data, store it and create timebound session.
F->>MBE: Return transaction data along with session details
MBE->>MFE: Pass the session data.
MFE->>F: POST /sessions/{public id}/attestation
F->>F: Validate session
F->>F: Create unique cryptographic challange
F->>MFE: Return challange
MFE->>C: Render biometric challange
C->>MFE: Approve challange with FaceId or fingerprint
MFE->>F: POST /sessions/{public id}/result and pass encrypted challange
F->>F: Validate encrypted data
F->>F: Issue a one time JWT (oJWT), containing encrypted data
F->>MFE: Return oJWT and result code
MFE->>MBE: Pass the oJWT
MBE->>F: POST /transactions/{id}/token with oJWT
F->>F: Validate the oJWT, produce cryptogram
F->>MBE: Return cryptogram
MBE->>PM: Create transaction with payment data and cryptogram as proof.
PM->>PM: Process transaction
PM->>MBE: Return result
MBE->>MFE: Return result
MFE->>C: Show result and redirect customer to success page
end
Systems thinking: a decoupled, scalable architecture that lets the business change its own rules.
Payment routing, fraud detection and fee calculation logic is often hard-coded into monoliths. Changing even a simple business rule then costs developer time and a full deployment cycle.
I architected an isolated rule engine micro-service. It evaluates business logic dynamically against each incoming transaction payload. Rules are stored and versioned as code so the business has flexibility to do whatever it needs.
The structure follows the C4 model. The Apple Pay and SEPA connectors are example clients.
Level 1: System context. Who uses the rule engine, and which systems depend on it:
flowchart TB
OPS["<b>Business Operations</b><br/><small>[Person]</small><br/>Configures routing, fraud<br/>and fee rules"]
APPLE["<b>Apple Pay Connector</b><br/><small>[Software System]</small><br/>Processes Apple Pay<br/>tokenised card payments"]
SEPA["<b>SEPA Connector</b><br/><small>[Software System]</small><br/>Processes SEPA credit<br/>transfers and direct debits"]
ENGINE["<b>Rule Engine</b><br/><small>[Software System]</small><br/>Evaluates business rules against<br/>transaction payloads and<br/>returns decisions"]
APPLE -- "Requests routing, fraud<br/>and fee decisions<br/><small>[REST/JSON]</small>" --> ENGINE
SEPA -- "Requests scheme, limit<br/>and fee decisions<br/><small>[REST/JSON]</small>" --> ENGINE
OPS -- "Manages rules<br/><small>[HTTPS]</small>" --> ENGINE
classDef person fill:#08427b,stroke:#052e56,color:#fff
classDef system fill:#1168bd,stroke:#0b4884,color:#fff
classDef external fill:#999,stroke:#6b6b6b,color:#fff
class OPS person
class ENGINE system
class APPLE,SEPA external
Rule - A javascript code, which is executed in a secure environment on the server. The code and the server belong to the same entity and access to both is restricted.
Rule set - A collection of rules with sequence of execution.
Triggers - Defined via rule management API. Each trigger is created to reflect a specific processing step and service.
Contracts - The format of messages being exchanged for each trigger.
Run rules - A specific execution environment, which allows rule creators to test their code execution. Each rule must comply with specific requirement (execution time, memory consumption), otherwise it cannot be included in a rule-set.
Connectors - A list of pre-defined functions, which rule creators can invoke in their javascript code (such as external systems, caching or data storage). Some example connectors are “list”, “dictionary” or “call”.
Example: Making a SEPA payment on sepa-input trigger:
Request
POST /triggers/sepa-input/
{
"transactionId": "txn_8f2c1a",
"amount": { "value": 1250.00, "currency": "EUR" },
"reference": "Your_Order_12345",
"paymentMethod": {
"type": "sepadirectdebit",
"ownerName": "A. Klaassen",
"ibanNumber": "NL98ABNA0410108103"
},
"returnUrl": "https://your-company.com/checkout/success",
"merchantAccount": "YourMerchantAccount",
"channel": "ECOM"
}
Response
{
"transactionId": "txn_8f2c1a",
"amount": { "value": 1250.00, "currency": "EUR" },
"reference": "Your_Order_12345",
"paymentMethod": {
"type": "sepadirectdebit",
"ownerName": "A. Klaassen",
"ibanNumber": "NL98ABNA0410108103"
},
"returnUrl": "https://your-company.com/checkout/success",
"merchantAccount": "YourMerchantAccount",
"channel": "ECOM",
"evaluation": {
"ruleSet": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"ruleSetVersionStamp": "hiu597ag...",
"startProcessingAt": "17907785582312",
"finishProcessingAt": "17907785583711",
"isSuccess": true,
"isStopped": false
},
"actions": [
{
"op": "add",
"path": "riskScore",
"value": 75
},
{
"op": "add",
"path": "multipayment",
"value": true
},
{
"op": "add",
"path": "merchantAccounts",
"value": [{"accountNumber":"YourMerchantAccount", "merchantName":"myMerchantName"}, {"accountNumber":"Somenumber", "merchantName":"someName"}]
}
]
}
High-stakes compliance, risk reduction and enterprise data security.
Any system that stores, processes or transmits a raw Primary Account Number (PAN) and/or CVV falls within PCI-DSS scope. Handling raw card data puts merchants and payment providers into the highest and most expensive compliance tiers.
I designed a proxy that intercepts raw card data before it reaches the merchant’s backend. It produces a token, which is not bound by any acquirer/issuer and can be later used to create network tokens.
sequenceDiagram
autonumber
actor C as Customer
participant FE as PCI Proxy Web SDK
participant MFE as Merchant Website
participant MBE as Merchant server
participant PRX as PCI Proxy
participant FD as FirstData (example)
rect rgba(128,128,128,0.08)
C->>MFE: Select a payment method and click "Pay"
MFE->>FE: Initiate tokenisation component
FE->>C: Render credit card input forms (one for PAN, one for CVV)
C->>FE: Enter credit card details
FE->>PRX: Create two temporary tokens - 1 for PAN, 1 for CVV
PRX->>PRX: Split PAN into segments, produce public token, hash the segments
PRX->>PRX: Produce public token for CVV, hash
PRX->>FE: Return public tokens for PAN and CVV
FE->>MFE: Return public tokens
MFE->>MBE: Pass the public tokens
MBE->>PRX: POST /vault/pan with public token to exchange it for perm
PRX->>PRX: Invalidate public PAN token, create permanent
PRX->>MBE: Return permanent PAN token
MBE->>PRX: POST /vault/cvv with public token to exchange it for perm
PRX->>PRX: Invalidate public CVV token, create permanent
PRX->>MBE: Return permanent CVV token
MBE->>PRX: POST /authorisation/ (with perm tokens)
PRX->>PRX: Replace tokens with PAN and CVV
PRX->>FD: Forward authorization
FD->>PRX: Return result
PRX->>PRX: Remove the CVV token after authorisation is completed
PRX->>MBE: Return result
MBE->>MFE: Return result
MFE->>C: Show result
end