diff --git a/JPMorgan.idric b/JPMorgan.idric new file mode 120000 index 0000000..a3e5cd4 --- /dev/null +++ b/JPMorgan.idric @@ -0,0 +1 @@ +checkpoints/jpmorgan/idric/JPMorgan.idric \ No newline at end of file diff --git a/README.md b/README.md index 8dbb14f..f4db762 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repository is the consolidation point for the CLI/API-access programs that - `Economist.idric` — Economist API checkpoint. - `Ft.idric` — Financial Times API checkpoint. - `Guardian.idric` — Guardian API checkpoint. +- `JPMorgan.idric` — J.P. Morgan Online Payments API checkpoint; complete v2 endpoint inventory plus a first read-only payment-retrieval slice. - `Nyt.idric` — New York Times API checkpoint. - `Reddit.idric` — Reddit Data API checkpoint, with a synthetic fixture and manual receipt. - `Stripe.idric` — Stripe API checkpoint; first slice is pinned, read-only Balance access with a synthetic fixture and manual receipt. @@ -28,6 +29,6 @@ Where these clients need networking, ICU/Idric-Net remains the intended transpor ## Tests -`make test` runs the existing Amazon and AbeBooks smoke tests. Reddit and Stripe have separate manual compiler checkpoints under `checkpoints/reddit/check` and `checkpoints/stripe/check`; they are not part of `make test` while named Idriç holes remain. +`make test` runs the existing Amazon and AbeBooks smoke tests. Reddit, Stripe, and J.P. Morgan have separate manual compiler checkpoints under `checkpoints/`; they are not part of `make test` while named Idriç holes remain. See `PROVENANCE.md` for the source branches copied into this repository. diff --git a/checkpoints/jpmorgan/README.md b/checkpoints/jpmorgan/README.md new file mode 100644 index 0000000..713a74d --- /dev/null +++ b/checkpoints/jpmorgan/README.md @@ -0,0 +1,146 @@ +# J.P. Morgan Online Payments CLI compiler checkpoint + +This directory is the Idriç checkpoint for J.P. Morgan's Online Payments API. It is intentionally scoped to Online Payments rather than every API on the J.P. Morgan Payments portal. + +The first slice is read-only. The API has operations that create, update, capture, refund, verify, and fraud-score payment activity; money-moving writes do not belong in the first transport/compiler checkpoint. + +## Command contract + +```text +jpmorgan version +jpmorgan endpoints +jpmorgan url payment TRANSACTION_ID +jpmorgan fixture FILE +jpmorgan payment TRANSACTION_ID +``` + +### `version` + +Print the Online Payments API specification version used for this pass: + +```text +2.19.0 +``` + +The URL major version remains `v2`; the specification release is tracked separately because J.P. Morgan updates fields and enums within that major-version surface. + +### `endpoints` + +No network. Print the complete Online Payments endpoint inventory captured in `SURFACE.md` as TSV: + +```text +method path action +``` + +This includes all 16 method/path operations in the current surface pass: payments, captures, refunds, verifications, and fraud checks. + +### `url payment` + +No network. Build the mock-environment URL for transaction-id retrieval: + +```text +https://api-mock.payments.jpmorgan.com/api/v2/payments/TRANSACTION_ID +``` + +### `fixture` + +No network. Read an Online Payments payment-response-shaped JSON file and emit TSV. + +The committed synthetic fixture is: + +```text +fixture/payment.json +``` + +Expected output is: + +```text +fixture/expected.tsv +``` + +Columns are: + +```text +transaction_id request_id state status code amount currency +``` + +`amount` remains the integer value returned by the API; the checkpoint does not guess a decimal convention from the currency. + +### `payment` + +Retrieve one payment by transaction ID from the mock Online Payments v2 base, decode the same shape as the fixture, and emit the same TSV. + +The process environment supplies: + +```text +JPMORGAN_ACCESS_TOKEN=... +JPMORGAN_MERCHANT_ID=... +``` + +The request seam carries: + +```text +Authorization: Bearer ACCESS_TOKEN +merchant-id: MERCHANT_ID +request-id: freshly generated UUID +Accept: application/json +``` + +Token acquisition is deliberately outside this first slice. J.P. Morgan uses OAuth 2.0 client credentials; a separately acquired bearer token is enough to exercise the client without putting client secrets into this repository or command-line arguments. + +## Idriç and ICU + +`idric/JPMorgan.idric` is the implementation target. + +ICU/Idric-Net remains the networking boundary. The J.P. Morgan checkpoint adds no private socket/TLS implementation and no curl fallback. The generic transport needs to support caller-supplied headers; this is the same underlying seam already exposed by the Reddit and Stripe checkpoints. + +The remaining named holes are intentionally visible: + +- file input; +- Online Payments JSON decoding; +- environment access; +- UUID request-id generation; +- authenticated ICU GET with caller-supplied headers. + +Do not replace these with a hidden untyped implementation merely to make the checkpoint green. + +## Manual receipt + +Run: + +```text +ysh check +``` + +or: + +```text +IDRIC=/opt/Idric/build/exec/idris2 ysh check +``` + +The runner emits only: + +```text +PASS checkpoint +FAIL checkpoint +SKIP checkpoint +``` + +A focused failure at a named hole is useful evidence of the remaining compiler/library boundary. + +## Checkpoint ladder + +1. source parses/checks; +2. `version` prints `2.19.0`; +3. `endpoints` prints the byte-stable 16-operation inventory; +4. `url payment ID` prints the exact mock v2 URL; +5. `fixture` reads the synthetic file; +6. `fixture` decodes a payment response and matches `expected.tsv` byte-for-byte; +7. `payment` reads the bearer token and merchant ID from the environment; +8. request-id generation produces a fresh value; +9. ICU sends Authorization, merchant-id, request-id, and Accept headers; +10. live mock payment retrieval uses the same TSV contract as the fixture; +11. add request-id lookup forms for payments/captures/refunds/verifications/fraud checks; +12. add mutating commands only after request bodies, consequence-specific verbs, and duplicate-request behavior have fixtures and tests. + +See `SURFACE.md` for the audited endpoint list and sequencing. diff --git a/checkpoints/jpmorgan/SURFACE.md b/checkpoints/jpmorgan/SURFACE.md new file mode 100644 index 0000000..843bc07 --- /dev/null +++ b/checkpoints/jpmorgan/SURFACE.md @@ -0,0 +1,149 @@ +# J.P. Morgan Online Payments API surface pass + +Audit baseline: 2026-09-06. + +Online Payments API specification release observed in the official changelog: `2.19.0` (2026-08-04). + +This inventory is for the **Online Payments API only**. Checkout, Disputes, Wallet Decryption, Merchant Configuration, Treasury, Embedded Payments, and the other J.P. Morgan Payments APIs are separate surfaces and are not silently mixed into this client. + +Canonical references: + +- API reference: https://developer.payments.jpmorgan.com/api/home +- Online Payments overview: https://developer.payments.jpmorgan.com/api/commerce/online-payments/overview +- Online Payments changelog: https://developer.payments.jpmorgan.com/api/commerce/online-payments/online-payments/changelog +- Online Payments response codes: https://developer.payments.jpmorgan.com/api/commerce/online-payments/online-payments/error-codes +- OAuth authentication: https://developer.payments.jpmorgan.com/api/commerce/online-payments/oauth-authentication +- Quick start: https://developer.payments.jpmorgan.com/docs/quick-start +- authorize/capture guide: https://developer.payments.jpmorgan.com/docs/commerce/online-payments/capabilities/online-payments/how-to/auth-and-capture-payment +- refund guide: https://developer.payments.jpmorgan.com/docs/commerce/online-payments/capabilities/online-payments/how-to/refund-payment +- verification guide: https://developer.payments.jpmorgan.com/docs/commerce/online-payments/capabilities/online-payments/how-to/verify-payment +- fraud-score guide: https://developer.payments.jpmorgan.com/docs/commerce/online-payments/capabilities/online-payments/how-to/fraud-scores + +## Wire contract + +The documented mock major-version base is: + +```text +https://api-mock.payments.jpmorgan.com/api/v2 +``` + +J.P. Morgan documents Online Payments versioning in the URL prefix (`v2`). The product's specification has its own release number; the latest changelog entry in this pass is `2.19.0`. + +The Quick Start request carries JSON and uses OAuth bearer authentication plus merchant and request identifiers: + +```text +Accept: application/json +Content-Type: application/json +Authorization: Bearer ACCESS_TOKEN +merchant-id: MERCHANT_ID +request-id: REQUEST_ID +minorVersion: ... +``` + +The first read-only checkpoint does not acquire OAuth tokens itself. Keep client credentials/private keys outside source. `JPMORGAN_ACCESS_TOKEN` supplies an already-issued bearer token and `JPMORGAN_MERCHANT_ID` supplies the processing merchant identifier. + +## Complete Online Payments endpoint inventory + +The current surface pass yields 16 HTTP operations across payments, captures, refunds, verifications, and fraud checks. + +| Method | Path | Meaning | First CLI phase | +| --- | --- | --- | --- | +| `POST` | `/payments` | Create/authorize a payment; capture can be immediate depending on request | later write | +| `GET` | `/payments` | Retrieve a payment by `requestId` query parameter | read | +| `GET` | `/payments/{id}` | Retrieve a payment by transaction ID | first live read | +| `PATCH` | `/payments/{id}` | Update a payment; documented uses include incremental authorization, reversal/void, and reauthorization | later write | +| `POST` | `/payments/{id}/captures` | Capture a previously authorized payment; supports split/multiple capture cases | later write | +| `GET` | `/captures` | Retrieve capture/payment details by `requestId` | read | +| `GET` | `/captures/{id}` | Retrieve capture/payment details by transaction ID | read | +| `POST` | `/refunds` | Create full, partial, standalone, or capture-related refund | later write | +| `GET` | `/refunds` | Retrieve a refund by `requestId` | read | +| `GET` | `/refunds/{id}` | Retrieve a refund by transaction ID | read | +| `POST` | `/verifications` | Verify a payment instrument | later write-like request, no money movement | +| `GET` | `/verifications` | Retrieve verification details by `requestId` | read | +| `GET` | `/verifications/{id}` | Retrieve verification details by transaction ID | read | +| `POST` | `/fraudcheck` | Request a fraud score/check | later request | +| `GET` | `/fraudcheck` | Retrieve fraud-check details by `requestId` | read | +| `GET` | `/fraudcheck/{id}` | Retrieve fraud-check details by transaction ID | read | + +The no-network `jpmorgan endpoints` command emits exactly this operation set in machine-friendly TSV form. + +## Identifier model + +Do not collapse J.P. Morgan's identifiers into one generic `id` in the normalized model. Important values include: + +- `transactionId` — identifies a payment/refund/verification/fraud transaction and is used by path retrieval forms; +- `requestId` — caller/request identifier, also usable to retrieve several resource types through query forms; +- `paymentRequestId` — groups payment request state; +- `authorizationId` — identifies an authorization; +- `captureId` — identifies a capture; +- `refundId` — identifies a refund where present; +- `networkTransactionId` — network-side identifier, not interchangeable with J.P. Morgan's transaction ID; +- `merchantOrderNumber` — merchant business/order reference. + +The first fixture keeps `transactionId` and `requestId` separate. Later capture/refund fixtures should preserve their nested identifiers rather than flattening them into one opaque string. + +## Response model + +The Quick Start payment response exposes a useful stable diagnostic spine: + +```text +transactionId +requestId +transactionState +responseStatus +responseCode +responseMessage +amount +currency +``` + +The first TSV contract uses all except the human-readable response message. Keep the integer amount intact; currency-specific decimal presentation belongs above the wire model. + +`transactionState` and `responseStatus` answer different questions. The former describes transaction lifecycle state; the latter reports the request/business result (`SUCCESS`, `DENIED`, or `ERROR`). Do not merge them into one status field. + +## Read side first + +The first implementation slice is deliberately narrow: + +1. print the specification release; +2. print every endpoint/method pair; +3. construct a transaction-ID payment retrieval URL; +4. decode a synthetic payment response; +5. retrieve one payment from the mock environment using an already-issued token. + +Then add the other GET forms before implementing payment mutations. This exercises URL construction, OAuth headers, request IDs, JSON decoding, and stable CLI output without accidentally creating or moving money. + +## Mutation boundary + +The write/request surfaces are not interchangeable generic POSTs: + +- `POST /payments` may authorize and/or capture money; +- `PATCH /payments/{id}` can alter an existing authorization, including reauthorization and reversal/void cases; +- `POST /payments/{id}/captures` completes all or part of a manual authorization and can participate in multiple-capture shipment flows; +- `POST /refunds` can return captured funds and has full, partial, standalone, and multi-capture cases; +- `POST /verifications` checks a payment instrument without being a payment; +- `POST /fraudcheck` asks for a fraud decision/score rather than processing the payment itself. + +Give each one a named CLI verb and typed request model. Do not expose a primary `post PATH JSON` interface that erases the consequence differences. + +`request-id` is also part of duplicate-request handling, so mutation support needs explicit tests for retry/duplicate behavior before it is treated as safe. + +## Expansion order + +1. Endpoint inventory and exact URL construction. +2. Synthetic payment retrieval fixture. +3. Live mock `GET /payments/{id}` through ICU/Idric-Net. +4. `GET /payments?requestId=...`. +5. Capture GET forms and capture-specific fixture. +6. Refund GET forms and refund-specific fixture. +7. Verification GET forms and fixture. +8. Fraud-check GET forms and fixture. +9. Shared OAuth/merchant/request-id header construction. +10. Typed request bodies for `POST /verifications` and `POST /fraudcheck`. +11. `POST /payments` only in mock/test usage with duplicate-request receipts. +12. `POST /payments/{id}/captures` with full/partial/multiple-capture fixtures. +13. `PATCH /payments/{id}` with separate fixtures for each supported update intent. +14. `POST /refunds` with full/partial/standalone/multi-capture fixtures. +15. Production base/configuration only after the client-specific environment contract is known; do not guess production endpoints from the mock hostname. + +Each resource follows the same receipt pattern: exact URL and headers, synthetic fixture, byte-stable output, mock/live transport, then consequence-specific write tests. diff --git a/checkpoints/jpmorgan/check b/checkpoints/jpmorgan/check new file mode 100755 index 0000000..9e74c65 --- /dev/null +++ b/checkpoints/jpmorgan/check @@ -0,0 +1,57 @@ +#!/usr/bin/env ysh + +# Manual Idriç J.P. Morgan Online Payments checkpoint runner. +# Deliberately not part of the root make test while named holes remain. + +set -u + +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +IDRIC=${IDRIC:-idris2} + +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +status=0 + +available() { + case "$1" in + */*) test -x "$1" ;; + *) command -v "$1" >/dev/null 2>&1 ;; + esac +} + +pass() { + printf 'PASS\t%s\n' "$1" +} + +fail() { + printf 'FAIL\t%s\n' "$1" + status=1 +} + +skip() { + printf 'SKIP\t%s\n' "$1" +} + +show_diagnostics() { + for path in "$@"; do + if test -s "$path"; then + sed 's/^/ /' "$path" + fi + done +} + +if ! available "$IDRIC"; then + skip "idric (not found: $IDRIC)" + exit 0 +fi + +if "$IDRIC" --check "$HERE/idric/JPMorgan.idric" \ + >"$TMP/idric" 2>"$TMP/idric.err"; then + pass 'idric/check' +else + fail 'idric/check' + show_diagnostics "$TMP/idric" "$TMP/idric.err" +fi + +exit "$status" diff --git a/checkpoints/jpmorgan/fixture/expected.tsv b/checkpoints/jpmorgan/fixture/expected.tsv new file mode 100644 index 0000000..23eac42 --- /dev/null +++ b/checkpoints/jpmorgan/fixture/expected.tsv @@ -0,0 +1,2 @@ +transaction_id request_id state status code amount currency +11111111-2222-4333-8444-555555555555 aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee CLOSED SUCCESS APPROVED 1000 USD diff --git a/checkpoints/jpmorgan/fixture/payment.json b/checkpoints/jpmorgan/fixture/payment.json new file mode 100644 index 0000000..b9edf93 --- /dev/null +++ b/checkpoints/jpmorgan/fixture/payment.json @@ -0,0 +1,48 @@ +{ + "transactionId": "11111111-2222-4333-8444-555555555555", + "requestId": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", + "transactionState": "CLOSED", + "responseStatus": "SUCCESS", + "responseCode": "APPROVED", + "responseMessage": "Synthetic fixture approved", + "paymentMethodType": { + "card": { + "cardType": "VI", + "cardTypeName": "VISA", + "maskedAccountNumber": "411111XXXXXX1111" + } + }, + "captureMethod": "NOW", + "amount": 1000, + "currency": "USD", + "remainingRefundableAmount": 1000, + "remainingAuthAmount": 0, + "merchant": { + "merchantId": "000000000000", + "merchantSoftware": { + "companyName": "Synthetic Fixture", + "productName": "Idric CLI" + }, + "merchantCategoryCode": "0000" + }, + "paymentRequest": { + "paymentRequestId": "11111111-2222-4333-8444-555555555555", + "paymentRequestStatus": "CLOSED", + "authorizations": [ + { + "authorizationId": "11111111-2222-4333-8444-555555555555", + "amount": 1000, + "transactionStatusCode": "CAPTURED", + "authorizationType": "INITIAL" + } + ], + "captures": [ + { + "captureId": "11111111-2222-4333-8444-555555555555", + "amount": 1000, + "transactionStatusCode": "CLOSED", + "captureRemainingRefundableAmount": 1000 + } + ] + } +} diff --git a/checkpoints/jpmorgan/idric/JPMorgan.idric b/checkpoints/jpmorgan/idric/JPMorgan.idric new file mode 100644 index 0000000..840c186 --- /dev/null +++ b/checkpoints/jpmorgan/idric/JPMorgan.idric @@ -0,0 +1,162 @@ +module JPMorgan + +import System + + +api_base : String +api_base = "https://api-mock.payments.jpmorgan.com/api/v2" + +api_spec_version : String +api_spec_version = "2.19.0" + +header : String +header = "transaction_id\trequest_id\tstate\tstatus\tcode\tamount\tcurrency" + +endpoints_text : String +endpoints_text = + "method\tpath\taction\n" ++ + "POST\t/payments\tcreate-payment\n" ++ + "GET\t/payments\tretrieve-payment-by-request-id\n" ++ + "GET\t/payments/{id}\tretrieve-payment-by-transaction-id\n" ++ + "PATCH\t/payments/{id}\tupdate-payment\n" ++ + "POST\t/payments/{id}/captures\tcapture-payment\n" ++ + "GET\t/captures\tretrieve-capture-by-request-id\n" ++ + "GET\t/captures/{id}\tretrieve-capture-by-transaction-id\n" ++ + "POST\t/refunds\tcreate-refund\n" ++ + "GET\t/refunds\tretrieve-refund-by-request-id\n" ++ + "GET\t/refunds/{id}\tretrieve-refund-by-transaction-id\n" ++ + "POST\t/verifications\tverify-payment-instrument\n" ++ + "GET\t/verifications\tretrieve-verification-by-request-id\n" ++ + "GET\t/verifications/{id}\tretrieve-verification-by-transaction-id\n" ++ + "POST\t/fraudcheck\tfraud-check\n" ++ + "GET\t/fraudcheck\tretrieve-fraud-check-by-request-id\n" ++ + "GET\t/fraudcheck/{id}\tretrieve-fraud-check-by-transaction-id" + +usage : String +usage = "usage: jpmorgan {version | endpoints | url payment ID | fixture FILE | payment ID}" + + +choice command one_of + show_version + show_endpoints + show_payment_url String + fixture String + payment String + + +record PaymentSnapshot where + constructor make_payment_snapshot + transaction_id : String + request_id : String + state : String + status : String + code : String + amount : Integer + currency : String + + +-- Named holes are intentional compiler/library watchpoints. Do not replace +-- them with curl, Python, or another untyped transport merely to make the +-- checkpoint appear green. + +read_fixture : String → IO (Either String String) +read_fixture path = ?read_fixture_text + +decode_payment : String → Either String PaymentSnapshot +decode_payment body = ?decode_jpmorgan_payment + +environment_value : String → IO (Maybe String) +environment_value name = ?read_environment_value + +new_request_id : IO String +new_request_id = ?uuid_v4_text + +-- Online Payments uses OAuth bearer authentication plus merchant-id and +-- request-id headers. ICU/Idric-Net should own the generic header transport. +authenticated_icu_get : String → String → String → String → IO (Either String String) +authenticated_icu_get token merchant_id request_id url = ?icu_get_with_request_headers + + +payment_url : String → String +payment_url transaction_id = api_base ++ "/payments/" ++ transaction_id + + +parse_command : List String → Either String command +parse_command ["version"] = Right show_version +parse_command ["endpoints"] = Right show_endpoints +parse_command ["url", "payment", transaction_id] = Right (show_payment_url transaction_id) +parse_command ["fixture", path] = Right (fixture path) +parse_command ["payment", transaction_id] = Right (payment transaction_id) +parse_command _ = Left usage + + +print_payment : String → IO () +print_payment body = + case decode_payment body of + Left problem => do + putStrLn ("jpmorgan: " ++ problem) + exitFailure + Right snapshot => do + putStrLn header + putStrLn (snapshot.transaction_id ++ "\t" ++ + snapshot.request_id ++ "\t" ++ + snapshot.state ++ "\t" ++ + snapshot.status ++ "\t" ++ + snapshot.code ++ "\t" ++ + show snapshot.amount ++ "\t" ++ + snapshot.currency) + + +run : command → IO () +run show_version = + putStrLn api_spec_version + +run show_endpoints = + putStrLn endpoints_text + +run (show_payment_url transaction_id) = + putStrLn (payment_url transaction_id) + +run (fixture path) = do + outcome ← read_fixture path + case outcome of + Left problem => do + putStrLn ("jpmorgan: " ++ problem) + exitFailure + Right body => print_payment body + +run (payment transaction_id) = do + maybe_token ← environment_value "JPMORGAN_ACCESS_TOKEN" + maybe_merchant_id ← environment_value "JPMORGAN_MERCHANT_ID" + case maybe_token of + Nothing => do + putStrLn "jpmorgan: missing JPMORGAN_ACCESS_TOKEN" + exitFailure + Just token => + case maybe_merchant_id of + Nothing => do + putStrLn "jpmorgan: missing JPMORGAN_MERCHANT_ID" + exitFailure + Just merchant_id => do + request_id ← new_request_id + outcome ← authenticated_icu_get token merchant_id request_id (payment_url transaction_id) + case outcome of + Left problem => do + putStrLn ("jpmorgan: " ++ problem) + exitFailure + Right body => print_payment body + + +main : IO () +main = do + arguments ← getArgs + case arguments of + _ :: rest => + case parse_command rest of + Left problem => do + putStrLn problem + exitFailure + Right value => run value + [] => do + putStrLn usage + exitFailure