hardlink/CREDITCALL_STREAMING.md

125 lines
8.2 KiB
Markdown

# CreditCall payment streaming
Deploy ChipDNAClientCLI, then hardlink, then Operafyne. No database or configuration
migration is needed. The existing CreditCall provider selection enables the new
SALE and PREAUTH routes; CreditCall does not implement the generic payment provider interface.
## Contracts
`POST /api/payment/sale` accepts the existing JSON sale request, for example
`{"amount":1234,"confirmNo":"BOOKING-123","currency":"GBP"}`. Amount remains in
minor units. CreditCall uses its existing SDK transaction reference generation.
`POST /api/payment/preauth` accepts string fields `amount`, `transactionType`
and `checkoutDate`. Positive preauth sends the existing `Sale` input and checkout
string; zero-value verification sends `{"amount":"","transactionType":"AccountVerification","checkoutDate":""}`.
The returned `ACCOUNT VERIFICATION` type is matched case-insensitively and succeeds
without persistence. Returned approved `SALE` schedules existing SQL persistence
using provider-returned `TOTAL_AMOUNT`, never the request amount. PREAUTH does not
confirm. Checkout remains departure-derived midnight UTC in the existing string
format; SQL's existing date conversion and 48-hour release calculation are unchanged.
For CreditCall, responses contain newline-delimited JSON:
```json
{"type":"status","code":"PAYMENT_PRESENT_CARD"}
{"type":"result","result":{"outcome":"approved","message":"Payment approved","httpStatus":200,"status":{"code":200,"message":"Approved"},"transactionReference":"native-reference","cardType":"Visa","maskedCardNumber":"************1234","expiryDate":"1228","cardHash":"existing-hash","cardReference":"existing-card-reference"}}
```
`outcome` is `approved`, `declined`, `cancelled`, `timeout`, or `error`. Approval
is produced only by the shared CreditCall transaction/finalization core, including
SALE confirmation behavior; PREAUTH uses its existing unconfirmed-result rules. `httpStatus` preserves the equivalent legacy
operation status even after streaming commits HTTP 200. `status` preserves the
existing processor `StatusRec`. Failure `message` preserves the plain description
used by the legacy flow, including its existing interpretation quirks.
Card fields are emitted only on success. Unmasked or unexpected card-number data
is omitted; encoding is not masking. Receipts and raw SDK parameter bags are not
sent to Operafyne. The new adapter never builds a redirect URL. Dojo and PayBridge
continue using their existing `{"type":"result","response":...}` contract.
Hardlink calls `POST /start-transaction-stream/` on ChipDNAClientCLI with
`{"amount":"1234","transactionType":"Sale"}`. Its NDJSON consists of allowlisted
`status` frames (`source`, `value`), a single `result` containing allowlisted SDK
fields, or a sanitized `error`. `TRANSACTION_TYPE` and optional `TOTAL_AMOUNT`
are included only when returned by the provider. They are never synthesized and
are not added to the kiosk-facing result. AccountVerification normally omits
`TOTAL_AMOUNT`. Receipt XML may be a JSON string field needed by
hardlink's existing receipt handler; it is never a raw line in the stream.
SALE confirmation continues through the existing, separate XML endpoint.
## Lifetime and compatibility
- `/start-transaction/`, `/takepayment`, and `/takepreauth` retain their existing
external contracts. Both SALE and PREAUTH now stream to Operafyne; existing SQL
persistence/release behavior remains unchanged.
- Operafyne uses a private 120-second CreditCall client for SALE and PREAUTH.
This is only the kiosk wait boundary; Dojo/PayBridge clients are unchanged.
Timeout/cancellation is technical, not an authoritative decline or automatically
retryable result. It never triggers a second start or legacy endpoint fallback.
- Validation uses the inbound request. At financial dispatch handoff, hardlink
detaches cancellation with `context.WithoutCancel`. Inbound cancellation and
write failures control only delivery; result processing, SALE confirmation,
receipts and PREAUTH persistence scheduling continue independently.
- Start and each SALE confirmation call retain their independent 300-second timeout.
Confirmation still uses two attempts, retrying transport/read errors with the
existing two-second delay. The generic whole-operation timeout is not used.
- Kiosk cancellation or failed/blocked kiosk delivery does not cancel upstream reading,
confirmation, receipt handling, or PREAUTH persistence scheduling. Progress queues may drop hints when full;
final results have a separate slot and are delivered at most once.
- Only the response writer writes frames. Transaction execution never waits
for progress delivery. No additional delivery timer or whole-operation deadline
shortens the existing HTTP call bounds.
- Native SDK 3.17 updates use `UPDATE`; card notifications use `NOTIFICATION`,
retained as `CARD_STATUS` on the internal wire. Normal progress does not require
a reference: it is accepted only inside the existing serial transaction's active
observational window. A supplied nonempty reference must match. The window opens
immediately before the SDK start call and closes immediately at
`TransactionFinished`; finalization never waits for card removal.
- `CardRemovalRequested` and `CardRemovalEnforced` follow the same active-window
rule as other progress, including when no reference is supplied. Real Miura
insertion-recovery sequences can repeat present-card and remove-card prompts
within one transaction; these repeated prompts are not deduplicated. Outside
the active window they are discarded. `Removed` remains omitted even with a
reference, and the existing `Inserted` mapping is unchanged.
- Ownership is best-effort UI observation. SDK 3.17 does not establish that every
queued reference-less callback, including removal, has drained before another transaction
starts. Such a callback may briefly display stale progress in a later active
window. This accepted limitation must never affect success, decline, cancellation,
timeout outcomes, confirmation, retry, receipts, PMS posting, or business state.
- After timeout, an ambiguous start error, an asynchronous SDK error during an
active window, or reference reuse/overlap, progress remains suppressed for that
`Client` lifetime across SALE and PREAUTH. New transactions, elapsed time, callbacks and automatic
reconnect do not reset the guard. Payments remain enabled.
- Only the exact synchronous `ClientNotConnectedToServer` error safely clears an
unsuppressed window and releases its unused reference: SDK 3.17 `StartCommand`
returns it before `SendRequest`. Validation errors, mixed errors and all other
unproven errors suppress observation. Safe pre-dispatch rejection never clears
an existing suppression latch.
- Progress cannot approve, decline, confirm, cancel, or retry a transaction.
`OnlineAuthCompleted` means waiting; `Removed` and unknown events are omitted.
## Automated verification
In hardlink, use `go test -count=1 -skip '^Test_SendMail$' ./...` to exclude the
existing test that sends real email. Focused tests cover legacy parity, malformed
XML/NDJSON, retained confirmation fields and receipts, retries, disconnects,
backpressure, field filtering, and conservative status mapping. Race checks cover
the changed handler and mapper packages. No test proves absence of orphan holds.
In ChipDNAClientCLI, build `ChipDnaClient.sln` and `Tests/StreamingTests.csproj`
with MSBuild, then run `Tests/bin/Debug/ChipDNAClient.StreamingTests.exe`. The
test executable links the production streaming code and needs no terminal.
In Operafyne, run `go test -count=1 ./...`; the service tests cover structured
CreditCall SALE/PREAUTH, unchanged legacy requests, the 120-second UX boundary,
cancellation after dispatch, failure/retry parity, and existing
Dojo/PayBridge response handling. Run `go vet ./...`, `go build ./...`, and
`git diff --check` in both Go repositories.
Physical account-verification testing confirmed approved `ACCOUNT VERIFICATION`
with no `TOTAL_AMOUNT`. A monetary `/takepayment` test confirmed returned minor
units; positive PREAUTH through the new transport still needs physical Miura
validation. Automated tests do not replace that check. Receipt fields such as
`NoChargeDeclaration` retain the existing provider-entry rendering behavior.