Skip to content

Commit c7d1adb

Browse files
authored
test: add Tempo transaction memo conformance (#21)
* test: add Tempo transaction memo conformance * test: clarify Tempo receipt verification vector * fix: address tempo receipt conformance review
1 parent be78512 commit c7d1adb

12 files changed

Lines changed: 1809 additions & 8 deletions

‎conformance/HARNESS_SPEC.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,13 @@ Required for vector conformance:
175175
| `base64url.encode` | `{ "text": string }` | `{ "text": string }` |
176176
| `base64url.decode` | `{ "text": string }` | `{ "text": string }` |
177177
| `challenge.id` | challenge id params | `{ "id": string }` |
178+
| `tempo.receipt.verify` | Tempo credential plus receipt fixture | `{ "status": "success", "reference": string }` |
178179
| `stripe.external_id_binding` | Stripe request, credential payload, and PaymentIntent fixture | verification outcome |
179180
181+
`tempo.receipt.verify` is an optional staged vector capability. Adapters that do
182+
not advertise it are skipped; passing it demonstrates conformance for supplied
183+
receipt fixture verification, not full signed Tempo transaction verification.
184+
180185
Required for flow conformance:
181186
182187
| Operation | Input | Output |

‎conformance/adapters/python/adapter.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"receipt.format",
2323
"base64url.encode",
2424
"base64url.decode",
25-
"challenge.id"
25+
"challenge.id",
26+
"tempo.receipt.verify"
2627
]
2728
}

‎conformance/adapters/python/adapter.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import hashlib
88
import hmac
99
import subprocess
10+
import asyncio
1011

1112
from mpp import (
1213
Challenge,
@@ -148,9 +149,29 @@ def error(message: str, error_type: str = "unknown_error"):
148149
"base64url.encode": "base64url-encode",
149150
"base64url.decode": "base64url-decode",
150151
"challenge.id": "generate-challenge-id",
152+
"tempo.receipt.verify": "verify-tempo-receipt",
151153
}
152154

153155

156+
class FakeRpcResponse:
157+
def __init__(self, payload):
158+
self._payload = payload
159+
160+
def raise_for_status(self):
161+
return None
162+
163+
def json(self):
164+
return self._payload
165+
166+
167+
class FakeRpcClient:
168+
def __init__(self, receipt):
169+
self._receipt = receipt
170+
171+
async def post(self, *args, **kwargs):
172+
return FakeRpcResponse({"jsonrpc": "2.0", "result": self._receipt, "id": 1})
173+
174+
154175
def adapter_success(value):
155176
return {"ok": True, "value": value}
156177

@@ -177,6 +198,40 @@ def response_value_for_operation(op: str, result):
177198
return result
178199

179200

201+
async def verify_tempo_receipt(input_value: dict):
202+
from mpp.methods.tempo.intents import ChargeIntent
203+
from mpp.server.intent import VerificationError
204+
205+
credential_data = input_value["credential"]
206+
challenge_data = credential_data["challenge"]
207+
request_json = json.dumps(challenge_data["request"], separators=(",", ":"))
208+
request_b64 = base64url_encode(request_json)
209+
210+
credential = Credential(
211+
challenge=ChallengeEcho(
212+
id=challenge_data["id"],
213+
realm=challenge_data.get("realm", ""),
214+
method=challenge_data["method"],
215+
intent=challenge_data["intent"],
216+
request=request_b64,
217+
expires=challenge_data.get("expires"),
218+
digest=challenge_data.get("digest"),
219+
),
220+
payload=credential_data["payload"],
221+
source=credential_data.get("source"),
222+
)
223+
224+
intent = ChargeIntent(rpc_url="https://rpc.test")
225+
intent._http_client = FakeRpcClient(input_value["receipt"])
226+
227+
try:
228+
receipt = await intent.verify(credential, challenge_data["request"])
229+
except VerificationError as exc:
230+
return error(str(exc), "verification_error")
231+
232+
return success({"status": receipt.status, "reference": receipt.reference})
233+
234+
180235
def run_legacy_subprocess(command: str, input_data: str = ""):
181236
result = subprocess.run(
182237
[sys.executable, __file__, command],
@@ -296,6 +351,9 @@ def main():
296351
opaque=params.get("opaque"),
297352
)
298353
print(json.dumps(success(result)))
354+
elif command == "verify-tempo-receipt":
355+
params = json.loads(input_data)
356+
print(json.dumps(asyncio.run(verify_tempo_receipt(params))))
299357
else:
300358
print(json.dumps(error(f"Unknown command: {command}")))
301359
except Exception as e:
@@ -307,6 +365,8 @@ def main():
307365
error_type = "encoding_error"
308366
elif command.startswith("generate-"):
309367
error_type = "generation_error"
368+
elif command.startswith("verify-tempo-receipt"):
369+
error_type = "verification_error"
310370
else:
311371
error_type = "unknown_error"
312372
print(json.dumps(error(str(e), error_type)))

‎conformance/adapters/python/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ description = "Python conformance adapter for MPP tooling"
55
license = { text = "MIT" }
66
requires-python = ">=3.12"
77
dependencies = [
8-
"pympp==0.8.2",
8+
"pympp[tempo]==0.8.2",
99
]

‎conformance/adapters/python/uv.lock‎

Lines changed: 1322 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"schema": 1,
3+
"op": "tempo.receipt.verify",
4+
"input": {
5+
"credential": {
6+
"challenge": {
7+
"id": "challenge-123",
8+
"realm": "api.example.com",
9+
"method": "tempo",
10+
"intent": "charge",
11+
"expires": "2030-01-01T00:00:00Z",
12+
"request": {
13+
"amount": "1000",
14+
"currency": "0x1234567890123456789012345678901234567890",
15+
"recipient": "0x4567890123456789012345678901234567890123"
16+
}
17+
},
18+
"payload": {
19+
"type": "hash",
20+
"hash": "0x1111111111111111111111111111111111111111111111111111111111111111"
21+
}
22+
},
23+
"receipt": {
24+
"transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111",
25+
"status": "0x1",
26+
"logs": [
27+
{
28+
"address": "0x1234567890123456789012345678901234567890",
29+
"topics": [
30+
"0x57bc7354aa85aed339e000bccffabbc529466af35f0772c8f8ee1145927de7f0",
31+
"0x000000000000000000000000abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
32+
"0x0000000000000000000000004567890123456789012345678901234567890123",
33+
"0xef1ed712011ece072f76bd8b82350e00000000000000000000eb18f2d4402269"
34+
],
35+
"data": "0x00000000000000000000000000000000000000000000000000000000000003e8"
36+
}
37+
]
38+
}
39+
}
40+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ok": true,
3+
"value": {
4+
"status": "success",
5+
"reference": "0x1111111111111111111111111111111111111111111111111111111111111111"
6+
}
7+
}

‎conformance/operations.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@
8989
"errorTypes": ["http_error"],
9090
"comparison": "http",
9191
"description": "Send one normalized HTTP request through the SDK payment-aware transport."
92+
},
93+
"tempo.receipt.verify": {
94+
"category": "vector",
95+
"inputRef": "protocol.schema.json#/$defs/TempoReceiptVerificationInput",
96+
"successRef": "protocol.schema.json#/$defs/TempoReceiptVerificationOutput",
97+
"errorTypes": ["verification_error"],
98+
"comparison": "exact",
99+
"description": "Verify a Tempo credential against a supplied receipt fixture. This is an optional staged capability until full signed transaction verification vectors are available."
92100
}
93101
}
94102
}

‎conformance/schemas/adapter-request.schema.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
{
6464
"if": { "properties": { "op": { "const": "http.payment_request" } }, "required": ["op"] },
6565
"then": { "properties": { "input": { "$ref": "protocol.schema.json#/$defs/HttpPaymentRequest" } } }
66+
},
67+
{
68+
"if": { "properties": { "op": { "const": "tempo.receipt.verify" } }, "required": ["op"] },
69+
"then": { "properties": { "input": { "$ref": "protocol.schema.json#/$defs/TempoReceiptVerificationInput" } } }
6670
}
6771
]
6872
}

‎conformance/schemas/protocol.schema.json‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
"base64url.encode",
1616
"base64url.decode",
1717
"challenge.id",
18-
"stripe.external_id_binding",
19-
"http.payment_request"
18+
"tempo.receipt.verify",
19+
"http.payment_request",
20+
"stripe.external_id_binding"
2021
]
2122
},
2223
"ErrorType": {
@@ -227,6 +228,24 @@
227228
},
228229
"body": { "type": "string" }
229230
}
231+
},
232+
"TempoReceiptVerificationInput": {
233+
"type": "object",
234+
"required": ["credential", "receipt"],
235+
"additionalProperties": false,
236+
"properties": {
237+
"credential": { "$ref": "#/$defs/Credential" },
238+
"receipt": { "$ref": "#/$defs/JsonObject" }
239+
}
240+
},
241+
"TempoReceiptVerificationOutput": {
242+
"type": "object",
243+
"required": ["status", "reference"],
244+
"additionalProperties": false,
245+
"properties": {
246+
"status": { "type": "string", "const": "success" },
247+
"reference": { "type": "string", "minLength": 1 }
248+
}
230249
}
231250
}
232251
}

0 commit comments

Comments
 (0)