feat: Open Payments aggregator mock for acctest
openpayments-mock / check (push) Has been skipped
openpayments-mock / vulnerabilities (push) Has been skipped
openpayments-mock / build (push) Failing after 1m8s

Tiny in-memory stand-in for the Open Payments PSD2/BerlinGroup
aggregator, extracted from acctest where it lived inline (which
couldn't docker-build on the self-hosted CI runner — /.docker/buildx
was read-only). A separate repo:

- lets the mock build + publish its image via the standard shiny CI
  flow, pulled like any other service
- keeps acctest focused on test infra, not service source
- gives the mock its own versioned release lifecycle

**Endpoints** (see README for the full list)
- OAuth: POST /token → canned bearer token.
- AIS: aspsps catalog, consent lifecycle, /authorize self-redirect SCA
  stub, payment + card accounts + transactions.
- PIS: sepa-credit-transfers initiate + status (RCVD → PDNG → ACSC;
  creditor name 'REJECT ME' → RJCT), signing baskets (RCVD → ACCP →
  ACSC; basket ACSC advances all linked payments).
- Admin: /admin/* endpoints let acctest force deterministic
  transitions, seed transactions, reset state, override consent
  expiry.

**CI**
Standard shiny ci.yaml: check (go build + vet), vulnerabilities
(govulncheck), build (buildtools publishes oci.unbound.se/shiny/
openpayments-mock:${COMMIT}). No deploy job — image is consumed by
acctest only.

**k8s/deploy.yaml**
Deployment + Service on port 8080 with /healthz readiness/liveness.
acctest's infra manifest will reference the published tag.
This commit is contained in:
2026-04-20 22:11:12 +02:00
commit 898023c794
9 changed files with 275 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
name: openpayments-mock
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
if: gitea.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Build
run: go build ./...
- name: Vet
run: go vet ./...
vulnerabilities:
if: gitea.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Check vulnerabilities
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
build:
runs-on: ubuntu-latest
env:
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_CONTENT }}
GITEA_REPOSITORY: ${{ gitea.repository }}
steps:
- uses: actions/checkout@v6
- uses: buildtool/setup-buildtools-action@v1
- name: Build and push
run: unset GITEA_TOKEN && build && push
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: deployment-artifacts
path: release/
retention-days: 30
- name: Trigger acceptance tests
if: gitea.event_name == 'pull_request'
env:
GITEA_TOKEN: ${{ secrets.ACCTEST_TOKEN }}
GITEA_URL: ${{ gitea.server_url }}
run: |
curl --fail-with-body -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"ref": "main",
"inputs": {
"trigger_repo": "openpayments-mock",
"trigger_commit": "${{ gitea.sha }}",
"trigger_run": "${{ gitea.run_id }}"
}
}' \
"${GITEA_URL}/api/v1/repos/shiny/acctest/actions/workflows/ci.yaml/dispatches"
+4
View File
@@ -0,0 +1,4 @@
openpayments-mock
service
coverage.txt
coverage.out
+33
View File
@@ -0,0 +1,33 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args:
- --allow-multiple-documents
- id: check-added-large-files
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.24.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies: ['@commitlint/config-conventional']
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.1
hooks:
- id: go-mod-tidy
- id: go-imports
args:
- -local
- gitea.unbound.se/shiny/openpayments-mock
- repo: https://github.com/lietu/go-pre-commit
rev: v1.0.0
hooks:
- id: gofumpt
- id: golangci-lint-full
- repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0
hooks:
- id: gitleaks
+28
View File
@@ -0,0 +1,28 @@
## openpayments-mock
Tiny Go HTTP server that mimics the Open Payments (PSD2/BerlinGroup) aggregator for Shiny's acceptance tests. Consumed by `acctest`'s `banking-suite` via the image published from this repo's CI.
## Shared Documentation
@../docs/claude/architecture.md
@../docs/claude/go-services.md
@../docs/claude/conventions.md
@../docs/claude/cicd.md
## Service-Specific Information
### Purpose
Stand-in for the real Open Payments aggregator. Not safe for anything but tests — state is entirely in-memory, there are no authentication checks, and admin endpoints let the acctest suite force deterministic transitions (payment status, basket status, transaction seeding, consent expiry).
### Port
8080 (matching production aggregator convention; acctest reaches it via cluster DNS `openpayments-mock:8080`).
### Not deployed to staging/prod
CI builds + publishes the image but does not run `deploy` — the mock is test-only infrastructure. acctest's `k8s/infra/base/openpayments-mock.yaml` references the published image.
### Endpoints
See `README.md` for the full endpoint list.
+25
View File
@@ -0,0 +1,25 @@
FROM amd64/golang:1.26.2@sha256:3e677b9776e5fcb030321772b4fe13c58b22b8abe772c647be8f746159d1a2dc as modules
WORKDIR /build
ENV GOPRIVATE=gitea.unbound.se/shiny,gitea.unbound.se/unboundsoftware
ADD go.* /build
RUN go mod download
FROM modules as build
ARG CI_COMMIT
WORKDIR /build
ENV CGO_ENABLED=0
ADD . /build
RUN GOOS=linux GOARCH=amd64 go build \
-tags prod \
-a -installsuffix cgo \
-mod=readonly \
-o /release/service \
-ldflags "-w -s -X main.buildVersion=${CI_COMMIT}" \
./cmd/service/service.go
FROM scratch
ENV TZ Europe/Stockholm
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /release/service /
CMD ["/service"]
+48
View File
@@ -0,0 +1,48 @@
# openpayments-mock
Tiny in-memory stand-in for the Open Payments PSD2/BerlinGroup aggregator,
used by the `banking-suite` acceptance tests. Accepts any bearer token
and returns canned responses.
Not safe for anything but tests — state is entirely in-memory and there
are no authentication checks.
## Endpoints
### OAuth
- `POST /token` — returns static `{access_token: "acctest", ...}`
### AIS (BerlinGroup shape)
- `GET /psd2/aspspinformation/v1/aspsps` — catalog
- `POST /psd2/consent/v1/consents`
- `GET /psd2/consent/v1/consents/{id}/status`
- `DELETE /psd2/consent/v1/consents/{id}`
- `GET /authorize` — mock SCA stub that immediately redirects back to the
`TPP-Redirect-URI` with `?code=mock-auth-code&state=...`
- `GET /psd2/accountinformation/v1/accounts`
- `GET /psd2/accountinformation/v1/accounts/{id}/balances`
- `GET /psd2/accountinformation/v1/accounts/{id}/transactions`
- `GET /psd2/cardaccountinformation/v1/card-accounts`
### PIS
- `POST /psd2/paymentinitiation/v1/payments/sepa-credit-transfers`
- `GET /psd2/paymentinitiation/v1/payments/sepa-credit-transfers/{id}/status`
— transitions RCVD → PDNG → ACSC deterministically on successive polls.
Creditor name `"REJECT ME"` transitions to RJCT.
- `POST /psd2/v1/signing-baskets`
- `GET /psd2/v1/signing-baskets/{id}/status` — RCVD → ACCP → ACSC;
reaching ACSC advances all linked payments to ACSC.
### Admin (acctest only, not part of PSD2)
- `POST /admin/reset`
- `GET /admin/consents`
- `POST /admin/transactions` — body `{ accountId, bookingStatus, entries: [...] }`
- `POST /admin/payments/{id}/status` — body `{ status }`
- `POST /admin/baskets/{id}/status` — body `{ status }`
- `POST /admin/consents/{id}/expires-at` — body `{ expiresAt }`
## Build
```bash
docker build -t openpayments-mock:acctest .
```
+5
View File
@@ -0,0 +1,5 @@
module gitea.unbound.se/shiny/openpayments-mock
go 1.24
require github.com/google/uuid v1.6.0
+2
View File
@@ -0,0 +1,2 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+61
View File
@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: openpayments-mock
labels:
app.kubernetes.io/name: openpayments-mock
annotations:
kubernetes.io/change-cause: "${TIMESTAMP} Deployed commit id: ${COMMIT}"
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: openpayments-mock
template:
metadata:
labels:
app.kubernetes.io/name: openpayments-mock
app.kubernetes.io/instance: shiny
spec:
containers:
- name: openpayments-mock
image: oci.unbound.se/shiny/openpayments-mock:${COMMIT}
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: PUBLIC_BASE_URL
value: "https://openpayments-mock"
resources:
requests:
cpu: "10m"
memory: "20Mi"
limits:
memory: "64Mi"
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 2
failureThreshold: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: openpayments-mock
labels:
app.kubernetes.io/name: openpayments-mock
spec:
selector:
app.kubernetes.io/name: openpayments-mock
ports:
- name: http
port: 8080
targetPort: 8080