<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nanotech Computers</title>
    <link>https://www.nanotech-computers.com/blog/</link>
    <description>Crafting Seamless Code, Inspiring Limitless Solutions</description>
    <language>en-GB</language>
    <copyright>© 2026 Nanotech Computers (Pty) Ltd</copyright>
    <lastBuildDate>Sun, 12 Oct 2025 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://www.nanotech-computers.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Building an OpenAI Proxy for JetBrains</title>
      <link>https://www.nanotech-computers.com/blog/building-an-openai-proxy-for-jetbrains/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/building-an-openai-proxy-for-jetbrains/</guid>
      <pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[How I built a lightweight OpenAI-compatible proxy for Azure OpenAI, letting JetBrains IDEs connect without an Enterprise subscription.]]></description>
      <content:encoded><![CDATA[<p>I recently got back into heavier development work using JetBrains IDEs, and realised their AI integration had changed quite a bit since I last used it. You now run out of credits far quicker than before. That made me start looking for alternatives.</p>
<p>Since I already have OpenAI credits and access to some excellent high-context models, I wanted to connect JetBrains directly to my Azure OpenAI endpoint. I'd read this was supported.</p>
<p>That's when I discovered JetBrains only allows connecting a custom Azure OpenAI endpoint with an Enterprise subscription, due to the secret key requirement. I don't have Enterprise access (and definitely can't justify the cost right now), so I built an OpenAI-compatible proxy instead.</p>
<p>This proxy lets JetBrains, and likely other OpenAI-compatible clients, use Azure OpenAI without modification.</p>
<h3 id="project-overview"><a href="#project-overview">Project overview</a></h3>
<p>This project is a dotnet application that acts as an OpenAI-compatible proxy for Azure OpenAI Services. It is written in C# 13 and is fully containerised using Aspire with Docker Compose.</p>
<p><strong>Key features:</strong></p>
<ul>
<li>Exposes familiar endpoints such as <code>/v1/chat/completions</code> and <code>/v1/models</code> so existing OpenAI clients work without changes.</li>
<li>Translates OpenAI-style requests into the correct Azure API format automatically.</li>
<li>Uses Aspire for multi-service container deployments and health checks.</li>
<li>Instrumented with OpenTelemetry for metrics and distributed traces.</li>
<li>Includes a Scalar API Reference UI for interactive API documentation.</li>
</ul>
<p>The result is a gateway that provides compatibility, observability and Docker deployment. Teams can use Azure-hosted models like <code>gpt-5-chat</code> with OpenAI clients like JetBrains.</p>
<h3 id="api-code"><a href="#api-code">API code</a></h3>
<p>You can access the code for the proxy here: <a href="https://github.com/NanotechComputers/openai-proxy">OpenAI Proxy</a>.</p>
<p>Simply configure the Azure section in your <code>appsettings.json</code> file with your endpoint details and API key, then run the application.</p>
<h3 id="configuration"><a href="#configuration">Configuration</a></h3>
<p>Add an <code>Azure</code> section to your <code>appsettings.json</code> (or use environment variables) with the following keys:</p>
<pre class="shiki one-dark-pro" style="background-color:#282c34;color:#abb2bf" tabindex="0"><code><span class="line"><span style="color:#ABB2BF">{</span></span>
<span class="line"><span style="color:#E06C75">  "Azure"</span><span style="color:#ABB2BF">: {</span></span>
<span class="line"><span style="color:#E06C75">    "Base"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"https://{your-azure-resource}.openai.azure.com"</span><span style="color:#ABB2BF">,</span></span>
<span class="line"><span style="color:#E06C75">    "Deployment"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"gpt-5-chat"</span><span style="color:#ABB2BF">,</span></span>
<span class="line"><span style="color:#E06C75">    "ApiVersion"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"2025-01-01-preview"</span><span style="color:#ABB2BF">,</span></span>
<span class="line"><span style="color:#E06C75">    "Key"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"&#x3C;your-azure-api-key>"</span></span>
<span class="line"><span style="color:#ABB2BF">  }</span></span>
<span class="line"><span style="color:#ABB2BF">}</span></span></code></pre>
<p>If you prefer to supply a full URL instead of base and deployment, set <code>EndpointFull</code> to a complete Azure URL and the proxy will use that directly.</p>
<h3 id="deployment-notes"><a href="#deployment-notes">Deployment notes</a></h3>
<ul>
<li>The proxy is designed to run in Docker. Use your existing <code>Aspire</code> setup to publish docker-compose artefacts, then run <code>docker compose</code> locally or in your environment.</li>
<li>Ensure the Azure key is provided via a secure secret mechanism in production (environment variable, secret store or Docker secret).</li>
<li>OpenTelemetry and Scalar integration require additional configuration. See their respective docs for collectors and UI wiring.</li>
</ul>
<h3 id="closing-thoughts"><a href="#closing-thoughts">Closing thoughts</a></h3>
<p>This proxy is a small, pragmatic workaround that saved me from having to buy an Enterprise subscription just to point JetBrains at my Azure OpenAI endpoint. It is designed to be compatible, observable and simple to run in containers.</p>
<p>I designed the project to run <strong>locally in a secure environment</strong>, not to be exposed to the public internet. Running it publicly could lead to <strong>unintended or unauthorised resource usage</strong>. I accept no responsibility for this.</p>]]></content:encoded>
      <category>dotnet</category>
      <category>openai</category>
      <category>jetbrains</category>
      <category>open-source</category>
      <category>aspire</category>
    </item>
    <item>
      <title>Building Word Challenge: From Concept to the App Store</title>
      <link>https://www.nanotech-computers.com/blog/building-word-challenge-concept-to-app-store/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/building-word-challenge-concept-to-app-store/</guid>
      <pubDate>Fri, 10 Jan 2025 00:00:00 GMT</pubDate>
      <description><![CDATA[How Word Challenge grew from a complaint about daily word games into a full-stack iOS product with real-time multiplayer, AI hints and a companion solver site.]]></description>
      <content:encoded><![CDATA[<h2 id="the-idea-behind-the-game"><a href="#the-idea-behind-the-game">The Idea Behind the Game</a></h2>
<p>Word Challenge started with a straightforward frustration with the genre: why does a word puzzle only give you one go a day? Word games are one of the most enduringly popular categories on the App Store, but the daily-puzzle format leaves you waiting until tomorrow the moment you finish. We wanted to keep the satisfying core of a Wordle-style guessing game while removing that artificial ceiling.</p>
<p>So the central idea was simple: unlimited play. Solve as many hidden words as you like, choosing word lengths from four to seven letters on iPhone, or up to fourteen on the roomier iPad layout. For anyone who still enjoys the shared-puzzle ritual, an optional Daily Challenge remains: one global word a day, server-enforced, with streaks, a leaderboard and a shareable result card. The best of both, rather than one at the expense of the other.</p>
<h2 id="choosing-the-tech-stack"><a href="#choosing-the-tech-stack">Choosing the Tech Stack</a></h2>
<p>We built the iOS app in Swift using SwiftUI. Its declarative syntax and state-driven model are a natural fit for a game where the UI is in constant flux: tiles flipping through their guess states, a coin balance ticking over, timers running during a duel. When iOS 26 arrived, we took the opportunity to refresh the whole app with a Liquid Glass redesign, while keeping support back to iOS 18.</p>
<p>Behind the app sits a genuine full-stack backend rather than a thin leaderboard service. It is a purpose-built .NET 9 API with DynamoDB-backed game state and real-time multiplayer over SignalR. In-app purchases run through RevenueCat, and hints are generated by the OpenAI API under a hard constraint: a hint may nudge, but it must never give the answer away. The service is deployed as a container to AWS Lightsail, with three separately scoped IAM identities for deploy, runtime and storage. This is a deliberate least-privilege split rather than a single shared credential.</p>
<p>Full offline play was a requirement from the start. A classic five-letter mode, backed by a bundled dictionary of around 16,000 words, works with no connection at all. The online features layer on top rather than being load-bearing for the basic game.</p>
<h2 id="building-the-multiplayer-experience"><a href="#building-the-multiplayer-experience">Building the Multiplayer Experience</a></h2>
<p>The feature that turned Word Challenge from a solo puzzle into something people come back to was Play with Friends. Rather than a shared leaderboard dressed up as multiplayer, it is a live word duel over SignalR: either turn-based, with a shared board and shared hints, or a timed race on separate boards where the first to solve wins. Invitations go out as a shareable link, and push notifications tell players when it is their turn or when a game has ended.</p>
<p>A coin economy ties the systems together. Players earn coins by winning and spend them on AI hints and extra tries, alongside Game Center leaderboards and achievements. Balancing that economy (how many coins a win awards versus what a hint costs) took real playtesting. Too generous and hints feel free; too stingy and players feel punished for getting stuck.</p>
<h2 id="the-companion-site-earns-its-own-traffic"><a href="#the-companion-site-earns-its-own-traffic">The Companion Site Earns Its Own Traffic</a></h2>
<p>Word Challenge is not only an app. The companion site at word-challenge.com is a programmatic-SEO product in its own right, built on Next.js 16 with a committed SQLite database as the single source of truth for both the site and the app's word lists. It hosts five entirely client-side solver tools: Wordle, Scrabble, anagram, crossword and Spelling Bee. None of them need sign-up or a server round-trip. The site also holds a growing database of more than 23,000 words, with definitions, synonyms, antonyms and per-length insights. It is localised into English, German, Spanish, French, Italian and Brazilian Portuguese, and it draws organic search traffic that feeds straight back to the app.</p>
<h2 id="the-app-store-submission-process"><a href="#the-app-store-submission-process">The App Store Submission Process</a></h2>
<p>Submitting to the App Store is a rite of passage that every iOS developer approaches warily the first time. Apple's review guidelines are extensive. Configuring in-app purchases adds its own work in App Store Connect: defining each coin pack as a consumable product, setting pricing tiers, and validating receipts to guard against fraud. StoreKit and RevenueCat between them took most of the friction out of the client-side implementation.</p>
<p>One lesson worth repeating: your App Store listing is your only marketing material for organic discovery. The title, subtitle, description and screenshots all reward iteration, and it is worth revisiting them after launch as you learn what actually converts.</p>
<h2 id="post-launch-and-looking-ahead"><a href="#post-launch-and-looking-ahead">Post-Launch and Looking Ahead</a></h2>
<p>Launching is the start, not the finish. The weeks after release are about watching crash reports, reading reviews and working out which requested features are worth building next. Some of the best improvements have come straight from player feedback, and having a real multiplayer layer means the game keeps evolving around how people actually play it.</p>
<p>Building Word Challenge reinforced a familiar truth: the technical challenge is real, but the product challenge is bigger. For anyone building their first game, the advice is simple: ship early, listen to players, and iterate relentlessly.</p>]]></content:encoded>
      <category>ios</category>
      <category>swiftui</category>
      <category>game-development</category>
      <category>app-store</category>
      <category>word-challenge</category>
    </item>
    <item>
      <title>Building a Chrome Extension to Improve Your Development Workflow</title>
      <link>https://www.nanotech-computers.com/blog/building-chrome-extension-dev-workflow/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/building-chrome-extension-dev-workflow/</guid>
      <pubDate>Fri, 15 Nov 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[How a small Chrome extension that restores JSFiddle's sidebar taught us about browser extension development and solving developer friction.]]></description>
      <content:encoded><![CDATA[<h2 id="the-frustration-that-started-it-all"><a href="#the-frustration-that-started-it-all">The Frustration That Started It All</a></h2>
<p>Every developer has those small daily frustrations that they learn to live with. For us, one of those was JSFiddle's sidebar. JSFiddle is a fantastic tool for quick code experiments and sharing snippets. At some point, though, the sidebar toggle was locked behind JSFiddle PRO. That toggle gives you a cleaner, wider editing area. The sidebar itself was still there in the DOM, fully functional. It was just hidden behind a paywall check.</p>
<p>Rather than paying for a subscription or switching to a different tool, we built a Chrome extension that restores the sidebar toggle. It is a tiny project: just a <code>manifest.json</code> and a content script. It solved a real friction point in our daily workflow. It also turned into a useful exercise in Chrome extension architecture.</p>
<h2 id="how-chrome-extensions-work"><a href="#how-chrome-extensions-work">How Chrome Extensions Work</a></h2>
<p>A Chrome extension is fundamentally a bundle of web technologies (HTML, CSS, and JavaScript) with a manifest file that tells Chrome what the extension does and what permissions it needs. The <code>manifest.json</code> declares the extension's name, version, permissions, and which scripts to inject into which pages.</p>
<p>For <code>jsfiddle-give-sidebar</code>, the architecture is minimal. A content script runs on JSFiddle pages and modifies the DOM to restore the sidebar toggle behaviour. Content scripts have access to the page's DOM, but they run in an isolated JavaScript context. As a result, they cannot interfere with the page's own scripts, or vice versa. This isolation is a security feature, but it also means you sometimes need creative approaches to interact with the host page.</p>
<p>The extension is distributed through GitHub releases. Users download the release, enable Developer Mode in Chrome, and load the unpacked extension. It is not on the Chrome Web Store because the use case is niche and the approval process is not worth it for a personal productivity tool.</p>
<h2 id="lessons-for-developer-tool-builders"><a href="#lessons-for-developer-tool-builders">Lessons for Developer Tool Builders</a></h2>
<p>The most important lesson from this project is that not every problem needs a complex solution. The entire extension is a handful of files. There is no build step, no framework, no bundler. It loads instantly because there is almost nothing to load. When the goal is to fix a specific workflow friction, simplicity is a feature.</p>
<p>The second lesson is that developer tools do not need to be ambitious to be valuable. A Chrome extension that saves you ten seconds every time you open JSFiddle adds up to hours over a year. These micro-improvements compound. The best developer tools are often the smallest ones, such as a shell alias, a git hook, or a browser extension. Each removes a specific source of friction from a task you do every day.</p>
<p>If you spot a workflow problem that you encounter daily, consider whether a small Chrome extension could fix it. The Chrome extension API is well-documented, and the development cycle is fast: edit, reload, test. The result is a tool that lives right where you work, in the browser.</p>]]></content:encoded>
      <category>chrome-extension</category>
      <category>javascript</category>
      <category>developer-tools</category>
      <category>open-source</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Node-RED Plugin for Push Notifications</title>
      <link>https://www.nanotech-computers.com/blog/building-node-red-pushover-plugin/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/building-node-red-pushover-plugin/</guid>
      <pubDate>Sat, 28 Sep 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[How we built node-red-pushover, a Node-RED library for sending rich push notifications to iOS and Android devices via the Pushover API.]]></description>
      <content:encoded><![CDATA[<h2 id="node-red-and-the-iot-notification-gap"><a href="#node-red-and-the-iot-notification-gap">Node-RED and the IoT Notification Gap</a></h2>
<p>Node-RED is a visual programming tool that has become the backbone of countless IoT and home automation setups. It excels at wiring together APIs, hardware devices, and online services through a drag-and-drop flow editor. But one area where the default palette falls short is rich push notifications. Sure, you can send an email or a Telegram message, but for instant, reliable notifications on your phone (with priority levels, custom sounds, and image attachments), Pushover is hard to beat.</p>
<p>We built <code>node-red-pushover</code> to fill this gap. It is a Node-RED library that wraps the Pushover API into a simple, configurable node. Drop it into any flow, wire it up to a trigger, and you have instant push notifications on your iOS or Android device.</p>
<h2 id="designing-the-node"><a href="#designing-the-node">Designing the Node</a></h2>
<p>A Node-RED node consists of three pieces: an HTML file that defines the editor UI, a JavaScript file that implements the runtime behaviour, and a <code>package.json</code> that declares the node to Node-RED's Palette Manager. The design goal was to expose Pushover's full API surface (messages, priorities, sounds, devices, images, and HTML formatting) without overwhelming the user with configuration.</p>
<p>The node accepts its inputs through the flow's <code>msg</code> object. The payload becomes the message body. Optional properties like <code>msg.title</code>, <code>msg.image</code>, <code>msg.url</code>, and <code>msg.sound</code> override the defaults configured in the editor. This means you can set sensible defaults in the node properties and override them dynamically at runtime. That is exactly how Node-RED users expect things to work.</p>
<p>Priority levels range from Lowest to High, and each level changes how the notification behaves on the receiving device. High-priority messages bypass Do Not Disturb, which is essential for critical alerts like security system triggers or server outages.</p>
<h2 id="sound-image-and-html-support"><a href="#sound-image-and-html-support">Sound, Image, and HTML Support</a></h2>
<p>One of Pushover's best features is custom notification sounds. The node exposes all available sounds: bike, bugle, cashregister, classical, cosmic, falling, gamelan, incoming, intermission, magic, mechanical, pianobar, siren, spacealarm, tugboat, alien, climb, persistent, echo, updown, and vibrate. Users pick a default sound in the editor but can override it per message.</p>
<p>Image support was the trickiest feature to implement. Pushover's API accepts an image attachment as a multipart form upload. The node accepts a <code>Buffer</code> or file path in <code>msg.image</code> and handles the multipart encoding transparently. This is particularly useful for security camera integrations. When motion is detected, the flow captures a snapshot and sends it as a push notification with the image inline.</p>
<p>HTML formatting in message bodies is also supported. Pushover allows bold, italic, underline, colour, and hyperlink tags, which the node passes through without modification. This lets you create rich, formatted alerts directly from your automation flows.</p>
<h2 id="pushover-glances-and-smartwatch-support"><a href="#pushover-glances-and-smartwatch-support">Pushover Glances and Smartwatch Support</a></h2>
<p>Beyond standard notifications, the plugin also supports Pushover's Glances API. Glances provides data to widgets and smartwatch complications. These are small pieces of information, like a title, text, subtext, count, and percentage, that update on your device without triggering a full notification.</p>
<p>This is useful for dashboard-style monitoring. For example, a home automation flow might push the current temperature, humidity, and air quality index to your Apple Watch complication via Glances. This can update every five minutes without buzzing your wrist each time.</p>
<p>The <code>node-red-pushover</code> plugin can be installed through Node-RED's built-in Palette Manager or via npm. It has been used in home automation, server monitoring, security systems, and industrial IoT setups where reliable, instant notifications are non-negotiable.</p>]]></content:encoded>
      <category>node-red</category>
      <category>iot</category>
      <category>push-notifications</category>
      <category>javascript</category>
      <category>home-automation</category>
    </item>
    <item>
      <title>Integrating SMS Notifications in .NET with SMS Portal</title>
      <link>https://www.nanotech-computers.com/blog/integrating-sms-notifications-dotnet/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/integrating-sms-notifications-dotnet/</guid>
      <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[A guide to building a clean .NET Standard wrapper for the SMS Portal API, enabling programmatic SMS delivery in South African applications.]]></description>
      <content:encoded><![CDATA[<h2 id="why-sms-still-matters"><a href="#why-sms-still-matters">Why SMS Still Matters</a></h2>
<p>In the age of push notifications and chat apps, SMS might seem like legacy technology. But in South Africa and across the African continent, SMS remains the most reliable channel for reaching customers. Not everyone has a smartphone. Not everyone has consistent internet. But virtually every mobile phone can receive an SMS. Delivery rates for SMS consistently outperform those for email and push notifications.</p>
<p>Whether it is OTP verification, appointment reminders, delivery notifications, or marketing campaigns, programmatic SMS is a core requirement for many South African businesses. SMS Portal is one of the country's leading providers, offering a robust API for sending single and bulk messages with delivery reporting.</p>
<h2 id="building-the-ismsportalservice-wrapper"><a href="#building-the-ismsportalservice-wrapper">Building the ISmsPortalService Wrapper</a></h2>
<p>We followed the same interface-driven pattern used for our payment gateway wrappers. <code>ISmsPortalService</code> defines a clean contract for sending SMS messages. The concrete implementation handles authentication with SMS Portal's API, message construction, and response parsing.</p>
<p>The library targets .NET Standard 2.0. This makes it compatible with .NET Framework 4.6.1 and above, .NET Core, and modern .NET. Installation is a single NuGet command. Registration with the DI container follows the standard <code>IServiceCollection</code> pattern that ASP.NET developers expect.</p>
<p>The core methods cover the essential workflows: sending a single SMS to a specified number, sending bulk messages to a list of recipients, and querying delivery status. Each method returns a strongly typed response object so consumers get full IntelliSense and compile-time safety.</p>
<h2 id="authentication-and-configuration"><a href="#authentication-and-configuration">Authentication and Configuration</a></h2>
<p>SMS Portal uses token-based authentication. The wrapper handles the authentication flow transparently. It obtains a token on the first request, caches it for the token's lifetime, and refreshes it automatically when it expires. Consumers never have to think about token management.</p>
<p>Configuration follows the <code>IOptions</code> pattern. You provide your SMS Portal API key and sender ID in <code>appsettings.json</code> or environment variables. The library picks them up through dependency injection. This keeps secrets out of source code and makes it easy to switch between sandbox and production environments.</p>
<h2 id="real-world-usage"><a href="#real-world-usage">Real-World Usage</a></h2>
<p>We have deployed <code>ISmsPortalService</code> in several production applications. One common pattern is OTP verification during user registration: the service generates a one-time code, sends it via SMS, and validates the code when the user submits it. Another is appointment reminders for healthcare and service businesses. A background job queries upcoming appointments and dispatches reminders at a configurable interval.</p>
<p>The open-source nature of the library means other South African developers can integrate SMS Portal without writing HTTP plumbing from scratch. The MIT licence ensures there are no restrictions on commercial use. If you are building a .NET application that needs to send SMS in South Africa, ISmsPortalService is a solid foundation to build on.</p>]]></content:encoded>
      <category>dotnet</category>
      <category>sms</category>
      <category>open-source</category>
      <category>notifications</category>
      <category>south-africa</category>
    </item>
    <item>
      <title>Building .NET Payment Gateway Wrappers for South African Providers</title>
      <link>https://www.nanotech-computers.com/blog/dotnet-payment-gateway-wrappers-south-africa/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/dotnet-payment-gateway-wrappers-south-africa/</guid>
      <pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[How we built open-source .NET Standard wrappers for Paygate, PayFast, and DirectPay Online, three of South Africa's most widely used payment processors.]]></description>
      <content:encoded><![CDATA[<h2 id="the-problem-fragmented-payment-apis-in-south-africa"><a href="#the-problem-fragmented-payment-apis-in-south-africa">The Problem: Fragmented Payment APIs in South Africa</a></h2>
<p>South Africa has a handful of dominant payment processors, including Paygate, PayFast, and DirectPay Online, but each one exposes a completely different API surface. Some use SOAP, others REST. Some require server-side checksums, others rely on redirect flows with callback URLs. For .NET developers, integrating even a single provider means writing boilerplate HTTP code, handling edge cases around 3D Secure, and managing secrets in a way that plays nicely with ASP.NET dependency injection.</p>
<p>When we needed to support all three providers across multiple client projects, copy-pasting integration code between repositories quickly became unsustainable. The answer was a set of clean, reusable .NET Standard libraries, one per provider, published as NuGet packages and open-sourced for the community.</p>
<h2 id="design-principles"><a href="#design-principles">Design Principles</a></h2>
<p>Every wrapper follows the same architectural pattern. An interface (<code>IPaygateService</code>, <code>IPayFastService</code>, <code>IDirectPayService</code>) defines the contract so consumers can mock the payment gateway in unit tests. A concrete implementation handles HTTP communication, checksum generation, and response parsing. Configuration is injected through the standard <code>IOptions</code> pattern, and all libraries target .NET Standard 2.0 for maximum compatibility.</p>
<p>The interface-first approach turned out to be critical. Payment gateways are notoriously difficult to test because they involve real money, redirect flows, and asynchronous callbacks. By coding against an interface, developers can swap in a fake implementation during testing and only hit the live gateway in integration or staging environments.</p>
<p>Each library ships as a NuGet package with a single-line install, and registration with ASP.NET Core's service container is a one-liner as well. This lowers the barrier for any .NET developer in South Africa who needs to accept payments quickly.</p>
<h2 id="ipaygateservice-redirect-based-card-payments"><a href="#ipaygateservice-redirect-based-card-payments">IPaygateService: Redirect-Based Card Payments</a></h2>
<p>Paygate is one of the oldest and most trusted payment processors in South Africa. Its API uses a redirect model: your server creates a transaction, receives a redirect URL, sends the customer to Paygate's hosted payment page, and then Paygate posts the result back to your callback endpoint.</p>
<p>The wrapper handles the full lifecycle. <code>CreateTransaction</code> builds the parameter set, generates the required MD5 checksum from your encryption key, and returns a redirect URL. <code>QueryTransaction</code> polls Paygate for the current status of a pending transaction. The checksum logic is the trickiest part: Paygate requires a specific field ordering and a shared secret. The library encapsulates this entirely, so consumers never have to think about it.</p>
<p>We also built in 3D Secure handling. When a card requires additional authentication, Paygate returns a specific status code and a URL for the 3D Secure challenge. The library detects this automatically and provides a clear API for the developer to redirect the customer appropriately.</p>
<h2 id="ipayfastservice-and-idirectpayservice"><a href="#ipayfastservice-and-idirectpayservice">IPayFastService and IDirectPayService</a></h2>
<p>PayFast is arguably the most popular payment gateway for small and medium businesses in South Africa, offering both once-off and recurring payments. Our wrapper generates the signed payment form data, validates the ITN (Instant Transaction Notification) callbacks, and provides helpers for subscription management.</p>
<p>DirectPay Online supports a wider range of payment methods beyond cards, including mobile operator billing. The wrapper provides methods for <code>CreateToken</code>, <code>ChargeTokenCreditCard</code>, <code>ChargeTokenMobile</code>, and <code>ChargeTokenAuth</code>, covering the full spectrum of payment flows. It also supports the delayed-capture pattern where you authorise a card first and charge it later, which is common in hospitality and rental industries.</p>
<p>All three libraries share the same testing strategy: an xUnit project with mocked HTTP handlers that simulate the provider's responses. This means you can run the full test suite without any network calls or test credentials.</p>
<h2 id="lessons-learned-and-community-impact"><a href="#lessons-learned-and-community-impact">Lessons Learned and Community Impact</a></h2>
<p>Publishing these as open-source NuGet packages taught us a few things. First, documentation matters more than code quality when it comes to adoption. We included step-by-step guides for both console applications and ASP.NET Core web apps, complete with dependency injection setup and example controllers.</p>
<p>Second, maintaining backward compatibility across .NET Standard, .NET Core, and .NET 5+ requires discipline. Targeting .NET Standard 2.0 was the right call because it maximises compatibility, but it means avoiding newer APIs that only exist in later runtimes.</p>
<p>The libraries now power payment processing across several production applications in South Africa, handling transactions for e-commerce stores, SaaS platforms, and service businesses. For any .NET developer in the region who needs to integrate with local payment processors, these open-source wrappers eliminate weeks of boilerplate work.</p>]]></content:encoded>
      <category>dotnet</category>
      <category>payments</category>
      <category>open-source</category>
      <category>south-africa</category>
      <category>nuget</category>
    </item>
    <item>
      <title>Porting Pushover.NET to .NET Standard</title>
      <link>https://www.nanotech-computers.com/blog/porting-pushover-dotnet-standard/</link>
      <guid isPermaLink="true">https://www.nanotech-computers.com/blog/porting-pushover-dotnet-standard/</guid>
      <pubDate>Sun, 05 May 2024 00:00:00 GMT</pubDate>
      <description><![CDATA[The story of modernising a legacy .NET Framework library to .NET Standard, making Pushover push notifications accessible across all modern .NET platforms.]]></description>
      <content:encoded><![CDATA[<h2 id="the-net-standard-migration-challenge"><a href="#the-net-standard-migration-challenge">The .NET Standard Migration Challenge</a></h2>
<p>The original Pushover.NET library was built for .NET Framework. It was a solid library, but locked to Windows and unable to run on .NET Core, Xamarin, or the modern cross-platform .NET runtime. As more projects moved to .NET Core and beyond, we needed a Pushover client that worked everywhere. Rather than writing a new library from scratch, we forked the original and ported it to .NET Standard 2.0.</p>
<p>Targeting .NET Standard 2.0 was a deliberate choice. It provides the widest possible compatibility: .NET Framework 4.6.1+, .NET Core 2.0+, Xamarin, Unity, and every version of modern .NET from 5 through 9. This means a single package runs on all of these platforms without conditional compilation or multi-targeting.</p>
<h2 id="what-changed-in-the-port"><a href="#what-changed-in-the-port">What Changed in the Port</a></h2>
<p>The migration involved replacing .NET Framework-specific APIs with their .NET Standard equivalents. <code>HttpWebRequest</code> became <code>HttpClient</code>. Configuration that relied on <code>System.Configuration</code> moved to a constructor-injection model. Assembly attributes and project structure were updated from the legacy <code>packages.config</code> format to the SDK-style project format with <code>PackageReference</code>.</p>
<p>The public API surface remained largely unchanged to make migration easy for existing users. You create a <code>PushoverClient</code> with your application token, then set the recipient, message, priority, and optional parameters such as sound and HTML content. Finally, you call <code>SendAsync</code>. The library handles serialisation, HTTP communication, and response parsing.</p>
<p>We also added proper async support throughout. The original library used synchronous HTTP calls, which block threads. This was acceptable in .NET Framework desktop apps, but problematic in ASP.NET Core, where thread pool starvation can kill performance under load. The ported version is fully async with cancellation token support.</p>
<h2 id="publishing-to-nuget"><a href="#publishing-to-nuget">Publishing to NuGet</a></h2>
<p>The ported library is published as <code>PushoverNET.Standard</code> on NuGet, installable with a single command. The package includes XML documentation so consumers get full IntelliSense in Visual Studio, Rider, and VS Code.</p>
<p>One lesson from publishing was the importance of clear naming. The original package was <code>Pushover.NET</code>; ours is <code>PushoverNET.Standard</code>. The ".Standard" suffix immediately tells developers this is the cross-platform version, avoiding confusion with the original .NET Framework package.</p>
<p>The library supports all core Pushover features: sending notifications to individual users or groups, setting priority levels from lowest to emergency, and choosing from the full catalogue of notification sounds. It also supports HTML formatting in message bodies and attaching URLs and URL titles. Everything that works in the Pushover API is accessible through a clean C# interface.</p>
<h2 id="when-to-port-vs-rewrite"><a href="#when-to-port-vs-rewrite">When to Port vs. Rewrite</a></h2>
<p>This project reinforced an important principle: porting is almost always better than rewriting when the original code is well-structured. The original Pushover.NET library had clean abstractions and sensible separation of concerns, which made the migration straightforward. The bulk of the work was updating infrastructure (project format, NuGet references, async patterns) rather than rewriting business logic.</p>
<p>If the original code had been tightly coupled to Framework-specific APIs or had significant architectural issues, a rewrite might have been justified. However, in this case, a port preserved the battle-tested API surface, maintained backward compatibility for existing users, and took a fraction of the time a ground-up rewrite would have required.</p>]]></content:encoded>
      <category>dotnet</category>
      <category>open-source</category>
      <category>push-notifications</category>
      <category>nuget</category>
      <category>modernisation</category>
    </item>
  </channel>
</rss>
