Skip to content

Handle errors

Facade operations use Effect’s error channel. Error objects have a stable consumer-facing code and no internal _tag.

import { createCheckout } from "@byfungsi/sapa";
import { Effect } from "effect";
const response = createCheckout(command).pipe(
Effect.match({
onSuccess: (checkout) => ({
status: 201,
body: checkout,
}),
onFailure: (error) => {
switch (error.code) {
case "invalid_input":
return { status: 400, body: { code: error.code } };
case "offer_not_found":
case "price_not_found":
case "subscription_not_found":
case "invoice_not_found":
return { status: 404, body: { code: error.code } };
case "idempotency_conflict":
return { status: 409, body: { code: error.code } };
case "provider_unavailable":
case "provider_error":
case "outcome_unknown":
case "internal_error":
return { status: 503, body: { code: error.code } };
default:
return { status: 422, body: { code: error.code } };
}
},
}),
);

The fallback groups operation-specific business rejections for brevity. In a production API, map every code intentionally and add an exhaustive never check when your response contract needs compile-time drift detection.

Log the operation name, safe identities, and code. Do not log:

  • raw payment notifications or signatures;
  • provider credentials or raw provider responses;
  • raw license keys;
  • signed download URLs;
  • unbounded parse issue trees;
  • customer payloads by default.

internal_error deliberately hides storage corruption, driver failures, and other implementation detail. Correlate it with private structured telemetry, not a more revealing public response.