# Integration Checklist

> The normative go-live gate: the checklist your integration must satisfy before LoyaltyPlant
> activates it for production. Use it to self-assess before certification; LoyaltyPlant verifies
> the same items.

How to use: every box must be checked (or explicitly N/A with a reason) before go-live.
Items are stated in wire/contract terms, so they apply whether you built with the **SDK** or
the **raw API**. Links point to the section that explains each requirement.

## Contract conformance

- [ ] `GET /v1/capabilities` and `GET /v1/health` are implemented and return valid bodies.
- [ ] At least one payment-initiation endpoint is implemented — `authorize` (two-phase) **or**
      `sale` (single-phase).
- [ ] Every operation you support returns business outcomes as **HTTP 200 + `status`**
      (`SUCCESS`/`PENDING`/`DECLINED`/`RETRIABLE`/`UNKNOWN`).
      [↳](/payments/api/)
- [ ] HTTP `4xx`/`5xx` are used **only** for infrastructure errors; transient failures use
      `5xx` or `200 RETRIABLE` (never `4xx`).
      [↳](/payments/api/)
- [ ] Operations you don't implement return `501` (raw-API services return it explicitly; the SDK
      does it automatically for un-overridden methods).
- [ ] `transactionReference` is returned on every `SUCCESS` and `PENDING` response.

## Capabilities declared honestly

- [ ] `GET /v1/capabilities` lists **only** operations you've implemented **and** tested.
      [↳](03-payment-flows.md#2-capabilities--dispatch)
- [ ] Two-phase integrations declare `CAPTURE` and implement `authorize` + `capture` (+
      `reverse`); single-phase declare `SALE` and implement `sale`.
- [ ] Refunds (`DIRECT_REFUND`), redirect (`REDIRECT_FLOW`), webhooks (`DIRECT_WEBHOOK`), and
      status polling (`STATUS_POLLING`) are declared only if actually supported.
- [ ] Wallet/tokenization capabilities, if declared, are handled inside `authorize`/`sale`.

## Idempotency

- [ ] Mutating operations are **idempotent** — a retried request never double-charges. Dedupe on
      the `Idempotency-Key` header (raw API) or on `paymentId` / your PSP's own idempotency (SDK,
      where the header isn't passed to the `PaymentIntegration` method).
- [ ] A **replay** of the same logical operation returns the same response / has no second side
      effect (verified with a real duplicate request). [↳](/payments/api/)

## Asynchronous flow & result callback (if you use 3DS/redirect)

- [ ] `authorize`/`sale` returns `PENDING` with a `redirectUrl` **and** a `transactionReference`.
- [ ] Your PSP webhook endpoint **verifies the signature** before acting.
- [ ] You POST the result to `/gate/integration/{integrationId}/result` using the **inbound**
      token and an `Idempotency-Key`. [↳](03-payment-flows.md#42-the-result-callback)
- [ ] The callback's `transactionReference` matches the one from the `PENDING` response.
- [ ] **Tested end-to-end against a real PENDING transaction** — the suspended payment resumes.
- [ ] Callback retries until `200`; replays are no-ops.

## Card enrollment (if you declare `TOKENIZATION`)

- [ ] `POST /v1/enroll` returns `PENDING` with **both** `redirectUrl` and `transactionReference`,
      or `SUCCESS` with a `card` object. [↳](03-payment-flows.md#5-card-enrollment-and-tokenization)
- [ ] The card token is delivered **only** via
      `POST /gate/integration/{integrationId}/enrollment-result` — never appended to `returnUrls`.
- [ ] The callback echoes both `enrollmentId` and `transactionReference`, and uses the **inbound**
      token plus an `Idempotency-Key`.
- [ ] `card.maskedPan` is a display mask; the full card number is never sent to LoyaltyPlant.
- [ ] Only terminal statuses are sent (`SUCCESS` with `card`, or `DECLINED`); a `DECLINED`
      enrollment is never followed by a later `SUCCESS`.
- [ ] **Tested end-to-end**: add card → hosted page → callback → the card is visible to the
      customer; a replayed callback is a no-op `200`.
- [ ] `POST /v1/inquire-enrollment-status` is implemented (resolving by `enrollmentId`) so a lost
      callback doesn't strand a card that your PSP already bound.
- [ ] Payments then work with `paymentInstrument.type = CARD_TOKEN`, and wallet payloads are
      forwarded verbatim if you declared `APPLE_PAY`/`GOOGLE_PAY`.
- [ ] Neither `token` nor `walletPayload` is ever written to logs.

## Error handling

- [ ] PSP decline codes are mapped to standard `PaymentErrorCode` values where possible
      (unmappable → passed through; LoyaltyPlant falls back to `GATEWAY_ERROR`).
      [↳](/payments/api/)
- [ ] Business declines surface as `200 DECLINED` (not as an HTTP error, nor a generic `UNKNOWN`).
- [ ] Wire values are sent exactly as in the spec (e.g. `FAILURE_3DS_CHECK`).

## Security

- [ ] TLS with a valid certificate; no plaintext.
- [ ] The **outbound** token is validated (constant-time) on every `/v1/*` request; mismatches
      → `401`. [↳](02-general-requirements.md#4-token-directionality-read-this-twice)
- [ ] The **inbound** token (callback credential) is stored as a secret and never logged.
- [ ] Tokens are not in source control; rotation is supported (overlapping old+new).
- [ ] No full PANs/tokens/secrets in logs or `reason` strings.

## Health, limits & reconciliation

- [ ] `GET /v1/health` reflects **real** PSP connectivity (returns `DOWN`/`503` when unhealthy).
- [ ] Synchronous endpoints respond well within the request timeout (default 10 s).
      [↳](02-general-requirements.md#7-operational-limits)
- [ ] The service behaves correctly under the configured rate/concurrency limits.
- [ ] If you use async flows, `STATUS_POLLING` (`inquire-status`) is implemented for
      reconciliation. [↳](03-payment-flows.md#3-reconciliation-status-inquiry)

## Onboarding & certification

- [ ] All values are provisioned by LoyaltyPlant: `integrationId`, outbound token, inbound
      token, `settings`, limits, callback host. [↳](02-general-requirements.md#3-what-loyaltyplant-provisions-for-you)
- [ ] Separate credentials are used for staging vs production.
- [ ] LoyaltyPlant's certification test cases pass in staging.
- [ ] Test report with `orderId` for all test cases are provided to and validated by LoyaltyPlant.
- [ ] Demo is conducted for LoyaltyPlant.
- [ ] LoyaltyPlant confirms the integration is certified.

---

When every box is checked and certification passes, you're ready for go-live.

---

← Back to [Overview](01-overview-and-concepts.md)
