Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iuriiย ships.

Iurii Rogulia, IT partner for business & fractional CTO. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland ๐Ÿ‡ซ๐Ÿ‡ฎ

[email protected]
Back to projects

Iuriiย Builds: pi-pi.ee โ€” B2B E-commerce for Waterless Urinal Systems

January 17, 2026

i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.

Live demo

Stack

Next.jsReactTypeScriptTailwind CSS

Libraries

next-intlDaisyUIStripe SDKreact-pdf

Services

StripeVercelResendNotionGoogle Analytics

Topics

E-commerceB2Bi18nPDFSSRArchitectureSEOSchema.org

Key Results

  • Buyers self-serve the whole order โ€” validate VAT, see correct pricing, pay and get a compliant invoice, with no sales call
  • Sell across 32 European markets in 28 languages from one platform
  • Fewer abandoned checkouts โ€” 6 payment methods cover every major European B2B preference
  • VAT handled correctly at the border โ€” VIES validation and automatic reverse-charge on cross-border B2B orders
  • Found in every market โ€” 100% server-rendered so product pages rank in each local language
pi-pi.ee โ€” B2B E-commerce for Waterless Urinal Systems

The Business Problem

Pi-pi.ee sells waterless urinal systems to businesses across Europe โ€” maintenance companies, venue operators, facility managers. The product nearly sells itself: lower operating costs, no water connection, less maintenance. The sales process was the bottleneck.

B2B buyers in Europe have specific expectations that consumer e-commerce platforms treat as edge cases. A German buyer expects their VAT number to be validated against the VIES registry, the MwSt to be removed automatically from the invoice, and the correct reverse charge notation to appear on the document. A Portuguese buyer needs Multibanco as a payment option. A Finnish buyer expects the invoice in Finnish. Shopify Plus could handle portions of this โ€” at โ‚ฌ60,000+/year, plus bespoke development for everything it could not do natively. The business needed a platform built around B2B compliance from the ground up, not a consumer checkout with VAT bolted on.

The Solution

I built a headless B2B e-commerce platform where EU VAT compliance is a first-class architectural concern, not an afterthought. VAT rates for all 32 countries are stored as static JSON, updated by a script before each build. Calculation happens at checkout with no external API call in the critical path โ€” no latency, no third-party failures, no dependency on a service that can go down during checkout.

VIES integration validates EU VAT numbers in real time and auto-fills the company's registered address directly from the EU registry. Six payment methods โ€” cards, PayPal, Revolut, SEPA Direct Debit, bank transfer, and Multibanco โ€” cover every major European B2B payment preference without requiring multiple Stripe accounts or separate integrations. Server-rendered PDF invoices with correct reverse charge notation are generated and emailed automatically on payment confirmation, with Cyrillic character support for Eastern European markets. Orders and customer records sync to Notion automatically, keeping the operations team in a tool they already know.

Results

Professional buyers can now self-serve completely: validate their VAT number, see accurate country-specific pricing, pay via their preferred method, and receive a compliant invoice โ€” without a sales call.

MetricValue
Languages28 fully localized
Markets32 European countries
Payment methods6 (Cards, PayPal, Revolut, SEPA, Bank Transfer, Multibanco)
VAT calculation0 runtime dependencies (static JSON)
PDF invoicesServer-rendered with Cyrillic support
SEO100% server-rendered (Next.js App Router)

Technical SEO

Selling B2B across 32 European markets in 28 languages means organic discovery has to work in every one of them โ€” not just English. Technical SEO was part of the build from day one, not a phase that gets postponed until "after launch":

  • hreflang declarations for all 28 languages ร— 32 markets โ€” every locale points to the correct regional alternates so a Polish buyer searching in Polish lands on pl-PL, not the English fallback
  • 100% server-rendered via Next.js App Router โ€” product, category, and content pages reach Googlebot with full HTML, no client-side hydration gap
  • Per-locale JSON-LD Product schema with correct currency, B2B pricing, and availability โ€” the structured data survives localization, which is the most common place schema silently breaks on multilingual sites
  • Organization, BreadcrumbList, and FAQ schema for the long-tail B2B queries that drive most of this market
  • Auto-generated sitemap with locale alternates โ€” driven by the product catalogue, rebuilt every deploy, no manual maintenance
  • Canonical tags that respect regional pricing โ€” same product, 32 country variants, no duplicate-content penalty
  • Core Web Vitals tuned for the slowest mobile networks in the target markets โ€” the 53 KB per-locale bundle is part of that budget

Under the Hood

For the technically curious, here is how the core pieces are built.

Type-safe VAT Calculation

Price calculation without API requests โ€” data loaded statically, types guarantee correctness:

// src/lib/pricing/utils.ts
export interface PriceResult {
  basePrice: number; // Net price in EUR
  vatRate: number; // 0.22 = 22%
  vatAmount: number; // VAT amount in EUR
  totalPrice: number; // Net + VAT
  isEU: boolean; // EU country flag
  formattedTotal: string; // "โ‚ฌ365.78"
}
 
export function calculatePrice(countryCode: string): PriceResult {
  const vatRate = getVatRate(countryCode); // From static JSON
  const basePrice = 299; // EUR
  const vatAmount = Math.round(basePrice * vatRate * 100) / 100;
  const totalPrice = Math.round((basePrice + vatAmount) * 100) / 100;
 
  return {
    basePrice,
    vatRate,
    vatAmount,
    totalPrice,
    isEU: isEuCountry(countryCode),
    formattedTotal: new Intl.NumberFormat("de-DE", {
      style: "currency",
      currency: "EUR",
    }).format(totalPrice),
  };
}

Stripe Webhook Handler with Async Payments

Unified handler for all payment types โ€” instant (cards) and async (SEPA, bank transfers):

// src/app/api/webhooks/stripe/route.ts
const ASYNC_PAYMENT_METHODS = ["sepa_debit", "customer_balance", "multibanco"];
 
export async function POST(request: NextRequest) {
  const event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
 
  switch (event.type) {
    case "payment_intent.succeeded": {
      const orderData = parseOrderData(paymentIntent);
      await sendOrderNotification(orderData); // Warehouse
      await sendInvoiceEmail(orderData); // Customer
      await upsertOrder({ ...orderData, status: "Paid" }); // CRM
      break;
    }
 
    case "payment_intent.requires_action": {
      // Bank transfer โ€” send instructions
      if (nextActionType === "display_bank_transfer_instructions") {
        const bankTransfer = getBankTransferDetails(paymentIntent);
        await sendOrderConfirmationEmail(orderData, bankTransfer);
      }
      break;
    }
  }
}

Server-side PDF Invoice Generation

Invoices generated on server with Cyrillic support, reverse charge for B2B, and payment-specific instructions:

// src/lib/pdf/invoice-pdf.tsx
Font.register({
  family: "Roboto",
  fonts: [
    { src: "https://fonts.gstatic.com/.../Roboto.ttf", fontWeight: 400 },
    { src: "https://fonts.gstatic.com/.../Roboto-Bold.ttf", fontWeight: 700 },
  ],
});
 
export function InvoicePDF({ order, status, bankTransfer }: InvoicePDFProps) {
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        {/* Header with logo and company info */}
        <View style={styles.header}>...</View>
 
        {/* Status badge: PAID / PENDING / PROCESSING */}
        <View style={[styles.statusBadge, statusStyle]}>
          <Text>{statusText}</Text>
        </View>
 
        {/* Line items table */}
        {order.items.map((item) => (
          <View style={styles.tableRow}>
            <Text>{productNames[item.productId]}</Text>
            <Text>{item.quantity}</Text>
            <Text>{formatCurrency(item.price * item.quantity)}</Text>
          </View>
        ))}
 
        {/* Bank transfer instructions (if applicable) */}
        {bankTransfer && (
          <View style={styles.bankTransferBox}>
            <Text>IBAN: {bankTransfer.iban}</Text>
            <Text>Reference: {bankTransfer.reference}</Text>
          </View>
        )}
 
        {/* Reverse charge note for B2B */}
        {order.isReverseCharge && (
          <Text>VAT reverse charge per Article 196 Directive 2006/112/EC</Text>
        )}
      </Page>
    </Document>
  );
}

Multi-locale Routing with 28 Languages

Internationalization via next-intl with SEO and server-side rendering support:

// src/i18n/config.ts
export const locales = [
  "bg",
  "cs",
  "da",
  "de",
  "el",
  "en",
  "es",
  "et",
  "fi",
  "fr",
  "hr",
  "hu",
  "it",
  "lt",
  "lv",
  "nl",
  "no",
  "pl",
  "pt",
  "ro",
  "ru",
  "sk",
  "sl",
  "sv",
  "tr",
  "uk",
  "vi",
  "zh",
] as const;
 
export const countries = [
  "AT",
  "AX",
  "BE",
  "BG",
  "CH",
  "CY",
  "CZ",
  "DE",
  "DK",
  "EE",
  "ES",
  "FI",
  "FR",
  "GB",
  "GR",
  "HR",
  "HU",
  "IE",
  "IT",
  "LT",
  "LU",
  "LV",
  "MT",
  "NL",
  "NO",
  "PL",
  "PT",
  "RO",
  "SE",
  "SI",
  "SK",
] as const;
 
// Each country has VAT pattern, example, phone format
export const countryInfo: Record<Country, CountryConfig> = {
  DE: {
    vatPrefix: "DE",
    vatPattern: /^DE\d{9}$/,
    vatExample: "DE123456789",
    phoneCode: "+49",
  },
  // ... 31 more countries
};

Project Review

โ€œ

Iurii built our entire B2B platform from scratch in a remarkably short time โ€” a fully multilingual webshop covering 32 European markets in 28 languages, with automated VAT calculation, VIES validation, Stripe payments across 6 methods including SEPA and bank transfers, server-generated PDF invoices, and Notion as our CRM. Everything was integrated and working end-to-end, not just a prototype. As someone responsible for both sales and operations, I especially valued that he understood the business side โ€” compliance requirements, B2B buyer expectations, different payment preferences across markets. He asked the right questions upfront, kept things transparent throughout, and delivered without dragging it out. Highly recommend.

Mark Tamm ๐Ÿ‡ช๐Ÿ‡ช

Account Manager, pi-pi.ee

Services

StripeNotion

Topics

E-commerceB2Bi18nPaymentsPDFTax/VAT
Iurii RoguliaAvailable

Need something similar?

I build custom solutions โ€” from APIs to full products. Let's talk about your project.

View all projects

Related projects

Pikkuna โ€” E-commerce for Vinyl Curtains & PVC Products
Pikkuna โ€” E-commerce for Vinyl Curtains & PVC Products
October 12, 2024
Pikkuna โ€” E-commerce for Vinyl Curtains & PVC Products

International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe โ†’ Zoho CRM โ†’ Airtable โ†’ Mailgun โ†’

Stack

Next.jsReactTypeScript

Libraries

next-intlZodpdf-libPuppeteerStripe.js

Databases

Redis

Services

StripeZohoMailgunPostNordNetvisorVercelVercel BlobGoogle AnalyticsMeta CAPIAirtableUpstash

Topics

E-commercePaymentsShippingPDFPWACTOArchitectureSEOSchema.orgi18nPerformance
pi-pi.ee โ€” B2B Deal & Document Portal
pi-pi.ee โ€” B2B Deal & Document Portal
July 1, 2026
pi-pi.ee โ€” B2B Deal & Document Portal

Internal sales portal that turns a wholesale deal into a full set of trade paperwork โ€” pro forma, contract, commercial invoice, packing list, CMR and more โ€”

Stack

Next.jsReactTypeScript

Services

Notion

Topics

B2BCRMSales AutomationPDFSSRArchitectureInternational TradeInternal Tools

Related posts

Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access
July 24, 2026ยท 9 min
Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access

Gated B2B pricing in Next.js: per-account price lists, server-side access control so prices never leak to crawlers, and a deal-record data model where every

Stack

Next.jsReactTypeScript

Databases

PostgreSQL

Topics

B2BE-commerceAuthSSRArchitectureSecurity
React Email + Resend Tutorial: Transactional Emails with PDF Invoices
April 5, 2026ยท 13 min
React Email + Resend Tutorial: Transactional Emails with PDF Invoices

Production transactional emails with React Email and Resend: PDF invoice generation via react-pdf, Vercel Blob temporary storage, and BullMQ Stripe webhook

Stack

Next.jsTypeScriptReact

Libraries

React EmailStripe SDKStripe.jsreact-pdf

Services

Vercel BlobResend

Topics

E-commercePaymentsDocument Automation