POS Adapter Architecture
SalesArck currently implements Square-first normalization and ingestion in apps/api/src/services.
Canonical Transaction Contract
Provider payloads are normalized to NormalizedTransaction (packages/types/src/pos.ts):
{
providerTransactionId: string,
provider: "square" | "clover",
amountMinorUnits: number,
currency: string,
transactionAt: string,
customerRef?: {
mobile?: string,
email?: string,
externalCustomerId?: string
},
lineItems?: [...]
}
Current Normalization Components
square-adapter.ts
Responsibilities:
- read webhook/list-payment record variants (snake_case and camelCase)
- ignore non-completed payment records
- map money fields and timestamps
- extract customer references when available
square-api.ts
Responsibilities:
ListPaymentspagination API- optional
RetrieveCustomerenrichment API
ingestion.ts
Responsibilities:
- idempotent ingest orchestration
- customer identity resolution
- transaction storage and reward application
Processing Outcomes
Ingestion reports one of:
creditedstored_anonymousduplicateno_rule_no_credit
These outcomes make provider noise and business impact explicit.
Idempotency Strategy
Two layers are used:
- application-level idempotency key (
idempotency_keystable) - schema-level transaction unique constraint (
transactions_provider_dedup)
This protects against retries and race conditions.
Clover Integration Status
Clover OAuth, webhook receive, REST payment fetch, customer enrichment, and ingestion are fully implemented. Both providers share the same executeIngestion() core. Clover-specifics (notification-only webhooks, no refresh token, signature fail-closed in production) are documented in POS Integrations.
Why This Pattern Works
- Provider-specific payload complexity is isolated.
- Reward and wallet services remain provider-agnostic.
- New providers can be added by implementing normalize + ingest integration without rewriting reward logic.
Related Docs
Written byDhruv Doshi