Skip to content

INTEGRATION DESIGN

VietGara — Multi-Tenant SaaS Platform for Auto Garage Management & Maintenance

Version 1.3 — October 9, 2026 · Cloud: Google Cloud Platform (GCP) · Backend: Go

1. Introduction

This document describes the integration design with third-party systems: transactional e-mail, Google sign-in, file storage, e-invoice providers, payment gateways, and insurance companies. General principle: each partner has its own adapter implementing a common business interface, isolating the risk of external API changes from the core business logic.

2. Integrations in Phase 1

2.1. Transactional E-mail (ADR-011)

EnvironmentProviderConfiguration
Local development, integration testsMailpit (SMTP catcher with a web inbox on port 8025)MAIL_PROVIDER=smtp, SMTP_HOST, SMTP_PORT
Staging, productionMailjet Send API v3.1MAIL_PROVIDER=mailjet, MAILJET_API_KEY, MAILJET_SECRET_KEY (Secret Manager), MAIL_FROM_ADDRESS on a domain validated with SPF and DKIM
type Sender interface {
Send(ctx context.Context, msg Message) error // smtp or Mailjet, chosen by MAIL_PROVIDER
}
  • Templates (email_verification, password_reset, staff_invitation) are embedded per language (vi, en) and rendered in the recipient’s locale.
  • OTP e-mails are sent synchronously: if sending fails, the OTP is discarded and the request fails, so the user can retry at once. Invitation e-mails are sent after the invitation commits; a failure is logged and the invitation remains visible in the invitee’s console.
  • From Release 1.5 the Notification Service takes over non-OTP e-mails from events (e.g. the registration confirmation and subscription.expiring_soon). Staff invitation e-mails (staff.invited) stay a direct send after commit in Release 1.x (Event Design, Section 4).

2.2. Google Sign-in

sequenceDiagram
  participant W as Owner web
  participant P as Popup window
  participant B as Backend (Identity)
  participant G as Google OAuth 2.0
  W->>P: open /api/v1/auth/google/login
  P->>B: GET /auth/google/login
  B-->>P: redirect to Google (state cookie)
  P->>G: consent
  G-->>P: redirect /auth/google/callback?code&state
  P->>B: callback
  B->>G: exchange code, read verified e-mail
  B->>B: find or create the user (verified), issue a session
  B-->>P: page posting {type, session} to window.opener (same origin)
  P-->>W: postMessage → signed in

Configuration: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and APP_BASE_URL (the owner web’s host, e.g. https://vietgara.viettechno.com). The redirect URI is APP_BASE_URL + /api/v1/auth/google/callback, registered in the Google Cloud console; the popup posts its result to APP_BASE_URL’s origin. Mobile apps sign in with an ID token instead (GOOGLE_MOBILE_CLIENT_IDS, POST /api/v1/auth/google/id-token).

2.3. File Storage

EnvironmentBackendNotes
Local, stagingLocal disk (STORAGE_BACKEND=local)Persistent volume; served by the backend at /api/v1/files/…
ProductionCloud Storage (STORAGE_BACKEND=gcs)Public bucket STORAGE_GCS_BUCKET (logos and other public kinds), public URL prefix STORAGE_PUBLIC_BASE_URL (bucket or CDN); private bucket STORAGE_GCS_PRIVATE_BUCKET served as V4 signed URLs; Workload Identity credentials

On local disk and staging, private objects are served by the backend with an HMAC signature keyed by STORAGE_SIGNING_KEY (a secret). File kinds and their access are listed in Database Design, Section 6.7.

Uploads are validated before storage (garage logos: PNG, JPEG or WebP, at most 1 MB) and stored under random object names.

3. Adapter Pattern Design

// Common business interface, defined in the core package
type EInvoiceProvider interface {
IssueInvoice(ctx context.Context, req IssueInvoiceRequest) (InvoiceResult, error)
GetInvoiceStatus(ctx context.Context, invoiceRef string) (InvoiceStatus, error)
CancelInvoice(ctx context.Context, invoiceRef string, reason string) error
}
// Each provider implements this interface separately
type VNPTAdapter struct { client *vnpt.Client }
type ViettelAdapter struct { client *viettel.Client }
func (a *VNPTAdapter) IssueInvoice(...) (...) { /* calls VNPT's own API */ }
func (a *ViettelAdapter) IssueInvoice(...) (...) { /* calls Viettel's own API */ }
// Factory selects the adapter based on each garage's configuration
provider := factory.GetProvider(garage.EInvoiceProviderCode)
result, err := provider.IssueInvoice(ctx, req)

Adding a new provider (MISA, BKAV, etc.) only requires writing one new struct implementing the interface, without touching the business logic in Billing & Settlement or the E-Invoice Service — following the Open/Closed Principle.

4. E-Invoicing Integration

4.1. Invoice Issuance Flow

sequenceDiagram
  participant E as E-Invoice Service
  participant P as VNPT / Viettel API
  E->>E: build invoice payload
  E->>P: signed issue request
  P-->>E: invoice_number, signature status
  E->>E: save invoice record
  E-)E: publish invoice.issued
  alt error or timeout
    E->>P: retry (exponential backoff, up to 3 attempts)
    E-)E: status = failed, publish invoice.issue_failed
  end

4.2. Circuit Breaker

When the error rate from a provider exceeds a threshold (e.g. >50% of requests fail within 1 minute), the circuit breaker trips to the ‘open’ state — temporarily suspending direct calls, failing fast, and queuing requests, to avoid slowing down the entire settlement flow because one partner is having an outage. After a configured cooldown, it moves to ‘half-open’ to retry.

4.3. Providers by Rollout Phase

PhaseProviders
Phase 16VNPT, Viettel
Phase 17MISA, BKAV (based on real onboarding demand)

5. Payment Gateway Integration

5.1. Online Payment Flow

sequenceDiagram
  actor C as Customer App
  participant A as Payment Adapter
  participant G as VNPay / Momo / ZaloPay
  C->>A: request payment link
  A->>G: create transaction
  G-->>A: payment_url
  A-->>C: payment_url
  C->>G: redirect, complete payment
  G->>A: webhook: payment success (signed)
  A->>A: verify signature, dedupe by transaction_id
  A-)A: publish payment.confirmed
  A-->>C: push "Payment successful"
  • Webhooks are signature-verified (HMAC) as provided by the payment gateway, preventing forged webhook requests.
  • Idempotency: each webhook has a unique transaction_id, processed exactly once even if received multiple times due to gateway retries.
  • Multiple gateways: a garage can configure several gateways at once; the system prioritizes the primary gateway and automatically switches to a backup gateway when the primary fails repeatedly (per RISK-3RD-03 in the Risk Assessment).

6. Insurance Integration (Phase 17)

In Vietnam the vehicle owner files the claim with the insurer; the garage supports it with the quote, the repair and the documents. Most insurers have no open API, so the design is a claim-tracking workflow first, and an integration second.

6.1. Claim Procedure Modelled

StepWhat happensWhat VietGara records
1. ReportThe owner secures the scene, calls the insurer’s hotline, and notifies it in writing, by app or by e-mail within 5 working days of the incident (Decree 67/2023/ND-CP). Serious or disputed accidents also need the traffic police’s reportIncident date, place, description, notification date and a countdown to the 5-working-day limit
2. AssessmentThe insurer’s assessor inspects the vehicle within 24 hours, at the scene or at the garage, and writes the assessment reportAssessor, date and the attached assessment report
3. Quote and agreementThe garage quotes the repair; the insurer and the owner or garage agree the cost and repair methodThe insurer-agreed quote, a versioned snapshot of the garage quote
4. Claim fileVehicle registration, driver’s licence, ID card, insurance certificate or e-policy, accident report, damage and assessment reports, repair invoice and vouchersA document checklist with private attachments; the owner can upload from the Customer App
5. Approval and payoutApproval in 3-5 days (small loss) or 7-15 days (complex). The owner pays only the deductible or out-of-cover part, or the insurer pays the ownerClaim status, the settlement split and the insurer’s payments, which feed Receivables per claim

6.2. Claim Tracking (baseline, every insurer)

  • A claim is linked to a repair order, a vehicle and an insurer from the garage’s insurers directory (not a customer, not a supplier). Its status moves through reported, assessed, quote agreed, file submitted, under review, approved or rejected, and paid, with an event log.
  • Deadlines and the checklist drive reminders by e-mail and push.
  • The claim file is packaged as a PDF set according to the insurer’s template (Document/Print) for manual e-mail or portal submission. Staff update the status by hand when the insurer answers.

6.3. API Submission (where the insurer supports it)

sequenceDiagram
  participant S as Staff
  participant I as Insurance
  participant X as Insurer API
  S->>I: submit claim file
  I->>X: submit claim (adapter, circuit breaker)
  X-->>I: claim_id, status
  X-->>I: status updates (poll or webhook)
  I-)I: publish claim.status_changed

6.4. Rollout Priority

  • Release 17.0: claim tracking for every insurer.
  • Release 17.1: PVI, PTI, Bao Viet first (the insurers with the largest affiliated garage networks), then others by real onboarding demand.

7. Integration Security

  • Each partner’s credentials (API keys, signing certificates) are stored in Google Secret Manager, never hard-coded or stored in the application database.
  • Least-privilege principle: each adapter only has access to its own credentials, not a shared service user across all integrations.
  • All third-party requests/responses are logged (with sensitive data masked) to support debugging and audit in case of disputes.

7.1. Print Agent (Release 14.1)

  • The agent opens only an outbound secure WebSocket; it never listens on a port.
  • It is paired with one garage by a revocable key and accepts jobs for that garage only.
  • Jobs are signed by the backend, carry no more data than the print output, and are kept only in a short local queue until printed.
  • Printing from mobile apps to LAN printers (ESC/POS over TCP 9100, IPP) stays on the garage network and never passes through VietGara.