#
tokens: 38680/50000 5/69 files (page 3/4)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 4. Use http://codebase.md/razorpay/razorpay-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       └── new-tool-from-docs.mdc
├── .cursorignore
├── .dockerignore
├── .github
│   ├── CODEOWNERS
│   ├── 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_test.go
│       ├── main.go
│       ├── stdio_test.go
│       └── stdio.go
├── codecov.yml
├── CONTRIBUTING.md
├── coverage.out
├── Dockerfile
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── pkg
│   ├── contextkey
│   │   ├── context_key_test.go
│   │   └── context_key.go
│   ├── log
│   │   ├── config_test.go
│   │   ├── config.go
│   │   ├── log.go
│   │   ├── slog_test.go
│   │   └── slog.go
│   ├── mcpgo
│   │   ├── README.md
│   │   ├── server_test.go
│   │   ├── server.go
│   │   ├── stdio_test.go
│   │   ├── stdio.go
│   │   ├── tool_test.go
│   │   ├── tool.go
│   │   └── transport.go
│   ├── observability
│   │   ├── observability_test.go
│   │   └── 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_test.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_test.go
│       └── toolsets.go
├── README.md
└── SECURITY.md
```

# Files

--------------------------------------------------------------------------------
/pkg/mcpgo/tool_test.go:
--------------------------------------------------------------------------------

```go
package mcpgo

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
)

func TestNewTool(t *testing.T) {
	t.Run("creates tool with all fields", func(t *testing.T) {
		handler := func(
			ctx context.Context, req CallToolRequest) (*ToolResult, error) {
			return NewToolResultText("success"), nil
		}
		tool := NewTool(
			"test-tool",
			"Test description",
			[]ToolParameter{WithString("param1")},
			handler,
		)
		assert.NotNil(t, tool)
		assert.NotNil(t, tool.GetHandler())
	})

	t.Run("creates tool with empty parameters", func(t *testing.T) {
		handler := func(
			ctx context.Context, req CallToolRequest) (*ToolResult, error) {
			return NewToolResultText("success"), nil
		}
		tool := NewTool("test-tool", "Test", []ToolParameter{}, handler)
		assert.NotNil(t, tool)
	})
}

func TestMark3labsToolImpl_GetHandler(t *testing.T) {
	t.Run("returns handler", func(t *testing.T) {
		handler := func(
			ctx context.Context, req CallToolRequest) (*ToolResult, error) {
			return NewToolResultText("success"), nil
		}
		tool := NewTool("test-tool", "Test", []ToolParameter{}, handler)
		assert.NotNil(t, tool.GetHandler())
	})
}

func TestMark3labsToolImpl_ToMCPServerTool(t *testing.T) {
	t.Run("converts tool with string parameter", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{WithString("param1")},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
		assert.NotNil(t, mcpTool.Handler)
	})

	t.Run("converts tool with number parameter", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{WithNumber("param1")},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with boolean parameter", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{WithBoolean("param1")},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with object parameter", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{WithObject("param1")},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with array parameter", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{WithArray("param1")},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with integer parameter", func(t *testing.T) {
		param := ToolParameter{
			Name:   "param1",
			Schema: map[string]interface{}{"type": "integer"},
		}
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{param},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with unknown type parameter", func(t *testing.T) {
		param := ToolParameter{
			Name:   "param1",
			Schema: map[string]interface{}{"type": "unknown"},
		}
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{param},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with missing type parameter", func(t *testing.T) {
		param := ToolParameter{
			Name:   "param1",
			Schema: map[string]interface{}{},
		}
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{param},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("converts tool with non-string type", func(t *testing.T) {
		param := ToolParameter{
			Name:   "param1",
			Schema: map[string]interface{}{"type": 123},
		}
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{param},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultText("success"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Tool)
	})

	t.Run("handler returns error result", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return NewToolResultError("error occurred"), nil
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Handler)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Name:      "test-tool",
				Arguments: map[string]interface{}{},
			},
		}
		result, err := mcpTool.Handler(context.Background(), req)
		assert.NoError(t, err)
		assert.NotNil(t, result)
	})

	t.Run("handler returns handler error", func(t *testing.T) {
		tool := NewTool(
			"test-tool",
			"Test",
			[]ToolParameter{},
			func(ctx context.Context, req CallToolRequest) (*ToolResult, error) {
				return nil, assert.AnError
			},
		)
		mcpTool := tool.toMCPServerTool()
		assert.NotNil(t, mcpTool.Handler)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Name:      "test-tool",
				Arguments: map[string]interface{}{},
			},
		}
		result, err := mcpTool.Handler(context.Background(), req)
		assert.Error(t, err)
		assert.Nil(t, result)
	})
}

func TestPropertyOption_Min(t *testing.T) {
	t.Run("sets minimum for number", func(t *testing.T) {
		schema := map[string]interface{}{"type": "number"}
		Min(10.0)(schema)
		assert.Equal(t, 10.0, schema["minimum"])
	})

	t.Run("sets minimum for integer", func(t *testing.T) {
		schema := map[string]interface{}{"type": "integer"}
		Min(5.0)(schema)
		assert.Equal(t, 5.0, schema["minimum"])
	})

	t.Run("sets minLength for string", func(t *testing.T) {
		schema := map[string]interface{}{"type": "string"}
		Min(3.0)(schema)
		assert.Equal(t, 3, schema["minLength"])
	})

	t.Run("sets minItems for array", func(t *testing.T) {
		schema := map[string]interface{}{"type": "array"}
		Min(2.0)(schema)
		assert.Equal(t, 2, schema["minItems"])
	})

	t.Run("ignores for unknown type", func(t *testing.T) {
		schema := map[string]interface{}{"type": "boolean"}
		Min(1.0)(schema)
		assert.NotContains(t, schema, "minimum")
		assert.NotContains(t, schema, "minLength")
		assert.NotContains(t, schema, "minItems")
	})

	t.Run("ignores for missing type", func(t *testing.T) {
		schema := map[string]interface{}{}
		Min(1.0)(schema)
		assert.NotContains(t, schema, "minimum")
	})

	t.Run("ignores for non-string type", func(t *testing.T) {
		schema := map[string]interface{}{"type": 123}
		Min(1.0)(schema)
		assert.NotContains(t, schema, "minimum")
	})
}

func TestPropertyOption_Max(t *testing.T) {
	t.Run("sets maximum for number", func(t *testing.T) {
		schema := map[string]interface{}{"type": "number"}
		Max(100.0)(schema)
		assert.Equal(t, 100.0, schema["maximum"])
	})

	t.Run("sets maximum for integer", func(t *testing.T) {
		schema := map[string]interface{}{"type": "integer"}
		Max(50.0)(schema)
		assert.Equal(t, 50.0, schema["maximum"])
	})

	t.Run("sets maxLength for string", func(t *testing.T) {
		schema := map[string]interface{}{"type": "string"}
		Max(10.0)(schema)
		assert.Equal(t, 10, schema["maxLength"])
	})

	t.Run("sets maxItems for array", func(t *testing.T) {
		schema := map[string]interface{}{"type": "array"}
		Max(5.0)(schema)
		assert.Equal(t, 5, schema["maxItems"])
	})

	t.Run("ignores for unknown type", func(t *testing.T) {
		schema := map[string]interface{}{"type": "boolean"}
		Max(1.0)(schema)
		assert.NotContains(t, schema, "maximum")
	})

	t.Run("ignores for missing type", func(t *testing.T) {
		schema := map[string]interface{}{}
		Max(1.0)(schema)
		assert.NotContains(t, schema, "maximum")
	})

	t.Run("ignores for non-string type value", func(t *testing.T) {
		schema := map[string]interface{}{"type": 123}
		Max(1.0)(schema)
		assert.NotContains(t, schema, "maximum")
	})
}

func TestPropertyOption_Pattern(t *testing.T) {
	t.Run("sets pattern for string", func(t *testing.T) {
		schema := map[string]interface{}{"type": "string"}
		Pattern("^[a-z]+$")(schema)
		assert.Equal(t, "^[a-z]+$", schema["pattern"])
	})

	t.Run("ignores for non-string type", func(t *testing.T) {
		schema := map[string]interface{}{"type": "number"}
		Pattern("^[a-z]+$")(schema)
		assert.NotContains(t, schema, "pattern")
	})

	t.Run("ignores for missing type", func(t *testing.T) {
		schema := map[string]interface{}{}
		Pattern("^[a-z]+$")(schema)
		assert.NotContains(t, schema, "pattern")
	})

	t.Run("ignores for non-string type value", func(t *testing.T) {
		schema := map[string]interface{}{"type": 123}
		Pattern("^[a-z]+$")(schema)
		assert.NotContains(t, schema, "pattern")
	})
}

func TestPropertyOption_Enum(t *testing.T) {
	t.Run("sets enum values", func(t *testing.T) {
		schema := map[string]interface{}{}
		Enum("value1", "value2", "value3")(schema)
		assert.Equal(t, []interface{}{"value1", "value2", "value3"}, schema["enum"])
	})

	t.Run("sets enum with mixed types", func(t *testing.T) {
		schema := map[string]interface{}{}
		Enum("value1", 123, true)(schema)
		assert.Equal(t, []interface{}{"value1", 123, true}, schema["enum"])
	})
}

func TestPropertyOption_DefaultValue(t *testing.T) {
	t.Run("sets default string value", func(t *testing.T) {
		schema := map[string]interface{}{}
		DefaultValue("default")(schema)
		assert.Equal(t, "default", schema["default"])
	})

	t.Run("sets default number value", func(t *testing.T) {
		schema := map[string]interface{}{}
		DefaultValue(42.0)(schema)
		assert.Equal(t, 42.0, schema["default"])
	})

	t.Run("sets default boolean value", func(t *testing.T) {
		schema := map[string]interface{}{}
		DefaultValue(true)(schema)
		assert.Equal(t, true, schema["default"])
	})
}

func TestPropertyOption_MaxProperties(t *testing.T) {
	t.Run("sets maxProperties for object", func(t *testing.T) {
		schema := map[string]interface{}{"type": "object"}
		MaxProperties(5)(schema)
		assert.Equal(t, 5, schema["maxProperties"])
	})

	t.Run("ignores for non-object type", func(t *testing.T) {
		schema := map[string]interface{}{"type": "string"}
		MaxProperties(5)(schema)
		assert.NotContains(t, schema, "maxProperties")
	})

	t.Run("ignores for missing type", func(t *testing.T) {
		schema := map[string]interface{}{}
		MaxProperties(5)(schema)
		assert.NotContains(t, schema, "maxProperties")
	})
}

func TestPropertyOption_MinProperties(t *testing.T) {
	t.Run("sets minProperties for object", func(t *testing.T) {
		schema := map[string]interface{}{"type": "object"}
		MinProperties(2)(schema)
		assert.Equal(t, 2, schema["minProperties"])
	})

	t.Run("ignores for non-object type", func(t *testing.T) {
		schema := map[string]interface{}{"type": "string"}
		MinProperties(2)(schema)
		assert.NotContains(t, schema, "minProperties")
	})
}

func TestPropertyOption_Required(t *testing.T) {
	t.Run("sets required flag", func(t *testing.T) {
		schema := map[string]interface{}{}
		Required()(schema)
		assert.Equal(t, true, schema["required"])
	})
}

func TestPropertyOption_Description(t *testing.T) {
	t.Run("sets description", func(t *testing.T) {
		schema := map[string]interface{}{}
		Description("Test description")(schema)
		assert.Equal(t, "Test description", schema["description"])
	})
}

func TestToolParameter_ApplyPropertyOptions(t *testing.T) {
	t.Run("applies single option", func(t *testing.T) {
		param := ToolParameter{
			Name:   "test",
			Schema: map[string]interface{}{"type": "string"},
		}
		param.applyPropertyOptions(Description("Test desc"))
		assert.Equal(t, "Test desc", param.Schema["description"])
	})

	t.Run("applies multiple options", func(t *testing.T) {
		param := ToolParameter{
			Name:   "test",
			Schema: map[string]interface{}{"type": "string"},
		}
		param.applyPropertyOptions(
			Description("Test desc"),
			Required(),
			Min(3.0),
		)
		assert.Equal(t, "Test desc", param.Schema["description"])
		assert.Equal(t, true, param.Schema["required"])
		assert.Equal(t, 3, param.Schema["minLength"])
	})

	t.Run("applies no options", func(t *testing.T) {
		param := ToolParameter{
			Name:   "test",
			Schema: map[string]interface{}{"type": "string"},
		}
		param.applyPropertyOptions()
		assert.Equal(t, "string", param.Schema["type"])
	})
}

func TestWithString(t *testing.T) {
	t.Run("creates string parameter without options", func(t *testing.T) {
		param := WithString("test")
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "string", param.Schema["type"])
	})

	t.Run("creates string parameter with options", func(t *testing.T) {
		param := WithString("test", Description("Test"), Required(), Min(3.0))
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "string", param.Schema["type"])
		assert.Equal(t, "Test", param.Schema["description"])
		assert.Equal(t, true, param.Schema["required"])
		assert.Equal(t, 3, param.Schema["minLength"])
	})
}

func TestWithNumber(t *testing.T) {
	t.Run("creates number parameter without options", func(t *testing.T) {
		param := WithNumber("test")
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "number", param.Schema["type"])
	})

	t.Run("creates number parameter with options", func(t *testing.T) {
		param := WithNumber("test", Min(1.0), Max(100.0))
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "number", param.Schema["type"])
		assert.Equal(t, 1.0, param.Schema["minimum"])
		assert.Equal(t, 100.0, param.Schema["maximum"])
	})
}

func TestWithBoolean(t *testing.T) {
	t.Run("creates boolean parameter", func(t *testing.T) {
		param := WithBoolean("test")
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "boolean", param.Schema["type"])
	})
}

func TestWithObject(t *testing.T) {
	t.Run("creates object parameter", func(t *testing.T) {
		param := WithObject("test")
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "object", param.Schema["type"])
	})

	t.Run("creates object parameter with options", func(t *testing.T) {
		param := WithObject("test", MinProperties(1), MaxProperties(5))
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "object", param.Schema["type"])
		assert.Equal(t, 1, param.Schema["minProperties"])
		assert.Equal(t, 5, param.Schema["maxProperties"])
	})
}

func TestWithArray(t *testing.T) {
	t.Run("creates array parameter", func(t *testing.T) {
		param := WithArray("test")
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "array", param.Schema["type"])
	})

	t.Run("creates array parameter with options", func(t *testing.T) {
		param := WithArray("test", Min(1.0), Max(10.0))
		assert.Equal(t, "test", param.Name)
		assert.Equal(t, "array", param.Schema["type"])
		assert.Equal(t, 1, param.Schema["minItems"])
		assert.Equal(t, 10, param.Schema["maxItems"])
	})
}

func TestAddNumberPropertyOptions(t *testing.T) {
	t.Run("adds minimum", func(t *testing.T) {
		schema := map[string]interface{}{"minimum": 10.0}
		opts := addNumberPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds maximum", func(t *testing.T) {
		schema := map[string]interface{}{"maximum": 100.0}
		opts := addNumberPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds both minimum and maximum", func(t *testing.T) {
		schema := map[string]interface{}{
			"minimum": 10.0,
			"maximum": 100.0,
		}
		opts := addNumberPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("handles non-float64 minimum", func(t *testing.T) {
		schema := map[string]interface{}{"minimum": "not-a-number"}
		opts := addNumberPropertyOptions(nil, schema)
		assert.Nil(t, opts)
	})
}

func TestAddStringPropertyOptions(t *testing.T) {
	t.Run("adds minLength", func(t *testing.T) {
		schema := map[string]interface{}{"minLength": 3}
		opts := addStringPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds maxLength", func(t *testing.T) {
		schema := map[string]interface{}{"maxLength": 10}
		opts := addStringPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds pattern", func(t *testing.T) {
		schema := map[string]interface{}{"pattern": "^[a-z]+$"}
		opts := addStringPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds all string options", func(t *testing.T) {
		schema := map[string]interface{}{
			"minLength": 3,
			"maxLength": 10,
			"pattern":   "^[a-z]+$",
		}
		opts := addStringPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})
}

func TestAddDefaultValueOptions(t *testing.T) {
	t.Run("adds string default", func(t *testing.T) {
		opts := addDefaultValueOptions(nil, "default")
		assert.NotNil(t, opts)
	})

	t.Run("adds float64 default", func(t *testing.T) {
		opts := addDefaultValueOptions(nil, 42.0)
		assert.NotNil(t, opts)
	})

	t.Run("adds bool default", func(t *testing.T) {
		opts := addDefaultValueOptions(nil, true)
		assert.NotNil(t, opts)
	})

	t.Run("ignores unknown type", func(t *testing.T) {
		opts := addDefaultValueOptions(nil, []string{"test"})
		assert.Nil(t, opts)
	})
}

func TestAddEnumOptions(t *testing.T) {
	t.Run("adds enum with string values", func(t *testing.T) {
		enumValues := []interface{}{"value1", "value2", "value3"}
		opts := addEnumOptions(nil, enumValues)
		assert.NotNil(t, opts)
	})

	t.Run("adds enum with mixed values", func(t *testing.T) {
		enumValues := []interface{}{"value1", 123, "value2"}
		opts := addEnumOptions(nil, enumValues)
		assert.NotNil(t, opts)
	})

	t.Run("handles non-array enum", func(t *testing.T) {
		opts := addEnumOptions(nil, "not-an-array")
		assert.Nil(t, opts)
	})

	t.Run("handles empty enum array", func(t *testing.T) {
		enumValues := []interface{}{123, 456} // Non-string values
		opts := addEnumOptions(nil, enumValues)
		assert.Nil(t, opts) // Should return nil since no string values
	})
}

func TestAddObjectPropertyOptions(t *testing.T) {
	t.Run("adds maxProperties", func(t *testing.T) {
		schema := map[string]interface{}{"maxProperties": 5}
		opts := addObjectPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds minProperties", func(t *testing.T) {
		schema := map[string]interface{}{"minProperties": 2}
		opts := addObjectPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds both properties", func(t *testing.T) {
		schema := map[string]interface{}{
			"minProperties": 1,
			"maxProperties": 5,
		}
		opts := addObjectPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})
}

func TestAddArrayPropertyOptions(t *testing.T) {
	t.Run("adds minItems", func(t *testing.T) {
		schema := map[string]interface{}{"minItems": 1}
		opts := addArrayPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds maxItems", func(t *testing.T) {
		schema := map[string]interface{}{"maxItems": 10}
		opts := addArrayPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})

	t.Run("adds both items", func(t *testing.T) {
		schema := map[string]interface{}{
			"minItems": 1,
			"maxItems": 10,
		}
		opts := addArrayPropertyOptions(nil, schema)
		assert.NotNil(t, opts)
	})
}

func TestConvertSchemaToPropertyOptions(t *testing.T) {
	t.Run("converts complete schema", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":        "string",
			"description": "Test param",
			"required":    true,
			"minLength":   3,
			"maxLength":   10,
			"pattern":     "^[a-z]+$",
			"default":     "default",
		}
		opts := convertSchemaToPropertyOptions(schema)
		assert.NotNil(t, opts)
	})

	t.Run("converts number schema", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":    "number",
			"minimum": 1.0,
			"maximum": 100.0,
			"default": 42.0,
		}
		opts := convertSchemaToPropertyOptions(schema)
		assert.NotNil(t, opts)
	})

	t.Run("converts object schema", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":          "object",
			"minProperties": 1,
			"maxProperties": 5,
		}
		opts := convertSchemaToPropertyOptions(schema)
		assert.NotNil(t, opts)
	})

	t.Run("converts array schema", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":     "array",
			"minItems": 1,
			"maxItems": 10,
		}
		opts := convertSchemaToPropertyOptions(schema)
		assert.NotNil(t, opts)
	})

	t.Run("converts schema with enum", func(t *testing.T) {
		schema := map[string]interface{}{
			"type": "string",
			"enum": []interface{}{"value1", "value2"},
		}
		opts := convertSchemaToPropertyOptions(schema)
		assert.NotNil(t, opts)
	})

	t.Run("handles empty description", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":        "string",
			"description": "",
		}
		opts := convertSchemaToPropertyOptions(schema)
		// Empty description should not be added
		// In Go, a nil slice is valid and has length 0
		assert.Len(t, opts, 0)
	})

	t.Run("handles false required", func(t *testing.T) {
		schema := map[string]interface{}{
			"type":     "string",
			"required": false,
		}
		opts := convertSchemaToPropertyOptions(schema)
		// False required should not be added
		// In Go, a nil slice is valid and has length 0
		assert.Len(t, opts, 0)
	})
}

func TestNewToolResultJSON(t *testing.T) {
	t.Run("creates JSON result from map", func(t *testing.T) {
		data := map[string]interface{}{
			"key": "value",
			"num": 42,
		}
		result, err := NewToolResultJSON(data)
		assert.NoError(t, err)
		assert.NotNil(t, result)
		assert.False(t, result.IsError)
		assert.NotEmpty(t, result.Text)

		// Verify it's valid JSON
		var decoded map[string]interface{}
		err = json.Unmarshal([]byte(result.Text), &decoded)
		assert.NoError(t, err)
		assert.Equal(t, "value", decoded["key"])
	})

	t.Run("creates JSON result from struct", func(t *testing.T) {
		type TestStruct struct {
			Name string `json:"name"`
			Age  int    `json:"age"`
		}
		data := TestStruct{Name: "Test", Age: 30}
		result, err := NewToolResultJSON(data)
		assert.NoError(t, err)
		assert.NotNil(t, result)
		assert.False(t, result.IsError)
	})

	t.Run("creates JSON result from array", func(t *testing.T) {
		data := []string{"item1", "item2"}
		result, err := NewToolResultJSON(data)
		assert.NoError(t, err)
		assert.NotNil(t, result)
	})

	t.Run("handles unmarshalable data", func(t *testing.T) {
		// Create a channel which cannot be marshaled to JSON
		data := make(chan int)
		result, err := NewToolResultJSON(data)
		assert.Error(t, err)
		assert.Nil(t, result)
	})
}

func TestNewToolResultText(t *testing.T) {
	t.Run("creates text result", func(t *testing.T) {
		result := NewToolResultText("test text")
		assert.NotNil(t, result)
		assert.Equal(t, "test text", result.Text)
		assert.False(t, result.IsError)
		assert.Nil(t, result.Content)
	})

	t.Run("creates empty text result", func(t *testing.T) {
		result := NewToolResultText("")
		assert.NotNil(t, result)
		assert.Equal(t, "", result.Text)
		assert.False(t, result.IsError)
	})
}

func TestNewToolResultError(t *testing.T) {
	t.Run("creates error result", func(t *testing.T) {
		result := NewToolResultError("error message")
		assert.NotNil(t, result)
		assert.Equal(t, "error message", result.Text)
		assert.True(t, result.IsError)
		assert.Nil(t, result.Content)
	})

	t.Run("creates empty error result", func(t *testing.T) {
		result := NewToolResultError("")
		assert.NotNil(t, result)
		assert.Equal(t, "", result.Text)
		assert.True(t, result.IsError)
	})
}

```

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

```go
package razorpay

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/razorpay/razorpay-go/constants"

	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
)

func Test_CreateQRCode(t *testing.T) {
	createQRCodePath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.QRCODE_URL,
	)

	qrCodeWithAllParamsResp := map[string]interface{}{
		"id":                       "qr_HMsVL8HOpbMcjU",
		"entity":                   "qr_code",
		"created_at":               float64(1623660301),
		"name":                     "Store Front Display",
		"usage":                    "single_use",
		"type":                     "upi_qr",
		"image_url":                "https://rzp.io/i/BWcUVrLp",
		"payment_amount":           float64(300),
		"status":                   "active",
		"description":              "For Store 1",
		"fixed_amount":             true,
		"payments_amount_received": float64(0),
		"payments_count_received":  float64(0),
		"notes": map[string]interface{}{
			"purpose": "Test UPI QR Code notes",
		},
		"customer_id": "cust_HKsR5se84c5LTO",
		"close_by":    float64(1681615838),
	}

	qrCodeWithRequiredParamsResp := map[string]interface{}{
		"id":                       "qr_HMsVL8HOpbMcjU",
		"entity":                   "qr_code",
		"created_at":               float64(1623660301),
		"usage":                    "multiple_use",
		"type":                     "upi_qr",
		"image_url":                "https://rzp.io/i/BWcUVrLp",
		"status":                   "active",
		"fixed_amount":             false,
		"payments_amount_received": float64(0),
		"payments_count_received":  float64(0),
	}

	qrCodeWithoutPaymentAmountResp := map[string]interface{}{
		"id":                       "qr_HMsVL8HOpbMcjU",
		"entity":                   "qr_code",
		"created_at":               float64(1623660301),
		"name":                     "Store Front Display",
		"usage":                    "single_use",
		"type":                     "upi_qr",
		"image_url":                "https://rzp.io/i/BWcUVrLp",
		"status":                   "active",
		"description":              "For Store 1",
		"fixed_amount":             false,
		"payments_amount_received": float64(0),
		"payments_count_received":  float64(0),
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "The type field is invalid",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful QR code creation with all parameters",
			Request: map[string]interface{}{
				"type":           "upi_qr",
				"name":           "Store Front Display",
				"usage":          "single_use",
				"fixed_amount":   true,
				"payment_amount": float64(300),
				"description":    "For Store 1",
				"customer_id":    "cust_HKsR5se84c5LTO",
				"close_by":       float64(1681615838),
				"notes": map[string]interface{}{
					"purpose": "Test UPI QR Code notes",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createQRCodePath,
						Method:   "POST",
						Response: qrCodeWithAllParamsResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: qrCodeWithAllParamsResp,
		},
		{
			Name: "successful QR code creation with required params only",
			Request: map[string]interface{}{
				"type":  "upi_qr",
				"usage": "multiple_use",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createQRCodePath,
						Method:   "POST",
						Response: qrCodeWithRequiredParamsResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: qrCodeWithRequiredParamsResp,
		},
		{
			Name: "successful QR code creation without payment amount",
			Request: map[string]interface{}{
				"type":         "upi_qr",
				"name":         "Store Front Display",
				"usage":        "single_use",
				"fixed_amount": false,
				"description":  "For Store 1",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createQRCodePath,
						Method:   "POST",
						Response: qrCodeWithoutPaymentAmountResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: qrCodeWithoutPaymentAmountResp,
		},
		{
			Name: "missing required type parameter",
			Request: map[string]interface{}{
				"usage": "single_use",
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: type",
		},
		{
			Name: "missing required usage parameter",
			Request: map[string]interface{}{
				"type": "upi_qr",
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: usage",
		},
		{
			Name: "validator error - invalid parameter type",
			Request: map[string]interface{}{
				"type":  123,
				"usage": "single_use",
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "Validation errors",
		},
		{
			Name: "fixed_amount true but payment_amount missing",
			Request: map[string]interface{}{
				"type":         "upi_qr",
				"usage":        "single_use",
				"fixed_amount": true,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "payment_amount is required when fixed_amount is true",
		},
		{
			Name: "invalid type parameter",
			Request: map[string]interface{}{
				"type":  "invalid_type",
				"usage": "single_use",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createQRCodePath,
						Method:   "POST",
						Response: errorResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "creating QR code failed: The type field is invalid",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, CreateQRCode, "QR Code")
		})
	}
}

func Test_FetchAllQRCodes(t *testing.T) {
	qrCodesPath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.QRCODE_URL,
	)

	allQRCodesResp := map[string]interface{}{
		"entity": "collection",
		"count":  float64(2),
		"items": []interface{}{
			map[string]interface{}{
				"id":                       "qr_HO2jGkWReVBMNu",
				"entity":                   "qr_code",
				"created_at":               float64(1623914648),
				"name":                     "Store_1",
				"usage":                    "single_use",
				"type":                     "upi_qr",
				"image_url":                "https://rzp.io/i/w2CEwYmkAu",
				"payment_amount":           float64(300),
				"status":                   "active",
				"description":              "For Store 1",
				"fixed_amount":             true,
				"payments_amount_received": float64(0),
				"payments_count_received":  float64(0),
				"notes": map[string]interface{}{
					"purpose": "Test UPI QR Code notes",
				},
				"customer_id":  "cust_HKsR5se84c5LTO",
				"close_by":     float64(1681615838),
				"closed_at":    nil,
				"close_reason": nil,
			},
			map[string]interface{}{
				"id":                       "qr_HO2e0813YlchUn",
				"entity":                   "qr_code",
				"created_at":               float64(1623914349),
				"name":                     "Acme Groceries",
				"usage":                    "multiple_use",
				"type":                     "upi_qr",
				"image_url":                "https://rzp.io/i/X6QM7LL",
				"payment_amount":           nil,
				"status":                   "closed",
				"description":              "Buy fresh groceries",
				"fixed_amount":             false,
				"payments_amount_received": float64(200),
				"payments_count_received":  float64(1),
				"notes": map[string]interface{}{
					"Branch": "Bangalore - Rajaji Nagar",
				},
				"customer_id":  "cust_HKsR5se84c5LTO",
				"close_by":     float64(1625077799),
				"closed_at":    float64(1623914515),
				"close_reason": "on_demand",
			},
		},
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "The query parameters are invalid",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name:    "successful fetch all QR codes with no parameters",
			Request: map[string]interface{}{},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: allQRCodesResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: allQRCodesResp,
		},
		{
			Name: "successful fetch all QR codes with count parameter",
			Request: map[string]interface{}{
				"count": float64(2),
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: allQRCodesResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: allQRCodesResp,
		},
		{
			Name: "successful fetch all QR codes with pagination parameters",
			Request: map[string]interface{}{
				"from":  float64(1622000000),
				"to":    float64(1625000000),
				"count": float64(2),
				"skip":  float64(0),
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: allQRCodesResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: allQRCodesResp,
		},
		{
			Name: "invalid parameters - caught by SDK",
			Request: map[string]interface{}{
				"count": float64(-1),
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:   qrCodesPath,
						Method: "GET",
						Response: map[string]interface{}{
							"error": map[string]interface{}{
								"code":        "BAD_REQUEST_ERROR",
								"description": "The count value should be greater than or equal to 1",
							},
						},
					},
				)
			},
			ExpectError: true,
			ExpectedErrMsg: "fetching QR codes failed: " +
				"The count value should be greater than or equal to 1",
		},
		{
			Name: "validator error - invalid count parameter type",
			Request: map[string]interface{}{
				"count": "not-a-number",
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "Validation errors",
		},
		{
			Name: "API error response",
			Request: map[string]interface{}{
				"count": float64(1000),
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: errorResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "fetching QR codes failed: The query parameters are invalid",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchAllQRCodes, "QR Codes")
		})
	}
}

func Test_FetchQRCodesByCustomerID(t *testing.T) {
	qrCodesPath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.QRCODE_URL,
	)

	customerQRCodesResp := map[string]interface{}{
		"entity": "collection",
		"count":  float64(1),
		"items": []interface{}{
			map[string]interface{}{
				"id":                       "qr_HMsgvioW64f0vh",
				"entity":                   "qr_code",
				"created_at":               float64(1623660959),
				"name":                     "Store_1",
				"usage":                    "single_use",
				"type":                     "upi_qr",
				"image_url":                "https://rzp.io/i/DTa2eQR",
				"payment_amount":           float64(300),
				"status":                   "active",
				"description":              "For Store 1",
				"fixed_amount":             true,
				"payments_amount_received": float64(0),
				"payments_count_received":  float64(0),
				"notes": map[string]interface{}{
					"purpose": "Test UPI QR Code notes",
				},
				"customer_id":  "cust_HKsR5se84c5LTO",
				"close_by":     float64(1681615838),
				"closed_at":    nil,
				"close_reason": nil,
			},
		},
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "The id provided is not a valid id",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful fetch QR codes by customer ID",
			Request: map[string]interface{}{
				"customer_id": "cust_HKsR5se84c5LTO",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: customerQRCodesResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: customerQRCodesResp,
		},
		{
			Name:           "missing required customer_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: customer_id",
		},
		{
			Name: "validator error - invalid customer_id parameter type",
			Request: map[string]interface{}{
				"customer_id": 12345,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: customer_id",
		},
		{
			Name: "API error - invalid customer ID",
			Request: map[string]interface{}{
				"customer_id": "invalid_customer_id",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: errorResp,
					},
				)
			},
			ExpectError: true,
			ExpectedErrMsg: "fetching QR codes failed: " +
				"The id provided is not a valid id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchQRCodesByCustomerID, "QR Codes by Customer ID")
		})
	}
}

func Test_FetchQRCodesByPaymentID(t *testing.T) {
	qrCodesPath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.QRCODE_URL,
	)

	paymentQRCodesResp := map[string]interface{}{
		"entity": "collection",
		"count":  float64(1),
		"items": []interface{}{
			map[string]interface{}{
				"id":                       "qr_HMsqRoeVwKbwAF",
				"entity":                   "qr_code",
				"created_at":               float64(1623661499),
				"name":                     "Fresh Groceries",
				"usage":                    "multiple_use",
				"type":                     "upi_qr",
				"image_url":                "https://rzp.io/i/eI9XD54Q",
				"payment_amount":           nil,
				"status":                   "active",
				"description":              "Buy fresh groceries",
				"fixed_amount":             false,
				"payments_amount_received": float64(1000),
				"payments_count_received":  float64(1),
				"notes":                    []interface{}{},
				"customer_id":              "cust_HKsR5se84c5LTO",
				"close_by":                 float64(1624472999),
				"close_reason":             nil,
			},
		},
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "The id provided is not a valid id",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful fetch QR codes by payment ID",
			Request: map[string]interface{}{
				"payment_id": "pay_Di5iqCqA1WEHq6",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: paymentQRCodesResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: paymentQRCodesResp,
		},
		{
			Name:           "missing required payment_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: payment_id",
		},
		{
			Name: "validator error - invalid payment_id parameter type",
			Request: map[string]interface{}{
				"payment_id": 12345,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: payment_id",
		},
		{
			Name: "API error - invalid payment ID",
			Request: map[string]interface{}{
				"payment_id": "invalid_payment_id",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     qrCodesPath,
						Method:   "GET",
						Response: errorResp,
					},
				)
			},
			ExpectError: true,
			ExpectedErrMsg: "fetching QR codes failed: " +
				"The id provided is not a valid id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchQRCodesByPaymentID, "QR Codes by Payment ID")
		})
	}
}

func TestFetchQRCode(t *testing.T) {
	// Initialize necessary variables
	qrID := "qr_FuZIYx6rMbP6gs"
	apiPath := fmt.Sprintf(
		"/%s%s/%s",
		constants.VERSION_V1,
		constants.QRCODE_URL,
		qrID,
	)

	// Successful response based on Razorpay docs
	successResponse := map[string]interface{}{
		"id":                       qrID,
		"entity":                   "qr_code",
		"created_at":               float64(1623915088),
		"name":                     "Store_1",
		"usage":                    "single_use",
		"type":                     "upi_qr",
		"image_url":                "https://rzp.io/i/oCswTOcCo",
		"payment_amount":           float64(300),
		"status":                   "active",
		"description":              "For Store 1",
		"fixed_amount":             true,
		"payments_amount_received": float64(0),
		"payments_count_received":  float64(0),
		"notes": map[string]interface{}{
			"purpose": "Test UPI QR Code notes",
		},
		"customer_id":  "cust_HKsR5se84c5LTO",
		"close_by":     float64(1681615838),
		"closed_at":    nil,
		"close_reason": nil,
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "The QR code ID provided is invalid",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful fetch QR code by ID",
			Request: map[string]interface{}{
				"qr_code_id": qrID,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     apiPath,
						Method:   "GET",
						Response: successResponse,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: successResponse,
		},
		{
			Name:           "missing required qr_code_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: qr_code_id",
		},
		{
			Name: "validator error - invalid qr_code_id parameter type",
			Request: map[string]interface{}{
				"qr_code_id": 12345,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: qr_code_id",
		},
		{
			Name: "API error - invalid QR code ID",
			Request: map[string]interface{}{
				"qr_code_id": qrID,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     apiPath,
						Method:   "GET",
						Response: errorResp,
					},
				)
			},
			ExpectError: true,
			ExpectedErrMsg: "fetching QR code failed: " +
				"The QR code ID provided is invalid",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchQRCode, "QR Code")
		})
	}
}

func TestFetchPaymentsForQRCode(t *testing.T) {
	apiPath := "/" + constants.VERSION_V1 +
		constants.QRCODE_URL + "/qr_test123/payments"

	successResponse := map[string]interface{}{
		"entity": "collection",
		"count":  float64(2),
		"items": []interface{}{
			map[string]interface{}{
				"id":              "pay_test123",
				"entity":          "payment",
				"amount":          float64(500),
				"currency":        "INR",
				"status":          "captured",
				"method":          "upi",
				"amount_refunded": float64(0),
				"refund_status":   nil,
				"captured":        true,
				"description":     "QRv2 Payment",
				"customer_id":     "cust_test123",
				"created_at":      float64(1623662800),
			},
			map[string]interface{}{
				"id":              "pay_test456",
				"entity":          "payment",
				"amount":          float64(1000),
				"currency":        "INR",
				"status":          "refunded",
				"method":          "upi",
				"amount_refunded": float64(1000),
				"refund_status":   "full",
				"captured":        true,
				"description":     "QRv2 Payment",
				"customer_id":     "cust_test123",
				"created_at":      float64(1623661533),
			},
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful fetch payments for QR code",
			Request: map[string]interface{}{
				"qr_code_id": "qr_test123",
				"count":      10,
				"from":       1623661000,
				"to":         1623663000,
				"skip":       0,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     apiPath,
						Method:   "GET",
						Response: successResponse,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: successResponse,
		},
		{
			Name: "missing required parameter",
			Request: map[string]interface{}{
				"count": 10,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: qr_code_id",
		},
		{
			Name: "invalid parameter type",
			Request: map[string]interface{}{
				"qr_code_id": 123,
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: qr_code_id",
		},
		{
			Name: "API error",
			Request: map[string]interface{}{
				"qr_code_id": "qr_test123",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:   apiPath,
						Method: "GET",
						Response: map[string]interface{}{
							"error": map[string]interface{}{
								"code":        "BAD_REQUEST_ERROR",
								"description": "mock error",
							},
						},
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "fetching payments for QR code failed: mock error",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchPaymentsForQRCode, "QR Code Payments")
		})
	}
}

func TestCloseQRCode(t *testing.T) {
	successResponse := map[string]interface{}{
		"id":                       "qr_HMsVL8HOpbMcjU",
		"entity":                   "qr_code",
		"created_at":               float64(1623660301),
		"name":                     "Store_1",
		"usage":                    "single_use",
		"type":                     "upi_qr",
		"image_url":                "https://rzp.io/i/BWcUVrLp",
		"payment_amount":           float64(300),
		"status":                   "closed",
		"description":              "For Store 1",
		"fixed_amount":             true,
		"payments_amount_received": float64(0),
		"payments_count_received":  float64(0),
		"notes": map[string]interface{}{
			"purpose": "Test UPI QR Code notes",
		},
		"customer_id":  "cust_HKsR5se84c5LTO",
		"close_by":     float64(1681615838),
		"closed_at":    float64(1623660445),
		"close_reason": "on_demand",
	}

	baseAPIPath := fmt.Sprintf("/%s%s", constants.VERSION_V1, constants.QRCODE_URL)
	qrCodeID := "qr_HMsVL8HOpbMcjU"
	apiPath := fmt.Sprintf("%s/%s/close", baseAPIPath, qrCodeID)

	tests := []RazorpayToolTestCase{
		{
			Name: "successful close QR code",
			Request: map[string]interface{}{
				"qr_code_id": qrCodeID,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     apiPath,
						Method:   "POST",
						Response: successResponse,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: successResponse,
		},
		{
			Name:           "missing required qr_code_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: qr_code_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, CloseQRCode, "QR Code")
		})
	}
}

```

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

```go
package razorpay

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/razorpay/razorpay-go/constants"

	"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
)

func Test_CreateOrder(t *testing.T) {
	createOrderPath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.ORDER_URL,
	)

	// Define common response maps to be reused
	orderWithAllParamsResp := map[string]interface{}{
		"id":                       "order_EKwxwAgItmmXdp",
		"amount":                   float64(10000),
		"currency":                 "INR",
		"receipt":                  "receipt-123",
		"partial_payment":          true,
		"first_payment_min_amount": float64(5000),
		"notes": map[string]interface{}{
			"customer_name": "test-customer",
			"product_name":  "test-product",
		},
		"status": "created",
	}

	orderWithRequiredParamsResp := map[string]interface{}{
		"id":       "order_EKwxwAgItmmXdp",
		"amount":   float64(10000),
		"currency": "INR",
		"status":   "created",
	}

	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "Razorpay API error: Bad request",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful order creation with all parameters",
			Request: map[string]interface{}{
				"amount":                   float64(10000),
				"currency":                 "INR",
				"receipt":                  "receipt-123",
				"partial_payment":          true,
				"first_payment_min_amount": float64(5000),
				"notes": map[string]interface{}{
					"customer_name": "test-customer",
					"product_name":  "test-product",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createOrderPath,
						Method:   "POST",
						Response: orderWithAllParamsResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: orderWithAllParamsResp,
		},
		{
			Name: "successful order creation with required params only",
			Request: map[string]interface{}{
				"amount":   float64(10000),
				"currency": "INR",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createOrderPath,
						Method:   "POST",
						Response: orderWithRequiredParamsResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: orderWithRequiredParamsResp,
		},
		{
			Name: "multiple validation errors",
			Request: map[string]interface{}{
				// Missing both amount and currency (required parameters)
				"partial_payment":          "invalid_boolean", // Wrong type for boolean
				"first_payment_min_amount": "invalid_number",  // Wrong type for number
			},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "Validation errors:\n- " +
				"missing required parameter: amount\n- " +
				"missing required parameter: currency\n- " +
				"invalid parameter type: partial_payment",
		},
		{
			Name: "first_payment_min_amount validation when partial_payment is true",
			Request: map[string]interface{}{
				"amount":                   float64(10000),
				"currency":                 "INR",
				"partial_payment":          true,
				"first_payment_min_amount": "invalid_number",
			},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "Validation errors:\n- " +
				"invalid parameter type: first_payment_min_amount",
		},
		{
			Name: "order creation fails",
			Request: map[string]interface{}{
				"amount":   float64(10000),
				"currency": "INR",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createOrderPath,
						Method:   "POST",
						Response: errorResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "creating order failed: Razorpay API error: Bad request",
		},
		{
			Name: "successful SBMD mandate order creation",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
					"type":       "single_block_multiple_debit",
				},
				"receipt": "Receipt No. 1",
				"notes": map[string]interface{}{
					"notes_key_1": "Tea, Earl Grey, Hot",
					"notes_key_2": "Tea, Earl Grey... decaf.",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				sbmdOrderResp := map[string]interface{}{
					"id":          "order_SBMD123456",
					"amount":      float64(500000),
					"currency":    "INR",
					"customer_id": "cust_4xbQrmEoA5WJ01",
					"method":      "upi",
					"token": map[string]interface{}{
						"max_amount": float64(500000),
						"expire_at":  float64(2709971120),
						"frequency":  "as_presented",
						"type":       "single_block_multiple_debit",
					},
					"receipt": "Receipt No. 1",
					"status":  "created",
					"notes": map[string]interface{}{
						"notes_key_1": "Tea, Earl Grey, Hot",
						"notes_key_2": "Tea, Earl Grey... decaf.",
					},
				}
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     createOrderPath,
						Method:   "POST",
						Response: sbmdOrderResp,
					},
				)
			},
			ExpectError: false,
			ExpectedResult: map[string]interface{}{
				"id":          "order_SBMD123456",
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
					"type":       "single_block_multiple_debit",
				},
				"receipt": "Receipt No. 1",
				"status":  "created",
				"notes": map[string]interface{}{
					"notes_key_1": "Tea, Earl Grey, Hot",
					"notes_key_2": "Tea, Earl Grey... decaf.",
				},
			},
		},
		{
			Name: "mandate order with invalid token parameter type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token":       "invalid_token_should_be_object",
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: token",
		},
		{
			Name: "mandate order with invalid method parameter type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      123,
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "invalid parameter type: method",
		},
		{
			Name: "token validation - missing max_amount",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"expire_at": float64(2709971120),
					"frequency": "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.max_amount is required",
		},
		{
			Name: "token validation - missing frequency",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.frequency is required",
		},
		{
			Name: "token validation - invalid max_amount type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": "invalid_string",
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.max_amount must be a number",
		},
		{
			Name: "token validation - invalid max_amount value",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(-100),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.max_amount must be greater than 0",
		},
		{
			Name: "token validation - invalid expire_at type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  "invalid_string",
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.expire_at must be a number",
		},
		{
			Name: "token validation - invalid expire_at value",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(-100),
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.expire_at must be greater than 0",
		},
		{
			Name: "token validation - invalid frequency type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  123,
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.frequency must be a string",
		},
		{
			Name: "token validation - invalid frequency value",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "invalid_frequency",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.frequency must be one of: as_presented, " +
				"monthly, one_time, yearly, weekly, daily",
		},
		{
			Name: "token validation - invalid type value",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
					"type":       "invalid_type",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.type must be one of: single_block_multiple_debit",
		},
		{
			Name: "token validation - invalid type type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
					"type":       123,
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.type must be a string",
		},
		{
			Name: "token validation - missing type",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"expire_at":  float64(2709971120),
					"frequency":  "as_presented",
				},
			},
			MockHttpClient: nil,
			ExpectError:    true,
			ExpectedErrMsg: "token.type is required",
		},
		{
			Name: "token validation - default expire_at when not provided",
			Request: map[string]interface{}{
				"amount":      float64(500000),
				"currency":    "INR",
				"customer_id": "cust_4xbQrmEoA5WJ01",
				"method":      "upi",
				"token": map[string]interface{}{
					"max_amount": float64(500000),
					"frequency":  "as_presented",
					"type":       "single_block_multiple_debit",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:   createOrderPath,
						Method: "POST",
						Response: map[string]interface{}{
							"id": "order_test_12345",
						},
					},
				)
			},
			ExpectError: false,
			ExpectedResult: map[string]interface{}{
				"id": "order_test_12345",
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, CreateOrder, "Order")
		})
	}
}

func Test_FetchOrder(t *testing.T) {
	fetchOrderPathFmt := fmt.Sprintf(
		"/%s%s/%%s",
		constants.VERSION_V1,
		constants.ORDER_URL,
	)

	orderResp := map[string]interface{}{
		"id":       "order_EKwxwAgItmmXdp",
		"amount":   float64(10000),
		"currency": "INR",
		"receipt":  "receipt-123",
		"status":   "created",
	}

	orderNotFoundResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "order not found",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful order fetch",
			Request: map[string]interface{}{
				"order_id": "order_EKwxwAgItmmXdp",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fmt.Sprintf(fetchOrderPathFmt, "order_EKwxwAgItmmXdp"),
						Method:   "GET",
						Response: orderResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: orderResp,
		},
		{
			Name: "order not found",
			Request: map[string]interface{}{
				"order_id": "order_invalid",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fmt.Sprintf(fetchOrderPathFmt, "order_invalid"),
						Method:   "GET",
						Response: orderNotFoundResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "fetching order failed: order not found",
		},
		{
			Name:           "missing order_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: order_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchOrder, "Order")
		})
	}
}

func Test_FetchAllOrders(t *testing.T) {
	fetchAllOrdersPath := fmt.Sprintf(
		"/%s%s",
		constants.VERSION_V1,
		constants.ORDER_URL,
	)

	// Define the sample response for all orders
	ordersResp := map[string]interface{}{
		"entity": "collection",
		"count":  float64(2),
		"items": []interface{}{
			map[string]interface{}{
				"id":          "order_EKzX2WiEWbMxmx",
				"entity":      "order",
				"amount":      float64(1234),
				"amount_paid": float64(0),
				"amount_due":  float64(1234),
				"currency":    "INR",
				"receipt":     "Receipt No. 1",
				"offer_id":    nil,
				"status":      "created",
				"attempts":    float64(0),
				"notes":       []interface{}{},
				"created_at":  float64(1582637108),
			},
			map[string]interface{}{
				"id":          "order_EAI5nRfThga2TU",
				"entity":      "order",
				"amount":      float64(100),
				"amount_paid": float64(0),
				"amount_due":  float64(100),
				"currency":    "INR",
				"receipt":     "Receipt No. 1",
				"offer_id":    nil,
				"status":      "created",
				"attempts":    float64(0),
				"notes":       []interface{}{},
				"created_at":  float64(1580300731),
			},
		},
	}

	// Define error response
	errorResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "Razorpay API error: Bad request",
		},
	}

	// Define the test cases
	tests := []RazorpayToolTestCase{
		{
			Name:    "successful fetch all orders with no parameters",
			Request: map[string]interface{}{},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: ordersResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: ordersResp,
		},
		{
			Name: "successful fetch all orders with pagination",
			Request: map[string]interface{}{
				"count": 2,
				"skip":  1,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: ordersResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: ordersResp,
		},
		{
			Name: "successful fetch all orders with time range",
			Request: map[string]interface{}{
				"from": 1580000000,
				"to":   1590000000,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: ordersResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: ordersResp,
		},
		{
			Name: "successful fetch all orders with filtering",
			Request: map[string]interface{}{
				"authorized": 1,
				"receipt":    "Receipt No. 1",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: ordersResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: ordersResp,
		},
		{
			Name: "successful fetch all orders with expand",
			Request: map[string]interface{}{
				"expand": []interface{}{"payments"},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: ordersResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: ordersResp,
		},
		{
			Name: "multiple validation errors",
			Request: map[string]interface{}{
				"count":  "not-a-number",
				"skip":   "not-a-number",
				"from":   "not-a-number",
				"to":     "not-a-number",
				"expand": "not-an-array",
			},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "Validation errors:\n- " +
				"invalid parameter type: count\n- " +
				"invalid parameter type: skip\n- " +
				"invalid parameter type: from\n- " +
				"invalid parameter type: to\n- " +
				"invalid parameter type: expand",
		},
		{
			Name: "fetch all orders fails",
			Request: map[string]interface{}{
				"count": 100,
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fetchAllOrdersPath,
						Method:   "GET",
						Response: errorResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "fetching orders failed: Razorpay API error: Bad request",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchAllOrders, "Order")
		})
	}
}

func Test_FetchOrderPayments(t *testing.T) {
	fetchOrderPaymentsPathFmt := fmt.Sprintf(
		"/%s%s/%%s/payments",
		constants.VERSION_V1,
		constants.ORDER_URL,
	)

	// Define the sample response for order payments
	paymentsResp := map[string]interface{}{
		"entity": "collection",
		"count":  float64(2),
		"items": []interface{}{
			map[string]interface{}{
				"id":              "pay_N8FUmetkCE2hZP",
				"entity":          "payment",
				"amount":          float64(100),
				"currency":        "INR",
				"status":          "failed",
				"order_id":        "order_N8FRN5zTm5S3wx",
				"invoice_id":      nil,
				"international":   false,
				"method":          "upi",
				"amount_refunded": float64(0),
				"refund_status":   nil,
				"captured":        false,
				"description":     nil,
				"card_id":         nil,
				"bank":            nil,
				"wallet":          nil,
				"vpa":             "failure@razorpay",
				"email":           "[email protected]",
				"contact":         "+919999999999",
				"notes": map[string]interface{}{
					"notes_key_1": "Tea, Earl Grey, Hot",
					"notes_key_2": "Tea, Earl Grey… decaf.",
				},
				"fee":               nil,
				"tax":               nil,
				"error_code":        "BAD_REQUEST_ERROR",
				"error_description": "Payment was unsuccessful due to a temporary issue.",
				"error_source":      "gateway",
				"error_step":        "payment_response",
				"error_reason":      "payment_failed",
				"acquirer_data": map[string]interface{}{
					"rrn": nil,
				},
				"created_at": float64(1701688684),
				"upi": map[string]interface{}{
					"vpa": "failure@razorpay",
				},
			},
			map[string]interface{}{
				"id":              "pay_N8FVRD1DzYzBh1",
				"entity":          "payment",
				"amount":          float64(100),
				"currency":        "INR",
				"status":          "captured",
				"order_id":        "order_N8FRN5zTm5S3wx",
				"invoice_id":      nil,
				"international":   false,
				"method":          "upi",
				"amount_refunded": float64(0),
				"refund_status":   nil,
				"captured":        true,
				"description":     nil,
				"card_id":         nil,
				"bank":            nil,
				"wallet":          nil,
				"vpa":             "success@razorpay",
				"email":           "[email protected]",
				"contact":         "+919999999999",
				"notes": map[string]interface{}{
					"notes_key_1": "Tea, Earl Grey, Hot",
					"notes_key_2": "Tea, Earl Grey… decaf.",
				},
				"fee":               float64(2),
				"tax":               float64(0),
				"error_code":        nil,
				"error_description": nil,
				"error_source":      nil,
				"error_step":        nil,
				"error_reason":      nil,
				"acquirer_data": map[string]interface{}{
					"rrn":                "267567962619",
					"upi_transaction_id": "F5B66C7C07CA6FEAD77E956DC2FC7ABE",
				},
				"created_at": float64(1701688721),
				"upi": map[string]interface{}{
					"vpa": "success@razorpay",
				},
			},
		},
	}

	orderNotFoundResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "order not found",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful fetch of order payments",
			Request: map[string]interface{}{
				"order_id": "order_N8FRN5zTm5S3wx",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path: fmt.Sprintf(
							fetchOrderPaymentsPathFmt,
							"order_N8FRN5zTm5S3wx",
						),
						Method:   "GET",
						Response: paymentsResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: paymentsResp,
		},
		{
			Name: "order not found",
			Request: map[string]interface{}{
				"order_id": "order_invalid",
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path: fmt.Sprintf(
							fetchOrderPaymentsPathFmt,
							"order_invalid",
						),
						Method:   "GET",
						Response: orderNotFoundResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "fetching payments for order failed: order not found",
		},
		{
			Name:           "missing order_id parameter",
			Request:        map[string]interface{}{},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: order_id",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, FetchOrderPayments, "Order")
		})
	}
}

func Test_UpdateOrder(t *testing.T) {
	updateOrderPathFmt := fmt.Sprintf(
		"/%s%s/%%s",
		constants.VERSION_V1,
		constants.ORDER_URL,
	)

	updatedOrderResp := map[string]interface{}{
		"id":         "order_EKwxwAgItmmXdp",
		"entity":     "order",
		"amount":     float64(10000),
		"currency":   "INR",
		"receipt":    "receipt-123",
		"status":     "created",
		"attempts":   float64(0),
		"created_at": float64(1572505143),
		"notes": map[string]interface{}{
			"customer_name": "updated-customer",
			"product_name":  "updated-product",
		},
	}

	orderNotFoundResp := map[string]interface{}{
		"error": map[string]interface{}{
			"code":        "BAD_REQUEST_ERROR",
			"description": "order not found",
		},
	}

	tests := []RazorpayToolTestCase{
		{
			Name: "successful order update",
			Request: map[string]interface{}{
				"order_id": "order_EKwxwAgItmmXdp",
				"notes": map[string]interface{}{
					"customer_name": "updated-customer",
					"product_name":  "updated-product",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path: fmt.Sprintf(
							updateOrderPathFmt, "order_EKwxwAgItmmXdp"),
						Method:   "PATCH",
						Response: updatedOrderResp,
					},
				)
			},
			ExpectError:    false,
			ExpectedResult: updatedOrderResp,
		},
		{
			Name: "missing required parameters - order_id",
			Request: map[string]interface{}{
				// Missing order_id
				"notes": map[string]interface{}{
					"customer_name": "updated-customer",
					"product_name":  "updated-product",
				},
			},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: order_id",
		},
		{
			Name: "missing required parameters - notes",
			Request: map[string]interface{}{
				"order_id": "order_EKwxwAgItmmXdp",
				// Missing notes
			},
			MockHttpClient: nil, // No HTTP client needed for validation error
			ExpectError:    true,
			ExpectedErrMsg: "missing required parameter: notes",
		},
		{
			Name: "order not found",
			Request: map[string]interface{}{
				"order_id": "order_invalid_id",
				"notes": map[string]interface{}{
					"customer_name": "updated-customer",
					"product_name":  "updated-product",
				},
			},
			MockHttpClient: func() (*http.Client, *httptest.Server) {
				return mock.NewHTTPClient(
					mock.Endpoint{
						Path:     fmt.Sprintf(updateOrderPathFmt, "order_invalid_id"),
						Method:   "PATCH",
						Response: orderNotFoundResp,
					},
				)
			},
			ExpectError:    true,
			ExpectedErrMsg: "updating order failed: order not found",
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			runToolTest(t, tc, UpdateOrder, "Order")
		})
	}
}

```

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

```go
package razorpay

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	rzpsdk "github.com/razorpay/razorpay-go"

	"github.com/razorpay/razorpay-mcp-server/pkg/mcpgo"
	"github.com/razorpay/razorpay-mcp-server/pkg/observability"
)

// FetchPayment returns a tool that fetches payment details using payment_id
func FetchPayment(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("payment_id is unique identifier "+
				"of the payment to be retrieved."),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "payment_id")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentId := params["payment_id"].(string)

		payment, err := client.Payment.Fetch(paymentId, nil, nil)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("fetching payment failed: %s", err.Error())), nil
		}

		return mcpgo.NewToolResultJSON(payment)
	}

	return mcpgo.NewTool(
		"fetch_payment",
		"Use this tool to retrieve the details of a specific payment "+
			"using its id. Amount returned is in paisa",
		parameters,
		handler,
	)
}

// FetchPaymentCardDetails returns a tool that fetches card details
// for a payment
func FetchPaymentCardDetails(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("Unique identifier of the payment for which "+
				"you want to retrieve card details. Must start with 'pay_'"),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "payment_id")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentId := params["payment_id"].(string)

		cardDetails, err := client.Payment.FetchCardDetails(
			paymentId, nil, nil)

		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("fetching card details failed: %s", err.Error())), nil
		}

		return mcpgo.NewToolResultJSON(cardDetails)
	}

	return mcpgo.NewTool(
		"fetch_payment_card_details",
		"Use this tool to retrieve the details of the card used to make a payment. "+
			"Only works for payments made using a card.",
		parameters,
		handler,
	)
}

// UpdatePayment returns a tool that updates the notes for a payment
func UpdatePayment(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("Unique identifier of the payment to be updated. "+
				"Must start with 'pay_'"),
			mcpgo.Required(),
		),
		mcpgo.WithObject(
			"notes",
			mcpgo.Description("Key-value pairs that can be used to store additional "+
				"information about the payment. Values must be strings or integers."),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})
		paymentUpdateReq := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "payment_id").
			ValidateAndAddRequiredMap(paymentUpdateReq, "notes")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentId := params["payment_id"].(string)

		// Update the payment
		updatedPayment, err := client.Payment.Edit(paymentId, paymentUpdateReq, nil)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("updating payment failed: %s", err.Error())), nil
		}

		return mcpgo.NewToolResultJSON(updatedPayment)
	}

	return mcpgo.NewTool(
		"update_payment",
		"Use this tool to update the notes field of a payment. Notes are "+
			"key-value pairs that can be used to store additional information.", //nolint:lll
		parameters,
		handler,
	)
}

// CapturePayment returns a tool that captures an authorized payment
func CapturePayment(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("Unique identifier of the payment to be captured. Should start with 'pay_'"), //nolint:lll
			mcpgo.Required(),
		),
		mcpgo.WithNumber(
			"amount",
			mcpgo.Description("The amount to be captured (in paisa). "+
				"Should be equal to the authorized amount"),
			mcpgo.Required(),
		),
		mcpgo.WithString(
			"currency",
			mcpgo.Description("ISO code of the currency in which the payment "+
				"was made (e.g., INR)"),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})
		paymentCaptureReq := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "payment_id").
			ValidateAndAddRequiredInt(params, "amount").
			ValidateAndAddRequiredString(paymentCaptureReq, "currency")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentId := params["payment_id"].(string)
		amount := int(params["amount"].(int64))

		// Capture the payment
		payment, err := client.Payment.Capture(
			paymentId,
			amount,
			paymentCaptureReq,
			nil,
		)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("capturing payment failed: %s", err.Error())), nil
		}

		return mcpgo.NewToolResultJSON(payment)
	}

	return mcpgo.NewTool(
		"capture_payment",
		"Use this tool to capture a previously authorized payment. Only payments with 'authorized' status can be captured", //nolint:lll
		parameters,
		handler,
	)
}

// FetchAllPayments returns a tool to fetch multiple payments with filtering and pagination
//
//nolint:lll
func FetchAllPayments(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		// Pagination parameters
		mcpgo.WithNumber(
			"count",
			mcpgo.Description("Number of payments to fetch "+
				"(default: 10, max: 100)"),
			mcpgo.Min(1),
			mcpgo.Max(100),
		),
		mcpgo.WithNumber(
			"skip",
			mcpgo.Description("Number of payments to skip (default: 0)"),
			mcpgo.Min(0),
		),
		// Time range filters
		mcpgo.WithNumber(
			"from",
			mcpgo.Description("Unix timestamp (in seconds) from when "+
				"payments are to be fetched"),
			mcpgo.Min(0),
		),
		mcpgo.WithNumber(
			"to",
			mcpgo.Description("Unix timestamp (in seconds) up till when "+
				"payments are to be fetched"),
			mcpgo.Min(0),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		// Create query parameters map
		paymentListOptions := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddPagination(paymentListOptions).
			ValidateAndAddOptionalInt(paymentListOptions, "from").
			ValidateAndAddOptionalInt(paymentListOptions, "to")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		// Fetch all payments using Razorpay SDK
		payments, err := client.Payment.All(paymentListOptions, nil)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("fetching payments failed: %s", err.Error())), nil
		}

		return mcpgo.NewToolResultJSON(payments)
	}

	return mcpgo.NewTool(
		"fetch_all_payments",
		"Fetch all payments with optional filtering and pagination",
		parameters,
		handler,
	)
}

// extractPaymentID extracts the payment ID from the payment response
func extractPaymentID(payment map[string]interface{}) string {
	if id, exists := payment["razorpay_payment_id"]; exists && id != nil {
		return id.(string)
	}
	return ""
}

// extractNextActions extracts all available actions from the payment response
func extractNextActions(
	payment map[string]interface{},
) []map[string]interface{} {
	var actions []map[string]interface{}
	if nextArray, exists := payment["next"]; exists && nextArray != nil {
		if nextSlice, ok := nextArray.([]interface{}); ok {
			for _, item := range nextSlice {
				if nextItem, ok := item.(map[string]interface{}); ok {
					actions = append(actions, nextItem)
				}
			}
		}
	}
	return actions
}

// OTPResponse represents the response from OTP generation API

// sendOtp sends an OTP to the customer and returns the response
func sendOtp(otpUrl string) error {
	if otpUrl == "" {
		return fmt.Errorf("OTP URL is empty")
	}
	// Validate URL is safe and from Razorpay domain for security
	parsedURL, err := url.Parse(otpUrl)
	if err != nil {
		return fmt.Errorf("invalid OTP URL: %s", err.Error())
	}

	if parsedURL.Scheme != "https" {
		return fmt.Errorf("OTP URL must use HTTPS")
	}

	if !strings.Contains(parsedURL.Host, "razorpay.com") {
		return fmt.Errorf("OTP URL must be from Razorpay domain")
	}

	// Create a secure HTTP client with timeout
	client := &http.Client{
		Timeout: 10 * time.Second,
	}

	req, err := http.NewRequest("POST", otpUrl, nil)
	if err != nil {
		return fmt.Errorf("failed to create OTP request: %s", err.Error())
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("OTP generation failed: %s", err.Error())
	}
	defer resp.Body.Close()

	// Validate HTTP response status
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("OTP generation failed with HTTP status: %d",
			resp.StatusCode)
	}
	return nil
}

// buildInitiatePaymentResponse constructs the response for initiate payment
func buildInitiatePaymentResponse(
	payment map[string]interface{},
	paymentID string,
	actions []map[string]interface{},
) (map[string]interface{}, string) {
	response := map[string]interface{}{
		"razorpay_payment_id": paymentID,
		"payment_details":     payment,
		"status":              "payment_initiated",
		"message": "Payment initiated successfully using " +
			"S2S JSON v1 flow",
	}
	otpUrl := ""

	if len(actions) > 0 {
		response["available_actions"] = actions

		// Add guidance based on available actions
		var actionTypes []string
		hasOTP := false
		hasRedirect := false
		hasUPICollect := false
		hasUPIIntent := false

		for _, action := range actions {
			if actionType, exists := action["action"]; exists {
				actionStr := actionType.(string)
				actionTypes = append(actionTypes, actionStr)
				if actionStr == "otp_generate" {
					hasOTP = true
					otpUrl = action["url"].(string)
				}

				if actionStr == "redirect" {
					hasRedirect = true
				}

				if actionStr == "upi_collect" {
					hasUPICollect = true
				}

				if actionStr == "upi_intent" {
					hasUPIIntent = true
				}
			}
		}

		switch {
		case hasOTP:
			response["message"] = "Payment initiated. OTP authentication is " +
				"available. " +
				"Use the 'submit_otp' tool to submit OTP received by the customer " +
				"for authentication."
			addNextStepInstructions(response, paymentID)
		case hasRedirect:
			response["message"] = "Payment initiated. Redirect authentication is " +
				"available. Use the redirect URL provided in available_actions."
		case hasUPICollect:
			response["message"] = fmt.Sprintf(
				"Payment initiated. Available actions: %v", actionTypes)
		case hasUPIIntent:
			response["message"] = fmt.Sprintf(
				"Payment initiated. Available actions: %v", actionTypes)
		default:
			response["message"] = fmt.Sprintf(
				"Payment initiated. Available actions: %v", actionTypes)
		}
	} else {
		addFallbackNextStepInstructions(response, paymentID)
	}

	return response, otpUrl
}

// addNextStepInstructions adds next step guidance to the response
func addNextStepInstructions(
	response map[string]interface{},
	paymentID string,
) {
	if paymentID != "" {
		response["next_step"] = "Use 'resend_otp' to regenerate OTP or " +
			"'submit_otp' to proceed to enter OTP."
		response["next_tool"] = "resend_otp"
		response["next_tool_params"] = map[string]interface{}{
			"payment_id": paymentID,
		}
	}
}

// addFallbackNextStepInstructions adds fallback next step guidance
func addFallbackNextStepInstructions(
	response map[string]interface{},
	paymentID string,
) {
	if paymentID != "" {
		response["next_step"] = "Use 'resend_otp' to regenerate OTP or " +
			"'submit_otp' to proceed to enter OTP if " +
			"OTP authentication is required."
		response["next_tool"] = "resend_otp"
		response["next_tool_params"] = map[string]interface{}{
			"payment_id": paymentID,
		}
	}
}

// addContactAndEmailToPaymentData adds contact and email to payment data
func addContactAndEmailToPaymentData(
	paymentData map[string]interface{},
	params map[string]interface{},
) {
	// Add contact if provided
	if contact, exists := params["contact"]; exists && contact != "" {
		paymentData["contact"] = contact
	}

	// Add email if provided, otherwise generate from contact
	if email, exists := params["email"]; exists && email != "" {
		paymentData["email"] = email
	} else if contact, exists := paymentData["contact"]; exists && contact != "" {
		paymentData["email"] = contact.(string) + "@mcp.razorpay.com"
	}
}

// addAdditionalPaymentParameters adds additional parameters for UPI collect
// and other flows
func addAdditionalPaymentParameters(
	paymentData map[string]interface{},
	params map[string]interface{},
) {
	// Note: customer_id is now handled explicitly in buildPaymentData

	// Add method if provided
	if method, exists := params["method"]; exists && method != "" {
		paymentData["method"] = method
	}

	// Add save if provided
	if save, exists := params["save"]; exists {
		paymentData["save"] = save
	}

	// Add recurring if provided
	if recurring, exists := params["recurring"]; exists {
		paymentData["recurring"] = recurring
	}

	// Add UPI parameters if provided
	if upiParams, exists := params["upi"]; exists && upiParams != nil {
		if upiMap, ok := upiParams.(map[string]interface{}); ok {
			paymentData["upi"] = upiMap
		}
	}
}

// processUPIParameters handles VPA and UPI intent parameter processing
func processUPIParameters(params map[string]interface{}) {
	vpa, hasVPA := params["vpa"]
	upiIntent, hasUPIIntent := params["upi_intent"]

	// Handle VPA parameter (UPI collect flow)
	if hasVPA && vpa != "" {
		// Set method to UPI
		params["method"] = "upi"
		// Set UPI parameters for collect flow
		params["upi"] = map[string]interface{}{
			"flow":        "collect",
			"expiry_time": "6",
			"vpa":         vpa,
		}
	}

	// Handle UPI intent parameter (UPI intent flow)
	if hasUPIIntent && upiIntent == true {
		// Set method to UPI
		params["method"] = "upi"
		// Set UPI parameters for intent flow
		params["upi"] = map[string]interface{}{
			"flow": "intent",
		}
	}
}

// createOrGetCustomer creates or gets a customer if contact is provided
func createOrGetCustomer(
	client *rzpsdk.Client,
	params map[string]interface{},
) (map[string]interface{}, error) {
	contactValue, exists := params["contact"]
	if !exists || contactValue == "" {
		return nil, nil
	}

	contact := contactValue.(string)
	customerData := map[string]interface{}{
		"contact":       contact,
		"fail_existing": "0", // Get existing customer if exists
	}

	// Create/get customer using Razorpay SDK
	customer, err := client.Customer.Create(customerData, nil)
	if err != nil {
		return nil, fmt.Errorf(
			"failed to create/fetch customer with contact %s: %v",
			contact,
			err,
		)
	}
	return customer, nil
}

// buildPaymentData constructs the payment data for the API call
func buildPaymentData(
	params map[string]interface{},
	currency string,
	customerId string,
) *map[string]interface{} {
	paymentData := map[string]interface{}{
		"amount":   params["amount"],
		"currency": currency,
		"order_id": params["order_id"],
	}
	if customerId != "" {
		paymentData["customer_id"] = customerId
	}

	// Add token if provided (required for saved payment methods,
	// optional for UPI collect)
	if token, exists := params["token"]; exists && token != "" {
		paymentData["token"] = token
	}

	// Add contact and email parameters
	addContactAndEmailToPaymentData(paymentData, params)

	// Add additional parameters for UPI collect and other flows
	addAdditionalPaymentParameters(paymentData, params)

	// Add force_terminal_id if provided (for single block multiple debit orders)
	if terminalID, exists := params["force_terminal_id"]; exists &&
		terminalID != "" {
		paymentData["force_terminal_id"] = terminalID
	}

	return &paymentData
}

// processPaymentResult processes the payment creation result
func processPaymentResult(
	payment map[string]interface{},
) (map[string]interface{}, error) {
	// Extract payment ID and next actions from the response
	paymentID := extractPaymentID(payment)
	actions := extractNextActions(payment)

	// Build structured response using the helper function
	response, otpUrl := buildInitiatePaymentResponse(payment, paymentID, actions)

	// Only send OTP if there's an OTP URL
	if otpUrl != "" {
		err := sendOtp(otpUrl)
		if err != nil {
			return nil, fmt.Errorf("OTP generation failed: %s", err.Error())
		}
	}

	return response, nil
}

// InitiatePayment returns a tool that initiates a payment using order_id
// and token
// This implements the S2S JSON v1 flow for creating payments
func InitiatePayment(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithNumber(
			"amount",
			mcpgo.Description("Payment amount in the smallest currency sub-unit "+
				"(e.g., for ₹100, use 10000)"),
			mcpgo.Required(),
			mcpgo.Min(100),
		),
		mcpgo.WithString(
			"currency",
			mcpgo.Description("Currency code for the payment. Default is 'INR'"),
		),
		mcpgo.WithString(
			"token",
			mcpgo.Description("Token ID of the saved payment method. "+
				"Must start with 'token_'"),
		),
		mcpgo.WithString(
			"order_id",
			mcpgo.Description("Order ID for which the payment is being initiated. "+
				"Must start with 'order_'"),
			mcpgo.Required(),
		),
		mcpgo.WithString(
			"email",
			mcpgo.Description("Customer's email address (optional)"),
		),
		mcpgo.WithString(
			"contact",
			mcpgo.Description("Customer's phone number"),
		),
		mcpgo.WithString(
			"customer_id",
			mcpgo.Description("Customer ID for the payment. "+
				"Must start with 'cust_'"),
		),
		mcpgo.WithBoolean(
			"save",
			mcpgo.Description("Whether to save the payment method for future use"),
		),
		mcpgo.WithString(
			"vpa",
			mcpgo.Description("Virtual Payment Address (VPA) for UPI payment. "+
				"When provided, automatically sets method='upi' and UPI parameters "+
				"with flow='collect' and expiry_time='6' (e.g., '9876543210@ptsbi')"),
		),
		mcpgo.WithBoolean(
			"upi_intent",
			mcpgo.Description("Enable UPI intent flow. "+
				"When set to true, automatically sets method='upi' and UPI parameters "+
				"with flow='intent'. The API will return a UPI URL in the response."),
		),
		mcpgo.WithBoolean(
			"recurring",
			mcpgo.Description("Set this to true for recurring payments like "+
				"single block multiple debit."),
		),
		mcpgo.WithString(
			"force_terminal_id",
			mcpgo.Description("Terminal ID to be passed in case of single block "+
				"multiple debit order."),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredInt(params, "amount").
			ValidateAndAddOptionalString(params, "currency").
			ValidateAndAddOptionalString(params, "token").
			ValidateAndAddRequiredString(params, "order_id").
			ValidateAndAddOptionalString(params, "email").
			ValidateAndAddOptionalString(params, "contact").
			ValidateAndAddOptionalString(params, "customer_id").
			ValidateAndAddOptionalBool(params, "save").
			ValidateAndAddOptionalString(params, "vpa").
			ValidateAndAddOptionalBool(params, "upi_intent").
			ValidateAndAddOptionalBool(params, "recurring").
			ValidateAndAddOptionalString(params, "force_terminal_id")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		// Set default currency
		currency := "INR"
		if c, exists := params["currency"]; exists && c != "" {
			currency = c.(string)
		}

		// Process UPI parameters (VPA for collect flow, upi_intent for intent flow)
		processUPIParameters(params)

		// Handle customer ID
		var customerID string
		if custID, exists := params["customer_id"]; exists && custID != "" {
			customerID = custID.(string)
		} else {
			// Create or get customer if contact is provided
			customer, err := createOrGetCustomer(client, params)
			if err != nil {
				return mcpgo.NewToolResultError(err.Error()), nil
			}
			if customer != nil {
				if id, ok := customer["id"].(string); ok {
					customerID = id
				}
			}
		}

		// Build payment data
		paymentDataPtr := buildPaymentData(params, currency, customerID)
		paymentData := *paymentDataPtr

		// Create payment using Razorpay SDK's CreatePaymentJson method
		// This follows the S2S JSON v1 flow:
		// https://api.razorpay.com/v1/payments/create/json
		payment, err := client.Payment.CreatePaymentJson(paymentData, nil)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("initiating payment failed: %s", err.Error())), nil
		}

		// Process payment result
		response, err := processPaymentResult(payment)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		return mcpgo.NewToolResultJSON(response)
	}

	return mcpgo.NewTool(
		"initiate_payment",
		"Initiate a payment using the S2S JSON v1 flow. "+
			"Required parameters: amount and order_id. "+
			"For saved payment methods, provide token. "+
			"For UPI collect flow, provide 'vpa' parameter "+
			"which automatically sets UPI with flow='collect' and expiry_time='6'. "+
			"For UPI intent flow, set 'upi_intent=true' parameter "+
			"which automatically sets UPI with flow='intent' and API returns UPI URL. "+
			"Supports additional parameters like customer_id, email, "+
			"contact, save, and recurring. "+
			"Returns payment details including next action steps if required.",
		parameters,
		handler,
	)
}

// ResendOtp returns a tool that sends OTP for payment authentication
func ResendOtp(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("Unique identifier of the payment for which "+
				"OTP needs to be generated. Must start with 'pay_'"),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {

		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "payment_id")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentID := params["payment_id"].(string)

		// Resend OTP using Razorpay SDK
		otpResponse, err := client.Payment.OtpResend(paymentID, nil, nil)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("OTP resend failed: %s", err.Error())), nil
		}

		// Extract OTP submit URL from response
		otpSubmitURL := extractOtpSubmitURL(otpResponse)

		// Prepare response
		response := map[string]interface{}{
			"payment_id": paymentID,
			"status":     "success",
			"message": "OTP sent successfully. Please enter the OTP received on your " +
				"mobile number to complete the payment.",
			"response_data": otpResponse,
		}

		// Add next step instructions if OTP submit URL is available
		if otpSubmitURL != "" {
			response["otp_submit_url"] = otpSubmitURL
			response["next_step"] = "Use 'submit_otp' tool with the OTP code received " +
				"from user to complete payment authentication."
			response["next_tool"] = "submit_otp"
			response["next_tool_params"] = map[string]interface{}{
				"payment_id": paymentID,
				"otp_string": "{OTP_CODE_FROM_USER}",
			}
		} else {
			response["next_step"] = "Use 'submit_otp' tool with the OTP code received " +
				"from user to complete payment authentication."
			response["next_tool"] = "submit_otp"
			response["next_tool_params"] = map[string]interface{}{
				"payment_id": paymentID,
				"otp_string": "{OTP_CODE_FROM_USER}",
			}
		}

		result, err := mcpgo.NewToolResultJSON(response)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("JSON marshal error: %v", err)), nil
		}
		return result, nil
	}

	return mcpgo.NewTool(
		"resend_otp",
		"Resend OTP to the customer's registered mobile number if the previous "+
			"OTP was not received or has expired.",
		parameters,
		handler,
	)
}

// SubmitOtp returns a tool that submits OTP for payment verification
func SubmitOtp(
	obs *observability.Observability,
	client *rzpsdk.Client,
) mcpgo.Tool {
	parameters := []mcpgo.ToolParameter{
		mcpgo.WithString(
			"otp_string",
			mcpgo.Description("OTP string received from the user"),
			mcpgo.Required(),
		),
		mcpgo.WithString(
			"payment_id",
			mcpgo.Description("Unique identifier of the payment for which "+
				"OTP needs to be submitted. Must start with 'pay_'"),
			mcpgo.Required(),
		),
	}

	handler := func(
		ctx context.Context,
		r mcpgo.CallToolRequest,
	) (*mcpgo.ToolResult, error) {
		// Get client from context or use default
		client, err := getClientFromContextOrDefault(ctx, client)
		if err != nil {
			return mcpgo.NewToolResultError(err.Error()), nil
		}

		params := make(map[string]interface{})

		validator := NewValidator(&r).
			ValidateAndAddRequiredString(params, "otp_string").
			ValidateAndAddRequiredString(params, "payment_id")

		if result, err := validator.HandleErrorsIfAny(); result != nil {
			return result, err
		}

		paymentID := params["payment_id"].(string)
		data := map[string]interface{}{
			"otp": params["otp_string"].(string),
		}
		otpResponse, err := client.Payment.OtpSubmit(paymentID, data, nil)

		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("OTP verification failed: %s", err.Error())), nil
		}

		// Prepare response
		response := map[string]interface{}{
			"payment_id":    paymentID,
			"status":        "success",
			"message":       "OTP verified successfully.",
			"response_data": otpResponse,
		}
		result, err := mcpgo.NewToolResultJSON(response)
		if err != nil {
			return mcpgo.NewToolResultError(
				fmt.Sprintf("JSON marshal error: %v", err)), nil
		}
		return result, nil
	}

	return mcpgo.NewTool(
		"submit_otp",
		"Verify and submit the OTP received by the customer to complete "+
			"the payment authentication process.",
		parameters,
		handler,
	)
}

// extractOtpSubmitURL extracts the OTP submit URL from the payment response
func extractOtpSubmitURL(responseData interface{}) string {
	jsonData, ok := responseData.(map[string]interface{})
	if !ok {
		return ""
	}

	nextArray, exists := jsonData["next"]
	if !exists || nextArray == nil {
		return ""
	}

	nextSlice, ok := nextArray.([]interface{})
	if !ok {
		return ""
	}

	for _, item := range nextSlice {
		nextItem, ok := item.(map[string]interface{})
		if !ok {
			continue
		}

		action, exists := nextItem["action"]
		if !exists || action != "otp_submit" {
			continue
		}

		submitURL, exists := nextItem["url"]
		if exists && submitURL != nil {
			if urlStr, ok := submitURL.(string); ok {
				return urlStr
			}
		}
	}

	return ""
}

```

--------------------------------------------------------------------------------
/pkg/razorpay/tools_params_test.go:
--------------------------------------------------------------------------------

```go
package razorpay

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/razorpay/razorpay-mcp-server/pkg/mcpgo"
)

func TestValidator(t *testing.T) {
	tests := []struct {
		name           string
		args           map[string]interface{}
		paramName      string
		validationFunc func(*Validator, map[string]interface{}, string) *Validator
		expectError    bool
		expectValue    interface{}
		expectKey      string
	}{
		// String tests
		{
			name:           "required string - valid",
			args:           map[string]interface{}{"test_param": "test_value"},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredString,
			expectError:    false,
			expectValue:    "test_value",
			expectKey:      "test_param",
		},
		{
			name:           "required string - missing",
			args:           map[string]interface{}{},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredString,
			expectError:    true,
			expectValue:    nil,
			expectKey:      "test_param",
		},
		{
			name:           "optional string - valid",
			args:           map[string]interface{}{"test_param": "test_value"},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalString,
			expectError:    false,
			expectValue:    "test_value",
			expectKey:      "test_param",
		},
		{
			name:           "optional string - empty",
			args:           map[string]interface{}{"test_param": ""},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalString,
			expectError:    false,
			expectValue:    "",
			expectKey:      "test_param",
		},

		// Int tests
		{
			name:           "required int - valid",
			args:           map[string]interface{}{"test_param": float64(123)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredInt,
			expectError:    false,
			expectValue:    int64(123),
			expectKey:      "test_param",
		},
		{
			name:           "optional int - valid",
			args:           map[string]interface{}{"test_param": float64(123)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalInt,
			expectError:    false,
			expectValue:    int64(123),
			expectKey:      "test_param",
		},
		{
			name:           "optional int - zero",
			args:           map[string]interface{}{"test_param": float64(0)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalInt,
			expectError:    false,
			expectValue:    int64(0), // we expect the zero values as is
			expectKey:      "test_param",
		},

		// Float tests
		{
			name:           "required float - valid",
			args:           map[string]interface{}{"test_param": float64(123.45)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredFloat,
			expectError:    false,
			expectValue:    float64(123.45),
			expectKey:      "test_param",
		},
		{
			name:           "optional float - valid",
			args:           map[string]interface{}{"test_param": float64(123.45)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalFloat,
			expectError:    false,
			expectValue:    float64(123.45),
			expectKey:      "test_param",
		},
		{
			name:           "optional float - zero",
			args:           map[string]interface{}{"test_param": float64(0)},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalFloat,
			expectError:    false,
			expectValue:    float64(0),
			expectKey:      "test_param",
		},

		// Bool tests
		{
			name:           "required bool - true",
			args:           map[string]interface{}{"test_param": true},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredBool,
			expectError:    false,
			expectValue:    true,
			expectKey:      "test_param",
		},
		{
			name:           "required bool - false",
			args:           map[string]interface{}{"test_param": false},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredBool,
			expectError:    false,
			expectValue:    false,
			expectKey:      "test_param",
		},
		{
			name:           "optional bool - true",
			args:           map[string]interface{}{"test_param": true},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalBool,
			expectError:    false,
			expectValue:    true,
			expectKey:      "test_param",
		},
		{
			name:           "optional bool - false",
			args:           map[string]interface{}{"test_param": false},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalBool,
			expectError:    false,
			expectValue:    false,
			expectKey:      "test_param",
		},

		// Map tests
		{
			name: "required map - valid",
			args: map[string]interface{}{
				"test_param": map[string]interface{}{"key": "value"},
			},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredMap,
			expectError:    false,
			expectValue:    map[string]interface{}{"key": "value"},
			expectKey:      "test_param",
		},
		{
			name: "optional map - valid",
			args: map[string]interface{}{
				"test_param": map[string]interface{}{"key": "value"},
			},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalMap,
			expectError:    false,
			expectValue:    map[string]interface{}{"key": "value"},
			expectKey:      "test_param",
		},
		{
			name: "optional map - empty",
			args: map[string]interface{}{
				"test_param": map[string]interface{}{},
			},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalMap,
			expectError:    false,
			expectValue:    map[string]interface{}{},
			expectKey:      "test_param",
		},

		// Array tests
		{
			name: "required array - valid",
			args: map[string]interface{}{
				"test_param": []interface{}{"value1", "value2"},
			},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredArray,
			expectError:    false,
			expectValue:    []interface{}{"value1", "value2"},
			expectKey:      "test_param",
		},
		{
			name: "optional array - valid",
			args: map[string]interface{}{
				"test_param": []interface{}{"value1", "value2"},
			},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalArray,
			expectError:    false,
			expectValue:    []interface{}{"value1", "value2"},
			expectKey:      "test_param",
		},
		{
			name:           "optional array - empty",
			args:           map[string]interface{}{"test_param": []interface{}{}},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddOptionalArray,
			expectError:    false,
			expectValue:    []interface{}{},
			expectKey:      "test_param",
		},

		// Invalid type tests
		{
			name:           "required string - wrong type",
			args:           map[string]interface{}{"test_param": 123},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredString,
			expectError:    true,
			expectValue:    nil,
			expectKey:      "test_param",
		},
		{
			name:           "required int - wrong type",
			args:           map[string]interface{}{"test_param": "not a number"},
			paramName:      "test_param",
			validationFunc: (*Validator).ValidateAndAddRequiredInt,
			expectError:    true,
			expectValue:    nil,
			expectKey:      "test_param",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := make(map[string]interface{})
			request := &mcpgo.CallToolRequest{
				Arguments: tt.args,
			}
			validator := NewValidator(request)

			tt.validationFunc(validator, result, tt.paramName)

			if tt.expectError {
				assert.True(t, validator.HasErrors(), "Expected validation error")
			} else {
				assert.False(t, validator.HasErrors(), "Did not expect validation error")
				assert.Equal(t,
					tt.expectValue,
					result[tt.expectKey],
					"Parameter value mismatch",
				)
			}
		})
	}
}

func TestValidatorPagination(t *testing.T) {
	tests := []struct {
		name        string
		args        map[string]interface{}
		expectCount interface{}
		expectSkip  interface{}
		expectError bool
	}{
		{
			name: "valid pagination params",
			args: map[string]interface{}{
				"count": float64(10),
				"skip":  float64(5),
			},
			expectCount: int64(10),
			expectSkip:  int64(5),
			expectError: false,
		},
		{
			name:        "zero pagination params",
			args:        map[string]interface{}{"count": float64(0), "skip": float64(0)},
			expectCount: int64(0),
			expectSkip:  int64(0),
			expectError: false,
		},
		{
			name: "invalid count type",
			args: map[string]interface{}{
				"count": "not a number",
				"skip":  float64(5),
			},
			expectCount: nil,
			expectSkip:  int64(5),
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := make(map[string]interface{})
			request := &mcpgo.CallToolRequest{
				Arguments: tt.args,
			}
			validator := NewValidator(request)

			validator.ValidateAndAddPagination(result)

			if tt.expectError {
				assert.True(t, validator.HasErrors(), "Expected validation error")
			} else {
				assert.False(t, validator.HasErrors(), "Did not expect validation error")
			}

			if tt.expectCount != nil {
				assert.Equal(t, tt.expectCount, result["count"], "Count mismatch")
			} else {
				_, exists := result["count"]
				assert.False(t, exists, "Count should not be added")
			}

			if tt.expectSkip != nil {
				assert.Equal(t, tt.expectSkip, result["skip"], "Skip mismatch")
			} else {
				_, exists := result["skip"]
				assert.False(t, exists, "Skip should not be added")
			}
		})
	}
}

func TestValidatorExpand(t *testing.T) {
	tests := []struct {
		name         string
		args         map[string]interface{}
		expectExpand string
		expectError  bool
	}{
		{
			name:         "valid expand param",
			args:         map[string]interface{}{"expand": []interface{}{"payments"}},
			expectExpand: "payments",
			expectError:  false,
		},
		{
			name:         "empty expand array",
			args:         map[string]interface{}{"expand": []interface{}{}},
			expectExpand: "",
			expectError:  false,
		},
		{
			name:         "invalid expand type",
			args:         map[string]interface{}{"expand": "not an array"},
			expectExpand: "",
			expectError:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := make(map[string]interface{})
			request := &mcpgo.CallToolRequest{
				Arguments: tt.args,
			}
			validator := NewValidator(request)

			validator.ValidateAndAddExpand(result)

			if tt.expectError {
				assert.True(t, validator.HasErrors(), "Expected validation error")
			} else {
				assert.False(t, validator.HasErrors(), "Did not expect validation error")
				if tt.expectExpand != "" {
					assert.Equal(t,
						tt.expectExpand,
						result["expand[]"],
						"Expand value mismatch",
					)
				} else {
					_, exists := result["expand[]"]
					assert.False(t, exists, "Expand should not be added")
				}
			}
		})
	}
}

// Test validator "To" functions which write to target maps
func TestValidatorToFunctions(t *testing.T) {
	tests := []struct {
		name      string
		args      map[string]interface{}
		paramName string
		targetKey string
		testFunc  func(
			*Validator, map[string]interface{}, string, string,
		) *Validator
		expectValue interface{}
		expectError bool
	}{
		// ValidateAndAddOptionalStringToPath tests
		{
			name:        "optional string to target - valid",
			args:        map[string]interface{}{"customer_name": "Test User"},
			paramName:   "customer_name",
			targetKey:   "name",
			testFunc:    (*Validator).ValidateAndAddOptionalStringToPath,
			expectValue: "Test User",
			expectError: false,
		},
		{
			name:        "optional string to target - empty",
			args:        map[string]interface{}{"customer_name": ""},
			paramName:   "customer_name",
			targetKey:   "name",
			testFunc:    (*Validator).ValidateAndAddOptionalStringToPath,
			expectValue: "",
			expectError: false,
		},
		{
			name:        "optional string to target - missing",
			args:        map[string]interface{}{},
			paramName:   "customer_name",
			targetKey:   "name",
			testFunc:    (*Validator).ValidateAndAddOptionalStringToPath,
			expectValue: nil,
			expectError: false,
		},
		{
			name:        "optional string to target - wrong type",
			args:        map[string]interface{}{"customer_name": 123},
			paramName:   "customer_name",
			targetKey:   "name",
			testFunc:    (*Validator).ValidateAndAddOptionalStringToPath,
			expectValue: nil,
			expectError: true,
		},

		// ValidateAndAddOptionalBoolToPath tests
		{
			name:        "optional bool to target - true",
			args:        map[string]interface{}{"notify_sms": true},
			paramName:   "notify_sms",
			targetKey:   "sms",
			testFunc:    (*Validator).ValidateAndAddOptionalBoolToPath,
			expectValue: true,
			expectError: false,
		},
		{
			name:        "optional bool to target - false",
			args:        map[string]interface{}{"notify_sms": false},
			paramName:   "notify_sms",
			targetKey:   "sms",
			testFunc:    (*Validator).ValidateAndAddOptionalBoolToPath,
			expectValue: false,
			expectError: false,
		},
		{
			name:        "optional bool to target - wrong type",
			args:        map[string]interface{}{"notify_sms": "not a bool"},
			paramName:   "notify_sms",
			targetKey:   "sms",
			testFunc:    (*Validator).ValidateAndAddOptionalBoolToPath,
			expectValue: nil,
			expectError: true,
		},

		// ValidateAndAddOptionalIntToPath tests
		{
			name:        "optional int to target - valid",
			args:        map[string]interface{}{"age": float64(25)},
			paramName:   "age",
			targetKey:   "customer_age",
			testFunc:    (*Validator).ValidateAndAddOptionalIntToPath,
			expectValue: int64(25),
			expectError: false,
		},
		{
			name:        "optional int to target - zero",
			args:        map[string]interface{}{"age": float64(0)},
			paramName:   "age",
			targetKey:   "customer_age",
			testFunc:    (*Validator).ValidateAndAddOptionalIntToPath,
			expectValue: int64(0),
			expectError: false,
		},
		{
			name:        "optional int to target - missing",
			args:        map[string]interface{}{},
			paramName:   "age",
			targetKey:   "customer_age",
			testFunc:    (*Validator).ValidateAndAddOptionalIntToPath,
			expectValue: nil,
			expectError: false,
		},
		{
			name:        "optional int to target - wrong type",
			args:        map[string]interface{}{"age": "not a number"},
			paramName:   "age",
			targetKey:   "customer_age",
			testFunc:    (*Validator).ValidateAndAddOptionalIntToPath,
			expectValue: nil,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a target map for this specific test
			target := make(map[string]interface{})

			// Create the request and validator
			request := &mcpgo.CallToolRequest{
				Arguments: tt.args,
			}
			validator := NewValidator(request)

			// Call the test function with target and verify its return value
			tt.testFunc(validator, target, tt.paramName, tt.targetKey)

			// Check if we got the expected errors
			if tt.expectError {
				assert.True(t, validator.HasErrors(), "Expected validation error")
			} else {
				assert.False(t, validator.HasErrors(), "Did not expect validation error")

				// For non-error cases, check target map value
				if tt.expectValue != nil {
					// Should have the value with the target key
					assert.Equal(t,
						tt.expectValue,
						target[tt.targetKey],
						"Target map value mismatch")
				} else {
					// Target key should not exist
					_, exists := target[tt.targetKey]
					assert.False(t, exists, "Key should not be in target map when value is empty") // nolint:lll
				}
			}
		})
	}
}

// Test for nested validation with multiple fields into target maps
func TestValidatorNestedObjects(t *testing.T) {
	t.Run("customer object validation", func(t *testing.T) {
		// Create request with customer details
		args := map[string]interface{}{
			"customer_name":    "John Doe",
			"customer_email":   "[email protected]",
			"customer_contact": "+1234567890",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Customer target map
		customer := make(map[string]interface{})

		// Create validator and validate customer fields
		validator := NewValidator(request).
			ValidateAndAddOptionalStringToPath(customer, "customer_name", "name").
			ValidateAndAddOptionalStringToPath(customer, "customer_email", "email").
			ValidateAndAddOptionalStringToPath(customer, "customer_contact", "contact")

		// Should not have errors
		assert.False(t, validator.HasErrors())

		// Customer map should have all three fields
		assert.Equal(t, "John Doe", customer["name"])
		assert.Equal(t, "[email protected]", customer["email"])
		assert.Equal(t, "+1234567890", customer["contact"])
	})

	t.Run("notification object validation", func(t *testing.T) {
		// Create request with notification settings
		args := map[string]interface{}{
			"notify_sms":   true,
			"notify_email": false,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Notify target map
		notify := make(map[string]interface{})

		// Create validator and validate notification fields
		validator := NewValidator(request).
			ValidateAndAddOptionalBoolToPath(notify, "notify_sms", "sms").
			ValidateAndAddOptionalBoolToPath(notify, "notify_email", "email")

		// Should not have errors
		assert.False(t, validator.HasErrors())

		// Notify map should have both fields
		assert.Equal(t, true, notify["sms"])
		assert.Equal(t, false, notify["email"])
	})

	t.Run("mixed object with error", func(t *testing.T) {
		// Create request with mixed valid and invalid data
		args := map[string]interface{}{
			"customer_name":  "Jane Doe",
			"customer_email": 12345, // Wrong type
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Target map
		customer := make(map[string]interface{})

		// Create validator and validate fields
		validator := NewValidator(request).
			ValidateAndAddOptionalStringToPath(customer, "customer_name", "name").
			ValidateAndAddOptionalStringToPath(customer, "customer_email", "email")

		// Should have errors
		assert.True(t, validator.HasErrors())

		// Customer map should have only the valid field
		assert.Equal(t, "Jane Doe", customer["name"])
		_, hasEmail := customer["email"]
		assert.False(t, hasEmail, "Invalid field should not be added to target map")
	})
}

// Test for optional bool handling
func TestOptionalBoolBehavior(t *testing.T) {
	t.Run("explicit bool values", func(t *testing.T) {
		// Create request with explicit bool values
		args := map[string]interface{}{
			"true_param":  true,
			"false_param": false,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Create result map
		result := make(map[string]interface{})

		// Validate both parameters
		validator := NewValidator(request).
			ValidateAndAddOptionalBool(result, "true_param").
			ValidateAndAddOptionalBool(result, "false_param")

		// Verify no errors occurred
		assert.False(t, validator.HasErrors())

		// Both parameters should be set in the result
		assert.Equal(t, true, result["true_param"])
		assert.Equal(t, false, result["false_param"])
	})

	t.Run("missing bool parameter", func(t *testing.T) {
		// Create request without bool parameters
		args := map[string]interface{}{
			"other_param": "some value",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Create result map
		result := make(map[string]interface{})

		// Try to validate missing bool parameters
		validator := NewValidator(request).
			ValidateAndAddOptionalBool(result, "true_param").
			ValidateAndAddOptionalBool(result, "false_param")

		// Verify no errors occurred
		assert.False(t, validator.HasErrors())

		// Result should be empty since no bool values were provided
		assert.Empty(t, result)
	})

	t.Run("explicit bool values with 'To' functions", func(t *testing.T) {
		// Create request with explicit bool values
		args := map[string]interface{}{
			"notify_sms":   true,
			"notify_email": false,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Create target map
		target := make(map[string]interface{})

		// Validate both parameters
		validator := NewValidator(request).
			ValidateAndAddOptionalBoolToPath(target, "notify_sms", "sms").
			ValidateAndAddOptionalBoolToPath(target, "notify_email", "email")

		// Verify no errors occurred
		assert.False(t, validator.HasErrors())

		// Both parameters should be set in the target map
		assert.Equal(t, true, target["sms"])
		assert.Equal(t, false, target["email"])
	})

	t.Run("missing bool parameter with 'To' functions", func(t *testing.T) {
		// Create request without bool parameters
		args := map[string]interface{}{
			"other_param": "some value",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		// Create target map
		target := make(map[string]interface{})

		// Try to validate missing bool parameters
		validator := NewValidator(request).
			ValidateAndAddOptionalBoolToPath(target, "notify_sms", "sms").
			ValidateAndAddOptionalBoolToPath(target, "notify_email", "email")

		// Verify no errors occurred
		assert.False(t, validator.HasErrors())

		// Target map should be empty since no bool values were provided
		assert.Empty(t, target)
	})
}

// Test for extractValueGeneric function edge cases
func TestExtractValueGeneric(t *testing.T) {
	t.Run("invalid arguments type", func(t *testing.T) {
		request := &mcpgo.CallToolRequest{
			Arguments: "invalid_type", // Not a map
		}

		result, err := extractValueGeneric[string](request, "test", false)
		assert.Error(t, err)
		assert.Equal(t, "invalid arguments type", err.Error())
		assert.Nil(t, result)
	})

	t.Run("json marshal error", func(t *testing.T) {
		// Create a value that can't be marshaled to JSON
		args := map[string]interface{}{
			"test_param": make(chan int), // Channels can't be marshaled
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		result, err := extractValueGeneric[string](request, "test_param", false)
		assert.Error(t, err)
		assert.Equal(t, "invalid parameter type: test_param", err.Error())
		assert.Nil(t, result)
	})

	t.Run("json unmarshal error", func(t *testing.T) {
		// Provide a value that can't be unmarshaled to the target type
		args := map[string]interface{}{
			"test_param": []interface{}{1, 2, 3}, // Array can't be unmarshaled to string
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		result, err := extractValueGeneric[string](request, "test_param", false)
		assert.Error(t, err)
		assert.Equal(t, "invalid parameter type: test_param", err.Error())
		assert.Nil(t, result)
	})
}

// Test for validateAndAddRequired function
func TestValidateAndAddRequired(t *testing.T) {
	t.Run("successful validation", func(t *testing.T) {
		args := map[string]interface{}{
			"test_param": "test_value",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddRequired[string](validator, params, "test_param")

		assert.False(t, result.HasErrors())
		assert.Equal(t, "test_value", params["test_param"])
	})

	t.Run("validation error", func(t *testing.T) {
		request := &mcpgo.CallToolRequest{
			Arguments: "invalid_type",
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddRequired[string](validator, params, "test_param")

		assert.True(t, result.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("nil value after successful extraction", func(t *testing.T) {
		// This edge case is hard to trigger directly, but we can simulate it
		// by using a type that extractValueGeneric might return as nil
		args := map[string]interface{}{
			"test_param": nil,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddRequired[string](validator, params, "test_param")

		// This should result in an error because the parameter is required
		assert.True(t, result.HasErrors())
		assert.Empty(t, params)
	})
}

// Test for validateAndAddOptional function
func TestValidateAndAddOptional(t *testing.T) {
	t.Run("successful validation", func(t *testing.T) {
		args := map[string]interface{}{
			"test_param": "test_value",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddOptional[string](validator, params, "test_param")

		assert.False(t, result.HasErrors())
		assert.Equal(t, "test_value", params["test_param"])
	})

	t.Run("validation error", func(t *testing.T) {
		request := &mcpgo.CallToolRequest{
			Arguments: "invalid_type",
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddOptional[string](validator, params, "test_param")

		assert.True(t, result.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("nil value handling", func(t *testing.T) {
		args := map[string]interface{}{
			"test_param": nil,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddOptional[string](validator, params, "test_param")

		assert.False(t, result.HasErrors())
		assert.Empty(t, params)
	})
}

// Test for validateAndAddToPath function
func TestValidateAndAddToPath(t *testing.T) {
	t.Run("successful validation", func(t *testing.T) {
		args := map[string]interface{}{
			"test_param": "test_value",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		target := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddToPath[string](
			validator, target, "test_param", "target_key")

		assert.False(t, result.HasErrors())
		assert.Equal(t, "test_value", target["target_key"])
	})

	t.Run("validation error", func(t *testing.T) {
		request := &mcpgo.CallToolRequest{
			Arguments: "invalid_type",
		}

		target := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddToPath[string](
			validator, target, "test_param", "target_key")

		assert.True(t, result.HasErrors())
		assert.Empty(t, target)
	})

	t.Run("nil value handling", func(t *testing.T) {
		args := map[string]interface{}{
			"test_param": nil,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		target := make(map[string]interface{})
		validator := NewValidator(request)

		result := validateAndAddToPath[string](
			validator, target, "test_param", "target_key")

		assert.False(t, result.HasErrors())
		assert.Empty(t, target)
	})
}

// Test for ValidateAndAddPagination function
func TestValidateAndAddPagination(t *testing.T) {
	t.Run("all pagination parameters", func(t *testing.T) {
		args := map[string]interface{}{
			"count": 10,
			"skip":  5,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddPagination(params)

		assert.False(t, validator.HasErrors())
		assert.Equal(t, int64(10), params["count"])
		assert.Equal(t, int64(5), params["skip"])
	})

	t.Run("missing pagination parameters", func(t *testing.T) {
		args := map[string]interface{}{}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddPagination(params)

		assert.False(t, validator.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("invalid count type", func(t *testing.T) {
		args := map[string]interface{}{
			"count": "invalid",
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddPagination(params)

		assert.True(t, validator.HasErrors())
	})
}

// Test for ValidateAndAddExpand function
func TestValidateAndAddExpand(t *testing.T) {
	t.Run("valid expand parameter", func(t *testing.T) {
		args := map[string]interface{}{
			"expand": []string{"payments", "customer"},
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddExpand(params)

		assert.False(t, validator.HasErrors())
		// The function sets expand[] for each value, so check the last one
		assert.Equal(t, "customer", params["expand[]"])
	})

	t.Run("missing expand parameter", func(t *testing.T) {
		args := map[string]interface{}{}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddExpand(params)

		assert.False(t, validator.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("invalid expand type", func(t *testing.T) {
		args := map[string]interface{}{
			"expand": "invalid", // Should be []string, not string
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddExpand(params)

		assert.True(t, validator.HasErrors())
	})
}

// Test for token validation functions edge cases
func TestTokenValidationEdgeCases(t *testing.T) {
	t.Run("validateTokenMaxAmount - int conversion", func(t *testing.T) {
		token := map[string]interface{}{
			"max_amount": 100, // int instead of float64
		}

		request := &mcpgo.CallToolRequest{Arguments: map[string]interface{}{}}
		validator := NewValidator(request).validateTokenMaxAmount(token)

		assert.False(t, validator.HasErrors())
		assert.Equal(t, float64(100), token["max_amount"])
	})

	t.Run("validateTokenExpireAt - int conversion", func(t *testing.T) {
		token := map[string]interface{}{
			"expire_at": 1234567890, // int instead of float64
		}

		request := &mcpgo.CallToolRequest{Arguments: map[string]interface{}{}}
		validator := NewValidator(request).validateTokenExpireAt(token)

		assert.False(t, validator.HasErrors())
		assert.Equal(t, float64(1234567890), token["expire_at"])
	})

	t.Run("validateTokenExpireAt - zero value", func(t *testing.T) {
		token := map[string]interface{}{
			"expire_at": 0,
		}

		request := &mcpgo.CallToolRequest{Arguments: map[string]interface{}{}}
		validator := NewValidator(request).validateTokenExpireAt(token)

		assert.True(t, validator.HasErrors())
	})

	t.Run("validateTokenMaxAmount - zero value", func(t *testing.T) {
		token := map[string]interface{}{
			"max_amount": 0,
		}

		request := &mcpgo.CallToolRequest{Arguments: map[string]interface{}{}}
		validator := NewValidator(request).validateTokenMaxAmount(token)

		assert.True(t, validator.HasErrors())
	})
}

// Test for ValidateAndAddToken edge cases
func TestValidateAndAddTokenEdgeCases(t *testing.T) {
	t.Run("token extraction error", func(t *testing.T) {
		request := &mcpgo.CallToolRequest{
			Arguments: "invalid_type",
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddToken(params, "token")

		assert.True(t, validator.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("nil token value", func(t *testing.T) {
		args := map[string]interface{}{
			"token": nil,
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddToken(params, "token")

		assert.False(t, validator.HasErrors())
		assert.Empty(t, params)
	})

	t.Run("token validation errors", func(t *testing.T) {
		args := map[string]interface{}{
			"token": map[string]interface{}{
				"max_amount": -100, // Invalid value
			},
		}
		request := &mcpgo.CallToolRequest{
			Arguments: args,
		}

		params := make(map[string]interface{})
		validator := NewValidator(request).ValidateAndAddToken(params, "token")

		assert.True(t, validator.HasErrors())
		assert.Empty(t, params)
	})
}

```
Page 3/4FirstPrevNextLast