#
tokens: 46253/50000 5/59 files (page 3/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 5. Use http://codebase.md/razorpay/razorpay-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       └── new-tool-from-docs.mdc
├── .cursorignore
├── .dockerignore
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows
│       ├── assign.yml
│       ├── build.yml
│       ├── ci.yml
│       ├── docker-publish.yml
│       ├── lint.yml
│       └── release.yml
├── .gitignore
├── .golangci.yaml
├── .goreleaser.yaml
├── cmd
│   └── razorpay-mcp-server
│       ├── main.go
│       └── stdio.go
├── codecov.yml
├── CONTRIBUTING.md
├── Dockerfile
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── pkg
│   ├── contextkey
│   │   └── context_key.go
│   ├── log
│   │   ├── config.go
│   │   ├── log.go
│   │   ├── slog_test.go
│   │   └── slog.go
│   ├── mcpgo
│   │   ├── README.md
│   │   ├── server.go
│   │   ├── stdio.go
│   │   ├── tool.go
│   │   └── transport.go
│   ├── observability
│   │   └── observability.go
│   ├── razorpay
│   │   ├── mock
│   │   │   ├── server_test.go
│   │   │   └── server.go
│   │   ├── orders_test.go
│   │   ├── orders.go
│   │   ├── payment_links_test.go
│   │   ├── payment_links.go
│   │   ├── payments_test.go
│   │   ├── payments.go
│   │   ├── payouts_test.go
│   │   ├── payouts.go
│   │   ├── qr_codes_test.go
│   │   ├── qr_codes.go
│   │   ├── README.md
│   │   ├── refunds_test.go
│   │   ├── refunds.go
│   │   ├── server.go
│   │   ├── settlements_test.go
│   │   ├── settlements.go
│   │   ├── test_helpers.go
│   │   ├── tokens_test.go
│   │   ├── tokens.go
│   │   ├── tools_params_test.go
│   │   ├── tools_params.go
│   │   ├── tools_test.go
│   │   └── tools.go
│   └── toolsets
│       └── toolsets.go
├── README.md
└── SECURITY.md
```

# Files

--------------------------------------------------------------------------------
/pkg/razorpay/refunds_test.go:
--------------------------------------------------------------------------------

```go
  1 | package razorpay
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"net/http"
  6 | 	"net/http/httptest"
  7 | 	"testing"
  8 | 
  9 | 	"github.com/razorpay/razorpay-go/constants"
 10 | 
 11 | 	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
 12 | )
 13 | 
 14 | func Test_CreateRefund(t *testing.T) {
 15 | 	createRefundPathFmt := fmt.Sprintf(
 16 | 		"/%s%s/%%s/refund",
 17 | 		constants.VERSION_V1,
 18 | 		constants.PAYMENT_URL,
 19 | 	)
 20 | 
 21 | 	// Define test responses
 22 | 	successfulRefundResp := map[string]interface{}{
 23 | 		"id":              "rfnd_FP8QHiV938haTz",
 24 | 		"entity":          "refund",
 25 | 		"amount":          float64(500100),
 26 | 		"currency":        "INR",
 27 | 		"payment_id":      "pay_29QQoUBi66xm2f",
 28 | 		"notes":           map[string]interface{}{},
 29 | 		"receipt":         "Receipt No. 31",
 30 | 		"acquirer_data":   map[string]interface{}{"arn": nil},
 31 | 		"created_at":      float64(1597078866),
 32 | 		"batch_id":        nil,
 33 | 		"status":          "processed",
 34 | 		"speed_processed": "normal",
 35 | 		"speed_requested": "normal",
 36 | 	}
 37 | 
 38 | 	errorResp := map[string]interface{}{
 39 | 		"error": map[string]interface{}{
 40 | 			"code":        "BAD_REQUEST_ERROR",
 41 | 			"description": "Razorpay API error: Bad request",
 42 | 		},
 43 | 	}
 44 | 
 45 | 	tests := []RazorpayToolTestCase{
 46 | 		{
 47 | 			Name: "successful full refund",
 48 | 			Request: map[string]interface{}{
 49 | 				"payment_id": "pay_29QQoUBi66xm2f",
 50 | 				"amount":     float64(500100),
 51 | 				"receipt":    "Receipt No. 31",
 52 | 			},
 53 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 54 | 				return mock.NewHTTPClient(
 55 | 					mock.Endpoint{
 56 | 						Path:     fmt.Sprintf(createRefundPathFmt, "pay_29QQoUBi66xm2f"),
 57 | 						Method:   "POST",
 58 | 						Response: successfulRefundResp,
 59 | 					},
 60 | 				)
 61 | 			},
 62 | 			ExpectError:    false,
 63 | 			ExpectedResult: successfulRefundResp,
 64 | 		},
 65 | 		{
 66 | 			Name: "refund with speed parameter",
 67 | 			Request: map[string]interface{}{
 68 | 				"payment_id": "pay_29QQoUBi66xm2f",
 69 | 				"amount":     float64(500100),
 70 | 				"speed":      "optimum",
 71 | 			},
 72 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 73 | 				speedRefundResp := map[string]interface{}{
 74 | 					"id":              "rfnd_HzAbPEkKtRq48V",
 75 | 					"entity":          "refund",
 76 | 					"amount":          float64(500100),
 77 | 					"payment_id":      "pay_29QQoUBi66xm2f",
 78 | 					"status":          "processed",
 79 | 					"speed_processed": "instant",
 80 | 					"speed_requested": "optimum",
 81 | 				}
 82 | 				return mock.NewHTTPClient(
 83 | 					mock.Endpoint{
 84 | 						Path:     fmt.Sprintf(createRefundPathFmt, "pay_29QQoUBi66xm2f"),
 85 | 						Method:   "POST",
 86 | 						Response: speedRefundResp,
 87 | 					},
 88 | 				)
 89 | 			},
 90 | 			ExpectError: false,
 91 | 			ExpectedResult: map[string]interface{}{
 92 | 				"id":              "rfnd_HzAbPEkKtRq48V",
 93 | 				"entity":          "refund",
 94 | 				"amount":          float64(500100),
 95 | 				"payment_id":      "pay_29QQoUBi66xm2f",
 96 | 				"status":          "processed",
 97 | 				"speed_processed": "instant",
 98 | 				"speed_requested": "optimum",
 99 | 			},
100 | 		},
101 | 		{
102 | 			Name: "refund API server error",
103 | 			Request: map[string]interface{}{
104 | 				"payment_id": "pay_29QQoUBi66xm2f",
105 | 				"amount":     float64(500100),
106 | 			},
107 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
108 | 				return mock.NewHTTPClient(
109 | 					mock.Endpoint{
110 | 						Path:     fmt.Sprintf(createRefundPathFmt, "pay_29QQoUBi66xm2f"),
111 | 						Method:   "POST",
112 | 						Response: errorResp,
113 | 					},
114 | 				)
115 | 			},
116 | 			ExpectError:    true,
117 | 			ExpectedErrMsg: "creating refund failed: Razorpay API error: Bad request",
118 | 		},
119 | 		{
120 | 			Name: "multiple validation errors",
121 | 			Request: map[string]interface{}{
122 | 				// Missing payment_id parameter
123 | 				"amount": "not-a-number",  // Wrong type for amount
124 | 				"speed":  12345,           // Wrong type for speed
125 | 				"notes":  "not-an-object", // Wrong type for notes
126 | 			},
127 | 			MockHttpClient: nil,
128 | 			ExpectError:    true,
129 | 			ExpectedErrMsg: "Validation errors:\n- " +
130 | 				"missing required parameter: payment_id\n- " +
131 | 				"invalid parameter type: amount\n- " +
132 | 				"invalid parameter type: speed\n- " +
133 | 				"invalid parameter type: notes",
134 | 		},
135 | 	}
136 | 
137 | 	for _, tc := range tests {
138 | 		t.Run(tc.Name, func(t *testing.T) {
139 | 			runToolTest(t, tc, CreateRefund, "Refund")
140 | 		})
141 | 	}
142 | }
143 | 
144 | func Test_FetchRefund(t *testing.T) {
145 | 	fetchRefundPathFmt := fmt.Sprintf(
146 | 		"/%s%s/%%s",
147 | 		constants.VERSION_V1,
148 | 		constants.REFUND_URL,
149 | 	)
150 | 
151 | 	// Define test response for successful refund fetch
152 | 	successfulRefundResp := map[string]interface{}{
153 | 		"id":         "rfnd_DfjjhJC6eDvUAi",
154 | 		"entity":     "refund",
155 | 		"amount":     float64(6000),
156 | 		"currency":   "INR",
157 | 		"payment_id": "pay_EpkFDYRirena0f",
158 | 		"notes": map[string]interface{}{
159 | 			"comment": "Issuing an instant refund",
160 | 		},
161 | 		"receipt": nil,
162 | 		"acquirer_data": map[string]interface{}{
163 | 			"arn": "10000000000000",
164 | 		},
165 | 		"created_at":      float64(1589521675),
166 | 		"batch_id":        nil,
167 | 		"status":          "processed",
168 | 		"speed_processed": "optimum",
169 | 		"speed_requested": "optimum",
170 | 	}
171 | 
172 | 	// Define error responses
173 | 	notFoundResp := map[string]interface{}{
174 | 		"error": map[string]interface{}{
175 | 			"code":        "BAD_REQUEST_ERROR",
176 | 			"description": "The id provided does not exist",
177 | 		},
178 | 	}
179 | 
180 | 	tests := []RazorpayToolTestCase{
181 | 		{
182 | 			Name: "successful refund fetch",
183 | 			Request: map[string]interface{}{
184 | 				"refund_id": "rfnd_DfjjhJC6eDvUAi",
185 | 			},
186 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
187 | 				return mock.NewHTTPClient(
188 | 					mock.Endpoint{
189 | 						Path:     fmt.Sprintf(fetchRefundPathFmt, "rfnd_DfjjhJC6eDvUAi"),
190 | 						Method:   "GET",
191 | 						Response: successfulRefundResp,
192 | 					},
193 | 				)
194 | 			},
195 | 			ExpectError:    false,
196 | 			ExpectedResult: successfulRefundResp,
197 | 		},
198 | 		{
199 | 			Name: "refund id not found",
200 | 			Request: map[string]interface{}{
201 | 				"refund_id": "rfnd_nonexistent",
202 | 			},
203 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
204 | 				return mock.NewHTTPClient(
205 | 					mock.Endpoint{
206 | 						Path:     fmt.Sprintf(fetchRefundPathFmt, "rfnd_nonexistent"),
207 | 						Method:   "GET",
208 | 						Response: notFoundResp,
209 | 					},
210 | 				)
211 | 			},
212 | 			ExpectError:    true,
213 | 			ExpectedErrMsg: "fetching refund failed: The id provided does not exist",
214 | 		},
215 | 		{
216 | 			Name:           "missing refund_id parameter",
217 | 			Request:        map[string]interface{}{},
218 | 			MockHttpClient: nil,
219 | 			ExpectError:    true,
220 | 			ExpectedErrMsg: "missing required parameter: refund_id",
221 | 		},
222 | 		{
223 | 			Name: "multiple validation errors",
224 | 			Request: map[string]interface{}{
225 | 				// Missing refund_id parameter
226 | 				"non_existent_param": 12345, // Additional parameter that doesn't exist
227 | 			},
228 | 			MockHttpClient: nil,
229 | 			ExpectError:    true,
230 | 			ExpectedErrMsg: "missing required parameter: refund_id",
231 | 		},
232 | 	}
233 | 
234 | 	for _, tc := range tests {
235 | 		t.Run(tc.Name, func(t *testing.T) {
236 | 			runToolTest(t, tc, FetchRefund, "Refund")
237 | 		})
238 | 	}
239 | }
240 | 
241 | func Test_UpdateRefund(t *testing.T) {
242 | 	updateRefundPathFmt := fmt.Sprintf(
243 | 		"/%s%s/%%s",
244 | 		constants.VERSION_V1,
245 | 		constants.REFUND_URL,
246 | 	)
247 | 
248 | 	// Define test response for successful refund update
249 | 	successfulUpdateResp := map[string]interface{}{
250 | 		"id":         "rfnd_DfjjhJC6eDvUAi",
251 | 		"entity":     "refund",
252 | 		"amount":     float64(300100),
253 | 		"currency":   "INR",
254 | 		"payment_id": "pay_FIKOnlyii5QGNx",
255 | 		"notes": map[string]interface{}{
256 | 			"notes_key_1": "Beam me up Scotty.",
257 | 			"notes_key_2": "Engage",
258 | 		},
259 | 		"receipt":         nil,
260 | 		"acquirer_data":   map[string]interface{}{"arn": "10000000000000"},
261 | 		"created_at":      float64(1597078124),
262 | 		"batch_id":        nil,
263 | 		"status":          "processed",
264 | 		"speed_processed": "normal",
265 | 		"speed_requested": "optimum",
266 | 	}
267 | 
268 | 	// Define error responses
269 | 	notFoundResp := map[string]interface{}{
270 | 		"error": map[string]interface{}{
271 | 			"code":        "BAD_REQUEST_ERROR",
272 | 			"description": "The id provided does not exist",
273 | 		},
274 | 	}
275 | 
276 | 	tests := []RazorpayToolTestCase{
277 | 		{
278 | 			Name: "successful refund update",
279 | 			Request: map[string]interface{}{
280 | 				"refund_id": "rfnd_DfjjhJC6eDvUAi",
281 | 				"notes": map[string]interface{}{
282 | 					"notes_key_1": "Beam me up Scotty.",
283 | 					"notes_key_2": "Engage",
284 | 				},
285 | 			},
286 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
287 | 				return mock.NewHTTPClient(
288 | 					mock.Endpoint{
289 | 						Path:     fmt.Sprintf(updateRefundPathFmt, "rfnd_DfjjhJC6eDvUAi"),
290 | 						Method:   "PATCH",
291 | 						Response: successfulUpdateResp,
292 | 					},
293 | 				)
294 | 			},
295 | 			ExpectError:    false,
296 | 			ExpectedResult: successfulUpdateResp,
297 | 		},
298 | 		{
299 | 			Name: "refund id not found",
300 | 			Request: map[string]interface{}{
301 | 				"refund_id": "rfnd_nonexistent",
302 | 				"notes": map[string]interface{}{
303 | 					"note_key": "Test note",
304 | 				},
305 | 			},
306 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
307 | 				return mock.NewHTTPClient(
308 | 					mock.Endpoint{
309 | 						Path:     fmt.Sprintf(updateRefundPathFmt, "rfnd_nonexistent"),
310 | 						Method:   "PATCH",
311 | 						Response: notFoundResp,
312 | 					},
313 | 				)
314 | 			},
315 | 			ExpectError:    true,
316 | 			ExpectedErrMsg: "updating refund failed: The id provided does not exist",
317 | 		},
318 | 		{
319 | 			Name:           "missing refund_id parameter",
320 | 			Request:        map[string]interface{}{},
321 | 			MockHttpClient: nil,
322 | 			ExpectError:    true,
323 | 			ExpectedErrMsg: "missing required parameter: refund_id",
324 | 		},
325 | 		{
326 | 			Name: "missing notes parameter",
327 | 			Request: map[string]interface{}{
328 | 				"refund_id": "rfnd_DfjjhJC6eDvUAi",
329 | 			},
330 | 			MockHttpClient: nil,
331 | 			ExpectError:    true,
332 | 			ExpectedErrMsg: "missing required parameter: notes",
333 | 		},
334 | 		{
335 | 			Name: "multiple validation errors",
336 | 			Request: map[string]interface{}{
337 | 				// Missing both refund_id and notes parameters
338 | 				"non_existent_param": 12345, // Additional parameter that doesn't exist
339 | 			},
340 | 			MockHttpClient: nil,
341 | 			ExpectError:    true,
342 | 			ExpectedErrMsg: "Validation errors:\n- " +
343 | 				"missing required parameter: refund_id\n- " +
344 | 				"missing required parameter: notes",
345 | 		},
346 | 	}
347 | 
348 | 	for _, tc := range tests {
349 | 		t.Run(tc.Name, func(t *testing.T) {
350 | 			runToolTest(t, tc, UpdateRefund, "Refund")
351 | 		})
352 | 	}
353 | }
354 | 
355 | func Test_FetchMultipleRefundsForPayment(t *testing.T) {
356 | 	fetchMultipleRefundsPathFmt := fmt.Sprintf(
357 | 		"/%s%s/%%s/refunds",
358 | 		constants.VERSION_V1,
359 | 		constants.PAYMENT_URL,
360 | 	)
361 | 
362 | 	// Define test response for successful multiple refunds fetch
363 | 	successfulMultipleRefundsResp := map[string]interface{}{
364 | 		"entity": "collection",
365 | 		"count":  float64(2),
366 | 		"items": []interface{}{
367 | 			map[string]interface{}{
368 | 				"id":         "rfnd_FP8DDKxqJif6ca",
369 | 				"entity":     "refund",
370 | 				"amount":     float64(300100),
371 | 				"currency":   "INR",
372 | 				"payment_id": "pay_29QQoUBi66xm2f",
373 | 				"notes": map[string]interface{}{
374 | 					"comment": "Comment for refund",
375 | 				},
376 | 				"receipt": nil,
377 | 				"acquirer_data": map[string]interface{}{
378 | 					"arn": "10000000000000",
379 | 				},
380 | 				"created_at":      float64(1597078124),
381 | 				"batch_id":        nil,
382 | 				"status":          "processed",
383 | 				"speed_processed": "normal",
384 | 				"speed_requested": "optimum",
385 | 			},
386 | 			map[string]interface{}{
387 | 				"id":         "rfnd_FP8DRfu3ygfOaC",
388 | 				"entity":     "refund",
389 | 				"amount":     float64(200000),
390 | 				"currency":   "INR",
391 | 				"payment_id": "pay_29QQoUBi66xm2f",
392 | 				"notes": map[string]interface{}{
393 | 					"comment": "Comment for refund",
394 | 				},
395 | 				"receipt": nil,
396 | 				"acquirer_data": map[string]interface{}{
397 | 					"arn": "10000000000000",
398 | 				},
399 | 				"created_at":      float64(1597078137),
400 | 				"batch_id":        nil,
401 | 				"status":          "processed",
402 | 				"speed_processed": "normal",
403 | 				"speed_requested": "optimum",
404 | 			},
405 | 		},
406 | 	}
407 | 
408 | 	// Define error responses
409 | 	errorResp := map[string]interface{}{
410 | 		"error": map[string]interface{}{
411 | 			"code":        "BAD_REQUEST_ERROR",
412 | 			"description": "Bad request",
413 | 		},
414 | 	}
415 | 
416 | 	tests := []RazorpayToolTestCase{
417 | 		{
418 | 			Name: "fetch multiple refunds with query params",
419 | 			Request: map[string]interface{}{
420 | 				"payment_id": "pay_29QQoUBi66xm2f",
421 | 				"from":       1500826740,
422 | 				"to":         1500826760,
423 | 				"count":      10,
424 | 				"skip":       0,
425 | 			},
426 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
427 | 				return mock.NewHTTPClient(
428 | 					mock.Endpoint{
429 | 						Path: fmt.Sprintf(
430 | 							fetchMultipleRefundsPathFmt,
431 | 							"pay_29QQoUBi66xm2f",
432 | 						),
433 | 						Method:   "GET",
434 | 						Response: successfulMultipleRefundsResp,
435 | 					},
436 | 				)
437 | 			},
438 | 			ExpectError:    false,
439 | 			ExpectedResult: successfulMultipleRefundsResp,
440 | 		},
441 | 		{
442 | 			Name: "fetch multiple refunds api error",
443 | 			Request: map[string]interface{}{
444 | 				"payment_id": "pay_invalid",
445 | 			},
446 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
447 | 				return mock.NewHTTPClient(
448 | 					mock.Endpoint{
449 | 						Path: fmt.Sprintf(
450 | 							fetchMultipleRefundsPathFmt,
451 | 							"pay_invalid",
452 | 						),
453 | 						Method:   "GET",
454 | 						Response: errorResp,
455 | 					},
456 | 				)
457 | 			},
458 | 			ExpectError:    true,
459 | 			ExpectedErrMsg: "fetching multiple refunds failed: Bad request",
460 | 		},
461 | 		{
462 | 			Name:           "missing payment_id parameter",
463 | 			Request:        map[string]interface{}{},
464 | 			MockHttpClient: nil,
465 | 			ExpectError:    true,
466 | 			ExpectedErrMsg: "missing required parameter: payment_id",
467 | 		},
468 | 		{
469 | 			Name: "multiple validation errors",
470 | 			Request: map[string]interface{}{
471 | 				// Missing payment_id parameter
472 | 				"from":  "not-a-number", // Wrong type for from
473 | 				"to":    "not-a-number", // Wrong type for to
474 | 				"count": "not-a-number", // Wrong type for count
475 | 				"skip":  "not-a-number", // Wrong type for skip
476 | 			},
477 | 			MockHttpClient: nil,
478 | 			ExpectError:    true,
479 | 			ExpectedErrMsg: "Validation errors:\n- " +
480 | 				"missing required parameter: payment_id\n- " +
481 | 				"invalid parameter type: from\n- " +
482 | 				"invalid parameter type: to\n- " +
483 | 				"invalid parameter type: count\n- " +
484 | 				"invalid parameter type: skip",
485 | 		},
486 | 	}
487 | 
488 | 	for _, tc := range tests {
489 | 		t.Run(tc.Name, func(t *testing.T) {
490 | 			runToolTest(t, tc, FetchMultipleRefundsForPayment, "Refund")
491 | 		})
492 | 	}
493 | }
494 | 
495 | func Test_FetchSpecificRefundForPayment(t *testing.T) {
496 | 	fetchSpecificRefundPathFmt := fmt.Sprintf(
497 | 		"/%s%s/%%s/refunds/%%s",
498 | 		constants.VERSION_V1,
499 | 		constants.PAYMENT_URL,
500 | 	)
501 | 
502 | 	// Define test response for successful specific refund fetch
503 | 	successfulSpecificRefundResp := map[string]interface{}{
504 | 		"id":         "rfnd_AABBdHIieexn5c",
505 | 		"entity":     "refund",
506 | 		"amount":     float64(300100),
507 | 		"currency":   "INR",
508 | 		"payment_id": "pay_FIKOnlyii5QGNx",
509 | 		"notes": map[string]interface{}{
510 | 			"comment": "Comment for refund",
511 | 		},
512 | 		"receipt":         nil,
513 | 		"acquirer_data":   map[string]interface{}{"arn": "10000000000000"},
514 | 		"created_at":      float64(1597078124),
515 | 		"batch_id":        nil,
516 | 		"status":          "processed",
517 | 		"speed_processed": "normal",
518 | 		"speed_requested": "optimum",
519 | 	}
520 | 
521 | 	// Define error responses
522 | 	notFoundResp := map[string]interface{}{
523 | 		"error": map[string]interface{}{
524 | 			"code":        "BAD_REQUEST_ERROR",
525 | 			"description": "The id provided does not exist",
526 | 		},
527 | 	}
528 | 
529 | 	tests := []RazorpayToolTestCase{
530 | 		{
531 | 			Name: "successful specific refund fetch",
532 | 			Request: map[string]interface{}{
533 | 				"payment_id": "pay_FIKOnlyii5QGNx",
534 | 				"refund_id":  "rfnd_AABBdHIieexn5c",
535 | 			},
536 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
537 | 				return mock.NewHTTPClient(
538 | 					mock.Endpoint{
539 | 						Path: fmt.Sprintf(
540 | 							fetchSpecificRefundPathFmt,
541 | 							"pay_FIKOnlyii5QGNx",
542 | 							"rfnd_AABBdHIieexn5c",
543 | 						),
544 | 						Method:   "GET",
545 | 						Response: successfulSpecificRefundResp,
546 | 					},
547 | 				)
548 | 			},
549 | 			ExpectError:    false,
550 | 			ExpectedResult: successfulSpecificRefundResp,
551 | 		},
552 | 		{
553 | 			Name: "refund id not found",
554 | 			Request: map[string]interface{}{
555 | 				"payment_id": "pay_FIKOnlyii5QGNx",
556 | 				"refund_id":  "rfnd_nonexistent",
557 | 			},
558 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
559 | 				return mock.NewHTTPClient(
560 | 					mock.Endpoint{
561 | 						Path: fmt.Sprintf(
562 | 							fetchSpecificRefundPathFmt,
563 | 							"pay_FIKOnlyii5QGNx",
564 | 							"rfnd_nonexistent",
565 | 						),
566 | 						Method:   "GET",
567 | 						Response: notFoundResp,
568 | 					},
569 | 				)
570 | 			},
571 | 			ExpectError: true,
572 | 			ExpectedErrMsg: "fetching specific refund for payment failed: " +
573 | 				"The id provided does not exist",
574 | 		},
575 | 		{
576 | 			Name: "missing payment_id parameter",
577 | 			Request: map[string]interface{}{
578 | 				"refund_id": "rfnd_AABBdHIieexn5c",
579 | 			},
580 | 			MockHttpClient: nil,
581 | 			ExpectError:    true,
582 | 			ExpectedErrMsg: "missing required parameter: payment_id",
583 | 		},
584 | 		{
585 | 			Name: "missing refund_id parameter",
586 | 			Request: map[string]interface{}{
587 | 				"payment_id": "pay_FIKOnlyii5QGNx",
588 | 			},
589 | 			MockHttpClient: nil,
590 | 			ExpectError:    true,
591 | 			ExpectedErrMsg: "missing required parameter: refund_id",
592 | 		},
593 | 		{
594 | 			Name: "multiple validation errors",
595 | 			Request: map[string]interface{}{
596 | 				// Missing both payment_id and refund_id parameters
597 | 				"non_existent_param": 12345, // Additional parameter that doesn't exist
598 | 			},
599 | 			MockHttpClient: nil,
600 | 			ExpectError:    true,
601 | 			ExpectedErrMsg: "Validation errors:\n- " +
602 | 				"missing required parameter: payment_id\n- " +
603 | 				"missing required parameter: refund_id",
604 | 		},
605 | 	}
606 | 
607 | 	for _, tc := range tests {
608 | 		t.Run(tc.Name, func(t *testing.T) {
609 | 			runToolTest(t, tc, FetchSpecificRefundForPayment, "Refund")
610 | 		})
611 | 	}
612 | }
613 | 
614 | func Test_FetchAllRefunds(t *testing.T) {
615 | 	fetchAllRefundsPath := fmt.Sprintf(
616 | 		"/%s%s",
617 | 		constants.VERSION_V1,
618 | 		constants.REFUND_URL,
619 | 	)
620 | 
621 | 	// Define test response for successful refund fetch
622 | 	successfulRefundsResp := map[string]interface{}{
623 | 		"entity": "collection",
624 | 		"count":  float64(2),
625 | 		"items": []interface{}{
626 | 			map[string]interface{}{
627 | 				"id":         "rfnd_FFX6AnnIN3puqW",
628 | 				"entity":     "refund",
629 | 				"amount":     float64(88800),
630 | 				"currency":   "INR",
631 | 				"payment_id": "pay_FFX5FdEYx8jPwA",
632 | 				"notes": map[string]interface{}{
633 | 					"comment": "Issuing an instant refund",
634 | 				},
635 | 				"receipt":         nil,
636 | 				"acquirer_data":   map[string]interface{}{},
637 | 				"created_at":      float64(1594982363),
638 | 				"batch_id":        nil,
639 | 				"status":          "processed",
640 | 				"speed_processed": "optimum",
641 | 				"speed_requested": "optimum",
642 | 			},
643 | 			map[string]interface{}{
644 | 				"id":         "rfnd_EqWThTE7dd7utf",
645 | 				"entity":     "refund",
646 | 				"amount":     float64(6000),
647 | 				"currency":   "INR",
648 | 				"payment_id": "pay_EpkFDYRirena0f",
649 | 				"notes": map[string]interface{}{
650 | 					"comment": "Issuing a normal refund",
651 | 				},
652 | 				"receipt": nil,
653 | 				"acquirer_data": map[string]interface{}{
654 | 					"arn": "10000000000000",
655 | 				},
656 | 				"created_at":      float64(1589521675),
657 | 				"batch_id":        nil,
658 | 				"status":          "processed",
659 | 				"speed_processed": "normal",
660 | 				"speed_requested": "normal",
661 | 			},
662 | 		},
663 | 	}
664 | 
665 | 	// Define error response
666 | 	errorResp := map[string]interface{}{
667 | 		"error": map[string]interface{}{
668 | 			"code":        "BAD_REQUEST_ERROR",
669 | 			"description": "Bad request",
670 | 		},
671 | 	}
672 | 
673 | 	tests := []RazorpayToolTestCase{
674 | 		{
675 | 			Name: "successful fetch with pagination parameters",
676 | 			Request: map[string]interface{}{
677 | 				"count": 2,
678 | 				"skip":  1,
679 | 				"from":  1589000000,
680 | 				"to":    1595000000,
681 | 			},
682 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
683 | 				return mock.NewHTTPClient(
684 | 					mock.Endpoint{
685 | 						Path:     fetchAllRefundsPath,
686 | 						Method:   "GET",
687 | 						Response: successfulRefundsResp,
688 | 					},
689 | 				)
690 | 			},
691 | 			ExpectError:    false,
692 | 			ExpectedResult: successfulRefundsResp,
693 | 		},
694 | 		{
695 | 			Name:    "fetch with API error",
696 | 			Request: map[string]interface{}{},
697 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
698 | 				return mock.NewHTTPClient(
699 | 					mock.Endpoint{
700 | 						Path:     fetchAllRefundsPath,
701 | 						Method:   "GET",
702 | 						Response: errorResp,
703 | 					},
704 | 				)
705 | 			},
706 | 			ExpectError:    true,
707 | 			ExpectedErrMsg: "fetching refunds failed",
708 | 		},
709 | 		{
710 | 			Name: "multiple validation errors",
711 | 			Request: map[string]interface{}{
712 | 				"from":  "not-a-number", // Wrong type for from
713 | 				"to":    "not-a-number", // Wrong type for to
714 | 				"count": "not-a-number", // Wrong type for count
715 | 				"skip":  "not-a-number", // Wrong type for skip
716 | 			},
717 | 			MockHttpClient: nil,
718 | 			ExpectError:    true,
719 | 			ExpectedErrMsg: "Validation errors:\n- " +
720 | 				"invalid parameter type: from\n- " +
721 | 				"invalid parameter type: to\n- " +
722 | 				"invalid parameter type: count\n- " +
723 | 				"invalid parameter type: skip",
724 | 		},
725 | 	}
726 | 
727 | 	for _, tc := range tests {
728 | 		t.Run(tc.Name, func(t *testing.T) {
729 | 			runToolTest(t, tc, FetchAllRefunds, "Refund")
730 | 		})
731 | 	}
732 | }
733 | 
```

--------------------------------------------------------------------------------
/pkg/razorpay/settlements_test.go:
--------------------------------------------------------------------------------

```go
  1 | package razorpay
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"net/http"
  6 | 	"net/http/httptest"
  7 | 	"testing"
  8 | 
  9 | 	"github.com/razorpay/razorpay-go/constants"
 10 | 
 11 | 	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
 12 | )
 13 | 
 14 | func Test_FetchSettlement(t *testing.T) {
 15 | 	fetchSettlementPathFmt := fmt.Sprintf(
 16 | 		"/%s%s/%%s",
 17 | 		constants.VERSION_V1,
 18 | 		constants.SETTLEMENT_URL,
 19 | 	)
 20 | 
 21 | 	settlementResp := map[string]interface{}{
 22 | 		"id":         "setl_FNj7g2YS5J67Rz",
 23 | 		"entity":     "settlement",
 24 | 		"amount":     float64(9973635),
 25 | 		"status":     "processed",
 26 | 		"fees":       float64(471),
 27 | 		"tax":        float64(72),
 28 | 		"utr":        "1568176198",
 29 | 		"created_at": float64(1568176198),
 30 | 	}
 31 | 
 32 | 	settlementNotFoundResp := map[string]interface{}{
 33 | 		"error": map[string]interface{}{
 34 | 			"code":        "BAD_REQUEST_ERROR",
 35 | 			"description": "settlement not found",
 36 | 		},
 37 | 	}
 38 | 
 39 | 	tests := []RazorpayToolTestCase{
 40 | 		{
 41 | 			Name: "successful settlement fetch",
 42 | 			Request: map[string]interface{}{
 43 | 				"settlement_id": "setl_FNj7g2YS5J67Rz",
 44 | 			},
 45 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 46 | 				return mock.NewHTTPClient(
 47 | 					mock.Endpoint{
 48 | 						Path:     fmt.Sprintf(fetchSettlementPathFmt, "setl_FNj7g2YS5J67Rz"),
 49 | 						Method:   "GET",
 50 | 						Response: settlementResp,
 51 | 					},
 52 | 				)
 53 | 			},
 54 | 			ExpectError:    false,
 55 | 			ExpectedResult: settlementResp,
 56 | 		},
 57 | 		{
 58 | 			Name: "settlement not found",
 59 | 			Request: map[string]interface{}{
 60 | 				"settlement_id": "setl_invalid",
 61 | 			},
 62 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 63 | 				return mock.NewHTTPClient(
 64 | 					mock.Endpoint{
 65 | 						Path:     fmt.Sprintf(fetchSettlementPathFmt, "setl_invalid"),
 66 | 						Method:   "GET",
 67 | 						Response: settlementNotFoundResp,
 68 | 					},
 69 | 				)
 70 | 			},
 71 | 			ExpectError:    true,
 72 | 			ExpectedErrMsg: "fetching settlement failed: settlement not found",
 73 | 		},
 74 | 		{
 75 | 			Name:           "missing settlement_id parameter",
 76 | 			Request:        map[string]interface{}{},
 77 | 			MockHttpClient: nil, // No HTTP client needed for validation error
 78 | 			ExpectError:    true,
 79 | 			ExpectedErrMsg: "missing required parameter: settlement_id",
 80 | 		},
 81 | 	}
 82 | 
 83 | 	for _, tc := range tests {
 84 | 		t.Run(tc.Name, func(t *testing.T) {
 85 | 			runToolTest(t, tc, FetchSettlement, "Settlement")
 86 | 		})
 87 | 	}
 88 | }
 89 | 
 90 | func Test_FetchSettlementRecon(t *testing.T) {
 91 | 	fetchSettlementReconPath := fmt.Sprintf(
 92 | 		"/%s%s/recon/combined",
 93 | 		constants.VERSION_V1,
 94 | 		constants.SETTLEMENT_URL,
 95 | 	)
 96 | 
 97 | 	settlementReconResp := map[string]interface{}{
 98 | 		"entity": "collection",
 99 | 		"count":  float64(1),
100 | 		"items": []interface{}{
101 | 			map[string]interface{}{
102 | 				"entity":            "settlement",
103 | 				"settlement_id":     "setl_FNj7g2YS5J67Rz",
104 | 				"settlement_utr":    "1568176198",
105 | 				"amount":            float64(9973635),
106 | 				"settlement_type":   "regular",
107 | 				"settlement_status": "processed",
108 | 				"created_at":        float64(1568176198),
109 | 			},
110 | 		},
111 | 	}
112 | 
113 | 	invalidParamsResp := map[string]interface{}{
114 | 		"error": map[string]interface{}{
115 | 			"code":        "BAD_REQUEST_ERROR",
116 | 			"description": "missing required parameters",
117 | 		},
118 | 	}
119 | 
120 | 	tests := []RazorpayToolTestCase{
121 | 		{
122 | 			Name: "successful settlement reconciliation fetch",
123 | 			Request: map[string]interface{}{
124 | 				"year":  float64(2022),
125 | 				"month": float64(10),
126 | 				"day":   float64(15),
127 | 				"count": float64(10),
128 | 				"skip":  float64(0),
129 | 			},
130 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
131 | 				return mock.NewHTTPClient(
132 | 					mock.Endpoint{
133 | 						Path:     fetchSettlementReconPath,
134 | 						Method:   "GET",
135 | 						Response: settlementReconResp,
136 | 					},
137 | 				)
138 | 			},
139 | 			ExpectError:    false,
140 | 			ExpectedResult: settlementReconResp,
141 | 		},
142 | 		{
143 | 			Name: "settlement reconciliation with required params only",
144 | 			Request: map[string]interface{}{
145 | 				"year":  float64(2022),
146 | 				"month": float64(10),
147 | 			},
148 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
149 | 				return mock.NewHTTPClient(
150 | 					mock.Endpoint{
151 | 						Path:     fetchSettlementReconPath,
152 | 						Method:   "GET",
153 | 						Response: settlementReconResp,
154 | 					},
155 | 				)
156 | 			},
157 | 			ExpectError:    false,
158 | 			ExpectedResult: settlementReconResp,
159 | 		},
160 | 		{
161 | 			Name: "settlement reconciliation with invalid params",
162 | 			Request: map[string]interface{}{
163 | 				"year": float64(2022),
164 | 				// missing month parameter
165 | 			},
166 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
167 | 				return mock.NewHTTPClient(
168 | 					mock.Endpoint{
169 | 						Path:     fetchSettlementReconPath,
170 | 						Method:   "GET",
171 | 						Response: invalidParamsResp,
172 | 					},
173 | 				)
174 | 			},
175 | 			ExpectError:    true,
176 | 			ExpectedErrMsg: "missing required parameter: month",
177 | 		},
178 | 		{
179 | 			Name:           "missing required parameters",
180 | 			Request:        map[string]interface{}{},
181 | 			MockHttpClient: nil, // No HTTP client needed for validation error
182 | 			ExpectError:    true,
183 | 			ExpectedErrMsg: "missing required parameter: year",
184 | 		},
185 | 	}
186 | 
187 | 	for _, tc := range tests {
188 | 		t.Run(tc.Name, func(t *testing.T) {
189 | 			runToolTest(t, tc, FetchSettlementRecon, "Settlement Reconciliation")
190 | 		})
191 | 	}
192 | }
193 | 
194 | func Test_FetchAllSettlements(t *testing.T) {
195 | 	fetchAllSettlementsPath := fmt.Sprintf(
196 | 		"/%s%s",
197 | 		constants.VERSION_V1,
198 | 		constants.SETTLEMENT_URL,
199 | 	)
200 | 
201 | 	// Define the sample response for all settlements
202 | 	settlementsResp := map[string]interface{}{
203 | 		"entity": "collection",
204 | 		"count":  float64(2),
205 | 		"items": []interface{}{
206 | 			map[string]interface{}{
207 | 				"id":     "setl_FNj7g2YS5J67Rz",
208 | 				"entity": "settlement",
209 | 				"amount": float64(9973635),
210 | 				"status": "processed",
211 | 			},
212 | 			map[string]interface{}{
213 | 				"id":     "setl_FJOp0jOWlalIvt",
214 | 				"entity": "settlement",
215 | 				"amount": float64(299114),
216 | 				"status": "processed",
217 | 			},
218 | 		},
219 | 	}
220 | 
221 | 	invalidParamsResp := map[string]interface{}{
222 | 		"error": map[string]interface{}{
223 | 			"code":        "BAD_REQUEST_ERROR",
224 | 			"description": "from must be between 946684800 and 4765046400",
225 | 		},
226 | 	}
227 | 
228 | 	tests := []RazorpayToolTestCase{
229 | 		{
230 | 			Name:    "successful settlements fetch with no parameters",
231 | 			Request: map[string]interface{}{},
232 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
233 | 				return mock.NewHTTPClient(
234 | 					mock.Endpoint{
235 | 						Path:     fetchAllSettlementsPath,
236 | 						Method:   "GET",
237 | 						Response: settlementsResp,
238 | 					},
239 | 				)
240 | 			},
241 | 			ExpectError:    false,
242 | 			ExpectedResult: settlementsResp,
243 | 		},
244 | 		{
245 | 			Name: "successful settlements fetch with pagination",
246 | 			Request: map[string]interface{}{
247 | 				"count": float64(10),
248 | 				"skip":  float64(0),
249 | 			},
250 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
251 | 				return mock.NewHTTPClient(
252 | 					mock.Endpoint{
253 | 						Path:     fetchAllSettlementsPath,
254 | 						Method:   "GET",
255 | 						Response: settlementsResp,
256 | 					},
257 | 				)
258 | 			},
259 | 			ExpectError:    false,
260 | 			ExpectedResult: settlementsResp,
261 | 		},
262 | 		{
263 | 			Name: "successful settlements fetch with date range",
264 | 			Request: map[string]interface{}{
265 | 				"from": float64(1609459200), // 2021-01-01
266 | 				"to":   float64(1640995199), // 2021-12-31
267 | 			},
268 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
269 | 				return mock.NewHTTPClient(
270 | 					mock.Endpoint{
271 | 						Path:     fetchAllSettlementsPath,
272 | 						Method:   "GET",
273 | 						Response: settlementsResp,
274 | 					},
275 | 				)
276 | 			},
277 | 			ExpectError:    false,
278 | 			ExpectedResult: settlementsResp,
279 | 		},
280 | 		{
281 | 			Name: "settlements fetch with invalid timestamp",
282 | 			Request: map[string]interface{}{
283 | 				"from": float64(900000000), // Invalid timestamp (too early)
284 | 				"to":   float64(1600000000),
285 | 			},
286 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
287 | 				return mock.NewHTTPClient(
288 | 					mock.Endpoint{
289 | 						Path:     fetchAllSettlementsPath,
290 | 						Method:   "GET",
291 | 						Response: invalidParamsResp,
292 | 					},
293 | 				)
294 | 			},
295 | 			ExpectError: true,
296 | 			ExpectedErrMsg: "fetching settlements failed: from must be " +
297 | 				"between 946684800 and 4765046400",
298 | 		},
299 | 	}
300 | 
301 | 	for _, tc := range tests {
302 | 		t.Run(tc.Name, func(t *testing.T) {
303 | 			runToolTest(t, tc, FetchAllSettlements, "Settlements List")
304 | 		})
305 | 	}
306 | }
307 | 
308 | func Test_CreateInstantSettlement(t *testing.T) {
309 | 	createInstantSettlementPath := fmt.Sprintf(
310 | 		"/%s%s/ondemand",
311 | 		constants.VERSION_V1,
312 | 		constants.SETTLEMENT_URL,
313 | 	)
314 | 
315 | 	// Successful response with all parameters
316 | 	successfulSettlementResp := map[string]interface{}{
317 | 		"id":                  "setlod_FNj7g2YS5J67Rz",
318 | 		"entity":              "settlement.ondemand",
319 | 		"amount_requested":    float64(200000),
320 | 		"amount_settled":      float64(0),
321 | 		"amount_pending":      float64(199410),
322 | 		"amount_reversed":     float64(0),
323 | 		"fees":                float64(590),
324 | 		"tax":                 float64(90),
325 | 		"currency":            "INR",
326 | 		"settle_full_balance": false,
327 | 		"status":              "initiated",
328 | 		"description":         "Need this to make vendor payments.",
329 | 		"notes": map[string]interface{}{
330 | 			"notes_key_1": "Tea, Earl Grey, Hot",
331 | 			"notes_key_2": "Tea, Earl Grey… decaf.",
332 | 		},
333 | 		"created_at": float64(1596771429),
334 | 	}
335 | 
336 | 	// Error response for insufficient amount
337 | 	insufficientAmountResp := map[string]interface{}{
338 | 		"error": map[string]interface{}{
339 | 			"code":        "BAD_REQUEST_ERROR",
340 | 			"description": "Minimum amount that can be settled is ₹ 1.",
341 | 		},
342 | 	}
343 | 
344 | 	tests := []RazorpayToolTestCase{
345 | 		{
346 | 			Name: "successful settlement creation with all parameters",
347 | 			Request: map[string]interface{}{
348 | 				"amount":              float64(200000),
349 | 				"settle_full_balance": false,
350 | 				"description":         "Need this to make vendor payments.",
351 | 				"notes": map[string]interface{}{
352 | 					"notes_key_1": "Tea, Earl Grey, Hot",
353 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
354 | 				},
355 | 			},
356 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
357 | 				return mock.NewHTTPClient(
358 | 					mock.Endpoint{
359 | 						Path:     createInstantSettlementPath,
360 | 						Method:   "POST",
361 | 						Response: successfulSettlementResp,
362 | 					},
363 | 				)
364 | 			},
365 | 			ExpectError:    false,
366 | 			ExpectedResult: successfulSettlementResp,
367 | 		},
368 | 		{
369 | 			Name: "settlement creation with required parameters only",
370 | 			Request: map[string]interface{}{
371 | 				"amount": float64(200000),
372 | 			},
373 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
374 | 				return mock.NewHTTPClient(
375 | 					mock.Endpoint{
376 | 						Path:     createInstantSettlementPath,
377 | 						Method:   "POST",
378 | 						Response: successfulSettlementResp,
379 | 					},
380 | 				)
381 | 			},
382 | 			ExpectError:    false,
383 | 			ExpectedResult: successfulSettlementResp,
384 | 		},
385 | 		{
386 | 			Name: "settlement creation with insufficient amount",
387 | 			Request: map[string]interface{}{
388 | 				"amount": float64(10), // Less than minimum
389 | 			},
390 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
391 | 				return mock.NewHTTPClient(
392 | 					mock.Endpoint{
393 | 						Path:     createInstantSettlementPath,
394 | 						Method:   "POST",
395 | 						Response: insufficientAmountResp,
396 | 					},
397 | 				)
398 | 			},
399 | 			ExpectError: true,
400 | 			ExpectedErrMsg: "creating instant settlement failed: Minimum amount that " +
401 | 				"can be settled is ₹ 1.",
402 | 		},
403 | 		{
404 | 			Name:           "missing amount parameter",
405 | 			Request:        map[string]interface{}{},
406 | 			MockHttpClient: nil, // No HTTP client needed for validation error
407 | 			ExpectError:    true,
408 | 			ExpectedErrMsg: "missing required parameter: amount",
409 | 		},
410 | 	}
411 | 
412 | 	for _, tc := range tests {
413 | 		t.Run(tc.Name, func(t *testing.T) {
414 | 			runToolTest(t, tc, CreateInstantSettlement, "Instant Settlement")
415 | 		})
416 | 	}
417 | }
418 | 
419 | func Test_FetchAllInstantSettlements(t *testing.T) {
420 | 	fetchAllInstantSettlementsPath := fmt.Sprintf(
421 | 		"/%s%s/ondemand",
422 | 		constants.VERSION_V1,
423 | 		constants.SETTLEMENT_URL,
424 | 	)
425 | 
426 | 	// Sample response for successful fetch without expanded payouts
427 | 	basicSettlementListResp := map[string]interface{}{
428 | 		"entity": "collection",
429 | 		"count":  float64(2),
430 | 		"items": []interface{}{
431 | 			map[string]interface{}{
432 | 				"id":                  "setlod_FNj7g2YS5J67Rz",
433 | 				"entity":              "settlement.ondemand",
434 | 				"amount_requested":    float64(200000),
435 | 				"amount_settled":      float64(199410),
436 | 				"amount_pending":      float64(0),
437 | 				"amount_reversed":     float64(0),
438 | 				"fees":                float64(590),
439 | 				"tax":                 float64(90),
440 | 				"currency":            "INR",
441 | 				"settle_full_balance": false,
442 | 				"status":              "processed",
443 | 				"description":         "Need this to make vendor payments.",
444 | 				"notes": map[string]interface{}{
445 | 					"notes_key_1": "Tea, Earl Grey, Hot",
446 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
447 | 				},
448 | 				"created_at": float64(1596771429),
449 | 			},
450 | 			map[string]interface{}{
451 | 				"id":                  "setlod_FJOp0jOWlalIvt",
452 | 				"entity":              "settlement.ondemand",
453 | 				"amount_requested":    float64(300000),
454 | 				"amount_settled":      float64(299114),
455 | 				"amount_pending":      float64(0),
456 | 				"amount_reversed":     float64(0),
457 | 				"fees":                float64(886),
458 | 				"tax":                 float64(136),
459 | 				"currency":            "INR",
460 | 				"settle_full_balance": false,
461 | 				"status":              "processed",
462 | 				"description":         "Need this to buy stock.",
463 | 				"notes": map[string]interface{}{
464 | 					"notes_key_1": "Tea, Earl Grey, Hot",
465 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
466 | 				},
467 | 				"created_at": float64(1595826576),
468 | 			},
469 | 		},
470 | 	}
471 | 
472 | 	// Sample response with expanded payouts
473 | 	expandedSettlementListResp := map[string]interface{}{
474 | 		"entity": "collection",
475 | 		"count":  float64(2),
476 | 		"items": []interface{}{
477 | 			map[string]interface{}{
478 | 				"id":                  "setlod_FNj7g2YS5J67Rz",
479 | 				"entity":              "settlement.ondemand",
480 | 				"amount_requested":    float64(200000),
481 | 				"amount_settled":      float64(199410),
482 | 				"amount_pending":      float64(0),
483 | 				"amount_reversed":     float64(0),
484 | 				"fees":                float64(590),
485 | 				"tax":                 float64(90),
486 | 				"currency":            "INR",
487 | 				"settle_full_balance": false,
488 | 				"status":              "processed",
489 | 				"description":         "Need this to make vendor payments.",
490 | 				"notes": map[string]interface{}{
491 | 					"notes_key_1": "Tea, Earl Grey, Hot",
492 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
493 | 				},
494 | 				"created_at": float64(1596771429),
495 | 				"ondemand_payouts": []interface{}{
496 | 					map[string]interface{}{
497 | 						"id":     "pout_FNj7g2YS5J67Rz",
498 | 						"entity": "payout",
499 | 						"amount": float64(199410),
500 | 						"status": "processed",
501 | 					},
502 | 				},
503 | 			},
504 | 			map[string]interface{}{
505 | 				"id":                  "setlod_FJOp0jOWlalIvt",
506 | 				"entity":              "settlement.ondemand",
507 | 				"amount_requested":    float64(300000),
508 | 				"amount_settled":      float64(299114),
509 | 				"amount_pending":      float64(0),
510 | 				"amount_reversed":     float64(0),
511 | 				"fees":                float64(886),
512 | 				"tax":                 float64(136),
513 | 				"currency":            "INR",
514 | 				"settle_full_balance": false,
515 | 				"status":              "processed",
516 | 				"description":         "Need this to buy stock.",
517 | 				"notes": map[string]interface{}{
518 | 					"notes_key_1": "Tea, Earl Grey, Hot",
519 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
520 | 				},
521 | 				"created_at": float64(1595826576),
522 | 				"ondemand_payouts": []interface{}{
523 | 					map[string]interface{}{
524 | 						"id":     "pout_FJOp0jOWlalIvt",
525 | 						"entity": "payout",
526 | 						"amount": float64(299114),
527 | 						"status": "processed",
528 | 					},
529 | 				},
530 | 			},
531 | 		},
532 | 	}
533 | 
534 | 	// Error response when parameters are invalid
535 | 	invalidParamsResp := map[string]interface{}{
536 | 		"error": map[string]interface{}{
537 | 			"code":        "BAD_REQUEST_ERROR",
538 | 			"description": "from must be between 946684800 and 4765046400",
539 | 		},
540 | 	}
541 | 
542 | 	tests := []RazorpayToolTestCase{
543 | 		{
544 | 			Name:    "successful instant settlements fetch with no parameters",
545 | 			Request: map[string]interface{}{},
546 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
547 | 				return mock.NewHTTPClient(
548 | 					mock.Endpoint{
549 | 						Path:     fetchAllInstantSettlementsPath,
550 | 						Method:   "GET",
551 | 						Response: basicSettlementListResp,
552 | 					},
553 | 				)
554 | 			},
555 | 			ExpectError:    false,
556 | 			ExpectedResult: basicSettlementListResp,
557 | 		},
558 | 		{
559 | 			Name: "instant settlements fetch with pagination",
560 | 			Request: map[string]interface{}{
561 | 				"count": float64(10),
562 | 				"skip":  float64(0),
563 | 			},
564 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
565 | 				return mock.NewHTTPClient(
566 | 					mock.Endpoint{
567 | 						Path:     fetchAllInstantSettlementsPath,
568 | 						Method:   "GET",
569 | 						Response: basicSettlementListResp,
570 | 					},
571 | 				)
572 | 			},
573 | 			ExpectError:    false,
574 | 			ExpectedResult: basicSettlementListResp,
575 | 		},
576 | 		{
577 | 			Name: "instant settlements fetch with expanded payouts",
578 | 			Request: map[string]interface{}{
579 | 				"expand": []interface{}{"ondemand_payouts"},
580 | 			},
581 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
582 | 				return mock.NewHTTPClient(
583 | 					mock.Endpoint{
584 | 						Path:     fetchAllInstantSettlementsPath,
585 | 						Method:   "GET",
586 | 						Response: expandedSettlementListResp,
587 | 					},
588 | 				)
589 | 			},
590 | 			ExpectError:    false,
591 | 			ExpectedResult: expandedSettlementListResp,
592 | 		},
593 | 		{
594 | 			Name: "instant settlements fetch with date range",
595 | 			Request: map[string]interface{}{
596 | 				"from": float64(1609459200), // 2021-01-01
597 | 				"to":   float64(1640995199), // 2021-12-31
598 | 			},
599 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
600 | 				return mock.NewHTTPClient(
601 | 					mock.Endpoint{
602 | 						Path:     fetchAllInstantSettlementsPath,
603 | 						Method:   "GET",
604 | 						Response: basicSettlementListResp,
605 | 					},
606 | 				)
607 | 			},
608 | 			ExpectError:    false,
609 | 			ExpectedResult: basicSettlementListResp,
610 | 		},
611 | 		{
612 | 			Name: "instant settlements fetch with invalid timestamp",
613 | 			Request: map[string]interface{}{
614 | 				"from": float64(900000000), // Invalid timestamp (too early)
615 | 				"to":   float64(1600000000),
616 | 			},
617 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
618 | 				return mock.NewHTTPClient(
619 | 					mock.Endpoint{
620 | 						Path:     fetchAllInstantSettlementsPath,
621 | 						Method:   "GET",
622 | 						Response: invalidParamsResp,
623 | 					},
624 | 				)
625 | 			},
626 | 			ExpectError: true,
627 | 			ExpectedErrMsg: "fetching instant settlements failed: from must be " +
628 | 				"between 946684800 and 4765046400",
629 | 		},
630 | 	}
631 | 
632 | 	for _, tc := range tests {
633 | 		t.Run(tc.Name, func(t *testing.T) {
634 | 			runToolTest(t, tc, FetchAllInstantSettlements, "Instant Settlements List")
635 | 		})
636 | 	}
637 | }
638 | 
639 | func Test_FetchInstantSettlement(t *testing.T) {
640 | 	fetchInstantSettlementPathFmt := fmt.Sprintf(
641 | 		"/%s%s/ondemand/%%s",
642 | 		constants.VERSION_V1,
643 | 		constants.SETTLEMENT_URL,
644 | 	)
645 | 
646 | 	instantSettlementResp := map[string]interface{}{
647 | 		"id":                  "setlod_FNj7g2YS5J67Rz",
648 | 		"entity":              "settlement.ondemand",
649 | 		"amount_requested":    float64(200000),
650 | 		"amount_settled":      float64(199410),
651 | 		"amount_pending":      float64(0),
652 | 		"amount_reversed":     float64(0),
653 | 		"fees":                float64(590),
654 | 		"tax":                 float64(90),
655 | 		"currency":            "INR",
656 | 		"settle_full_balance": false,
657 | 		"status":              "processed",
658 | 		"description":         "Need this to make vendor payments.",
659 | 		"notes": map[string]interface{}{
660 | 			"notes_key_1": "Tea, Earl Grey, Hot",
661 | 			"notes_key_2": "Tea, Earl Grey… decaf.",
662 | 		},
663 | 		"created_at": float64(1596771429),
664 | 	}
665 | 
666 | 	instantSettlementNotFoundResp := map[string]interface{}{
667 | 		"error": map[string]interface{}{
668 | 			"code":        "BAD_REQUEST_ERROR",
669 | 			"description": "instant settlement not found",
670 | 		},
671 | 	}
672 | 
673 | 	tests := []RazorpayToolTestCase{
674 | 		{
675 | 			Name: "successful instant settlement fetch",
676 | 			Request: map[string]interface{}{
677 | 				"settlement_id": "setlod_FNj7g2YS5J67Rz",
678 | 			},
679 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
680 | 				return mock.NewHTTPClient(
681 | 					mock.Endpoint{
682 | 						Path: fmt.Sprintf(fetchInstantSettlementPathFmt,
683 | 							"setlod_FNj7g2YS5J67Rz"),
684 | 						Method:   "GET",
685 | 						Response: instantSettlementResp,
686 | 					},
687 | 				)
688 | 			},
689 | 			ExpectError:    false,
690 | 			ExpectedResult: instantSettlementResp,
691 | 		},
692 | 		{
693 | 			Name: "instant settlement not found",
694 | 			Request: map[string]interface{}{
695 | 				"settlement_id": "setlod_invalid",
696 | 			},
697 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
698 | 				return mock.NewHTTPClient(
699 | 					mock.Endpoint{
700 | 						Path:     fmt.Sprintf(fetchInstantSettlementPathFmt, "setlod_invalid"),
701 | 						Method:   "GET",
702 | 						Response: instantSettlementNotFoundResp,
703 | 					},
704 | 				)
705 | 			},
706 | 			ExpectError: true,
707 | 			ExpectedErrMsg: "fetching instant settlement failed: " +
708 | 				"instant settlement not found",
709 | 		},
710 | 		{
711 | 			Name:           "missing settlement_id parameter",
712 | 			Request:        map[string]interface{}{},
713 | 			MockHttpClient: nil, // No HTTP client needed for validation error
714 | 			ExpectError:    true,
715 | 			ExpectedErrMsg: "missing required parameter: settlement_id",
716 | 		},
717 | 	}
718 | 
719 | 	for _, tc := range tests {
720 | 		t.Run(tc.Name, func(t *testing.T) {
721 | 			runToolTest(t, tc, FetchInstantSettlement, "Instant Settlement")
722 | 		})
723 | 	}
724 | }
725 | 
```

--------------------------------------------------------------------------------
/pkg/razorpay/qr_codes_test.go:
--------------------------------------------------------------------------------

```go
  1 | package razorpay
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"net/http"
  6 | 	"net/http/httptest"
  7 | 	"testing"
  8 | 
  9 | 	"github.com/razorpay/razorpay-go/constants"
 10 | 
 11 | 	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
 12 | )
 13 | 
 14 | func Test_CreateQRCode(t *testing.T) {
 15 | 	createQRCodePath := fmt.Sprintf(
 16 | 		"/%s%s",
 17 | 		constants.VERSION_V1,
 18 | 		constants.QRCODE_URL,
 19 | 	)
 20 | 
 21 | 	qrCodeWithAllParamsResp := map[string]interface{}{
 22 | 		"id":                       "qr_HMsVL8HOpbMcjU",
 23 | 		"entity":                   "qr_code",
 24 | 		"created_at":               float64(1623660301),
 25 | 		"name":                     "Store Front Display",
 26 | 		"usage":                    "single_use",
 27 | 		"type":                     "upi_qr",
 28 | 		"image_url":                "https://rzp.io/i/BWcUVrLp",
 29 | 		"payment_amount":           float64(300),
 30 | 		"status":                   "active",
 31 | 		"description":              "For Store 1",
 32 | 		"fixed_amount":             true,
 33 | 		"payments_amount_received": float64(0),
 34 | 		"payments_count_received":  float64(0),
 35 | 		"notes": map[string]interface{}{
 36 | 			"purpose": "Test UPI QR Code notes",
 37 | 		},
 38 | 		"customer_id": "cust_HKsR5se84c5LTO",
 39 | 		"close_by":    float64(1681615838),
 40 | 	}
 41 | 
 42 | 	qrCodeWithRequiredParamsResp := map[string]interface{}{
 43 | 		"id":                       "qr_HMsVL8HOpbMcjU",
 44 | 		"entity":                   "qr_code",
 45 | 		"created_at":               float64(1623660301),
 46 | 		"usage":                    "multiple_use",
 47 | 		"type":                     "upi_qr",
 48 | 		"image_url":                "https://rzp.io/i/BWcUVrLp",
 49 | 		"status":                   "active",
 50 | 		"fixed_amount":             false,
 51 | 		"payments_amount_received": float64(0),
 52 | 		"payments_count_received":  float64(0),
 53 | 	}
 54 | 
 55 | 	qrCodeWithoutPaymentAmountResp := map[string]interface{}{
 56 | 		"id":                       "qr_HMsVL8HOpbMcjU",
 57 | 		"entity":                   "qr_code",
 58 | 		"created_at":               float64(1623660301),
 59 | 		"name":                     "Store Front Display",
 60 | 		"usage":                    "single_use",
 61 | 		"type":                     "upi_qr",
 62 | 		"image_url":                "https://rzp.io/i/BWcUVrLp",
 63 | 		"status":                   "active",
 64 | 		"description":              "For Store 1",
 65 | 		"fixed_amount":             false,
 66 | 		"payments_amount_received": float64(0),
 67 | 		"payments_count_received":  float64(0),
 68 | 	}
 69 | 
 70 | 	errorResp := map[string]interface{}{
 71 | 		"error": map[string]interface{}{
 72 | 			"code":        "BAD_REQUEST_ERROR",
 73 | 			"description": "The type field is invalid",
 74 | 		},
 75 | 	}
 76 | 
 77 | 	tests := []RazorpayToolTestCase{
 78 | 		{
 79 | 			Name: "successful QR code creation with all parameters",
 80 | 			Request: map[string]interface{}{
 81 | 				"type":           "upi_qr",
 82 | 				"name":           "Store Front Display",
 83 | 				"usage":          "single_use",
 84 | 				"fixed_amount":   true,
 85 | 				"payment_amount": float64(300),
 86 | 				"description":    "For Store 1",
 87 | 				"customer_id":    "cust_HKsR5se84c5LTO",
 88 | 				"close_by":       float64(1681615838),
 89 | 				"notes": map[string]interface{}{
 90 | 					"purpose": "Test UPI QR Code notes",
 91 | 				},
 92 | 			},
 93 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 94 | 				return mock.NewHTTPClient(
 95 | 					mock.Endpoint{
 96 | 						Path:     createQRCodePath,
 97 | 						Method:   "POST",
 98 | 						Response: qrCodeWithAllParamsResp,
 99 | 					},
100 | 				)
101 | 			},
102 | 			ExpectError:    false,
103 | 			ExpectedResult: qrCodeWithAllParamsResp,
104 | 		},
105 | 		{
106 | 			Name: "successful QR code creation with required params only",
107 | 			Request: map[string]interface{}{
108 | 				"type":  "upi_qr",
109 | 				"usage": "multiple_use",
110 | 			},
111 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
112 | 				return mock.NewHTTPClient(
113 | 					mock.Endpoint{
114 | 						Path:     createQRCodePath,
115 | 						Method:   "POST",
116 | 						Response: qrCodeWithRequiredParamsResp,
117 | 					},
118 | 				)
119 | 			},
120 | 			ExpectError:    false,
121 | 			ExpectedResult: qrCodeWithRequiredParamsResp,
122 | 		},
123 | 		{
124 | 			Name: "successful QR code creation without payment amount",
125 | 			Request: map[string]interface{}{
126 | 				"type":         "upi_qr",
127 | 				"name":         "Store Front Display",
128 | 				"usage":        "single_use",
129 | 				"fixed_amount": false,
130 | 				"description":  "For Store 1",
131 | 			},
132 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
133 | 				return mock.NewHTTPClient(
134 | 					mock.Endpoint{
135 | 						Path:     createQRCodePath,
136 | 						Method:   "POST",
137 | 						Response: qrCodeWithoutPaymentAmountResp,
138 | 					},
139 | 				)
140 | 			},
141 | 			ExpectError:    false,
142 | 			ExpectedResult: qrCodeWithoutPaymentAmountResp,
143 | 		},
144 | 		{
145 | 			Name: "missing required type parameter",
146 | 			Request: map[string]interface{}{
147 | 				"usage": "single_use",
148 | 			},
149 | 			MockHttpClient: nil,
150 | 			ExpectError:    true,
151 | 			ExpectedErrMsg: "missing required parameter: type",
152 | 		},
153 | 		{
154 | 			Name: "missing required usage parameter",
155 | 			Request: map[string]interface{}{
156 | 				"type": "upi_qr",
157 | 			},
158 | 			MockHttpClient: nil,
159 | 			ExpectError:    true,
160 | 			ExpectedErrMsg: "missing required parameter: usage",
161 | 		},
162 | 		{
163 | 			Name: "validator error - invalid parameter type",
164 | 			Request: map[string]interface{}{
165 | 				"type":  123,
166 | 				"usage": "single_use",
167 | 			},
168 | 			MockHttpClient: nil,
169 | 			ExpectError:    true,
170 | 			ExpectedErrMsg: "Validation errors",
171 | 		},
172 | 		{
173 | 			Name: "fixed_amount true but payment_amount missing",
174 | 			Request: map[string]interface{}{
175 | 				"type":         "upi_qr",
176 | 				"usage":        "single_use",
177 | 				"fixed_amount": true,
178 | 			},
179 | 			MockHttpClient: nil,
180 | 			ExpectError:    true,
181 | 			ExpectedErrMsg: "payment_amount is required when fixed_amount is true",
182 | 		},
183 | 		{
184 | 			Name: "invalid type parameter",
185 | 			Request: map[string]interface{}{
186 | 				"type":  "invalid_type",
187 | 				"usage": "single_use",
188 | 			},
189 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
190 | 				return mock.NewHTTPClient(
191 | 					mock.Endpoint{
192 | 						Path:     createQRCodePath,
193 | 						Method:   "POST",
194 | 						Response: errorResp,
195 | 					},
196 | 				)
197 | 			},
198 | 			ExpectError:    true,
199 | 			ExpectedErrMsg: "creating QR code failed: The type field is invalid",
200 | 		},
201 | 	}
202 | 
203 | 	for _, tc := range tests {
204 | 		t.Run(tc.Name, func(t *testing.T) {
205 | 			runToolTest(t, tc, CreateQRCode, "QR Code")
206 | 		})
207 | 	}
208 | }
209 | 
210 | func Test_FetchAllQRCodes(t *testing.T) {
211 | 	qrCodesPath := fmt.Sprintf(
212 | 		"/%s%s",
213 | 		constants.VERSION_V1,
214 | 		constants.QRCODE_URL,
215 | 	)
216 | 
217 | 	allQRCodesResp := map[string]interface{}{
218 | 		"entity": "collection",
219 | 		"count":  float64(2),
220 | 		"items": []interface{}{
221 | 			map[string]interface{}{
222 | 				"id":                       "qr_HO2jGkWReVBMNu",
223 | 				"entity":                   "qr_code",
224 | 				"created_at":               float64(1623914648),
225 | 				"name":                     "Store_1",
226 | 				"usage":                    "single_use",
227 | 				"type":                     "upi_qr",
228 | 				"image_url":                "https://rzp.io/i/w2CEwYmkAu",
229 | 				"payment_amount":           float64(300),
230 | 				"status":                   "active",
231 | 				"description":              "For Store 1",
232 | 				"fixed_amount":             true,
233 | 				"payments_amount_received": float64(0),
234 | 				"payments_count_received":  float64(0),
235 | 				"notes": map[string]interface{}{
236 | 					"purpose": "Test UPI QR Code notes",
237 | 				},
238 | 				"customer_id":  "cust_HKsR5se84c5LTO",
239 | 				"close_by":     float64(1681615838),
240 | 				"closed_at":    nil,
241 | 				"close_reason": nil,
242 | 			},
243 | 			map[string]interface{}{
244 | 				"id":                       "qr_HO2e0813YlchUn",
245 | 				"entity":                   "qr_code",
246 | 				"created_at":               float64(1623914349),
247 | 				"name":                     "Acme Groceries",
248 | 				"usage":                    "multiple_use",
249 | 				"type":                     "upi_qr",
250 | 				"image_url":                "https://rzp.io/i/X6QM7LL",
251 | 				"payment_amount":           nil,
252 | 				"status":                   "closed",
253 | 				"description":              "Buy fresh groceries",
254 | 				"fixed_amount":             false,
255 | 				"payments_amount_received": float64(200),
256 | 				"payments_count_received":  float64(1),
257 | 				"notes": map[string]interface{}{
258 | 					"Branch": "Bangalore - Rajaji Nagar",
259 | 				},
260 | 				"customer_id":  "cust_HKsR5se84c5LTO",
261 | 				"close_by":     float64(1625077799),
262 | 				"closed_at":    float64(1623914515),
263 | 				"close_reason": "on_demand",
264 | 			},
265 | 		},
266 | 	}
267 | 
268 | 	errorResp := map[string]interface{}{
269 | 		"error": map[string]interface{}{
270 | 			"code":        "BAD_REQUEST_ERROR",
271 | 			"description": "The query parameters are invalid",
272 | 		},
273 | 	}
274 | 
275 | 	tests := []RazorpayToolTestCase{
276 | 		{
277 | 			Name:    "successful fetch all QR codes with no parameters",
278 | 			Request: map[string]interface{}{},
279 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
280 | 				return mock.NewHTTPClient(
281 | 					mock.Endpoint{
282 | 						Path:     qrCodesPath,
283 | 						Method:   "GET",
284 | 						Response: allQRCodesResp,
285 | 					},
286 | 				)
287 | 			},
288 | 			ExpectError:    false,
289 | 			ExpectedResult: allQRCodesResp,
290 | 		},
291 | 		{
292 | 			Name: "successful fetch all QR codes with count parameter",
293 | 			Request: map[string]interface{}{
294 | 				"count": float64(2),
295 | 			},
296 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
297 | 				return mock.NewHTTPClient(
298 | 					mock.Endpoint{
299 | 						Path:     qrCodesPath,
300 | 						Method:   "GET",
301 | 						Response: allQRCodesResp,
302 | 					},
303 | 				)
304 | 			},
305 | 			ExpectError:    false,
306 | 			ExpectedResult: allQRCodesResp,
307 | 		},
308 | 		{
309 | 			Name: "successful fetch all QR codes with pagination parameters",
310 | 			Request: map[string]interface{}{
311 | 				"from":  float64(1622000000),
312 | 				"to":    float64(1625000000),
313 | 				"count": float64(2),
314 | 				"skip":  float64(0),
315 | 			},
316 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
317 | 				return mock.NewHTTPClient(
318 | 					mock.Endpoint{
319 | 						Path:     qrCodesPath,
320 | 						Method:   "GET",
321 | 						Response: allQRCodesResp,
322 | 					},
323 | 				)
324 | 			},
325 | 			ExpectError:    false,
326 | 			ExpectedResult: allQRCodesResp,
327 | 		},
328 | 		{
329 | 			Name: "invalid parameters - caught by SDK",
330 | 			Request: map[string]interface{}{
331 | 				"count": float64(-1),
332 | 			},
333 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
334 | 				return mock.NewHTTPClient(
335 | 					mock.Endpoint{
336 | 						Path:   qrCodesPath,
337 | 						Method: "GET",
338 | 						Response: map[string]interface{}{
339 | 							"error": map[string]interface{}{
340 | 								"code":        "BAD_REQUEST_ERROR",
341 | 								"description": "The count value should be greater than or equal to 1",
342 | 							},
343 | 						},
344 | 					},
345 | 				)
346 | 			},
347 | 			ExpectError: true,
348 | 			ExpectedErrMsg: "fetching QR codes failed: " +
349 | 				"The count value should be greater than or equal to 1",
350 | 		},
351 | 		{
352 | 			Name: "validator error - invalid count parameter type",
353 | 			Request: map[string]interface{}{
354 | 				"count": "not-a-number",
355 | 			},
356 | 			MockHttpClient: nil,
357 | 			ExpectError:    true,
358 | 			ExpectedErrMsg: "Validation errors",
359 | 		},
360 | 		{
361 | 			Name: "API error response",
362 | 			Request: map[string]interface{}{
363 | 				"count": float64(1000),
364 | 			},
365 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
366 | 				return mock.NewHTTPClient(
367 | 					mock.Endpoint{
368 | 						Path:     qrCodesPath,
369 | 						Method:   "GET",
370 | 						Response: errorResp,
371 | 					},
372 | 				)
373 | 			},
374 | 			ExpectError:    true,
375 | 			ExpectedErrMsg: "fetching QR codes failed: The query parameters are invalid",
376 | 		},
377 | 	}
378 | 
379 | 	for _, tc := range tests {
380 | 		t.Run(tc.Name, func(t *testing.T) {
381 | 			runToolTest(t, tc, FetchAllQRCodes, "QR Codes")
382 | 		})
383 | 	}
384 | }
385 | 
386 | func Test_FetchQRCodesByCustomerID(t *testing.T) {
387 | 	qrCodesPath := fmt.Sprintf(
388 | 		"/%s%s",
389 | 		constants.VERSION_V1,
390 | 		constants.QRCODE_URL,
391 | 	)
392 | 
393 | 	customerQRCodesResp := map[string]interface{}{
394 | 		"entity": "collection",
395 | 		"count":  float64(1),
396 | 		"items": []interface{}{
397 | 			map[string]interface{}{
398 | 				"id":                       "qr_HMsgvioW64f0vh",
399 | 				"entity":                   "qr_code",
400 | 				"created_at":               float64(1623660959),
401 | 				"name":                     "Store_1",
402 | 				"usage":                    "single_use",
403 | 				"type":                     "upi_qr",
404 | 				"image_url":                "https://rzp.io/i/DTa2eQR",
405 | 				"payment_amount":           float64(300),
406 | 				"status":                   "active",
407 | 				"description":              "For Store 1",
408 | 				"fixed_amount":             true,
409 | 				"payments_amount_received": float64(0),
410 | 				"payments_count_received":  float64(0),
411 | 				"notes": map[string]interface{}{
412 | 					"purpose": "Test UPI QR Code notes",
413 | 				},
414 | 				"customer_id":  "cust_HKsR5se84c5LTO",
415 | 				"close_by":     float64(1681615838),
416 | 				"closed_at":    nil,
417 | 				"close_reason": nil,
418 | 			},
419 | 		},
420 | 	}
421 | 
422 | 	errorResp := map[string]interface{}{
423 | 		"error": map[string]interface{}{
424 | 			"code":        "BAD_REQUEST_ERROR",
425 | 			"description": "The id provided is not a valid id",
426 | 		},
427 | 	}
428 | 
429 | 	tests := []RazorpayToolTestCase{
430 | 		{
431 | 			Name: "successful fetch QR codes by customer ID",
432 | 			Request: map[string]interface{}{
433 | 				"customer_id": "cust_HKsR5se84c5LTO",
434 | 			},
435 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
436 | 				return mock.NewHTTPClient(
437 | 					mock.Endpoint{
438 | 						Path:     qrCodesPath,
439 | 						Method:   "GET",
440 | 						Response: customerQRCodesResp,
441 | 					},
442 | 				)
443 | 			},
444 | 			ExpectError:    false,
445 | 			ExpectedResult: customerQRCodesResp,
446 | 		},
447 | 		{
448 | 			Name:           "missing required customer_id parameter",
449 | 			Request:        map[string]interface{}{},
450 | 			MockHttpClient: nil,
451 | 			ExpectError:    true,
452 | 			ExpectedErrMsg: "missing required parameter: customer_id",
453 | 		},
454 | 		{
455 | 			Name: "validator error - invalid customer_id parameter type",
456 | 			Request: map[string]interface{}{
457 | 				"customer_id": 12345,
458 | 			},
459 | 			MockHttpClient: nil,
460 | 			ExpectError:    true,
461 | 			ExpectedErrMsg: "invalid parameter type: customer_id",
462 | 		},
463 | 		{
464 | 			Name: "API error - invalid customer ID",
465 | 			Request: map[string]interface{}{
466 | 				"customer_id": "invalid_customer_id",
467 | 			},
468 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
469 | 				return mock.NewHTTPClient(
470 | 					mock.Endpoint{
471 | 						Path:     qrCodesPath,
472 | 						Method:   "GET",
473 | 						Response: errorResp,
474 | 					},
475 | 				)
476 | 			},
477 | 			ExpectError: true,
478 | 			ExpectedErrMsg: "fetching QR codes failed: " +
479 | 				"The id provided is not a valid id",
480 | 		},
481 | 	}
482 | 
483 | 	for _, tc := range tests {
484 | 		t.Run(tc.Name, func(t *testing.T) {
485 | 			runToolTest(t, tc, FetchQRCodesByCustomerID, "QR Codes by Customer ID")
486 | 		})
487 | 	}
488 | }
489 | 
490 | func Test_FetchQRCodesByPaymentID(t *testing.T) {
491 | 	qrCodesPath := fmt.Sprintf(
492 | 		"/%s%s",
493 | 		constants.VERSION_V1,
494 | 		constants.QRCODE_URL,
495 | 	)
496 | 
497 | 	paymentQRCodesResp := map[string]interface{}{
498 | 		"entity": "collection",
499 | 		"count":  float64(1),
500 | 		"items": []interface{}{
501 | 			map[string]interface{}{
502 | 				"id":                       "qr_HMsqRoeVwKbwAF",
503 | 				"entity":                   "qr_code",
504 | 				"created_at":               float64(1623661499),
505 | 				"name":                     "Fresh Groceries",
506 | 				"usage":                    "multiple_use",
507 | 				"type":                     "upi_qr",
508 | 				"image_url":                "https://rzp.io/i/eI9XD54Q",
509 | 				"payment_amount":           nil,
510 | 				"status":                   "active",
511 | 				"description":              "Buy fresh groceries",
512 | 				"fixed_amount":             false,
513 | 				"payments_amount_received": float64(1000),
514 | 				"payments_count_received":  float64(1),
515 | 				"notes":                    []interface{}{},
516 | 				"customer_id":              "cust_HKsR5se84c5LTO",
517 | 				"close_by":                 float64(1624472999),
518 | 				"close_reason":             nil,
519 | 			},
520 | 		},
521 | 	}
522 | 
523 | 	errorResp := map[string]interface{}{
524 | 		"error": map[string]interface{}{
525 | 			"code":        "BAD_REQUEST_ERROR",
526 | 			"description": "The id provided is not a valid id",
527 | 		},
528 | 	}
529 | 
530 | 	tests := []RazorpayToolTestCase{
531 | 		{
532 | 			Name: "successful fetch QR codes by payment ID",
533 | 			Request: map[string]interface{}{
534 | 				"payment_id": "pay_Di5iqCqA1WEHq6",
535 | 			},
536 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
537 | 				return mock.NewHTTPClient(
538 | 					mock.Endpoint{
539 | 						Path:     qrCodesPath,
540 | 						Method:   "GET",
541 | 						Response: paymentQRCodesResp,
542 | 					},
543 | 				)
544 | 			},
545 | 			ExpectError:    false,
546 | 			ExpectedResult: paymentQRCodesResp,
547 | 		},
548 | 		{
549 | 			Name:           "missing required payment_id parameter",
550 | 			Request:        map[string]interface{}{},
551 | 			MockHttpClient: nil,
552 | 			ExpectError:    true,
553 | 			ExpectedErrMsg: "missing required parameter: payment_id",
554 | 		},
555 | 		{
556 | 			Name: "validator error - invalid payment_id parameter type",
557 | 			Request: map[string]interface{}{
558 | 				"payment_id": 12345,
559 | 			},
560 | 			MockHttpClient: nil,
561 | 			ExpectError:    true,
562 | 			ExpectedErrMsg: "invalid parameter type: payment_id",
563 | 		},
564 | 		{
565 | 			Name: "API error - invalid payment ID",
566 | 			Request: map[string]interface{}{
567 | 				"payment_id": "invalid_payment_id",
568 | 			},
569 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
570 | 				return mock.NewHTTPClient(
571 | 					mock.Endpoint{
572 | 						Path:     qrCodesPath,
573 | 						Method:   "GET",
574 | 						Response: errorResp,
575 | 					},
576 | 				)
577 | 			},
578 | 			ExpectError: true,
579 | 			ExpectedErrMsg: "fetching QR codes failed: " +
580 | 				"The id provided is not a valid id",
581 | 		},
582 | 	}
583 | 
584 | 	for _, tc := range tests {
585 | 		t.Run(tc.Name, func(t *testing.T) {
586 | 			runToolTest(t, tc, FetchQRCodesByPaymentID, "QR Codes by Payment ID")
587 | 		})
588 | 	}
589 | }
590 | 
591 | func TestFetchQRCode(t *testing.T) {
592 | 	// Initialize necessary variables
593 | 	qrID := "qr_FuZIYx6rMbP6gs"
594 | 	apiPath := fmt.Sprintf(
595 | 		"/%s%s/%s",
596 | 		constants.VERSION_V1,
597 | 		constants.QRCODE_URL,
598 | 		qrID,
599 | 	)
600 | 
601 | 	// Successful response based on Razorpay docs
602 | 	successResponse := map[string]interface{}{
603 | 		"id":                       qrID,
604 | 		"entity":                   "qr_code",
605 | 		"created_at":               float64(1623915088),
606 | 		"name":                     "Store_1",
607 | 		"usage":                    "single_use",
608 | 		"type":                     "upi_qr",
609 | 		"image_url":                "https://rzp.io/i/oCswTOcCo",
610 | 		"payment_amount":           float64(300),
611 | 		"status":                   "active",
612 | 		"description":              "For Store 1",
613 | 		"fixed_amount":             true,
614 | 		"payments_amount_received": float64(0),
615 | 		"payments_count_received":  float64(0),
616 | 		"notes": map[string]interface{}{
617 | 			"purpose": "Test UPI QR Code notes",
618 | 		},
619 | 		"customer_id":  "cust_HKsR5se84c5LTO",
620 | 		"close_by":     float64(1681615838),
621 | 		"closed_at":    nil,
622 | 		"close_reason": nil,
623 | 	}
624 | 
625 | 	errorResp := map[string]interface{}{
626 | 		"error": map[string]interface{}{
627 | 			"code":        "BAD_REQUEST_ERROR",
628 | 			"description": "The QR code ID provided is invalid",
629 | 		},
630 | 	}
631 | 
632 | 	tests := []RazorpayToolTestCase{
633 | 		{
634 | 			Name: "successful fetch QR code by ID",
635 | 			Request: map[string]interface{}{
636 | 				"qr_code_id": qrID,
637 | 			},
638 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
639 | 				return mock.NewHTTPClient(
640 | 					mock.Endpoint{
641 | 						Path:     apiPath,
642 | 						Method:   "GET",
643 | 						Response: successResponse,
644 | 					},
645 | 				)
646 | 			},
647 | 			ExpectError:    false,
648 | 			ExpectedResult: successResponse,
649 | 		},
650 | 		{
651 | 			Name:           "missing required qr_code_id parameter",
652 | 			Request:        map[string]interface{}{},
653 | 			MockHttpClient: nil,
654 | 			ExpectError:    true,
655 | 			ExpectedErrMsg: "missing required parameter: qr_code_id",
656 | 		},
657 | 		{
658 | 			Name: "validator error - invalid qr_code_id parameter type",
659 | 			Request: map[string]interface{}{
660 | 				"qr_code_id": 12345,
661 | 			},
662 | 			MockHttpClient: nil,
663 | 			ExpectError:    true,
664 | 			ExpectedErrMsg: "invalid parameter type: qr_code_id",
665 | 		},
666 | 		{
667 | 			Name: "API error - invalid QR code ID",
668 | 			Request: map[string]interface{}{
669 | 				"qr_code_id": qrID,
670 | 			},
671 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
672 | 				return mock.NewHTTPClient(
673 | 					mock.Endpoint{
674 | 						Path:     apiPath,
675 | 						Method:   "GET",
676 | 						Response: errorResp,
677 | 					},
678 | 				)
679 | 			},
680 | 			ExpectError: true,
681 | 			ExpectedErrMsg: "fetching QR code failed: " +
682 | 				"The QR code ID provided is invalid",
683 | 		},
684 | 	}
685 | 
686 | 	for _, tc := range tests {
687 | 		t.Run(tc.Name, func(t *testing.T) {
688 | 			runToolTest(t, tc, FetchQRCode, "QR Code")
689 | 		})
690 | 	}
691 | }
692 | 
693 | func TestFetchPaymentsForQRCode(t *testing.T) {
694 | 	apiPath := "/" + constants.VERSION_V1 +
695 | 		constants.QRCODE_URL + "/qr_test123/payments"
696 | 
697 | 	successResponse := map[string]interface{}{
698 | 		"entity": "collection",
699 | 		"count":  float64(2),
700 | 		"items": []interface{}{
701 | 			map[string]interface{}{
702 | 				"id":              "pay_test123",
703 | 				"entity":          "payment",
704 | 				"amount":          float64(500),
705 | 				"currency":        "INR",
706 | 				"status":          "captured",
707 | 				"method":          "upi",
708 | 				"amount_refunded": float64(0),
709 | 				"refund_status":   nil,
710 | 				"captured":        true,
711 | 				"description":     "QRv2 Payment",
712 | 				"customer_id":     "cust_test123",
713 | 				"created_at":      float64(1623662800),
714 | 			},
715 | 			map[string]interface{}{
716 | 				"id":              "pay_test456",
717 | 				"entity":          "payment",
718 | 				"amount":          float64(1000),
719 | 				"currency":        "INR",
720 | 				"status":          "refunded",
721 | 				"method":          "upi",
722 | 				"amount_refunded": float64(1000),
723 | 				"refund_status":   "full",
724 | 				"captured":        true,
725 | 				"description":     "QRv2 Payment",
726 | 				"customer_id":     "cust_test123",
727 | 				"created_at":      float64(1623661533),
728 | 			},
729 | 		},
730 | 	}
731 | 
732 | 	tests := []RazorpayToolTestCase{
733 | 		{
734 | 			Name: "successful fetch payments for QR code",
735 | 			Request: map[string]interface{}{
736 | 				"qr_code_id": "qr_test123",
737 | 				"count":      10,
738 | 				"from":       1623661000,
739 | 				"to":         1623663000,
740 | 				"skip":       0,
741 | 			},
742 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
743 | 				return mock.NewHTTPClient(
744 | 					mock.Endpoint{
745 | 						Path:     apiPath,
746 | 						Method:   "GET",
747 | 						Response: successResponse,
748 | 					},
749 | 				)
750 | 			},
751 | 			ExpectError:    false,
752 | 			ExpectedResult: successResponse,
753 | 		},
754 | 		{
755 | 			Name: "missing required parameter",
756 | 			Request: map[string]interface{}{
757 | 				"count": 10,
758 | 			},
759 | 			MockHttpClient: nil,
760 | 			ExpectError:    true,
761 | 			ExpectedErrMsg: "missing required parameter: qr_code_id",
762 | 		},
763 | 		{
764 | 			Name: "invalid parameter type",
765 | 			Request: map[string]interface{}{
766 | 				"qr_code_id": 123,
767 | 			},
768 | 			MockHttpClient: nil,
769 | 			ExpectError:    true,
770 | 			ExpectedErrMsg: "invalid parameter type: qr_code_id",
771 | 		},
772 | 		{
773 | 			Name: "API error",
774 | 			Request: map[string]interface{}{
775 | 				"qr_code_id": "qr_test123",
776 | 			},
777 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
778 | 				return mock.NewHTTPClient(
779 | 					mock.Endpoint{
780 | 						Path:   apiPath,
781 | 						Method: "GET",
782 | 						Response: map[string]interface{}{
783 | 							"error": map[string]interface{}{
784 | 								"code":        "BAD_REQUEST_ERROR",
785 | 								"description": "mock error",
786 | 							},
787 | 						},
788 | 					},
789 | 				)
790 | 			},
791 | 			ExpectError:    true,
792 | 			ExpectedErrMsg: "fetching payments for QR code failed: mock error",
793 | 		},
794 | 	}
795 | 
796 | 	for _, tc := range tests {
797 | 		t.Run(tc.Name, func(t *testing.T) {
798 | 			runToolTest(t, tc, FetchPaymentsForQRCode, "QR Code Payments")
799 | 		})
800 | 	}
801 | }
802 | 
803 | func TestCloseQRCode(t *testing.T) {
804 | 	successResponse := map[string]interface{}{
805 | 		"id":                       "qr_HMsVL8HOpbMcjU",
806 | 		"entity":                   "qr_code",
807 | 		"created_at":               float64(1623660301),
808 | 		"name":                     "Store_1",
809 | 		"usage":                    "single_use",
810 | 		"type":                     "upi_qr",
811 | 		"image_url":                "https://rzp.io/i/BWcUVrLp",
812 | 		"payment_amount":           float64(300),
813 | 		"status":                   "closed",
814 | 		"description":              "For Store 1",
815 | 		"fixed_amount":             true,
816 | 		"payments_amount_received": float64(0),
817 | 		"payments_count_received":  float64(0),
818 | 		"notes": map[string]interface{}{
819 | 			"purpose": "Test UPI QR Code notes",
820 | 		},
821 | 		"customer_id":  "cust_HKsR5se84c5LTO",
822 | 		"close_by":     float64(1681615838),
823 | 		"closed_at":    float64(1623660445),
824 | 		"close_reason": "on_demand",
825 | 	}
826 | 
827 | 	baseAPIPath := fmt.Sprintf("/%s%s", constants.VERSION_V1, constants.QRCODE_URL)
828 | 	qrCodeID := "qr_HMsVL8HOpbMcjU"
829 | 	apiPath := fmt.Sprintf("%s/%s/close", baseAPIPath, qrCodeID)
830 | 
831 | 	tests := []RazorpayToolTestCase{
832 | 		{
833 | 			Name: "successful close QR code",
834 | 			Request: map[string]interface{}{
835 | 				"qr_code_id": qrCodeID,
836 | 			},
837 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
838 | 				return mock.NewHTTPClient(
839 | 					mock.Endpoint{
840 | 						Path:     apiPath,
841 | 						Method:   "POST",
842 | 						Response: successResponse,
843 | 					},
844 | 				)
845 | 			},
846 | 			ExpectError:    false,
847 | 			ExpectedResult: successResponse,
848 | 		},
849 | 		{
850 | 			Name:           "missing required qr_code_id parameter",
851 | 			Request:        map[string]interface{}{},
852 | 			MockHttpClient: nil,
853 | 			ExpectError:    true,
854 | 			ExpectedErrMsg: "missing required parameter: qr_code_id",
855 | 		},
856 | 	}
857 | 
858 | 	for _, tc := range tests {
859 | 		t.Run(tc.Name, func(t *testing.T) {
860 | 			runToolTest(t, tc, CloseQRCode, "QR Code")
861 | 		})
862 | 	}
863 | }
864 | 
```

--------------------------------------------------------------------------------
/pkg/razorpay/orders_test.go:
--------------------------------------------------------------------------------

```go
  1 | package razorpay
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"net/http"
  6 | 	"net/http/httptest"
  7 | 	"testing"
  8 | 
  9 | 	"github.com/razorpay/razorpay-go/constants"
 10 | 
 11 | 	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
 12 | )
 13 | 
 14 | func Test_CreateOrder(t *testing.T) {
 15 | 	createOrderPath := fmt.Sprintf(
 16 | 		"/%s%s",
 17 | 		constants.VERSION_V1,
 18 | 		constants.ORDER_URL,
 19 | 	)
 20 | 
 21 | 	// Define common response maps to be reused
 22 | 	orderWithAllParamsResp := map[string]interface{}{
 23 | 		"id":                       "order_EKwxwAgItmmXdp",
 24 | 		"amount":                   float64(10000),
 25 | 		"currency":                 "INR",
 26 | 		"receipt":                  "receipt-123",
 27 | 		"partial_payment":          true,
 28 | 		"first_payment_min_amount": float64(5000),
 29 | 		"notes": map[string]interface{}{
 30 | 			"customer_name": "test-customer",
 31 | 			"product_name":  "test-product",
 32 | 		},
 33 | 		"status": "created",
 34 | 	}
 35 | 
 36 | 	orderWithRequiredParamsResp := map[string]interface{}{
 37 | 		"id":       "order_EKwxwAgItmmXdp",
 38 | 		"amount":   float64(10000),
 39 | 		"currency": "INR",
 40 | 		"status":   "created",
 41 | 	}
 42 | 
 43 | 	errorResp := map[string]interface{}{
 44 | 		"error": map[string]interface{}{
 45 | 			"code":        "BAD_REQUEST_ERROR",
 46 | 			"description": "Razorpay API error: Bad request",
 47 | 		},
 48 | 	}
 49 | 
 50 | 	tests := []RazorpayToolTestCase{
 51 | 		{
 52 | 			Name: "successful order creation with all parameters",
 53 | 			Request: map[string]interface{}{
 54 | 				"amount":                   float64(10000),
 55 | 				"currency":                 "INR",
 56 | 				"receipt":                  "receipt-123",
 57 | 				"partial_payment":          true,
 58 | 				"first_payment_min_amount": float64(5000),
 59 | 				"notes": map[string]interface{}{
 60 | 					"customer_name": "test-customer",
 61 | 					"product_name":  "test-product",
 62 | 				},
 63 | 			},
 64 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 65 | 				return mock.NewHTTPClient(
 66 | 					mock.Endpoint{
 67 | 						Path:     createOrderPath,
 68 | 						Method:   "POST",
 69 | 						Response: orderWithAllParamsResp,
 70 | 					},
 71 | 				)
 72 | 			},
 73 | 			ExpectError:    false,
 74 | 			ExpectedResult: orderWithAllParamsResp,
 75 | 		},
 76 | 		{
 77 | 			Name: "successful order creation with required params only",
 78 | 			Request: map[string]interface{}{
 79 | 				"amount":   float64(10000),
 80 | 				"currency": "INR",
 81 | 			},
 82 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
 83 | 				return mock.NewHTTPClient(
 84 | 					mock.Endpoint{
 85 | 						Path:     createOrderPath,
 86 | 						Method:   "POST",
 87 | 						Response: orderWithRequiredParamsResp,
 88 | 					},
 89 | 				)
 90 | 			},
 91 | 			ExpectError:    false,
 92 | 			ExpectedResult: orderWithRequiredParamsResp,
 93 | 		},
 94 | 		{
 95 | 			Name: "multiple validation errors",
 96 | 			Request: map[string]interface{}{
 97 | 				// Missing both amount and currency (required parameters)
 98 | 				"partial_payment":          "invalid_boolean", // Wrong type for boolean
 99 | 				"first_payment_min_amount": "invalid_number",  // Wrong type for number
100 | 			},
101 | 			MockHttpClient: nil, // No HTTP client needed for validation error
102 | 			ExpectError:    true,
103 | 			ExpectedErrMsg: "Validation errors:\n- " +
104 | 				"missing required parameter: amount\n- " +
105 | 				"missing required parameter: currency\n- " +
106 | 				"invalid parameter type: partial_payment",
107 | 		},
108 | 		{
109 | 			Name: "first_payment_min_amount validation when partial_payment is true",
110 | 			Request: map[string]interface{}{
111 | 				"amount":                   float64(10000),
112 | 				"currency":                 "INR",
113 | 				"partial_payment":          true,
114 | 				"first_payment_min_amount": "invalid_number",
115 | 			},
116 | 			MockHttpClient: nil, // No HTTP client needed for validation error
117 | 			ExpectError:    true,
118 | 			ExpectedErrMsg: "Validation errors:\n- " +
119 | 				"invalid parameter type: first_payment_min_amount",
120 | 		},
121 | 		{
122 | 			Name: "order creation fails",
123 | 			Request: map[string]interface{}{
124 | 				"amount":   float64(10000),
125 | 				"currency": "INR",
126 | 			},
127 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
128 | 				return mock.NewHTTPClient(
129 | 					mock.Endpoint{
130 | 						Path:     createOrderPath,
131 | 						Method:   "POST",
132 | 						Response: errorResp,
133 | 					},
134 | 				)
135 | 			},
136 | 			ExpectError:    true,
137 | 			ExpectedErrMsg: "creating order failed: Razorpay API error: Bad request",
138 | 		},
139 | 		{
140 | 			Name: "successful SBMD mandate order creation",
141 | 			Request: map[string]interface{}{
142 | 				"amount":      float64(500000),
143 | 				"currency":    "INR",
144 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
145 | 				"method":      "upi",
146 | 				"token": map[string]interface{}{
147 | 					"max_amount": float64(500000),
148 | 					"expire_at":  float64(2709971120),
149 | 					"frequency":  "as_presented",
150 | 					"type":       "single_block_multiple_debit",
151 | 				},
152 | 				"receipt": "Receipt No. 1",
153 | 				"notes": map[string]interface{}{
154 | 					"notes_key_1": "Tea, Earl Grey, Hot",
155 | 					"notes_key_2": "Tea, Earl Grey... decaf.",
156 | 				},
157 | 			},
158 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
159 | 				sbmdOrderResp := map[string]interface{}{
160 | 					"id":          "order_SBMD123456",
161 | 					"amount":      float64(500000),
162 | 					"currency":    "INR",
163 | 					"customer_id": "cust_4xbQrmEoA5WJ01",
164 | 					"method":      "upi",
165 | 					"token": map[string]interface{}{
166 | 						"max_amount": float64(500000),
167 | 						"expire_at":  float64(2709971120),
168 | 						"frequency":  "as_presented",
169 | 						"type":       "single_block_multiple_debit",
170 | 					},
171 | 					"receipt": "Receipt No. 1",
172 | 					"status":  "created",
173 | 					"notes": map[string]interface{}{
174 | 						"notes_key_1": "Tea, Earl Grey, Hot",
175 | 						"notes_key_2": "Tea, Earl Grey... decaf.",
176 | 					},
177 | 				}
178 | 				return mock.NewHTTPClient(
179 | 					mock.Endpoint{
180 | 						Path:     createOrderPath,
181 | 						Method:   "POST",
182 | 						Response: sbmdOrderResp,
183 | 					},
184 | 				)
185 | 			},
186 | 			ExpectError: false,
187 | 			ExpectedResult: map[string]interface{}{
188 | 				"id":          "order_SBMD123456",
189 | 				"amount":      float64(500000),
190 | 				"currency":    "INR",
191 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
192 | 				"method":      "upi",
193 | 				"token": map[string]interface{}{
194 | 					"max_amount": float64(500000),
195 | 					"expire_at":  float64(2709971120),
196 | 					"frequency":  "as_presented",
197 | 					"type":       "single_block_multiple_debit",
198 | 				},
199 | 				"receipt": "Receipt No. 1",
200 | 				"status":  "created",
201 | 				"notes": map[string]interface{}{
202 | 					"notes_key_1": "Tea, Earl Grey, Hot",
203 | 					"notes_key_2": "Tea, Earl Grey... decaf.",
204 | 				},
205 | 			},
206 | 		},
207 | 		{
208 | 			Name: "mandate order with invalid token parameter type",
209 | 			Request: map[string]interface{}{
210 | 				"amount":      float64(500000),
211 | 				"currency":    "INR",
212 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
213 | 				"method":      "upi",
214 | 				"token":       "invalid_token_should_be_object",
215 | 			},
216 | 			MockHttpClient: nil,
217 | 			ExpectError:    true,
218 | 			ExpectedErrMsg: "invalid parameter type: token",
219 | 		},
220 | 		{
221 | 			Name: "mandate order with invalid method parameter type",
222 | 			Request: map[string]interface{}{
223 | 				"amount":      float64(500000),
224 | 				"currency":    "INR",
225 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
226 | 				"method":      123,
227 | 				"token": map[string]interface{}{
228 | 					"max_amount": float64(500000),
229 | 					"expire_at":  float64(2709971120),
230 | 					"frequency":  "as_presented",
231 | 				},
232 | 			},
233 | 			MockHttpClient: nil,
234 | 			ExpectError:    true,
235 | 			ExpectedErrMsg: "invalid parameter type: method",
236 | 		},
237 | 		{
238 | 			Name: "token validation - missing max_amount",
239 | 			Request: map[string]interface{}{
240 | 				"amount":      float64(500000),
241 | 				"currency":    "INR",
242 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
243 | 				"method":      "upi",
244 | 				"token": map[string]interface{}{
245 | 					"expire_at": float64(2709971120),
246 | 					"frequency": "as_presented",
247 | 				},
248 | 			},
249 | 			MockHttpClient: nil,
250 | 			ExpectError:    true,
251 | 			ExpectedErrMsg: "token.max_amount is required",
252 | 		},
253 | 		{
254 | 			Name: "token validation - missing frequency",
255 | 			Request: map[string]interface{}{
256 | 				"amount":      float64(500000),
257 | 				"currency":    "INR",
258 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
259 | 				"method":      "upi",
260 | 				"token": map[string]interface{}{
261 | 					"max_amount": float64(500000),
262 | 					"expire_at":  float64(2709971120),
263 | 				},
264 | 			},
265 | 			MockHttpClient: nil,
266 | 			ExpectError:    true,
267 | 			ExpectedErrMsg: "token.frequency is required",
268 | 		},
269 | 		{
270 | 			Name: "token validation - invalid max_amount type",
271 | 			Request: map[string]interface{}{
272 | 				"amount":      float64(500000),
273 | 				"currency":    "INR",
274 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
275 | 				"method":      "upi",
276 | 				"token": map[string]interface{}{
277 | 					"max_amount": "invalid_string",
278 | 					"expire_at":  float64(2709971120),
279 | 					"frequency":  "as_presented",
280 | 				},
281 | 			},
282 | 			MockHttpClient: nil,
283 | 			ExpectError:    true,
284 | 			ExpectedErrMsg: "token.max_amount must be a number",
285 | 		},
286 | 		{
287 | 			Name: "token validation - invalid max_amount value",
288 | 			Request: map[string]interface{}{
289 | 				"amount":      float64(500000),
290 | 				"currency":    "INR",
291 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
292 | 				"method":      "upi",
293 | 				"token": map[string]interface{}{
294 | 					"max_amount": float64(-100),
295 | 					"expire_at":  float64(2709971120),
296 | 					"frequency":  "as_presented",
297 | 				},
298 | 			},
299 | 			MockHttpClient: nil,
300 | 			ExpectError:    true,
301 | 			ExpectedErrMsg: "token.max_amount must be greater than 0",
302 | 		},
303 | 		{
304 | 			Name: "token validation - invalid expire_at type",
305 | 			Request: map[string]interface{}{
306 | 				"amount":      float64(500000),
307 | 				"currency":    "INR",
308 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
309 | 				"method":      "upi",
310 | 				"token": map[string]interface{}{
311 | 					"max_amount": float64(500000),
312 | 					"expire_at":  "invalid_string",
313 | 					"frequency":  "as_presented",
314 | 				},
315 | 			},
316 | 			MockHttpClient: nil,
317 | 			ExpectError:    true,
318 | 			ExpectedErrMsg: "token.expire_at must be a number",
319 | 		},
320 | 		{
321 | 			Name: "token validation - invalid expire_at value",
322 | 			Request: map[string]interface{}{
323 | 				"amount":      float64(500000),
324 | 				"currency":    "INR",
325 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
326 | 				"method":      "upi",
327 | 				"token": map[string]interface{}{
328 | 					"max_amount": float64(500000),
329 | 					"expire_at":  float64(-100),
330 | 					"frequency":  "as_presented",
331 | 				},
332 | 			},
333 | 			MockHttpClient: nil,
334 | 			ExpectError:    true,
335 | 			ExpectedErrMsg: "token.expire_at must be greater than 0",
336 | 		},
337 | 		{
338 | 			Name: "token validation - invalid frequency type",
339 | 			Request: map[string]interface{}{
340 | 				"amount":      float64(500000),
341 | 				"currency":    "INR",
342 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
343 | 				"method":      "upi",
344 | 				"token": map[string]interface{}{
345 | 					"max_amount": float64(500000),
346 | 					"expire_at":  float64(2709971120),
347 | 					"frequency":  123,
348 | 				},
349 | 			},
350 | 			MockHttpClient: nil,
351 | 			ExpectError:    true,
352 | 			ExpectedErrMsg: "token.frequency must be a string",
353 | 		},
354 | 		{
355 | 			Name: "token validation - invalid frequency value",
356 | 			Request: map[string]interface{}{
357 | 				"amount":      float64(500000),
358 | 				"currency":    "INR",
359 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
360 | 				"method":      "upi",
361 | 				"token": map[string]interface{}{
362 | 					"max_amount": float64(500000),
363 | 					"expire_at":  float64(2709971120),
364 | 					"frequency":  "invalid_frequency",
365 | 				},
366 | 			},
367 | 			MockHttpClient: nil,
368 | 			ExpectError:    true,
369 | 			ExpectedErrMsg: "token.frequency must be one of: as_presented, " +
370 | 				"monthly, one_time, yearly, weekly, daily",
371 | 		},
372 | 		{
373 | 			Name: "token validation - invalid type value",
374 | 			Request: map[string]interface{}{
375 | 				"amount":      float64(500000),
376 | 				"currency":    "INR",
377 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
378 | 				"method":      "upi",
379 | 				"token": map[string]interface{}{
380 | 					"max_amount": float64(500000),
381 | 					"expire_at":  float64(2709971120),
382 | 					"frequency":  "as_presented",
383 | 					"type":       "invalid_type",
384 | 				},
385 | 			},
386 | 			MockHttpClient: nil,
387 | 			ExpectError:    true,
388 | 			ExpectedErrMsg: "token.type must be one of: single_block_multiple_debit",
389 | 		},
390 | 		{
391 | 			Name: "token validation - invalid type type",
392 | 			Request: map[string]interface{}{
393 | 				"amount":      float64(500000),
394 | 				"currency":    "INR",
395 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
396 | 				"method":      "upi",
397 | 				"token": map[string]interface{}{
398 | 					"max_amount": float64(500000),
399 | 					"expire_at":  float64(2709971120),
400 | 					"frequency":  "as_presented",
401 | 					"type":       123,
402 | 				},
403 | 			},
404 | 			MockHttpClient: nil,
405 | 			ExpectError:    true,
406 | 			ExpectedErrMsg: "token.type must be a string",
407 | 		},
408 | 		{
409 | 			Name: "token validation - missing type",
410 | 			Request: map[string]interface{}{
411 | 				"amount":      float64(500000),
412 | 				"currency":    "INR",
413 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
414 | 				"method":      "upi",
415 | 				"token": map[string]interface{}{
416 | 					"max_amount": float64(500000),
417 | 					"expire_at":  float64(2709971120),
418 | 					"frequency":  "as_presented",
419 | 				},
420 | 			},
421 | 			MockHttpClient: nil,
422 | 			ExpectError:    true,
423 | 			ExpectedErrMsg: "token.type is required",
424 | 		},
425 | 		{
426 | 			Name: "token validation - default expire_at when not provided",
427 | 			Request: map[string]interface{}{
428 | 				"amount":      float64(500000),
429 | 				"currency":    "INR",
430 | 				"customer_id": "cust_4xbQrmEoA5WJ01",
431 | 				"method":      "upi",
432 | 				"token": map[string]interface{}{
433 | 					"max_amount": float64(500000),
434 | 					"frequency":  "as_presented",
435 | 					"type":       "single_block_multiple_debit",
436 | 				},
437 | 			},
438 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
439 | 				return mock.NewHTTPClient(
440 | 					mock.Endpoint{
441 | 						Path:   createOrderPath,
442 | 						Method: "POST",
443 | 						Response: map[string]interface{}{
444 | 							"id": "order_test_12345",
445 | 						},
446 | 					},
447 | 				)
448 | 			},
449 | 			ExpectError: false,
450 | 			ExpectedResult: map[string]interface{}{
451 | 				"id": "order_test_12345",
452 | 			},
453 | 		},
454 | 	}
455 | 
456 | 	for _, tc := range tests {
457 | 		t.Run(tc.Name, func(t *testing.T) {
458 | 			runToolTest(t, tc, CreateOrder, "Order")
459 | 		})
460 | 	}
461 | }
462 | 
463 | func Test_FetchOrder(t *testing.T) {
464 | 	fetchOrderPathFmt := fmt.Sprintf(
465 | 		"/%s%s/%%s",
466 | 		constants.VERSION_V1,
467 | 		constants.ORDER_URL,
468 | 	)
469 | 
470 | 	orderResp := map[string]interface{}{
471 | 		"id":       "order_EKwxwAgItmmXdp",
472 | 		"amount":   float64(10000),
473 | 		"currency": "INR",
474 | 		"receipt":  "receipt-123",
475 | 		"status":   "created",
476 | 	}
477 | 
478 | 	orderNotFoundResp := map[string]interface{}{
479 | 		"error": map[string]interface{}{
480 | 			"code":        "BAD_REQUEST_ERROR",
481 | 			"description": "order not found",
482 | 		},
483 | 	}
484 | 
485 | 	tests := []RazorpayToolTestCase{
486 | 		{
487 | 			Name: "successful order fetch",
488 | 			Request: map[string]interface{}{
489 | 				"order_id": "order_EKwxwAgItmmXdp",
490 | 			},
491 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
492 | 				return mock.NewHTTPClient(
493 | 					mock.Endpoint{
494 | 						Path:     fmt.Sprintf(fetchOrderPathFmt, "order_EKwxwAgItmmXdp"),
495 | 						Method:   "GET",
496 | 						Response: orderResp,
497 | 					},
498 | 				)
499 | 			},
500 | 			ExpectError:    false,
501 | 			ExpectedResult: orderResp,
502 | 		},
503 | 		{
504 | 			Name: "order not found",
505 | 			Request: map[string]interface{}{
506 | 				"order_id": "order_invalid",
507 | 			},
508 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
509 | 				return mock.NewHTTPClient(
510 | 					mock.Endpoint{
511 | 						Path:     fmt.Sprintf(fetchOrderPathFmt, "order_invalid"),
512 | 						Method:   "GET",
513 | 						Response: orderNotFoundResp,
514 | 					},
515 | 				)
516 | 			},
517 | 			ExpectError:    true,
518 | 			ExpectedErrMsg: "fetching order failed: order not found",
519 | 		},
520 | 		{
521 | 			Name:           "missing order_id parameter",
522 | 			Request:        map[string]interface{}{},
523 | 			MockHttpClient: nil, // No HTTP client needed for validation error
524 | 			ExpectError:    true,
525 | 			ExpectedErrMsg: "missing required parameter: order_id",
526 | 		},
527 | 	}
528 | 
529 | 	for _, tc := range tests {
530 | 		t.Run(tc.Name, func(t *testing.T) {
531 | 			runToolTest(t, tc, FetchOrder, "Order")
532 | 		})
533 | 	}
534 | }
535 | 
536 | func Test_FetchAllOrders(t *testing.T) {
537 | 	fetchAllOrdersPath := fmt.Sprintf(
538 | 		"/%s%s",
539 | 		constants.VERSION_V1,
540 | 		constants.ORDER_URL,
541 | 	)
542 | 
543 | 	// Define the sample response for all orders
544 | 	ordersResp := map[string]interface{}{
545 | 		"entity": "collection",
546 | 		"count":  float64(2),
547 | 		"items": []interface{}{
548 | 			map[string]interface{}{
549 | 				"id":          "order_EKzX2WiEWbMxmx",
550 | 				"entity":      "order",
551 | 				"amount":      float64(1234),
552 | 				"amount_paid": float64(0),
553 | 				"amount_due":  float64(1234),
554 | 				"currency":    "INR",
555 | 				"receipt":     "Receipt No. 1",
556 | 				"offer_id":    nil,
557 | 				"status":      "created",
558 | 				"attempts":    float64(0),
559 | 				"notes":       []interface{}{},
560 | 				"created_at":  float64(1582637108),
561 | 			},
562 | 			map[string]interface{}{
563 | 				"id":          "order_EAI5nRfThga2TU",
564 | 				"entity":      "order",
565 | 				"amount":      float64(100),
566 | 				"amount_paid": float64(0),
567 | 				"amount_due":  float64(100),
568 | 				"currency":    "INR",
569 | 				"receipt":     "Receipt No. 1",
570 | 				"offer_id":    nil,
571 | 				"status":      "created",
572 | 				"attempts":    float64(0),
573 | 				"notes":       []interface{}{},
574 | 				"created_at":  float64(1580300731),
575 | 			},
576 | 		},
577 | 	}
578 | 
579 | 	// Define error response
580 | 	errorResp := map[string]interface{}{
581 | 		"error": map[string]interface{}{
582 | 			"code":        "BAD_REQUEST_ERROR",
583 | 			"description": "Razorpay API error: Bad request",
584 | 		},
585 | 	}
586 | 
587 | 	// Define the test cases
588 | 	tests := []RazorpayToolTestCase{
589 | 		{
590 | 			Name:    "successful fetch all orders with no parameters",
591 | 			Request: map[string]interface{}{},
592 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
593 | 				return mock.NewHTTPClient(
594 | 					mock.Endpoint{
595 | 						Path:     fetchAllOrdersPath,
596 | 						Method:   "GET",
597 | 						Response: ordersResp,
598 | 					},
599 | 				)
600 | 			},
601 | 			ExpectError:    false,
602 | 			ExpectedResult: ordersResp,
603 | 		},
604 | 		{
605 | 			Name: "successful fetch all orders with pagination",
606 | 			Request: map[string]interface{}{
607 | 				"count": 2,
608 | 				"skip":  1,
609 | 			},
610 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
611 | 				return mock.NewHTTPClient(
612 | 					mock.Endpoint{
613 | 						Path:     fetchAllOrdersPath,
614 | 						Method:   "GET",
615 | 						Response: ordersResp,
616 | 					},
617 | 				)
618 | 			},
619 | 			ExpectError:    false,
620 | 			ExpectedResult: ordersResp,
621 | 		},
622 | 		{
623 | 			Name: "successful fetch all orders with time range",
624 | 			Request: map[string]interface{}{
625 | 				"from": 1580000000,
626 | 				"to":   1590000000,
627 | 			},
628 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
629 | 				return mock.NewHTTPClient(
630 | 					mock.Endpoint{
631 | 						Path:     fetchAllOrdersPath,
632 | 						Method:   "GET",
633 | 						Response: ordersResp,
634 | 					},
635 | 				)
636 | 			},
637 | 			ExpectError:    false,
638 | 			ExpectedResult: ordersResp,
639 | 		},
640 | 		{
641 | 			Name: "successful fetch all orders with filtering",
642 | 			Request: map[string]interface{}{
643 | 				"authorized": 1,
644 | 				"receipt":    "Receipt No. 1",
645 | 			},
646 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
647 | 				return mock.NewHTTPClient(
648 | 					mock.Endpoint{
649 | 						Path:     fetchAllOrdersPath,
650 | 						Method:   "GET",
651 | 						Response: ordersResp,
652 | 					},
653 | 				)
654 | 			},
655 | 			ExpectError:    false,
656 | 			ExpectedResult: ordersResp,
657 | 		},
658 | 		{
659 | 			Name: "successful fetch all orders with expand",
660 | 			Request: map[string]interface{}{
661 | 				"expand": []interface{}{"payments"},
662 | 			},
663 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
664 | 				return mock.NewHTTPClient(
665 | 					mock.Endpoint{
666 | 						Path:     fetchAllOrdersPath,
667 | 						Method:   "GET",
668 | 						Response: ordersResp,
669 | 					},
670 | 				)
671 | 			},
672 | 			ExpectError:    false,
673 | 			ExpectedResult: ordersResp,
674 | 		},
675 | 		{
676 | 			Name: "multiple validation errors",
677 | 			Request: map[string]interface{}{
678 | 				"count":  "not-a-number",
679 | 				"skip":   "not-a-number",
680 | 				"from":   "not-a-number",
681 | 				"to":     "not-a-number",
682 | 				"expand": "not-an-array",
683 | 			},
684 | 			MockHttpClient: nil, // No HTTP client needed for validation error
685 | 			ExpectError:    true,
686 | 			ExpectedErrMsg: "Validation errors:\n- " +
687 | 				"invalid parameter type: count\n- " +
688 | 				"invalid parameter type: skip\n- " +
689 | 				"invalid parameter type: from\n- " +
690 | 				"invalid parameter type: to\n- " +
691 | 				"invalid parameter type: expand",
692 | 		},
693 | 		{
694 | 			Name: "fetch all orders fails",
695 | 			Request: map[string]interface{}{
696 | 				"count": 100,
697 | 			},
698 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
699 | 				return mock.NewHTTPClient(
700 | 					mock.Endpoint{
701 | 						Path:     fetchAllOrdersPath,
702 | 						Method:   "GET",
703 | 						Response: errorResp,
704 | 					},
705 | 				)
706 | 			},
707 | 			ExpectError:    true,
708 | 			ExpectedErrMsg: "fetching orders failed: Razorpay API error: Bad request",
709 | 		},
710 | 	}
711 | 
712 | 	for _, tc := range tests {
713 | 		t.Run(tc.Name, func(t *testing.T) {
714 | 			runToolTest(t, tc, FetchAllOrders, "Order")
715 | 		})
716 | 	}
717 | }
718 | 
719 | func Test_FetchOrderPayments(t *testing.T) {
720 | 	fetchOrderPaymentsPathFmt := fmt.Sprintf(
721 | 		"/%s%s/%%s/payments",
722 | 		constants.VERSION_V1,
723 | 		constants.ORDER_URL,
724 | 	)
725 | 
726 | 	// Define the sample response for order payments
727 | 	paymentsResp := map[string]interface{}{
728 | 		"entity": "collection",
729 | 		"count":  float64(2),
730 | 		"items": []interface{}{
731 | 			map[string]interface{}{
732 | 				"id":              "pay_N8FUmetkCE2hZP",
733 | 				"entity":          "payment",
734 | 				"amount":          float64(100),
735 | 				"currency":        "INR",
736 | 				"status":          "failed",
737 | 				"order_id":        "order_N8FRN5zTm5S3wx",
738 | 				"invoice_id":      nil,
739 | 				"international":   false,
740 | 				"method":          "upi",
741 | 				"amount_refunded": float64(0),
742 | 				"refund_status":   nil,
743 | 				"captured":        false,
744 | 				"description":     nil,
745 | 				"card_id":         nil,
746 | 				"bank":            nil,
747 | 				"wallet":          nil,
748 | 				"vpa":             "failure@razorpay",
749 | 				"email":           "[email protected]",
750 | 				"contact":         "+919999999999",
751 | 				"notes": map[string]interface{}{
752 | 					"notes_key_1": "Tea, Earl Grey, Hot",
753 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
754 | 				},
755 | 				"fee":               nil,
756 | 				"tax":               nil,
757 | 				"error_code":        "BAD_REQUEST_ERROR",
758 | 				"error_description": "Payment was unsuccessful due to a temporary issue.",
759 | 				"error_source":      "gateway",
760 | 				"error_step":        "payment_response",
761 | 				"error_reason":      "payment_failed",
762 | 				"acquirer_data": map[string]interface{}{
763 | 					"rrn": nil,
764 | 				},
765 | 				"created_at": float64(1701688684),
766 | 				"upi": map[string]interface{}{
767 | 					"vpa": "failure@razorpay",
768 | 				},
769 | 			},
770 | 			map[string]interface{}{
771 | 				"id":              "pay_N8FVRD1DzYzBh1",
772 | 				"entity":          "payment",
773 | 				"amount":          float64(100),
774 | 				"currency":        "INR",
775 | 				"status":          "captured",
776 | 				"order_id":        "order_N8FRN5zTm5S3wx",
777 | 				"invoice_id":      nil,
778 | 				"international":   false,
779 | 				"method":          "upi",
780 | 				"amount_refunded": float64(0),
781 | 				"refund_status":   nil,
782 | 				"captured":        true,
783 | 				"description":     nil,
784 | 				"card_id":         nil,
785 | 				"bank":            nil,
786 | 				"wallet":          nil,
787 | 				"vpa":             "success@razorpay",
788 | 				"email":           "[email protected]",
789 | 				"contact":         "+919999999999",
790 | 				"notes": map[string]interface{}{
791 | 					"notes_key_1": "Tea, Earl Grey, Hot",
792 | 					"notes_key_2": "Tea, Earl Grey… decaf.",
793 | 				},
794 | 				"fee":               float64(2),
795 | 				"tax":               float64(0),
796 | 				"error_code":        nil,
797 | 				"error_description": nil,
798 | 				"error_source":      nil,
799 | 				"error_step":        nil,
800 | 				"error_reason":      nil,
801 | 				"acquirer_data": map[string]interface{}{
802 | 					"rrn":                "267567962619",
803 | 					"upi_transaction_id": "F5B66C7C07CA6FEAD77E956DC2FC7ABE",
804 | 				},
805 | 				"created_at": float64(1701688721),
806 | 				"upi": map[string]interface{}{
807 | 					"vpa": "success@razorpay",
808 | 				},
809 | 			},
810 | 		},
811 | 	}
812 | 
813 | 	orderNotFoundResp := map[string]interface{}{
814 | 		"error": map[string]interface{}{
815 | 			"code":        "BAD_REQUEST_ERROR",
816 | 			"description": "order not found",
817 | 		},
818 | 	}
819 | 
820 | 	tests := []RazorpayToolTestCase{
821 | 		{
822 | 			Name: "successful fetch of order payments",
823 | 			Request: map[string]interface{}{
824 | 				"order_id": "order_N8FRN5zTm5S3wx",
825 | 			},
826 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
827 | 				return mock.NewHTTPClient(
828 | 					mock.Endpoint{
829 | 						Path: fmt.Sprintf(
830 | 							fetchOrderPaymentsPathFmt,
831 | 							"order_N8FRN5zTm5S3wx",
832 | 						),
833 | 						Method:   "GET",
834 | 						Response: paymentsResp,
835 | 					},
836 | 				)
837 | 			},
838 | 			ExpectError:    false,
839 | 			ExpectedResult: paymentsResp,
840 | 		},
841 | 		{
842 | 			Name: "order not found",
843 | 			Request: map[string]interface{}{
844 | 				"order_id": "order_invalid",
845 | 			},
846 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
847 | 				return mock.NewHTTPClient(
848 | 					mock.Endpoint{
849 | 						Path: fmt.Sprintf(
850 | 							fetchOrderPaymentsPathFmt,
851 | 							"order_invalid",
852 | 						),
853 | 						Method:   "GET",
854 | 						Response: orderNotFoundResp,
855 | 					},
856 | 				)
857 | 			},
858 | 			ExpectError:    true,
859 | 			ExpectedErrMsg: "fetching payments for order failed: order not found",
860 | 		},
861 | 		{
862 | 			Name:           "missing order_id parameter",
863 | 			Request:        map[string]interface{}{},
864 | 			MockHttpClient: nil, // No HTTP client needed for validation error
865 | 			ExpectError:    true,
866 | 			ExpectedErrMsg: "missing required parameter: order_id",
867 | 		},
868 | 	}
869 | 
870 | 	for _, tc := range tests {
871 | 		t.Run(tc.Name, func(t *testing.T) {
872 | 			runToolTest(t, tc, FetchOrderPayments, "Order")
873 | 		})
874 | 	}
875 | }
876 | 
877 | func Test_UpdateOrder(t *testing.T) {
878 | 	updateOrderPathFmt := fmt.Sprintf(
879 | 		"/%s%s/%%s",
880 | 		constants.VERSION_V1,
881 | 		constants.ORDER_URL,
882 | 	)
883 | 
884 | 	updatedOrderResp := map[string]interface{}{
885 | 		"id":         "order_EKwxwAgItmmXdp",
886 | 		"entity":     "order",
887 | 		"amount":     float64(10000),
888 | 		"currency":   "INR",
889 | 		"receipt":    "receipt-123",
890 | 		"status":     "created",
891 | 		"attempts":   float64(0),
892 | 		"created_at": float64(1572505143),
893 | 		"notes": map[string]interface{}{
894 | 			"customer_name": "updated-customer",
895 | 			"product_name":  "updated-product",
896 | 		},
897 | 	}
898 | 
899 | 	orderNotFoundResp := map[string]interface{}{
900 | 		"error": map[string]interface{}{
901 | 			"code":        "BAD_REQUEST_ERROR",
902 | 			"description": "order not found",
903 | 		},
904 | 	}
905 | 
906 | 	tests := []RazorpayToolTestCase{
907 | 		{
908 | 			Name: "successful order update",
909 | 			Request: map[string]interface{}{
910 | 				"order_id": "order_EKwxwAgItmmXdp",
911 | 				"notes": map[string]interface{}{
912 | 					"customer_name": "updated-customer",
913 | 					"product_name":  "updated-product",
914 | 				},
915 | 			},
916 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
917 | 				return mock.NewHTTPClient(
918 | 					mock.Endpoint{
919 | 						Path: fmt.Sprintf(
920 | 							updateOrderPathFmt, "order_EKwxwAgItmmXdp"),
921 | 						Method:   "PATCH",
922 | 						Response: updatedOrderResp,
923 | 					},
924 | 				)
925 | 			},
926 | 			ExpectError:    false,
927 | 			ExpectedResult: updatedOrderResp,
928 | 		},
929 | 		{
930 | 			Name: "missing required parameters - order_id",
931 | 			Request: map[string]interface{}{
932 | 				// Missing order_id
933 | 				"notes": map[string]interface{}{
934 | 					"customer_name": "updated-customer",
935 | 					"product_name":  "updated-product",
936 | 				},
937 | 			},
938 | 			MockHttpClient: nil, // No HTTP client needed for validation error
939 | 			ExpectError:    true,
940 | 			ExpectedErrMsg: "missing required parameter: order_id",
941 | 		},
942 | 		{
943 | 			Name: "missing required parameters - notes",
944 | 			Request: map[string]interface{}{
945 | 				"order_id": "order_EKwxwAgItmmXdp",
946 | 				// Missing notes
947 | 			},
948 | 			MockHttpClient: nil, // No HTTP client needed for validation error
949 | 			ExpectError:    true,
950 | 			ExpectedErrMsg: "missing required parameter: notes",
951 | 		},
952 | 		{
953 | 			Name: "order not found",
954 | 			Request: map[string]interface{}{
955 | 				"order_id": "order_invalid_id",
956 | 				"notes": map[string]interface{}{
957 | 					"customer_name": "updated-customer",
958 | 					"product_name":  "updated-product",
959 | 				},
960 | 			},
961 | 			MockHttpClient: func() (*http.Client, *httptest.Server) {
962 | 				return mock.NewHTTPClient(
963 | 					mock.Endpoint{
964 | 						Path:     fmt.Sprintf(updateOrderPathFmt, "order_invalid_id"),
965 | 						Method:   "PATCH",
966 | 						Response: orderNotFoundResp,
967 | 					},
968 | 				)
969 | 			},
970 | 			ExpectError:    true,
971 | 			ExpectedErrMsg: "updating order failed: order not found",
972 | 		},
973 | 	}
974 | 
975 | 	for _, tc := range tests {
976 | 		t.Run(tc.Name, func(t *testing.T) {
977 | 			runToolTest(t, tc, UpdateOrder, "Order")
978 | 		})
979 | 	}
980 | }
981 | 
```

--------------------------------------------------------------------------------
/pkg/razorpay/payments.go:
--------------------------------------------------------------------------------

```go
   1 | package razorpay
   2 | 
   3 | import (
   4 | 	"context"
   5 | 	"fmt"
   6 | 	"net/http"
   7 | 	"net/url"
   8 | 	"strings"
   9 | 	"time"
  10 | 
  11 | 	rzpsdk "github.com/razorpay/razorpay-go"
  12 | 
  13 | 	"github.com/razorpay/razorpay-mcp-server/pkg/mcpgo"
  14 | 	"github.com/razorpay/razorpay-mcp-server/pkg/observability"
  15 | )
  16 | 
  17 | // FetchPayment returns a tool that fetches payment details using payment_id
  18 | func FetchPayment(
  19 | 	obs *observability.Observability,
  20 | 	client *rzpsdk.Client,
  21 | ) mcpgo.Tool {
  22 | 	parameters := []mcpgo.ToolParameter{
  23 | 		mcpgo.WithString(
  24 | 			"payment_id",
  25 | 			mcpgo.Description("payment_id is unique identifier "+
  26 | 				"of the payment to be retrieved."),
  27 | 			mcpgo.Required(),
  28 | 		),
  29 | 	}
  30 | 
  31 | 	handler := func(
  32 | 		ctx context.Context,
  33 | 		r mcpgo.CallToolRequest,
  34 | 	) (*mcpgo.ToolResult, error) {
  35 | 		// Get client from context or use default
  36 | 		client, err := getClientFromContextOrDefault(ctx, client)
  37 | 		if err != nil {
  38 | 			return mcpgo.NewToolResultError(err.Error()), nil
  39 | 		}
  40 | 
  41 | 		params := make(map[string]interface{})
  42 | 
  43 | 		validator := NewValidator(&r).
  44 | 			ValidateAndAddRequiredString(params, "payment_id")
  45 | 
  46 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
  47 | 			return result, err
  48 | 		}
  49 | 
  50 | 		paymentId := params["payment_id"].(string)
  51 | 
  52 | 		payment, err := client.Payment.Fetch(paymentId, nil, nil)
  53 | 		if err != nil {
  54 | 			return mcpgo.NewToolResultError(
  55 | 				fmt.Sprintf("fetching payment failed: %s", err.Error())), nil
  56 | 		}
  57 | 
  58 | 		return mcpgo.NewToolResultJSON(payment)
  59 | 	}
  60 | 
  61 | 	return mcpgo.NewTool(
  62 | 		"fetch_payment",
  63 | 		"Use this tool to retrieve the details of a specific payment "+
  64 | 			"using its id. Amount returned is in paisa",
  65 | 		parameters,
  66 | 		handler,
  67 | 	)
  68 | }
  69 | 
  70 | // FetchPaymentCardDetails returns a tool that fetches card details
  71 | // for a payment
  72 | func FetchPaymentCardDetails(
  73 | 	obs *observability.Observability,
  74 | 	client *rzpsdk.Client,
  75 | ) mcpgo.Tool {
  76 | 	parameters := []mcpgo.ToolParameter{
  77 | 		mcpgo.WithString(
  78 | 			"payment_id",
  79 | 			mcpgo.Description("Unique identifier of the payment for which "+
  80 | 				"you want to retrieve card details. Must start with 'pay_'"),
  81 | 			mcpgo.Required(),
  82 | 		),
  83 | 	}
  84 | 
  85 | 	handler := func(
  86 | 		ctx context.Context,
  87 | 		r mcpgo.CallToolRequest,
  88 | 	) (*mcpgo.ToolResult, error) {
  89 | 		// Get client from context or use default
  90 | 		client, err := getClientFromContextOrDefault(ctx, client)
  91 | 		if err != nil {
  92 | 			return mcpgo.NewToolResultError(err.Error()), nil
  93 | 		}
  94 | 
  95 | 		params := make(map[string]interface{})
  96 | 
  97 | 		validator := NewValidator(&r).
  98 | 			ValidateAndAddRequiredString(params, "payment_id")
  99 | 
 100 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 101 | 			return result, err
 102 | 		}
 103 | 
 104 | 		paymentId := params["payment_id"].(string)
 105 | 
 106 | 		cardDetails, err := client.Payment.FetchCardDetails(
 107 | 			paymentId, nil, nil)
 108 | 
 109 | 		if err != nil {
 110 | 			return mcpgo.NewToolResultError(
 111 | 				fmt.Sprintf("fetching card details failed: %s", err.Error())), nil
 112 | 		}
 113 | 
 114 | 		return mcpgo.NewToolResultJSON(cardDetails)
 115 | 	}
 116 | 
 117 | 	return mcpgo.NewTool(
 118 | 		"fetch_payment_card_details",
 119 | 		"Use this tool to retrieve the details of the card used to make a payment. "+
 120 | 			"Only works for payments made using a card.",
 121 | 		parameters,
 122 | 		handler,
 123 | 	)
 124 | }
 125 | 
 126 | // UpdatePayment returns a tool that updates the notes for a payment
 127 | func UpdatePayment(
 128 | 	obs *observability.Observability,
 129 | 	client *rzpsdk.Client,
 130 | ) mcpgo.Tool {
 131 | 	parameters := []mcpgo.ToolParameter{
 132 | 		mcpgo.WithString(
 133 | 			"payment_id",
 134 | 			mcpgo.Description("Unique identifier of the payment to be updated. "+
 135 | 				"Must start with 'pay_'"),
 136 | 			mcpgo.Required(),
 137 | 		),
 138 | 		mcpgo.WithObject(
 139 | 			"notes",
 140 | 			mcpgo.Description("Key-value pairs that can be used to store additional "+
 141 | 				"information about the payment. Values must be strings or integers."),
 142 | 			mcpgo.Required(),
 143 | 		),
 144 | 	}
 145 | 
 146 | 	handler := func(
 147 | 		ctx context.Context,
 148 | 		r mcpgo.CallToolRequest,
 149 | 	) (*mcpgo.ToolResult, error) {
 150 | 		// Get client from context or use default
 151 | 		client, err := getClientFromContextOrDefault(ctx, client)
 152 | 		if err != nil {
 153 | 			return mcpgo.NewToolResultError(err.Error()), nil
 154 | 		}
 155 | 
 156 | 		params := make(map[string]interface{})
 157 | 		paymentUpdateReq := make(map[string]interface{})
 158 | 
 159 | 		validator := NewValidator(&r).
 160 | 			ValidateAndAddRequiredString(params, "payment_id").
 161 | 			ValidateAndAddRequiredMap(paymentUpdateReq, "notes")
 162 | 
 163 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 164 | 			return result, err
 165 | 		}
 166 | 
 167 | 		paymentId := params["payment_id"].(string)
 168 | 
 169 | 		// Update the payment
 170 | 		updatedPayment, err := client.Payment.Edit(paymentId, paymentUpdateReq, nil)
 171 | 		if err != nil {
 172 | 			return mcpgo.NewToolResultError(
 173 | 				fmt.Sprintf("updating payment failed: %s", err.Error())), nil
 174 | 		}
 175 | 
 176 | 		return mcpgo.NewToolResultJSON(updatedPayment)
 177 | 	}
 178 | 
 179 | 	return mcpgo.NewTool(
 180 | 		"update_payment",
 181 | 		"Use this tool to update the notes field of a payment. Notes are "+
 182 | 			"key-value pairs that can be used to store additional information.", //nolint:lll
 183 | 		parameters,
 184 | 		handler,
 185 | 	)
 186 | }
 187 | 
 188 | // CapturePayment returns a tool that captures an authorized payment
 189 | func CapturePayment(
 190 | 	obs *observability.Observability,
 191 | 	client *rzpsdk.Client,
 192 | ) mcpgo.Tool {
 193 | 	parameters := []mcpgo.ToolParameter{
 194 | 		mcpgo.WithString(
 195 | 			"payment_id",
 196 | 			mcpgo.Description("Unique identifier of the payment to be captured. Should start with 'pay_'"), //nolint:lll
 197 | 			mcpgo.Required(),
 198 | 		),
 199 | 		mcpgo.WithNumber(
 200 | 			"amount",
 201 | 			mcpgo.Description("The amount to be captured (in paisa). "+
 202 | 				"Should be equal to the authorized amount"),
 203 | 			mcpgo.Required(),
 204 | 		),
 205 | 		mcpgo.WithString(
 206 | 			"currency",
 207 | 			mcpgo.Description("ISO code of the currency in which the payment "+
 208 | 				"was made (e.g., INR)"),
 209 | 			mcpgo.Required(),
 210 | 		),
 211 | 	}
 212 | 
 213 | 	handler := func(
 214 | 		ctx context.Context,
 215 | 		r mcpgo.CallToolRequest,
 216 | 	) (*mcpgo.ToolResult, error) {
 217 | 		// Get client from context or use default
 218 | 		client, err := getClientFromContextOrDefault(ctx, client)
 219 | 		if err != nil {
 220 | 			return mcpgo.NewToolResultError(err.Error()), nil
 221 | 		}
 222 | 
 223 | 		params := make(map[string]interface{})
 224 | 		paymentCaptureReq := make(map[string]interface{})
 225 | 
 226 | 		validator := NewValidator(&r).
 227 | 			ValidateAndAddRequiredString(params, "payment_id").
 228 | 			ValidateAndAddRequiredInt(params, "amount").
 229 | 			ValidateAndAddRequiredString(paymentCaptureReq, "currency")
 230 | 
 231 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 232 | 			return result, err
 233 | 		}
 234 | 
 235 | 		paymentId := params["payment_id"].(string)
 236 | 		amount := int(params["amount"].(int64))
 237 | 
 238 | 		// Capture the payment
 239 | 		payment, err := client.Payment.Capture(
 240 | 			paymentId,
 241 | 			amount,
 242 | 			paymentCaptureReq,
 243 | 			nil,
 244 | 		)
 245 | 		if err != nil {
 246 | 			return mcpgo.NewToolResultError(
 247 | 				fmt.Sprintf("capturing payment failed: %s", err.Error())), nil
 248 | 		}
 249 | 
 250 | 		return mcpgo.NewToolResultJSON(payment)
 251 | 	}
 252 | 
 253 | 	return mcpgo.NewTool(
 254 | 		"capture_payment",
 255 | 		"Use this tool to capture a previously authorized payment. Only payments with 'authorized' status can be captured", //nolint:lll
 256 | 		parameters,
 257 | 		handler,
 258 | 	)
 259 | }
 260 | 
 261 | // FetchAllPayments returns a tool to fetch multiple payments with filtering and pagination
 262 | //
 263 | //nolint:lll
 264 | func FetchAllPayments(
 265 | 	obs *observability.Observability,
 266 | 	client *rzpsdk.Client,
 267 | ) mcpgo.Tool {
 268 | 	parameters := []mcpgo.ToolParameter{
 269 | 		// Pagination parameters
 270 | 		mcpgo.WithNumber(
 271 | 			"count",
 272 | 			mcpgo.Description("Number of payments to fetch "+
 273 | 				"(default: 10, max: 100)"),
 274 | 			mcpgo.Min(1),
 275 | 			mcpgo.Max(100),
 276 | 		),
 277 | 		mcpgo.WithNumber(
 278 | 			"skip",
 279 | 			mcpgo.Description("Number of payments to skip (default: 0)"),
 280 | 			mcpgo.Min(0),
 281 | 		),
 282 | 		// Time range filters
 283 | 		mcpgo.WithNumber(
 284 | 			"from",
 285 | 			mcpgo.Description("Unix timestamp (in seconds) from when "+
 286 | 				"payments are to be fetched"),
 287 | 			mcpgo.Min(0),
 288 | 		),
 289 | 		mcpgo.WithNumber(
 290 | 			"to",
 291 | 			mcpgo.Description("Unix timestamp (in seconds) up till when "+
 292 | 				"payments are to be fetched"),
 293 | 			mcpgo.Min(0),
 294 | 		),
 295 | 	}
 296 | 
 297 | 	handler := func(
 298 | 		ctx context.Context,
 299 | 		r mcpgo.CallToolRequest,
 300 | 	) (*mcpgo.ToolResult, error) {
 301 | 		// Get client from context or use default
 302 | 		client, err := getClientFromContextOrDefault(ctx, client)
 303 | 		if err != nil {
 304 | 			return mcpgo.NewToolResultError(err.Error()), nil
 305 | 		}
 306 | 
 307 | 		// Create query parameters map
 308 | 		paymentListOptions := make(map[string]interface{})
 309 | 
 310 | 		validator := NewValidator(&r).
 311 | 			ValidateAndAddPagination(paymentListOptions).
 312 | 			ValidateAndAddOptionalInt(paymentListOptions, "from").
 313 | 			ValidateAndAddOptionalInt(paymentListOptions, "to")
 314 | 
 315 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 316 | 			return result, err
 317 | 		}
 318 | 
 319 | 		// Fetch all payments using Razorpay SDK
 320 | 		payments, err := client.Payment.All(paymentListOptions, nil)
 321 | 		if err != nil {
 322 | 			return mcpgo.NewToolResultError(
 323 | 				fmt.Sprintf("fetching payments failed: %s", err.Error())), nil
 324 | 		}
 325 | 
 326 | 		return mcpgo.NewToolResultJSON(payments)
 327 | 	}
 328 | 
 329 | 	return mcpgo.NewTool(
 330 | 		"fetch_all_payments",
 331 | 		"Fetch all payments with optional filtering and pagination",
 332 | 		parameters,
 333 | 		handler,
 334 | 	)
 335 | }
 336 | 
 337 | // extractPaymentID extracts the payment ID from the payment response
 338 | func extractPaymentID(payment map[string]interface{}) string {
 339 | 	if id, exists := payment["razorpay_payment_id"]; exists && id != nil {
 340 | 		return id.(string)
 341 | 	}
 342 | 	return ""
 343 | }
 344 | 
 345 | // extractNextActions extracts all available actions from the payment response
 346 | func extractNextActions(
 347 | 	payment map[string]interface{},
 348 | ) []map[string]interface{} {
 349 | 	var actions []map[string]interface{}
 350 | 	if nextArray, exists := payment["next"]; exists && nextArray != nil {
 351 | 		if nextSlice, ok := nextArray.([]interface{}); ok {
 352 | 			for _, item := range nextSlice {
 353 | 				if nextItem, ok := item.(map[string]interface{}); ok {
 354 | 					actions = append(actions, nextItem)
 355 | 				}
 356 | 			}
 357 | 		}
 358 | 	}
 359 | 	return actions
 360 | }
 361 | 
 362 | // OTPResponse represents the response from OTP generation API
 363 | 
 364 | // sendOtp sends an OTP to the customer and returns the response
 365 | func sendOtp(otpUrl string) error {
 366 | 	if otpUrl == "" {
 367 | 		return fmt.Errorf("OTP URL is empty")
 368 | 	}
 369 | 	// Validate URL is safe and from Razorpay domain for security
 370 | 	parsedURL, err := url.Parse(otpUrl)
 371 | 	if err != nil {
 372 | 		return fmt.Errorf("invalid OTP URL: %s", err.Error())
 373 | 	}
 374 | 
 375 | 	if parsedURL.Scheme != "https" {
 376 | 		return fmt.Errorf("OTP URL must use HTTPS")
 377 | 	}
 378 | 
 379 | 	if !strings.Contains(parsedURL.Host, "razorpay.com") {
 380 | 		return fmt.Errorf("OTP URL must be from Razorpay domain")
 381 | 	}
 382 | 
 383 | 	// Create a secure HTTP client with timeout
 384 | 	client := &http.Client{
 385 | 		Timeout: 10 * time.Second,
 386 | 	}
 387 | 
 388 | 	req, err := http.NewRequest("POST", otpUrl, nil)
 389 | 	if err != nil {
 390 | 		return fmt.Errorf("failed to create OTP request: %s", err.Error())
 391 | 	}
 392 | 	req.Header.Set("Content-Type", "application/json")
 393 | 
 394 | 	resp, err := client.Do(req)
 395 | 	if err != nil {
 396 | 		return fmt.Errorf("OTP generation failed: %s", err.Error())
 397 | 	}
 398 | 	defer resp.Body.Close()
 399 | 
 400 | 	// Validate HTTP response status
 401 | 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 402 | 		return fmt.Errorf("OTP generation failed with HTTP status: %d",
 403 | 			resp.StatusCode)
 404 | 	}
 405 | 	return nil
 406 | }
 407 | 
 408 | // buildInitiatePaymentResponse constructs the response for initiate payment
 409 | func buildInitiatePaymentResponse(
 410 | 	payment map[string]interface{},
 411 | 	paymentID string,
 412 | 	actions []map[string]interface{},
 413 | ) (map[string]interface{}, string) {
 414 | 	response := map[string]interface{}{
 415 | 		"razorpay_payment_id": paymentID,
 416 | 		"payment_details":     payment,
 417 | 		"status":              "payment_initiated",
 418 | 		"message": "Payment initiated successfully using " +
 419 | 			"S2S JSON v1 flow",
 420 | 	}
 421 | 	otpUrl := ""
 422 | 
 423 | 	if len(actions) > 0 {
 424 | 		response["available_actions"] = actions
 425 | 
 426 | 		// Add guidance based on available actions
 427 | 		var actionTypes []string
 428 | 		hasOTP := false
 429 | 		hasRedirect := false
 430 | 		hasUPICollect := false
 431 | 		hasUPIIntent := false
 432 | 
 433 | 		for _, action := range actions {
 434 | 			if actionType, exists := action["action"]; exists {
 435 | 				actionStr := actionType.(string)
 436 | 				actionTypes = append(actionTypes, actionStr)
 437 | 				if actionStr == "otp_generate" {
 438 | 					hasOTP = true
 439 | 					otpUrl = action["url"].(string)
 440 | 				}
 441 | 
 442 | 				if actionStr == "redirect" {
 443 | 					hasRedirect = true
 444 | 				}
 445 | 
 446 | 				if actionStr == "upi_collect" {
 447 | 					hasUPICollect = true
 448 | 				}
 449 | 
 450 | 				if actionStr == "upi_intent" {
 451 | 					hasUPIIntent = true
 452 | 				}
 453 | 			}
 454 | 		}
 455 | 
 456 | 		switch {
 457 | 		case hasOTP:
 458 | 			response["message"] = "Payment initiated. OTP authentication is " +
 459 | 				"available. " +
 460 | 				"Use the 'submit_otp' tool to submit OTP received by the customer " +
 461 | 				"for authentication."
 462 | 			addNextStepInstructions(response, paymentID)
 463 | 		case hasRedirect:
 464 | 			response["message"] = "Payment initiated. Redirect authentication is " +
 465 | 				"available. Use the redirect URL provided in available_actions."
 466 | 		case hasUPICollect:
 467 | 			response["message"] = fmt.Sprintf(
 468 | 				"Payment initiated. Available actions: %v", actionTypes)
 469 | 		case hasUPIIntent:
 470 | 			response["message"] = fmt.Sprintf(
 471 | 				"Payment initiated. Available actions: %v", actionTypes)
 472 | 		default:
 473 | 			response["message"] = fmt.Sprintf(
 474 | 				"Payment initiated. Available actions: %v", actionTypes)
 475 | 		}
 476 | 	} else {
 477 | 		addFallbackNextStepInstructions(response, paymentID)
 478 | 	}
 479 | 
 480 | 	return response, otpUrl
 481 | }
 482 | 
 483 | // addNextStepInstructions adds next step guidance to the response
 484 | func addNextStepInstructions(
 485 | 	response map[string]interface{},
 486 | 	paymentID string,
 487 | ) {
 488 | 	if paymentID != "" {
 489 | 		response["next_step"] = "Use 'resend_otp' to regenerate OTP or " +
 490 | 			"'submit_otp' to proceed to enter OTP."
 491 | 		response["next_tool"] = "resend_otp"
 492 | 		response["next_tool_params"] = map[string]interface{}{
 493 | 			"payment_id": paymentID,
 494 | 		}
 495 | 	}
 496 | }
 497 | 
 498 | // addFallbackNextStepInstructions adds fallback next step guidance
 499 | func addFallbackNextStepInstructions(
 500 | 	response map[string]interface{},
 501 | 	paymentID string,
 502 | ) {
 503 | 	if paymentID != "" {
 504 | 		response["next_step"] = "Use 'resend_otp' to regenerate OTP or " +
 505 | 			"'submit_otp' to proceed to enter OTP if " +
 506 | 			"OTP authentication is required."
 507 | 		response["next_tool"] = "resend_otp"
 508 | 		response["next_tool_params"] = map[string]interface{}{
 509 | 			"payment_id": paymentID,
 510 | 		}
 511 | 	}
 512 | }
 513 | 
 514 | // addContactAndEmailToPaymentData adds contact and email to payment data
 515 | func addContactAndEmailToPaymentData(
 516 | 	paymentData map[string]interface{},
 517 | 	params map[string]interface{},
 518 | ) {
 519 | 	// Add contact if provided
 520 | 	if contact, exists := params["contact"]; exists && contact != "" {
 521 | 		paymentData["contact"] = contact
 522 | 	}
 523 | 
 524 | 	// Add email if provided, otherwise generate from contact
 525 | 	if email, exists := params["email"]; exists && email != "" {
 526 | 		paymentData["email"] = email
 527 | 	} else if contact, exists := paymentData["contact"]; exists && contact != "" {
 528 | 		paymentData["email"] = contact.(string) + "@mcp.razorpay.com"
 529 | 	}
 530 | }
 531 | 
 532 | // addAdditionalPaymentParameters adds additional parameters for UPI collect
 533 | // and other flows
 534 | func addAdditionalPaymentParameters(
 535 | 	paymentData map[string]interface{},
 536 | 	params map[string]interface{},
 537 | ) {
 538 | 	// Note: customer_id is now handled explicitly in buildPaymentData
 539 | 
 540 | 	// Add method if provided
 541 | 	if method, exists := params["method"]; exists && method != "" {
 542 | 		paymentData["method"] = method
 543 | 	}
 544 | 
 545 | 	// Add save if provided
 546 | 	if save, exists := params["save"]; exists {
 547 | 		paymentData["save"] = save
 548 | 	}
 549 | 
 550 | 	// Add recurring if provided
 551 | 	if recurring, exists := params["recurring"]; exists {
 552 | 		paymentData["recurring"] = recurring
 553 | 	}
 554 | 
 555 | 	// Add UPI parameters if provided
 556 | 	if upiParams, exists := params["upi"]; exists && upiParams != nil {
 557 | 		if upiMap, ok := upiParams.(map[string]interface{}); ok {
 558 | 			paymentData["upi"] = upiMap
 559 | 		}
 560 | 	}
 561 | }
 562 | 
 563 | // processUPIParameters handles VPA and UPI intent parameter processing
 564 | func processUPIParameters(params map[string]interface{}) {
 565 | 	vpa, hasVPA := params["vpa"]
 566 | 	upiIntent, hasUPIIntent := params["upi_intent"]
 567 | 
 568 | 	// Handle VPA parameter (UPI collect flow)
 569 | 	if hasVPA && vpa != "" {
 570 | 		// Set method to UPI
 571 | 		params["method"] = "upi"
 572 | 		// Set UPI parameters for collect flow
 573 | 		params["upi"] = map[string]interface{}{
 574 | 			"flow":        "collect",
 575 | 			"expiry_time": "6",
 576 | 			"vpa":         vpa,
 577 | 		}
 578 | 	}
 579 | 
 580 | 	// Handle UPI intent parameter (UPI intent flow)
 581 | 	if hasUPIIntent && upiIntent == true {
 582 | 		// Set method to UPI
 583 | 		params["method"] = "upi"
 584 | 		// Set UPI parameters for intent flow
 585 | 		params["upi"] = map[string]interface{}{
 586 | 			"flow": "intent",
 587 | 		}
 588 | 	}
 589 | }
 590 | 
 591 | // createOrGetCustomer creates or gets a customer if contact is provided
 592 | func createOrGetCustomer(
 593 | 	client *rzpsdk.Client,
 594 | 	params map[string]interface{},
 595 | ) (map[string]interface{}, error) {
 596 | 	contactValue, exists := params["contact"]
 597 | 	if !exists || contactValue == "" {
 598 | 		return nil, nil
 599 | 	}
 600 | 
 601 | 	contact := contactValue.(string)
 602 | 	customerData := map[string]interface{}{
 603 | 		"contact":       contact,
 604 | 		"fail_existing": "0", // Get existing customer if exists
 605 | 	}
 606 | 
 607 | 	// Create/get customer using Razorpay SDK
 608 | 	customer, err := client.Customer.Create(customerData, nil)
 609 | 	if err != nil {
 610 | 		return nil, fmt.Errorf(
 611 | 			"failed to create/fetch customer with contact %s: %v",
 612 | 			contact,
 613 | 			err,
 614 | 		)
 615 | 	}
 616 | 	return customer, nil
 617 | }
 618 | 
 619 | // buildPaymentData constructs the payment data for the API call
 620 | func buildPaymentData(
 621 | 	params map[string]interface{},
 622 | 	currency string,
 623 | 	customerId string,
 624 | ) *map[string]interface{} {
 625 | 	paymentData := map[string]interface{}{
 626 | 		"amount":   params["amount"],
 627 | 		"currency": currency,
 628 | 		"order_id": params["order_id"],
 629 | 	}
 630 | 	if customerId != "" {
 631 | 		paymentData["customer_id"] = customerId
 632 | 	}
 633 | 
 634 | 	// Add token if provided (required for saved payment methods,
 635 | 	// optional for UPI collect)
 636 | 	if token, exists := params["token"]; exists && token != "" {
 637 | 		paymentData["token"] = token
 638 | 	}
 639 | 
 640 | 	// Add contact and email parameters
 641 | 	addContactAndEmailToPaymentData(paymentData, params)
 642 | 
 643 | 	// Add additional parameters for UPI collect and other flows
 644 | 	addAdditionalPaymentParameters(paymentData, params)
 645 | 
 646 | 	// Add force_terminal_id if provided (for single block multiple debit orders)
 647 | 	if terminalID, exists := params["force_terminal_id"]; exists &&
 648 | 		terminalID != "" {
 649 | 		paymentData["force_terminal_id"] = terminalID
 650 | 	}
 651 | 
 652 | 	return &paymentData
 653 | }
 654 | 
 655 | // processPaymentResult processes the payment creation result
 656 | func processPaymentResult(
 657 | 	payment map[string]interface{},
 658 | ) (map[string]interface{}, error) {
 659 | 	// Extract payment ID and next actions from the response
 660 | 	paymentID := extractPaymentID(payment)
 661 | 	actions := extractNextActions(payment)
 662 | 
 663 | 	// Build structured response using the helper function
 664 | 	response, otpUrl := buildInitiatePaymentResponse(payment, paymentID, actions)
 665 | 
 666 | 	// Only send OTP if there's an OTP URL
 667 | 	if otpUrl != "" {
 668 | 		err := sendOtp(otpUrl)
 669 | 		if err != nil {
 670 | 			return nil, fmt.Errorf("OTP generation failed: %s", err.Error())
 671 | 		}
 672 | 	}
 673 | 
 674 | 	return response, nil
 675 | }
 676 | 
 677 | // InitiatePayment returns a tool that initiates a payment using order_id
 678 | // and token
 679 | // This implements the S2S JSON v1 flow for creating payments
 680 | func InitiatePayment(
 681 | 	obs *observability.Observability,
 682 | 	client *rzpsdk.Client,
 683 | ) mcpgo.Tool {
 684 | 	parameters := []mcpgo.ToolParameter{
 685 | 		mcpgo.WithNumber(
 686 | 			"amount",
 687 | 			mcpgo.Description("Payment amount in the smallest currency sub-unit "+
 688 | 				"(e.g., for ₹100, use 10000)"),
 689 | 			mcpgo.Required(),
 690 | 			mcpgo.Min(100),
 691 | 		),
 692 | 		mcpgo.WithString(
 693 | 			"currency",
 694 | 			mcpgo.Description("Currency code for the payment. Default is 'INR'"),
 695 | 		),
 696 | 		mcpgo.WithString(
 697 | 			"token",
 698 | 			mcpgo.Description("Token ID of the saved payment method. "+
 699 | 				"Must start with 'token_'"),
 700 | 		),
 701 | 		mcpgo.WithString(
 702 | 			"order_id",
 703 | 			mcpgo.Description("Order ID for which the payment is being initiated. "+
 704 | 				"Must start with 'order_'"),
 705 | 			mcpgo.Required(),
 706 | 		),
 707 | 		mcpgo.WithString(
 708 | 			"email",
 709 | 			mcpgo.Description("Customer's email address (optional)"),
 710 | 		),
 711 | 		mcpgo.WithString(
 712 | 			"contact",
 713 | 			mcpgo.Description("Customer's phone number"),
 714 | 		),
 715 | 		mcpgo.WithString(
 716 | 			"customer_id",
 717 | 			mcpgo.Description("Customer ID for the payment. "+
 718 | 				"Must start with 'cust_'"),
 719 | 		),
 720 | 		mcpgo.WithBoolean(
 721 | 			"save",
 722 | 			mcpgo.Description("Whether to save the payment method for future use"),
 723 | 		),
 724 | 		mcpgo.WithString(
 725 | 			"vpa",
 726 | 			mcpgo.Description("Virtual Payment Address (VPA) for UPI payment. "+
 727 | 				"When provided, automatically sets method='upi' and UPI parameters "+
 728 | 				"with flow='collect' and expiry_time='6' (e.g., '9876543210@ptsbi')"),
 729 | 		),
 730 | 		mcpgo.WithBoolean(
 731 | 			"upi_intent",
 732 | 			mcpgo.Description("Enable UPI intent flow. "+
 733 | 				"When set to true, automatically sets method='upi' and UPI parameters "+
 734 | 				"with flow='intent'. The API will return a UPI URL in the response."),
 735 | 		),
 736 | 		mcpgo.WithBoolean(
 737 | 			"recurring",
 738 | 			mcpgo.Description("Set this to true for recurring payments like "+
 739 | 				"single block multiple debit."),
 740 | 		),
 741 | 		mcpgo.WithString(
 742 | 			"force_terminal_id",
 743 | 			mcpgo.Description("Terminal ID to be passed in case of single block "+
 744 | 				"multiple debit order."),
 745 | 		),
 746 | 	}
 747 | 
 748 | 	handler := func(
 749 | 		ctx context.Context,
 750 | 		r mcpgo.CallToolRequest,
 751 | 	) (*mcpgo.ToolResult, error) {
 752 | 		// Get client from context or use default
 753 | 		client, err := getClientFromContextOrDefault(ctx, client)
 754 | 		if err != nil {
 755 | 			return mcpgo.NewToolResultError(err.Error()), nil
 756 | 		}
 757 | 
 758 | 		params := make(map[string]interface{})
 759 | 
 760 | 		validator := NewValidator(&r).
 761 | 			ValidateAndAddRequiredInt(params, "amount").
 762 | 			ValidateAndAddOptionalString(params, "currency").
 763 | 			ValidateAndAddOptionalString(params, "token").
 764 | 			ValidateAndAddRequiredString(params, "order_id").
 765 | 			ValidateAndAddOptionalString(params, "email").
 766 | 			ValidateAndAddOptionalString(params, "contact").
 767 | 			ValidateAndAddOptionalString(params, "customer_id").
 768 | 			ValidateAndAddOptionalBool(params, "save").
 769 | 			ValidateAndAddOptionalString(params, "vpa").
 770 | 			ValidateAndAddOptionalBool(params, "upi_intent").
 771 | 			ValidateAndAddOptionalBool(params, "recurring").
 772 | 			ValidateAndAddOptionalString(params, "force_terminal_id")
 773 | 
 774 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 775 | 			return result, err
 776 | 		}
 777 | 
 778 | 		// Set default currency
 779 | 		currency := "INR"
 780 | 		if c, exists := params["currency"]; exists && c != "" {
 781 | 			currency = c.(string)
 782 | 		}
 783 | 
 784 | 		// Process UPI parameters (VPA for collect flow, upi_intent for intent flow)
 785 | 		processUPIParameters(params)
 786 | 
 787 | 		// Handle customer ID
 788 | 		var customerID string
 789 | 		if custID, exists := params["customer_id"]; exists && custID != "" {
 790 | 			customerID = custID.(string)
 791 | 		} else {
 792 | 			// Create or get customer if contact is provided
 793 | 			customer, err := createOrGetCustomer(client, params)
 794 | 			if err != nil {
 795 | 				return mcpgo.NewToolResultError(err.Error()), nil
 796 | 			}
 797 | 			if customer != nil {
 798 | 				if id, ok := customer["id"].(string); ok {
 799 | 					customerID = id
 800 | 				}
 801 | 			}
 802 | 		}
 803 | 
 804 | 		// Build payment data
 805 | 		paymentDataPtr := buildPaymentData(params, currency, customerID)
 806 | 		paymentData := *paymentDataPtr
 807 | 
 808 | 		// Create payment using Razorpay SDK's CreatePaymentJson method
 809 | 		// This follows the S2S JSON v1 flow:
 810 | 		// https://api.razorpay.com/v1/payments/create/json
 811 | 		payment, err := client.Payment.CreatePaymentJson(paymentData, nil)
 812 | 		if err != nil {
 813 | 			return mcpgo.NewToolResultError(
 814 | 				fmt.Sprintf("initiating payment failed: %s", err.Error())), nil
 815 | 		}
 816 | 
 817 | 		// Process payment result
 818 | 		response, err := processPaymentResult(payment)
 819 | 		if err != nil {
 820 | 			return mcpgo.NewToolResultError(err.Error()), nil
 821 | 		}
 822 | 
 823 | 		return mcpgo.NewToolResultJSON(response)
 824 | 	}
 825 | 
 826 | 	return mcpgo.NewTool(
 827 | 		"initiate_payment",
 828 | 		"Initiate a payment using the S2S JSON v1 flow. "+
 829 | 			"Required parameters: amount and order_id. "+
 830 | 			"For saved payment methods, provide token. "+
 831 | 			"For UPI collect flow, provide 'vpa' parameter "+
 832 | 			"which automatically sets UPI with flow='collect' and expiry_time='6'. "+
 833 | 			"For UPI intent flow, set 'upi_intent=true' parameter "+
 834 | 			"which automatically sets UPI with flow='intent' and API returns UPI URL. "+
 835 | 			"Supports additional parameters like customer_id, email, "+
 836 | 			"contact, save, and recurring. "+
 837 | 			"Returns payment details including next action steps if required.",
 838 | 		parameters,
 839 | 		handler,
 840 | 	)
 841 | }
 842 | 
 843 | // ResendOtp returns a tool that sends OTP for payment authentication
 844 | func ResendOtp(
 845 | 	obs *observability.Observability,
 846 | 	client *rzpsdk.Client,
 847 | ) mcpgo.Tool {
 848 | 	parameters := []mcpgo.ToolParameter{
 849 | 		mcpgo.WithString(
 850 | 			"payment_id",
 851 | 			mcpgo.Description("Unique identifier of the payment for which "+
 852 | 				"OTP needs to be generated. Must start with 'pay_'"),
 853 | 			mcpgo.Required(),
 854 | 		),
 855 | 	}
 856 | 
 857 | 	handler := func(
 858 | 		ctx context.Context,
 859 | 		r mcpgo.CallToolRequest,
 860 | 	) (*mcpgo.ToolResult, error) {
 861 | 
 862 | 		// Get client from context or use default
 863 | 		client, err := getClientFromContextOrDefault(ctx, client)
 864 | 		if err != nil {
 865 | 			return mcpgo.NewToolResultError(err.Error()), nil
 866 | 		}
 867 | 
 868 | 		params := make(map[string]interface{})
 869 | 
 870 | 		validator := NewValidator(&r).
 871 | 			ValidateAndAddRequiredString(params, "payment_id")
 872 | 
 873 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 874 | 			return result, err
 875 | 		}
 876 | 
 877 | 		paymentID := params["payment_id"].(string)
 878 | 
 879 | 		// Resend OTP using Razorpay SDK
 880 | 		otpResponse, err := client.Payment.OtpResend(paymentID, nil, nil)
 881 | 		if err != nil {
 882 | 			return mcpgo.NewToolResultError(
 883 | 				fmt.Sprintf("OTP resend failed: %s", err.Error())), nil
 884 | 		}
 885 | 
 886 | 		// Extract OTP submit URL from response
 887 | 		otpSubmitURL := extractOtpSubmitURL(otpResponse)
 888 | 
 889 | 		// Prepare response
 890 | 		response := map[string]interface{}{
 891 | 			"payment_id": paymentID,
 892 | 			"status":     "success",
 893 | 			"message": "OTP sent successfully. Please enter the OTP received on your " +
 894 | 				"mobile number to complete the payment.",
 895 | 			"response_data": otpResponse,
 896 | 		}
 897 | 
 898 | 		// Add next step instructions if OTP submit URL is available
 899 | 		if otpSubmitURL != "" {
 900 | 			response["otp_submit_url"] = otpSubmitURL
 901 | 			response["next_step"] = "Use 'submit_otp' tool with the OTP code received " +
 902 | 				"from user to complete payment authentication."
 903 | 			response["next_tool"] = "submit_otp"
 904 | 			response["next_tool_params"] = map[string]interface{}{
 905 | 				"payment_id": paymentID,
 906 | 				"otp_string": "{OTP_CODE_FROM_USER}",
 907 | 			}
 908 | 		} else {
 909 | 			response["next_step"] = "Use 'submit_otp' tool with the OTP code received " +
 910 | 				"from user to complete payment authentication."
 911 | 			response["next_tool"] = "submit_otp"
 912 | 			response["next_tool_params"] = map[string]interface{}{
 913 | 				"payment_id": paymentID,
 914 | 				"otp_string": "{OTP_CODE_FROM_USER}",
 915 | 			}
 916 | 		}
 917 | 
 918 | 		result, err := mcpgo.NewToolResultJSON(response)
 919 | 		if err != nil {
 920 | 			return mcpgo.NewToolResultError(
 921 | 				fmt.Sprintf("JSON marshal error: %v", err)), nil
 922 | 		}
 923 | 		return result, nil
 924 | 	}
 925 | 
 926 | 	return mcpgo.NewTool(
 927 | 		"resend_otp",
 928 | 		"Resend OTP to the customer's registered mobile number if the previous "+
 929 | 			"OTP was not received or has expired.",
 930 | 		parameters,
 931 | 		handler,
 932 | 	)
 933 | }
 934 | 
 935 | // SubmitOtp returns a tool that submits OTP for payment verification
 936 | func SubmitOtp(
 937 | 	obs *observability.Observability,
 938 | 	client *rzpsdk.Client,
 939 | ) mcpgo.Tool {
 940 | 	parameters := []mcpgo.ToolParameter{
 941 | 		mcpgo.WithString(
 942 | 			"otp_string",
 943 | 			mcpgo.Description("OTP string received from the user"),
 944 | 			mcpgo.Required(),
 945 | 		),
 946 | 		mcpgo.WithString(
 947 | 			"payment_id",
 948 | 			mcpgo.Description("Unique identifier of the payment for which "+
 949 | 				"OTP needs to be submitted. Must start with 'pay_'"),
 950 | 			mcpgo.Required(),
 951 | 		),
 952 | 	}
 953 | 
 954 | 	handler := func(
 955 | 		ctx context.Context,
 956 | 		r mcpgo.CallToolRequest,
 957 | 	) (*mcpgo.ToolResult, error) {
 958 | 		// Get client from context or use default
 959 | 		client, err := getClientFromContextOrDefault(ctx, client)
 960 | 		if err != nil {
 961 | 			return mcpgo.NewToolResultError(err.Error()), nil
 962 | 		}
 963 | 
 964 | 		params := make(map[string]interface{})
 965 | 
 966 | 		validator := NewValidator(&r).
 967 | 			ValidateAndAddRequiredString(params, "otp_string").
 968 | 			ValidateAndAddRequiredString(params, "payment_id")
 969 | 
 970 | 		if result, err := validator.HandleErrorsIfAny(); result != nil {
 971 | 			return result, err
 972 | 		}
 973 | 
 974 | 		paymentID := params["payment_id"].(string)
 975 | 		data := map[string]interface{}{
 976 | 			"otp": params["otp_string"].(string),
 977 | 		}
 978 | 		otpResponse, err := client.Payment.OtpSubmit(paymentID, data, nil)
 979 | 
 980 | 		if err != nil {
 981 | 			return mcpgo.NewToolResultError(
 982 | 				fmt.Sprintf("OTP verification failed: %s", err.Error())), nil
 983 | 		}
 984 | 
 985 | 		// Prepare response
 986 | 		response := map[string]interface{}{
 987 | 			"payment_id":    paymentID,
 988 | 			"status":        "success",
 989 | 			"message":       "OTP verified successfully.",
 990 | 			"response_data": otpResponse,
 991 | 		}
 992 | 		result, err := mcpgo.NewToolResultJSON(response)
 993 | 		if err != nil {
 994 | 			return mcpgo.NewToolResultError(
 995 | 				fmt.Sprintf("JSON marshal error: %v", err)), nil
 996 | 		}
 997 | 		return result, nil
 998 | 	}
 999 | 
1000 | 	return mcpgo.NewTool(
1001 | 		"submit_otp",
1002 | 		"Verify and submit the OTP received by the customer to complete "+
1003 | 			"the payment authentication process.",
1004 | 		parameters,
1005 | 		handler,
1006 | 	)
1007 | }
1008 | 
1009 | // extractOtpSubmitURL extracts the OTP submit URL from the payment response
1010 | func extractOtpSubmitURL(responseData interface{}) string {
1011 | 	jsonData, ok := responseData.(map[string]interface{})
1012 | 	if !ok {
1013 | 		return ""
1014 | 	}
1015 | 
1016 | 	nextArray, exists := jsonData["next"]
1017 | 	if !exists || nextArray == nil {
1018 | 		return ""
1019 | 	}
1020 | 
1021 | 	nextSlice, ok := nextArray.([]interface{})
1022 | 	if !ok {
1023 | 		return ""
1024 | 	}
1025 | 
1026 | 	for _, item := range nextSlice {
1027 | 		nextItem, ok := item.(map[string]interface{})
1028 | 		if !ok {
1029 | 			continue
1030 | 		}
1031 | 
1032 | 		action, exists := nextItem["action"]
1033 | 		if !exists || action != "otp_submit" {
1034 | 			continue
1035 | 		}
1036 | 
1037 | 		submitURL, exists := nextItem["url"]
1038 | 		if exists && submitURL != nil {
1039 | 			if urlStr, ok := submitURL.(string); ok {
1040 | 				return urlStr
1041 | 			}
1042 | 		}
1043 | 	}
1044 | 
1045 | 	return ""
1046 | }
1047 | 
```
Page 3/5FirstPrevNextLast