This is page 4 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/razorpay/payments_test.go:
--------------------------------------------------------------------------------
```go
package razorpay
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/razorpay/razorpay-go/constants"
"github.com/razorpay/razorpay-mcp-server/pkg/mcpgo"
"github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
)
func Test_FetchPayment(t *testing.T) {
fetchPaymentPathFmt := fmt.Sprintf(
"/%s%s/%%s",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
paymentResp := map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"amount": float64(1000),
"status": "captured",
}
paymentNotFoundResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "payment not found",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful payment fetch",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(fetchPaymentPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "GET",
Response: paymentResp,
},
)
},
ExpectError: false,
ExpectedResult: paymentResp,
},
{
Name: "payment not found",
Request: map[string]interface{}{
"payment_id": "pay_invalid",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(fetchPaymentPathFmt, "pay_invalid"),
Method: "GET",
Response: paymentNotFoundResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching payment failed: payment not found",
},
{
Name: "missing payment_id parameter",
Request: map[string]interface{}{},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, FetchPayment, "Payment")
})
}
}
func Test_FetchPaymentCardDetails(t *testing.T) {
fetchCardDetailsPathFmt := fmt.Sprintf(
"/%s%s/%%s/card",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
cardDetailsResp := map[string]interface{}{
"id": "card_JXPULjlKqC5j0i",
"entity": "card",
"name": "Gaurav Kumar",
"last4": "4366",
"network": "Visa",
"type": "credit",
"issuer": "UTIB",
"international": false,
"emi": false,
"sub_type": "consumer",
"token_iin": nil,
}
paymentNotFoundResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "The id provided does not exist",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful card details fetch",
Request: map[string]interface{}{
"payment_id": "pay_DtFYPi3IfUTgsL",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(fetchCardDetailsPathFmt, "pay_DtFYPi3IfUTgsL"),
Method: "GET",
Response: cardDetailsResp,
},
)
},
ExpectError: false,
ExpectedResult: cardDetailsResp,
},
{
Name: "payment not found",
Request: map[string]interface{}{
"payment_id": "pay_invalid",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(fetchCardDetailsPathFmt, "pay_invalid"),
Method: "GET",
Response: paymentNotFoundResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching card details failed: " +
"The id provided does not exist",
},
{
Name: "missing payment_id parameter",
Request: map[string]interface{}{},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, FetchPaymentCardDetails, "Card Details")
})
}
}
func Test_CapturePayment(t *testing.T) {
capturePaymentPathFmt := fmt.Sprintf(
"/%s%s/%%s/capture",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
successfulCaptureResp := map[string]interface{}{
"id": "pay_G3P9vcIhRs3NV4",
"entity": "payment",
"amount": float64(1000),
"currency": "INR",
"status": "captured",
"order_id": "order_GjCr5oKh4AVC51",
"invoice_id": nil,
"international": false,
"method": "card",
"amount_refunded": float64(0),
"refund_status": nil,
"captured": true,
"description": "Payment for Adidas shoes",
"card_id": "card_KOdY30ajbuyOYN",
"bank": nil,
"wallet": nil,
"vpa": nil,
"email": "[email protected]",
"contact": "9000090000",
"customer_id": "cust_K6fNE0WJZWGqtN",
"token_id": "token_KOdY$DBYQOv08n",
"notes": []interface{}{},
"fee": float64(1),
"tax": float64(0),
"error_code": nil,
"error_description": nil,
"error_source": nil,
"error_step": nil,
"error_reason": nil,
"acquirer_data": map[string]interface{}{
"authentication_reference_number": "100222021120200000000742753928",
},
"created_at": float64(1605871409),
}
alreadyCapturedResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "This payment has already been captured",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful payment capture",
Request: map[string]interface{}{
"payment_id": "pay_G3P9vcIhRs3NV4",
"amount": float64(1000),
"currency": "INR",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(capturePaymentPathFmt, "pay_G3P9vcIhRs3NV4"),
Method: "POST",
Response: successfulCaptureResp,
},
)
},
ExpectError: false,
ExpectedResult: successfulCaptureResp,
},
{
Name: "payment already captured",
Request: map[string]interface{}{
"payment_id": "pay_G3P9vcIhRs3NV4",
"amount": float64(1000),
"currency": "INR",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(capturePaymentPathFmt, "pay_G3P9vcIhRs3NV4"),
Method: "POST",
Response: alreadyCapturedResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "capturing payment failed: This payment has already been " +
"captured",
},
{
Name: "missing payment_id parameter",
Request: map[string]interface{}{
"amount": float64(1000),
"currency": "INR",
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
{
Name: "missing amount parameter",
Request: map[string]interface{}{
"payment_id": "pay_G3P9vcIhRs3NV4",
"currency": "INR",
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: amount",
},
{
Name: "missing currency parameter",
Request: map[string]interface{}{
"payment_id": "pay_G3P9vcIhRs3NV4",
"amount": float64(1000),
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: currency",
},
{
Name: "multiple validation errors",
Request: map[string]interface{}{
// All required parameters missing
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "Validation errors:\n- " +
"missing required parameter: payment_id\n- " +
"missing required parameter: amount\n- " +
"missing required parameter: currency",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, CapturePayment, "Payment")
})
}
}
func Test_UpdatePayment(t *testing.T) {
updatePaymentPathFmt := fmt.Sprintf(
"/%s%s/%%s",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
successfulUpdateResp := map[string]interface{}{
"id": "pay_KbCVlLqUbb3VhA",
"entity": "payment",
"amount": float64(400000),
"currency": "INR",
"status": "authorized",
"order_id": nil,
"invoice_id": nil,
"international": false,
"method": "emi",
"amount_refunded": float64(0),
"refund_status": nil,
"captured": false,
"description": "Test Transaction",
"card_id": "card_KbCVlPnxWRlOpH",
"bank": "HDFC",
"wallet": nil,
"vpa": nil,
"email": "[email protected]",
"contact": "+919000090000",
"notes": map[string]interface{}{
"key1": "value1",
"key2": "value2",
},
"fee": nil,
"tax": nil,
"error_code": nil,
"error_description": nil,
"error_source": nil,
"error_step": nil,
"error_reason": nil,
"acquirer_data": map[string]interface{}{
"auth_code": "205480",
},
"emi_plan": map[string]interface{}{
"issuer": "HDFC",
"type": "credit",
"rate": float64(1500),
"duration": float64(24),
},
"created_at": float64(1667398779),
}
paymentNotFoundResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "The id provided does not exist",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful payment notes update",
Request: map[string]interface{}{
"payment_id": "pay_KbCVlLqUbb3VhA",
"notes": map[string]interface{}{
"key1": "value1",
"key2": "value2",
},
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(updatePaymentPathFmt, "pay_KbCVlLqUbb3VhA"),
Method: "PATCH",
Response: successfulUpdateResp,
},
)
},
ExpectError: false,
ExpectedResult: successfulUpdateResp,
},
{
Name: "payment not found",
Request: map[string]interface{}{
"payment_id": "pay_invalid",
"notes": map[string]interface{}{
"key1": "value1",
},
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(updatePaymentPathFmt, "pay_invalid"),
Method: "PATCH",
Response: paymentNotFoundResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "updating payment failed: The id provided does not exist",
},
{
Name: "missing payment_id parameter",
Request: map[string]interface{}{
"notes": map[string]interface{}{
"key1": "value1",
},
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
{
Name: "missing notes parameter",
Request: map[string]interface{}{
"payment_id": "pay_KbCVlLqUbb3VhA",
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: notes",
},
{
Name: "multiple validation errors",
Request: map[string]interface{}{
// All required parameters missing
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "Validation errors:\n- " +
"missing required parameter: payment_id\n- " +
"missing required parameter: notes",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, UpdatePayment, "Payment")
})
}
}
func Test_FetchAllPayments(t *testing.T) {
fetchAllPaymentsPath := fmt.Sprintf(
"/%s%s",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
// Sample response for successful fetch
paymentsListResp := map[string]interface{}{
"entity": "collection",
"count": float64(2),
"items": []interface{}{
map[string]interface{}{
"id": "pay_KbCFyQ0t9Lmi1n",
"entity": "payment",
"amount": float64(1000),
"currency": "INR",
"status": "authorized",
"order_id": nil,
"invoice_id": nil,
"international": false,
"method": "netbanking",
"amount_refunded": float64(0),
"refund_status": nil,
"captured": false,
"description": "Test Transaction",
"card_id": nil,
"bank": "IBKL",
"wallet": nil,
"vpa": nil,
"email": "[email protected]",
"contact": "+919000090000",
"notes": map[string]interface{}{
"address": "Razorpay Corporate Office",
},
"fee": nil,
"tax": nil,
"error_code": nil,
"error_description": nil,
"error_source": nil,
"error_step": nil,
"error_reason": nil,
"acquirer_data": map[string]interface{}{
"bank_transaction_id": "5733649",
},
"created_at": float64(1667397881),
},
map[string]interface{}{
"id": "pay_KbCEDHh1IrU4RJ",
"entity": "payment",
"amount": float64(1000),
"currency": "INR",
"status": "authorized",
"order_id": nil,
"invoice_id": nil,
"international": false,
"method": "upi",
"amount_refunded": float64(0),
"refund_status": nil,
"captured": false,
"description": "Test Transaction",
"card_id": nil,
"bank": nil,
"wallet": nil,
"vpa": "gaurav.kumar@okhdfcbank",
"email": "[email protected]",
"contact": "+919000090000",
"notes": map[string]interface{}{
"address": "Razorpay Corporate Office",
},
"fee": nil,
"tax": nil,
"error_code": nil,
"error_description": nil,
"error_source": nil,
"error_step": nil,
"error_reason": nil,
"acquirer_data": map[string]interface{}{
"rrn": "230901495295",
"upi_transaction_id": "6935B87A72C2A7BC83FA927AA264AD53",
},
"created_at": float64(1667397781),
},
},
}
// Error response when parameters are invalid
invalidParamsResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "from must be between 946684800 and 4765046400",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful payments fetch with all parameters",
Request: map[string]interface{}{
"from": float64(1593320020),
"to": float64(1624856020),
"count": float64(2),
"skip": float64(1),
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fetchAllPaymentsPath,
Method: "GET",
Response: paymentsListResp,
},
)
},
ExpectError: false,
ExpectedResult: paymentsListResp,
},
{
Name: "payments fetch with invalid timestamp",
Request: map[string]interface{}{
"from": float64(900000000), // Invalid timestamp (too early)
"to": float64(1624856020),
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fetchAllPaymentsPath,
Method: "GET",
Response: invalidParamsResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching payments failed: from must be between " +
"946684800 and 4765046400",
},
{
Name: "multiple validation errors with wrong types",
Request: map[string]interface{}{
"count": "not_a_number",
"skip": "not_a_number",
"from": "not_a_number",
"to": "not_a_number",
},
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",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, FetchAllPayments, "Payments List")
})
}
}
func Test_InitiatePayment(t *testing.T) {
initiatePaymentPath := fmt.Sprintf(
"/%s%s/create/json",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
createCustomerPath := fmt.Sprintf(
"/%s%s",
constants.VERSION_V1,
constants.CUSTOMER_URL,
)
customerResp := map[string]interface{}{
"id": "cust_1Aa00000000003",
"entity": "customer",
"name": "",
"email": "",
"contact": "9876543210",
"gstin": nil,
"notes": map[string]interface{}{},
"created_at": float64(1234567890),
}
successPaymentWithRedirectResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
}
successPaymentWithoutNextResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "captured",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
}
paymentErrorResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid token",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful payment initiation without next actions",
Request: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"token": "token_MT48CvBhIC98MQ",
"order_id": "order_129837127313912",
"email": "[email protected]",
"contact": "9876543210",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successPaymentWithoutNextResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": successPaymentWithoutNextResp,
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "successful payment initiation with redirect",
Request: map[string]interface{}{
"amount": 10000,
"token": "token_MT48CvBhIC98MQ",
"order_id": "order_129837127313912",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successPaymentWithRedirectResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": successPaymentWithRedirectResp,
"status": "payment_initiated",
"message": "Payment initiated. Redirect authentication is available. " +
"Use the redirect URL provided in available_actions.",
"available_actions": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
},
},
{
Name: "successful payment initiation with contact only",
Request: map[string]interface{}{
"amount": 10000,
"token": "token_MT48CvBhIC98MQ",
"order_id": "order_129837127313912",
"contact": "9876543210",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successPaymentWithoutNextResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": successPaymentWithoutNextResp,
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "payment initiation with API error",
Request: map[string]interface{}{
"amount": 10000,
"token": "token_invalid",
"order_id": "order_129837127313912",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: paymentErrorResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "initiating payment failed:",
},
{
Name: "missing required amount parameter",
Request: map[string]interface{}{
"token": "token_MT48CvBhIC98MQ",
"order_id": "order_129837127313912",
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "missing required parameter: amount",
},
{
Name: "missing required order_id parameter",
Request: map[string]interface{}{
"amount": 10000,
"token": "token_MT48CvBhIC98MQ",
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "missing required parameter: order_id",
},
{
Name: "invalid amount parameter type",
Request: map[string]interface{}{
"amount": "not_a_number",
"token": "token_MT48CvBhIC98MQ",
"order_id": "order_129837127313912",
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "invalid parameter type: amount",
},
{
Name: "multiple validation errors",
Request: map[string]interface{}{
"amount": "not_a_number",
"token": 123,
"email": 456,
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "Validation errors:\n- invalid parameter type: amount\n- " +
"invalid parameter type: token\n- " +
"missing required parameter: order_id\n- " +
"invalid parameter type: email",
},
{
Name: "successful UPI collect flow payment initiation",
Request: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_129837127313912",
"email": "[email protected]",
"contact": "9876543210",
"customer_id": "cust_RGCgP2osfPKFq2",
"save": true,
"vpa": "9876543210@ptsbi",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiCollectResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiCollectResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
},
"status": "payment_initiated",
"message": "Payment initiated. Available actions: [upi_collect]",
"available_actions": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
},
},
{
Name: "successful UPI collect flow without token",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"contact": "9876543210",
"vpa": "9876543210@ptsbi",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiCollectResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiCollectResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
},
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "UPI collect flow with all optional parameters",
Request: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_129837127313912",
"email": "[email protected]",
"contact": "9876543210",
"customer_id": "cust_RGCgP2osfPKFq2",
"save": false,
"vpa": "test@paytm",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiCollectResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiCollectResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
},
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "invalid save parameter type",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"save": "invalid_string_instead_of_bool",
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "invalid parameter type: save",
},
{
Name: "invalid customer_id parameter type",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"customer_id": 123,
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "invalid parameter type: customer_id",
},
{
Name: "successful UPI intent flow payment initiation",
Request: map[string]interface{}{
"amount": 12000,
"currency": "INR",
"order_id": "order_INTENT123",
"email": "[email protected]",
"contact": "9876543210",
"upi_intent": true,
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiIntentResp := map[string]interface{}{
"razorpay_payment_id": "pay_INTENT123",
"status": "created",
"amount": float64(12000),
"currency": "INR",
"order_id": "order_INTENT123",
"method": "upi",
"upi": map[string]interface{}{
"flow": "intent",
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_intent",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_INTENT123/upi_intent",
},
},
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiIntentResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_INTENT123",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_INTENT123",
"status": "created",
"amount": float64(12000),
"currency": "INR",
"order_id": "order_INTENT123",
"method": "upi",
"upi": map[string]interface{}{
"flow": "intent",
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_intent",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_INTENT123/upi_intent",
},
},
},
"status": "payment_initiated",
"message": "Payment initiated. Available actions: [upi_intent]",
"available_actions": []interface{}{
map[string]interface{}{
"action": "upi_intent",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_INTENT123/upi_intent",
},
},
},
},
{
Name: "invalid upi_intent parameter type",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"upi_intent": "invalid_string",
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "invalid parameter type: upi_intent",
},
{
Name: "successful payment initiation with force_terminal_id " +
"for single block multiple debit",
Request: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_129837127313912",
"email": "[email protected]",
"contact": "9876543210",
"customer_id": "cust_RGCgP2osfPKFq2",
"recurring": true,
"force_terminal_id": "term_ABCD1234256732",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successPaymentWithTerminalResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"force_terminal_id": "term_ABCD1234256732",
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successPaymentWithTerminalResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"force_terminal_id": "term_ABCD1234256732",
},
"status": "payment_initiated",
"message": "Payment initiated successfully using S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "invalid force_terminal_id parameter type",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"force_terminal_id": 123,
},
MockHttpClient: nil,
ExpectError: true,
ExpectedErrMsg: "invalid parameter type: force_terminal_id",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, InitiatePayment, "Payment Initiation")
})
}
}
func Test_SubmitOtp(t *testing.T) {
submitOtpPathFmt := fmt.Sprintf(
"/%s%s/%%s/otp/submit",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
successOtpSubmitResp := map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"entity": "payment",
"amount": float64(10000),
"currency": "INR",
"status": "authorized",
"order_id": "order_129837127313912",
"description": "Test payment",
"method": "card",
"amount_refunded": float64(0),
"refund_status": nil,
"captured": false,
"email": "[email protected]",
"contact": "9876543210",
"fee": float64(236),
"tax": float64(36),
"error_code": nil,
"error_description": nil,
"created_at": float64(1234567890),
}
otpVerificationFailedResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid OTP provided",
"field": "otp",
},
}
paymentNotFoundResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Payment not found",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful OTP submission",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"otp_string": "123456",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "POST",
Response: successOtpSubmitResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"status": "success",
"message": "OTP verified successfully.",
"response_data": successOtpSubmitResp,
},
},
{
Name: "OTP verification failed - invalid OTP",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"otp_string": "000000",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "POST",
Response: otpVerificationFailedResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP verification failed: Invalid OTP provided",
},
{
Name: "payment not found",
Request: map[string]interface{}{
"payment_id": "pay_invalid",
"otp_string": "123456",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(submitOtpPathFmt, "pay_invalid"),
Method: "POST",
Response: paymentNotFoundResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP verification failed: Payment not found",
},
{
Name: "missing payment_id parameter",
Request: map[string]interface{}{
"otp_string": "123456",
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
{
Name: "missing otp_string parameter",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: otp_string",
},
{
Name: "missing both required parameters",
Request: map[string]interface{}{},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: otp_string",
},
{
Name: "empty otp_string",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"otp_string": "",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Authentication failed",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP verification failed: Authentication failed",
},
{
Name: "empty payment_id",
Request: map[string]interface{}{
"payment_id": "",
"otp_string": "123456",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(submitOtpPathFmt, ""),
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP verification failed:",
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, SubmitOtp, "OTP Submission")
})
}
}
func Test_InitiatePaymentWithVPA(t *testing.T) {
initiatePaymentPath := fmt.Sprintf(
"/%s%s/create/json",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
createCustomerPath := fmt.Sprintf(
"/%s%s",
constants.VERSION_V1,
constants.CUSTOMER_URL,
)
customerResp := map[string]interface{}{
"id": "cust_1Aa00000000003",
"entity": "customer",
"name": "",
"email": "",
"contact": "9876543210",
"gstin": nil,
"notes": map[string]interface{}{},
"created_at": float64(1234567890),
}
testCases := []RazorpayToolTestCase{
{
Name: "successful UPI payment with VPA parameter",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"vpa": "9876543210@ptsbi",
"email": "[email protected]",
"contact": "9876543210",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiVpaResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"email": "[email protected]",
"contact": "9876543210",
"upi_transaction_id": nil,
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "9876543210@ptsbi",
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
},
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: createCustomerPath,
Method: "POST",
Response: customerResp,
},
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiVpaResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
"method": "upi",
"email": "[email protected]",
"contact": "9876543210",
"upi_transaction_id": nil,
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "9876543210@ptsbi",
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
},
},
"status": "payment_initiated",
"message": "Payment initiated. Available actions: [upi_collect]",
"available_actions": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
},
},
},
{
Name: "UPI payment with VPA and custom currency",
Request: map[string]interface{}{
"amount": 20000,
"currency": "INR",
"order_id": "order_ABC123XYZ456",
"vpa": "test@upi",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiVpaResp := map[string]interface{}{
"razorpay_payment_id": "pay_ABC123XYZ456",
"status": "created",
"amount": float64(20000),
"currency": "INR",
"order_id": "order_ABC123XYZ456",
"method": "upi",
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "test@upi",
},
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiVpaResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_ABC123XYZ456",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_ABC123XYZ456",
"status": "created",
"amount": float64(20000),
"currency": "INR",
"order_id": "order_ABC123XYZ456",
"method": "upi",
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "test@upi",
},
},
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_ABC123XYZ456",
},
},
},
{
Name: "missing VPA parameter value",
Request: map[string]interface{}{
"amount": 10000,
"order_id": "order_129837127313912",
"vpa": "",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successRegularResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successRegularResp,
},
)
},
ExpectError: false, // Empty VPA should not trigger UPI logic
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
"amount": float64(10000),
"currency": "INR",
"order_id": "order_129837127313912",
},
"status": "payment_initiated",
"message": "Payment initiated successfully using " +
"S2S JSON v1 flow",
"next_step": "Use 'resend_otp' to regenerate OTP or " +
"'submit_otp' to proceed to enter OTP if " +
"OTP authentication is required.",
"next_tool": "resend_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
},
},
{
Name: "VPA parameter automatically sets UPI method",
Request: map[string]interface{}{
"amount": 15000,
"order_id": "order_OVERRIDE123",
"vpa": "new@upi",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
successUpiOverrideResp := map[string]interface{}{
"razorpay_payment_id": "pay_OVERRIDE123",
"status": "created",
"amount": float64(15000),
"currency": "INR",
"order_id": "order_OVERRIDE123",
"method": "upi", // Should be set to UPI by VPA
"upi": map[string]interface{}{
"flow": "collect", // Default flow
"expiry_time": "6", // Default expiry
"vpa": "new@upi", // VPA from parameter
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_OVERRIDE123/otp_generate",
},
},
}
return mock.NewHTTPClient(
mock.Endpoint{
Path: initiatePaymentPath,
Method: "POST",
Response: successUpiOverrideResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"razorpay_payment_id": "pay_OVERRIDE123",
"payment_details": map[string]interface{}{
"razorpay_payment_id": "pay_OVERRIDE123",
"status": "created",
"amount": float64(15000),
"currency": "INR",
"order_id": "order_OVERRIDE123",
"method": "upi",
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "new@upi",
},
"next": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_OVERRIDE123/otp_generate",
},
},
},
"status": "payment_initiated",
"message": "Payment initiated. Available actions: [upi_collect]",
"available_actions": []interface{}{
map[string]interface{}{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_OVERRIDE123/otp_generate",
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, InitiatePayment, "Payment")
})
}
}
// Test helper functions for better coverage
func Test_extractPaymentID(t *testing.T) {
tests := []struct {
name string
payment map[string]interface{}
expected string
}{
{
name: "valid payment ID",
payment: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
expected: "pay_MT48CvBhIC98MQ",
},
{
name: "missing payment ID",
payment: map[string]interface{}{
"status": "created",
},
expected: "",
},
{
name: "nil payment ID",
payment: map[string]interface{}{
"razorpay_payment_id": nil,
"status": "created",
},
expected: "",
},
{
name: "empty payment map",
payment: map[string]interface{}{},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractPaymentID(tt.payment)
if result != tt.expected {
t.Errorf("extractPaymentID() = %v, want %v", result, tt.expected)
}
})
}
}
func Test_buildInitiatePaymentResponse(t *testing.T) {
tests := []struct {
name string
payment map[string]interface{}
paymentID string
actions []map[string]interface{}
expectedMsg string
expectedOtpURL string
}{
{
name: "payment with OTP action",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{
{
"action": "otp_generate",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
},
expectedMsg: "Payment initiated. OTP authentication is available. " +
"Use the 'submit_otp' tool to submit OTP received by the customer " +
"for authentication.",
expectedOtpURL: "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
{
name: "payment with redirect action",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{
{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
expectedMsg: "Payment initiated. Redirect authentication is available. " +
"Use the redirect URL provided in available_actions.",
expectedOtpURL: "",
},
{
name: "payment with UPI collect action",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{
{
"action": "upi_collect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
expectedMsg: "Payment initiated. Available actions: [upi_collect]",
expectedOtpURL: "",
},
{
name: "payment with multiple actions including OTP",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{
{
"action": "otp_generate",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/authenticate",
},
},
expectedMsg: "Payment initiated. OTP authentication is available. " +
"Use the 'submit_otp' tool to submit OTP received by the customer " +
"for authentication.",
expectedOtpURL: "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp_generate",
},
{
name: "payment with no actions",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "captured",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{},
expectedMsg: "Payment initiated successfully using S2S JSON v1 flow",
expectedOtpURL: "",
},
{
name: "payment with unknown action",
payment: map[string]interface{}{
"id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
paymentID: "pay_MT48CvBhIC98MQ",
actions: []map[string]interface{}{
{
"action": "unknown_action",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/unknown",
},
},
expectedMsg: "Payment initiated. Available actions: [unknown_action]",
expectedOtpURL: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
response, otpURL := buildInitiatePaymentResponse(
tt.payment, tt.paymentID, tt.actions)
// Check basic response structure
if response["razorpay_payment_id"] != tt.paymentID {
t.Errorf("Expected payment ID %s, got %v", tt.paymentID,
response["razorpay_payment_id"])
}
if response["status"] != "payment_initiated" {
t.Errorf("Expected status 'payment_initiated', got %v", response["status"])
}
// Check message
if response["message"] != tt.expectedMsg {
t.Errorf("Expected message %s, got %v", tt.expectedMsg, response["message"])
}
// Check OTP URL
if otpURL != tt.expectedOtpURL {
t.Errorf("Expected OTP URL %s, got %s", tt.expectedOtpURL, otpURL)
}
// Check actions are included when present
if len(tt.actions) > 0 {
if _, exists := response["available_actions"]; !exists {
t.Error("Expected available_actions to be present in response")
}
}
// Check next step instructions for OTP case
if tt.paymentID != "" && len(tt.actions) == 0 {
if _, exists := response["next_step"]; !exists {
t.Error("Expected next_step to be present for fallback case")
}
}
})
}
}
func Test_addNextStepInstructions(t *testing.T) {
tests := []struct {
name string
paymentID string
expected bool // whether next_step should be added
}{
{
name: "valid payment ID",
paymentID: "pay_MT48CvBhIC98MQ",
expected: true,
},
{
name: "empty payment ID",
paymentID: "",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
response := make(map[string]interface{})
addNextStepInstructions(response, tt.paymentID)
if tt.expected {
if _, exists := response["next_step"]; !exists {
t.Error("Expected next_step to be added")
}
if _, exists := response["next_tool"]; !exists {
t.Error("Expected next_tool to be added")
}
if _, exists := response["next_tool_params"]; !exists {
t.Error("Expected next_tool_params to be added")
}
// Check specific values
if response["next_tool"] != "resend_otp" {
t.Errorf("Expected next_tool to be 'resend_otp', got %v",
response["next_tool"])
}
params, ok := response["next_tool_params"].(map[string]interface{})
if !ok || params["payment_id"] != tt.paymentID {
t.Errorf("Expected next_tool_params to contain payment_id %s",
tt.paymentID)
}
} else {
if _, exists := response["next_step"]; exists {
t.Error("Expected next_step NOT to be added for empty payment ID")
}
}
})
}
}
func Test_sendOtp_validation(t *testing.T) {
tests := []struct {
name string
otpURL string
expectedErr string
}{
{
name: "empty URL",
otpURL: "",
expectedErr: "OTP URL is empty",
},
{
name: "invalid URL",
otpURL: "not-a-valid-url",
expectedErr: "OTP URL must use HTTPS",
},
{
name: "non-HTTPS URL",
otpURL: "http://api.razorpay.com/v1/payments/pay_123/otp_generate",
expectedErr: "OTP URL must use HTTPS",
},
{
name: "non-Razorpay domain",
otpURL: "https://malicious.com/v1/payments/pay_123/otp_generate",
expectedErr: "OTP URL must be from Razorpay domain",
},
{
name: "valid Razorpay URL - should fail at HTTP call",
otpURL: "https://api.razorpay.com/v1/payments/pay_123/otp_generate",
expectedErr: "OTP generation failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := sendOtp(tt.otpURL)
if err == nil {
t.Error("Expected error but got nil")
return
}
if tt.expectedErr != "" && !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("Expected error to contain '%s', got '%s'",
tt.expectedErr, err.Error())
}
})
}
}
func Test_extractOtpSubmitURL(t *testing.T) {
tests := []struct {
name string
payment map[string]interface{}
expected string
}{
{
name: "payment with next actions containing otp_submit",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "otp_submit",
"url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
},
},
expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
{
name: "payment with multiple next actions",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/pay_123/authenticate",
},
map[string]interface{}{
"action": "otp_submit",
"url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
},
},
expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
{
name: "payment with no next actions",
payment: map[string]interface{}{
"status": "captured",
},
expected: "",
},
{
name: "payment with next actions but no otp_submit",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://api.razorpay.com/v1/payments/pay_123/authenticate",
},
},
},
expected: "",
},
{
name: "payment with empty next array",
payment: map[string]interface{}{
"next": []interface{}{},
},
expected: "",
},
{
name: "payment with invalid next structure",
payment: map[string]interface{}{
"next": "invalid_structure",
},
expected: "",
},
{
name: "payment with otp_submit action but nil URL",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "otp_submit",
"url": nil, // nil URL should return empty string
},
},
},
expected: "",
},
{
name: "payment with otp_submit action but non-string URL",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "otp_submit",
"url": 123, // non-string URL should cause type assertion to fail
},
},
},
expected: "",
},
{
name: "payment with otp_submit action but missing URL field",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "otp_submit",
// no url field
},
},
},
expected: "",
},
{
name: "payment with mixed valid and invalid items in next array",
payment: map[string]interface{}{
"next": []interface{}{
"invalid_item", // This should be skipped
map[string]interface{}{
"action": "redirect",
"url": "https://example.com/redirect",
},
123, // Another invalid item that should be skipped
map[string]interface{}{
"action": "otp_submit",
"url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
},
},
expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
{
name: "payment with otp_submit action but missing action field",
payment: map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
// no action field
"url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
},
},
},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractOtpSubmitURL(tt.payment)
if result != tt.expected {
t.Errorf("extractOtpSubmitURL() = %v, want %v", result, tt.expected)
}
})
}
}
// Test_extractOtpSubmitURL_invalidInput tests the
// function with invalid input types
func Test_extractOtpSubmitURL_invalidInput(t *testing.T) {
tests := []struct {
name string
input interface{}
expected string
}{
{
name: "nil input",
input: nil,
expected: "",
},
{
name: "string input instead of map",
input: "invalid_input",
expected: "",
},
{
name: "integer input instead of map",
input: 123,
expected: "",
},
{
name: "slice input instead of map",
input: []string{"invalid"},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractOtpSubmitURL(tt.input)
if result != tt.expected {
t.Errorf("extractOtpSubmitURL() = %v, want %v", result, tt.expected)
}
})
}
}
func Test_ResendOtp(t *testing.T) {
resendOtpPathFmt := fmt.Sprintf(
"/%s%s/%%s/otp/resend",
constants.VERSION_V1,
constants.PAYMENT_URL,
)
successResendOtpResp := map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"next": []interface{}{
map[string]interface{}{
"action": "otp_submit",
"url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp/submit",
},
},
}
paymentNotFoundResp := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Payment not found",
},
}
tests := []RazorpayToolTestCase{
{
Name: "successful OTP resend",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(resendOtpPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "POST",
Response: successResendOtpResp,
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"status": "success",
"message": "OTP sent successfully. Please enter the OTP received on your " +
"mobile number to complete the payment.",
"next_step": "Use 'submit_otp' tool with the OTP code received " +
"from user to complete payment authentication.",
"next_tool": "submit_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"otp_string": "{OTP_CODE_FROM_USER}",
},
"otp_submit_url": "https://api.razorpay.com/v1/payments/" +
"pay_MT48CvBhIC98MQ/otp/submit",
"response_data": successResendOtpResp,
},
},
{
Name: "payment not found for OTP resend",
Request: map[string]interface{}{
"payment_id": "pay_invalid",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(resendOtpPathFmt, "pay_invalid"),
Method: "POST",
Response: paymentNotFoundResp,
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP resend failed: Payment not found",
},
{
Name: "missing payment_id parameter for resend",
Request: map[string]interface{}{
// No payment_id provided
},
MockHttpClient: nil, // No HTTP client needed for validation error
ExpectError: true,
ExpectedErrMsg: "missing required parameter: payment_id",
},
{
Name: "OTP resend without next actions",
Request: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: fmt.Sprintf(resendOtpPathFmt, "pay_MT48CvBhIC98MQ"),
Method: "POST",
Response: map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
},
)
},
ExpectError: false,
ExpectedResult: map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"status": "success",
"message": "OTP sent successfully. Please enter the OTP received on your " +
"mobile number to complete the payment.",
"next_step": "Use 'submit_otp' tool with the OTP code received " +
"from user to complete payment authentication.",
"next_tool": "submit_otp",
"next_tool_params": map[string]interface{}{
"payment_id": "pay_MT48CvBhIC98MQ",
"otp_string": "{OTP_CODE_FROM_USER}",
},
"response_data": map[string]interface{}{
"razorpay_payment_id": "pay_MT48CvBhIC98MQ",
"status": "created",
},
},
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
runToolTest(t, tc, ResendOtp, "OTP Resend")
})
}
}
// Test_sendOtp_additionalCases tests additional cases for sendOtp function
func Test_sendOtp_additionalCases(t *testing.T) {
tests := []struct {
name string
otpURL string
expectedErr string
}{
{
name: "URL with invalid characters",
otpURL: "https://api.razorpay.com/v1/payments/pay_123/" +
"otp_generate?param=value with spaces",
expectedErr: "OTP generation failed",
},
{
name: "URL with special characters in domain",
otpURL: "https://api-test.razorpay.com/v1/payments/" +
"pay_123/otp_generate",
expectedErr: "OTP generation failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := sendOtp(tt.otpURL)
if err == nil {
t.Error("Expected error but got nil")
return
}
if tt.expectedErr != "" && !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf(
"Expected error to contain '%s', got '%s'", tt.expectedErr, err.Error(),
)
}
})
}
}
// Test_buildPaymentData_edgeCases tests edge cases for
// buildPaymentData function
func Test_buildPaymentData_edgeCases(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
currency string
customerID string
expectedError string
shouldContain map[string]interface{}
}{
{
name: "payment data with valid customer ID",
params: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
},
currency: "INR",
customerID: "cust_123456789",
shouldContain: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_123",
"customer_id": "cust_123456789",
},
},
{
name: "payment data with empty token",
params: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"token": "", // Empty token should not be added
},
currency: "INR",
customerID: "cust_123456789",
shouldContain: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_123",
"customer_id": "cust_123456789",
},
},
{
name: "payment data with empty customer ID",
params: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"token": "token_123",
},
currency: "INR",
customerID: "",
shouldContain: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_123",
"token": "token_123",
},
},
{
name: "payment data with all parameters",
params: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"token": "token_123",
"email": "[email protected]",
"contact": "9876543210",
"method": "upi",
"save": true,
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "test@upi",
},
},
currency: "INR",
customerID: "cust_123456789",
shouldContain: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"order_id": "order_123",
"customer_id": "cust_123456789",
"token": "token_123",
"email": "[email protected]",
"contact": "9876543210",
"method": "upi",
"save": true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := buildPaymentData(tt.params, tt.currency, tt.customerID)
if result == nil {
t.Error("Expected result but got nil")
return
}
// Check that all expected fields are present
for key, expectedValue := range tt.shouldContain {
actualValue, exists := (*result)[key]
if !exists {
t.Errorf("Expected key '%s' to exist in result", key)
} else if actualValue != expectedValue {
t.Errorf(
"Expected '%s' to be %v, got %v", key, expectedValue, actualValue,
)
}
}
// Check that empty token is not added
if tt.params["token"] == "" {
if _, exists := (*result)["token"]; exists {
t.Error("Empty token should not be added to payment data")
}
}
// Check that customer_id is not added when customerID is empty
if tt.customerID == "" {
if _, exists := (*result)["customer_id"]; exists {
t.Error("customer_id should not be added when customerID is empty")
}
}
})
}
}
// Test_createOrGetCustomer_scenarios tests
// createOrGetCustomer function scenarios
func Test_createOrGetCustomer_scenarios(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
mockSetup func() (*http.Client, *httptest.Server)
expectedError string
expectedResult map[string]interface{}
}{
{
name: "successful customer creation with contact",
params: map[string]interface{}{
"contact": "9876543210",
},
mockSetup: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/customers",
Method: "POST",
Response: map[string]interface{}{
"id": "cust_123456789",
"contact": "9876543210",
"email": "[email protected]",
},
},
)
},
expectedResult: map[string]interface{}{
"id": "cust_123456789",
"contact": "9876543210",
"email": "[email protected]",
},
},
{
name: "no contact provided - returns nil",
params: map[string]interface{}{
"amount": 10000,
},
mockSetup: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient()
},
expectedResult: nil,
},
{
name: "empty contact provided - returns nil",
params: map[string]interface{}{
"contact": "",
},
mockSetup: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient()
},
expectedResult: nil,
},
{
name: "customer creation API error",
params: map[string]interface{}{
"contact": "9876543210",
},
mockSetup: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/customers",
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid contact number",
},
},
},
)
},
expectedError: "failed to create/fetch customer with contact 9876543210",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, server := newMockRzpClient(tt.mockSetup)
if server != nil {
defer server.Close()
}
result, err := createOrGetCustomer(client, tt.params)
if tt.expectedError != "" {
if err == nil {
t.Error("Expected error but got nil")
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf(
"Expected error to contain '%s', got '%s'", tt.expectedError, err.Error(),
)
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
switch {
case tt.expectedResult == nil && result != nil:
t.Errorf("Expected nil result but got %v", result)
case tt.expectedResult != nil && result == nil:
t.Error("Expected result but got nil")
case tt.expectedResult != nil && result != nil:
if result["id"] != tt.expectedResult["id"] {
t.Errorf(
"Expected customer ID '%s', got '%s'", tt.expectedResult["id"],
result["id"],
)
}
}
}
})
}
}
// Test_processVPAParameters_scenarios tests
//
// processUPIParameters function scenarios
func Test_processUPIParameters_scenarios(t *testing.T) {
tests := []struct {
name string
inputParams map[string]interface{}
expectedParams map[string]interface{}
}{
{
name: "VPA parameter provided - sets UPI parameters",
inputParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"vpa": "9876543210@paytm",
},
expectedParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"vpa": "9876543210@paytm",
"method": "upi",
"upi": map[string]interface{}{
"flow": "collect",
"expiry_time": "6",
"vpa": "9876543210@paytm",
},
},
},
{
name: "empty VPA parameter - no changes",
inputParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"vpa": "",
},
expectedParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"vpa": "",
},
},
{
name: "no VPA parameter - no changes",
inputParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
},
expectedParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
},
},
{
name: "UPI intent parameter provided - sets UPI intent parameters",
inputParams: map[string]interface{}{
"amount": 15000,
"order_id": "order_456",
"upi_intent": true,
},
expectedParams: map[string]interface{}{
"amount": 15000,
"order_id": "order_456",
"upi_intent": true,
"method": "upi",
"upi": map[string]interface{}{
"flow": "intent",
},
},
},
{
name: "UPI intent false - no changes",
inputParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"upi_intent": false,
},
expectedParams: map[string]interface{}{
"amount": 10000,
"order_id": "order_123",
"upi_intent": false,
},
},
{
name: "both VPA and UPI intent provided - UPI intent takes precedence",
inputParams: map[string]interface{}{
"amount": 20000,
"order_id": "order_789",
"vpa": "test@upi",
"upi_intent": true,
},
expectedParams: map[string]interface{}{
"amount": 20000,
"order_id": "order_789",
"vpa": "test@upi",
"upi_intent": true,
"method": "upi",
"upi": map[string]interface{}{
"flow": "intent",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
params := make(map[string]interface{})
for k, v := range tt.inputParams {
params[k] = v
}
processUPIParameters(params)
for key, expectedValue := range tt.expectedParams {
actualValue, exists := params[key]
if !exists {
t.Errorf("Expected key '%s' to exist in params", key)
continue
}
if key == "upi" {
expectedUPI := expectedValue.(map[string]interface{})
actualUPI, ok := actualValue.(map[string]interface{})
if !ok {
t.Errorf("Expected UPI to be map[string]interface{}, got %T", actualValue)
continue
}
for upiKey, upiValue := range expectedUPI {
if actualUPI[upiKey] != upiValue {
t.Errorf(
"Expected UPI[%s] to be '%v', got '%v'",
upiKey, upiValue, actualUPI[upiKey],
)
}
}
} else if actualValue != expectedValue {
t.Errorf(
"Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
)
}
}
})
}
}
// Test_addAdditionalPaymentParameters_scenarios
// tests addAdditionalPaymentParameters function scenarios
// Note: method parameter is set internally by VPA processing, not by user input
func Test_addAdditionalPaymentParameters_scenarios(t *testing.T) {
tests := []struct {
name string
paymentData map[string]interface{}
params map[string]interface{}
expectedResult map[string]interface{}
}{
{
name: "all additional parameters provided (method set internally)",
paymentData: map[string]interface{}{
"amount": 10000,
"currency": "INR",
},
params: map[string]interface{}{
"method": "upi",
"save": true,
"upi": map[string]interface{}{
"flow": "collect",
"vpa": "test@upi",
},
},
expectedResult: map[string]interface{}{
"amount": 10000,
"currency": "INR",
"method": "upi",
"save": true,
"upi": map[string]interface{}{
"flow": "collect",
"vpa": "test@upi",
},
},
},
{
name: "empty method parameter - not added (internal processing)",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"method": "",
"save": false,
},
expectedResult: map[string]interface{}{
"amount": 10000,
"save": false,
},
},
{
name: "nil UPI parameters - not added (method set internally)",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"method": "card",
"upi": nil,
},
expectedResult: map[string]interface{}{
"amount": 10000,
"method": "card",
},
},
{
name: "invalid UPI parameter type - not added (method set internally)",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"method": "upi",
"upi": "invalid_type",
},
expectedResult: map[string]interface{}{
"amount": 10000,
"method": "upi",
},
},
{
name: "recurring parameter provided",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"recurring": true,
},
expectedResult: map[string]interface{}{
"amount": 10000,
"recurring": true,
},
},
{
name: "recurring parameter false",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"recurring": false,
},
expectedResult: map[string]interface{}{
"amount": 10000,
"recurring": false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paymentData := make(map[string]interface{})
for k, v := range tt.paymentData {
paymentData[k] = v
}
addAdditionalPaymentParameters(paymentData, tt.params)
for key, expectedValue := range tt.expectedResult {
actualValue, exists := paymentData[key]
if !exists {
t.Errorf("Expected key '%s' to exist in paymentData", key)
continue
}
if key == "upi" {
expectedUPI := expectedValue.(map[string]interface{})
actualUPI, ok := actualValue.(map[string]interface{})
if !ok {
t.Errorf("Expected UPI to be map[string]interface{}, got %T", actualValue)
continue
}
for upiKey, upiValue := range expectedUPI {
if actualUPI[upiKey] != upiValue {
t.Errorf(
"Expected UPI[%s] to be '%v', got '%v'", upiKey, upiValue,
actualUPI[upiKey],
)
}
}
} else if actualValue != expectedValue {
t.Errorf(
"Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
)
}
}
// Check that no unexpected keys were added
for key := range paymentData {
if _, expected := tt.expectedResult[key]; !expected {
t.Errorf("Unexpected key '%s' found in paymentData", key)
}
}
})
}
}
// Test_addContactAndEmailToPaymentData_scenarios
// tests addContactAndEmailToPaymentData function scenarios
func Test_addContactAndEmailToPaymentData_scenarios(t *testing.T) {
tests := []struct {
name string
paymentData map[string]interface{}
params map[string]interface{}
expectedResult map[string]interface{}
}{
{
name: "both contact and email provided",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"contact": "9876543210",
"email": "[email protected]",
},
expectedResult: map[string]interface{}{
"amount": 10000,
"contact": "9876543210",
"email": "[email protected]",
},
},
{
name: "only contact provided - email generated",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"contact": "9876543210",
},
expectedResult: map[string]interface{}{
"amount": 10000,
"contact": "9876543210",
"email": "[email protected]",
},
},
{
name: "empty contact and email - nothing added",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"contact": "",
"email": "",
},
expectedResult: map[string]interface{}{
"amount": 10000,
},
},
{
name: "contact provided but email is empty - email generated",
paymentData: map[string]interface{}{
"amount": 10000,
},
params: map[string]interface{}{
"contact": "9876543210",
"email": "",
},
expectedResult: map[string]interface{}{
"amount": 10000,
"contact": "9876543210",
"email": "[email protected]",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
paymentData := make(map[string]interface{})
for k, v := range tt.paymentData {
paymentData[k] = v
}
addContactAndEmailToPaymentData(paymentData, tt.params)
for key, expectedValue := range tt.expectedResult {
actualValue, exists := paymentData[key]
if !exists {
t.Errorf("Expected key '%s' to exist in paymentData", key)
continue
}
if actualValue != expectedValue {
t.Errorf(
"Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
)
}
}
// Check that no unexpected keys were added
for key := range paymentData {
if _, expected := tt.expectedResult[key]; !expected {
t.Errorf("Unexpected key '%s' found in paymentData", key)
}
}
})
}
}
// Test_processPaymentResult_edgeCases
// tests edge cases for processPaymentResult function
func Test_processPaymentResult_edgeCases(t *testing.T) {
tests := []struct {
name string
payment map[string]interface{}
expectedError string
shouldProcess bool
}{
{
name: "payment with OTP URL that causes sendOtp to fail",
payment: map[string]interface{}{
"razorpay_payment_id": "pay_123456789",
"next": []interface{}{
map[string]interface{}{
"action": "otp_generate",
"url": "http://invalid-url", // Invalid URL
},
},
},
expectedError: "OTP generation failed",
},
{
name: "payment with empty OTP URL",
payment: map[string]interface{}{
"razorpay_payment_id": "pay_123456789",
"next": []interface{}{
map[string]interface{}{
"action": "otp_generate",
"url": "", // Empty URL should not trigger sendOtp
},
},
},
shouldProcess: true,
},
{
name: "payment without next actions",
payment: map[string]interface{}{
"razorpay_payment_id": "pay_123456789",
},
shouldProcess: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := processPaymentResult(tt.payment)
if tt.expectedError != "" {
if err == nil {
t.Error("Expected error but got nil")
return
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf(
"Expected error to contain '%s', got '%s'", tt.expectedError, err.Error())
}
} else if tt.shouldProcess {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result == nil {
t.Error("Expected result but got nil")
} else {
// Verify the response structure
if paymentID, exists := result["razorpay_payment_id"]; !exists ||
paymentID == "" {
t.Error("Expected razorpay_payment_id in result")
}
if status, exists := result["status"]; !exists ||
status != "payment_initiated" {
t.Error("Expected status to be 'payment_initiated'")
}
}
}
})
}
}
// Test for sendOtp function - comprehensive coverage
func TestSendOtp(t *testing.T) {
t.Run("empty OTP URL", func(t *testing.T) {
err := sendOtp("")
if err == nil {
t.Error("Expected error for empty OTP URL")
}
if err.Error() != "OTP URL is empty" {
t.Errorf("Expected 'OTP URL is empty', got '%s'", err.Error())
}
})
t.Run("invalid URL format", func(t *testing.T) {
err := sendOtp("invalid-url")
if err == nil {
t.Error("Expected error for invalid URL")
}
// The URL parsing succeeds but fails on HTTPS check
if !strings.Contains(err.Error(), "OTP URL must use HTTPS") {
t.Errorf("Expected 'OTP URL must use HTTPS' error, got '%s'", err.Error())
}
})
t.Run("non-HTTPS URL", func(t *testing.T) {
err := sendOtp("http://api.razorpay.com/v1/payments/otp")
if err == nil {
t.Error("Expected error for non-HTTPS URL")
}
if err.Error() != "OTP URL must use HTTPS" {
t.Errorf("Expected 'OTP URL must use HTTPS', got '%s'", err.Error())
}
})
t.Run("non-Razorpay domain", func(t *testing.T) {
err := sendOtp("https://example.com/otp")
if err == nil {
t.Error("Expected error for non-Razorpay domain")
}
if err.Error() != "OTP URL must be from Razorpay domain" {
t.Errorf("Expected 'OTP URL must be from Razorpay domain', got '%s'",
err.Error())
}
})
t.Run("successful OTP request", func(t *testing.T) {
// Since we can't actually call external APIs in tests, we'll test the
// validation logic by testing with a URL that would fail at HTTP call stage
err := sendOtp(
"https://api.razorpay.com/v1/payments/invalid-endpoint-for-test")
if err == nil {
t.Error("Expected error for invalid endpoint")
}
// This should fail at the HTTP request stage, which is expected
if !strings.Contains(err.Error(), "OTP generation failed") {
t.Logf("Got expected error: %s", err.Error())
}
})
t.Run("HTTP request creation failure", func(t *testing.T) {
// Test with invalid characters that would cause http.NewRequest to fail
// This is difficult to trigger in practice, so we'll test URL validation
err := sendOtp("https://api.razorpay.com/v1/payments\x00/otp")
if err == nil {
t.Error("Expected error for invalid URL characters")
}
})
}
// Test for extractPaymentID function
func TestExtractPaymentID(t *testing.T) {
t.Run("payment ID exists", func(t *testing.T) {
payment := map[string]interface{}{
"razorpay_payment_id": "pay_test123",
"other_field": "value",
}
result := extractPaymentID(payment)
if result != "pay_test123" {
t.Errorf("Expected 'pay_test123', got '%s'", result)
}
})
t.Run("payment ID missing", func(t *testing.T) {
payment := map[string]interface{}{
"other_field": "value",
}
result := extractPaymentID(payment)
if result != "" {
t.Errorf("Expected empty string, got '%s'", result)
}
})
t.Run("payment ID is nil", func(t *testing.T) {
payment := map[string]interface{}{
"razorpay_payment_id": nil,
}
result := extractPaymentID(payment)
if result != "" {
t.Errorf("Expected empty string, got '%s'", result)
}
})
}
// Test for extractNextActions function
func TestExtractNextActions(t *testing.T) {
t.Run("next actions exist", func(t *testing.T) {
payment := map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://example.com",
},
map[string]interface{}{
"action": "otp",
"url": "https://otp.example.com",
},
},
}
result := extractNextActions(payment)
if len(result) != 2 {
t.Errorf("Expected 2 actions, got %d", len(result))
}
if result[0]["action"] != "redirect" {
t.Errorf("Expected first action to be 'redirect', got '%s'",
result[0]["action"])
}
})
t.Run("next field missing", func(t *testing.T) {
payment := map[string]interface{}{
"other_field": "value",
}
result := extractNextActions(payment)
if len(result) != 0 {
t.Errorf("Expected empty slice, got %d actions", len(result))
}
})
t.Run("next field is nil", func(t *testing.T) {
payment := map[string]interface{}{
"next": nil,
}
result := extractNextActions(payment)
if len(result) != 0 {
t.Errorf("Expected empty slice, got %d actions", len(result))
}
})
t.Run("next field is not a slice", func(t *testing.T) {
payment := map[string]interface{}{
"next": "invalid_type",
}
result := extractNextActions(payment)
if len(result) != 0 {
t.Errorf("Expected empty slice, got %d actions", len(result))
}
})
t.Run("next field contains non-map items", func(t *testing.T) {
payment := map[string]interface{}{
"next": []interface{}{
"invalid_item",
map[string]interface{}{
"action": "valid_action",
},
},
}
result := extractNextActions(payment)
if len(result) != 1 {
t.Errorf("Expected 1 valid action, got %d", len(result))
}
if result[0]["action"] != "valid_action" {
t.Errorf("Expected action to be 'valid_action', got '%s'",
result[0]["action"])
}
})
}
// Test for addNextStepInstructions function
func TestAddNextStepInstructions(t *testing.T) {
t.Run("add next step instructions with payment ID", func(t *testing.T) {
result := make(map[string]interface{})
paymentID := "pay_test123"
addNextStepInstructions(result, paymentID)
nextStep, exists := result["next_step"]
if !exists {
t.Error("Expected next_step to be added")
}
nextStepStr, ok := nextStep.(string)
if !ok {
t.Error("Expected next_step to be a string")
}
if !strings.Contains(nextStepStr, "resend_otp") {
t.Error("Expected instructions to contain 'resend_otp'")
}
if !strings.Contains(nextStepStr, "submit_otp") {
t.Error("Expected instructions to contain 'submit_otp'")
}
// Check next_tool
nextTool, exists := result["next_tool"]
if !exists {
t.Error("Expected next_tool to be added")
}
if nextTool != "resend_otp" {
t.Errorf("Expected next_tool to be 'resend_otp', got '%s'", nextTool)
}
// Check next_tool_params
params, exists := result["next_tool_params"]
if !exists {
t.Error("Expected next_tool_params to be added")
}
paramsMap, ok := params.(map[string]interface{})
if !ok {
t.Error("Expected next_tool_params to be a map")
}
if paramsMap["payment_id"] != paymentID {
t.Errorf("Expected payment_id to be '%s', got '%s'",
paymentID, paramsMap["payment_id"])
}
})
t.Run("empty payment ID", func(t *testing.T) {
result := make(map[string]interface{})
addNextStepInstructions(result, "")
// Should not add anything when payment ID is empty
if len(result) != 0 {
t.Error("Expected no fields to be added for empty payment ID")
}
})
}
// Test for processUPIParameters function
func TestProcessUPIParameters(t *testing.T) {
t.Run("processUPIParameters", func(t *testing.T) {
// Test with VPA
params := map[string]interface{}{
"vpa": "test@upi",
}
processUPIParameters(params)
if params["method"] != "upi" {
t.Errorf("Expected method to be 'upi', got '%s'", params["method"])
}
upi, exists := params["upi"]
if !exists {
t.Error("Expected upi field to be added")
}
upiMap, ok := upi.(map[string]interface{})
if !ok {
t.Error("Expected upi to be a map")
}
if upiMap["vpa"] != "test@upi" {
t.Errorf("Expected vpa to be 'test@upi', got '%s'", upiMap["vpa"])
}
if upiMap["flow"] != "collect" {
t.Errorf("Expected flow to be 'collect', got '%s'", upiMap["flow"])
}
})
t.Run("processUPIParameters - UPI intent", func(t *testing.T) {
// Test with UPI intent
params := map[string]interface{}{
"upi_intent": true,
}
processUPIParameters(params)
if params["method"] != "upi" {
t.Errorf("Expected method to be 'upi', got '%s'", params["method"])
}
upi, exists := params["upi"]
if !exists {
t.Error("Expected upi field to be added")
}
upiMap, ok := upi.(map[string]interface{})
if !ok {
t.Error("Expected upi to be a map")
}
if upiMap["flow"] != "intent" {
t.Errorf("Expected flow to be 'intent', got '%s'", upiMap["flow"])
}
})
t.Run("processUPIParameters - no UPI params", func(t *testing.T) {
// Test with no UPI parameters
params := map[string]interface{}{
"amount": 1000,
}
processUPIParameters(params)
// Should not modify params when no UPI parameters are present
if _, exists := params["method"]; exists {
t.Error("Expected method not to be added when no UPI params")
}
if _, exists := params["upi"]; exists {
t.Error("Expected upi not to be added when no UPI params")
}
})
}
// Test for createOrGetCustomer function
func TestCreateOrGetCustomer(t *testing.T) {
t.Run("createOrGetCustomer - no contact", func(t *testing.T) {
// Test with no contact parameter
params := map[string]interface{}{
"amount": 1000,
}
// This should return nil, nil since no contact is provided
result, err := createOrGetCustomer(nil, params)
if result != nil {
t.Error("Expected nil result when no contact provided")
}
if err != nil {
t.Errorf("Expected no error when no contact provided, got %v", err)
}
})
}
// Test for buildPaymentData function
func TestBuildPaymentData(t *testing.T) {
t.Run("buildPaymentData", func(t *testing.T) {
params := map[string]interface{}{
"amount": 1000,
"order_id": "order_test123",
}
currency := "INR"
customerId := "cust_test123"
result := buildPaymentData(params, currency, customerId)
if (*result)["amount"] != 1000 {
t.Errorf("Expected amount to be 1000, got %v", (*result)["amount"])
}
if (*result)["currency"] != "INR" {
t.Errorf("Expected currency to be 'INR', got '%s'", (*result)["currency"])
}
if (*result)["customer_id"] != customerId {
t.Errorf("Expected customer_id to be '%s', got '%s'",
customerId, (*result)["customer_id"])
}
})
t.Run("buildPaymentData - no customer ID", func(t *testing.T) {
params := map[string]interface{}{
"amount": 1000,
"order_id": "order_test123",
}
currency := "INR"
customerId := ""
result := buildPaymentData(params, currency, customerId)
if (*result)["amount"] != 1000 {
t.Errorf("Expected amount to be 1000, got %v", (*result)["amount"])
}
if (*result)["currency"] != "INR" {
t.Errorf("Expected currency to be 'INR', got '%s'", (*result)["currency"])
}
// Should not have customer_id when empty
if _, exists := (*result)["customer_id"]; exists {
t.Error("Expected no customer_id when empty string provided")
}
})
}
// Test for processPaymentResult function
func TestProcessPaymentResult(t *testing.T) {
t.Run("processPaymentResult", func(t *testing.T) {
paymentResult := map[string]interface{}{
"razorpay_payment_id": "pay_test123",
"status": "created",
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://example.com",
},
},
}
result, err := processPaymentResult(paymentResult)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result["razorpay_payment_id"] != "pay_test123" {
t.Errorf("Expected payment ID, got %v", result["razorpay_payment_id"])
}
if result["status"] != "payment_initiated" {
t.Errorf("Expected status to be 'payment_initiated', got '%s'",
result["status"])
}
})
t.Run("processPaymentResult - with error", func(t *testing.T) {
// Test with payment result that might cause an error
paymentResult := map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid payment data",
},
}
result, err := processPaymentResult(paymentResult)
// The function should handle this gracefully
if err != nil && result == nil {
t.Logf("Expected behavior - got error: %v", err)
} else if result != nil {
// If no error, result should be properly processed
if result["status"] != "payment_initiated" {
t.Errorf("Expected status to be 'payment_initiated', got '%s'",
result["status"])
}
}
})
}
// Test for extractOtpSubmitURL function
func TestExtractOtpSubmitURL(t *testing.T) {
t.Run("extractOtpSubmitURL", func(t *testing.T) {
// Test with valid response data containing OTP submit URL
responseData := map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://example.com/redirect",
},
map[string]interface{}{
"action": "otp_submit",
"url": "https://example.com/otp/submit",
},
},
}
result := extractOtpSubmitURL(responseData)
if result != "https://example.com/otp/submit" {
t.Errorf("Expected OTP submit URL, got '%s'", result)
}
})
t.Run("extractOtpSubmitURL - no OTP action", func(t *testing.T) {
responseData := map[string]interface{}{
"next": []interface{}{
map[string]interface{}{
"action": "redirect",
"url": "https://example.com/redirect",
},
},
}
result := extractOtpSubmitURL(responseData)
if result != "" {
t.Errorf("Expected empty string, got '%s'", result)
}
})
t.Run("extractOtpSubmitURL - invalid input", func(t *testing.T) {
// Test with invalid input type
result := extractOtpSubmitURL("invalid_input")
if result != "" {
t.Errorf("Expected empty string for invalid input, got '%s'", result)
}
})
t.Run("extractOtpSubmitURL - no next field", func(t *testing.T) {
responseData := map[string]interface{}{
"other_field": "value",
}
result := extractOtpSubmitURL(responseData)
if result != "" {
t.Errorf("Expected empty string when no next field, got '%s'", result)
}
})
}
// TestPayments100PercentCoverage_FetchPayment tests FetchPayment coverage
func TestPayments100PercentCoverage_FetchPayment(t *testing.T) {
// Test FetchPayment with SDK errors
t.Run("FetchPayment - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123",
Method: "GET",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid payment ID",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching payment failed",
}
runToolTest(t, testCase, FetchPayment, "Payment")
})
}
// TestPayments100PercentCoverage_FetchPaymentCardDetails tests
// FetchPaymentCardDetails coverage
func TestPayments100PercentCoverage_FetchPaymentCardDetails(t *testing.T) {
// Test FetchPaymentCardDetails with SDK errors
t.Run("FetchPaymentCardDetails - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/card",
Method: "GET",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Card details not available",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching card details failed",
}
runToolTest(t, testCase, FetchPaymentCardDetails, "PaymentCardDetails")
})
}
// TestPayments100PercentCoverage_UpdatePayment tests UpdatePayment coverage
func TestPayments100PercentCoverage_UpdatePayment(t *testing.T) {
// Test UpdatePayment with SDK errors
t.Run("UpdatePayment - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
"notes": map[string]interface{}{
"key": "value",
},
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123",
Method: "PATCH",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid notes",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "updating payment failed",
}
runToolTest(t, testCase, UpdatePayment, "Payment")
})
}
// TestPayments100PercentCoverage_CapturePayment tests CapturePayment coverage
func TestPayments100PercentCoverage_CapturePayment(t *testing.T) {
// Test CapturePayment with SDK errors
t.Run("CapturePayment - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
"amount": 1000,
"currency": "INR",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/capture",
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Payment cannot be captured",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "capturing payment failed",
}
runToolTest(t, testCase, CapturePayment, "Payment")
})
}
// TestPayments100PercentCoverage_FetchAllPayments tests
// FetchAllPayments coverage
func TestPayments100PercentCoverage_FetchAllPayments(t *testing.T) {
// Test FetchAllPayments with SDK errors
t.Run("FetchAllPayments - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"count": 10,
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments",
Method: "GET",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid request",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "fetching payments failed",
}
runToolTest(t, testCase, FetchAllPayments, "Collection")
})
}
// TestPayments100PercentCoverage_ResendOtp tests ResendOtp coverage
func TestPayments100PercentCoverage_ResendOtp(t *testing.T) {
// Test ResendOtp with SDK errors
t.Run("ResendOtp - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/otp/resend",
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Cannot resend OTP",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP resend failed",
}
runToolTest(t, testCase, ResendOtp, "ResendOtp")
})
}
// TestPayments100PercentCoverage_SubmitOtp tests SubmitOtp coverage
func TestPayments100PercentCoverage_SubmitOtp(t *testing.T) {
// Test SubmitOtp with SDK errors
t.Run("SubmitOtp - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
"otp_string": "123456",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/otp/submit",
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid OTP",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "OTP verification failed",
}
runToolTest(t, testCase, SubmitOtp, "SubmitOtp")
})
}
// TestPayments100PercentCoverage_InitiatePayment tests InitiatePayment coverage
func TestPayments100PercentCoverage_InitiatePayment(t *testing.T) {
// Test InitiatePayment with errors
t.Run("InitiatePayment - SDK error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "SDK error",
Request: map[string]interface{}{
"amount": 1000,
"currency": "INR",
"order_id": "order_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/create/json",
Method: "POST",
Response: map[string]interface{}{
"error": map[string]interface{}{
"code": "BAD_REQUEST_ERROR",
"description": "Invalid payment data",
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "initiating payment failed",
}
runToolTest(t, testCase, InitiatePayment, "InitiatePayment")
})
// Test sendOtp with HTTP error status
t.Run("sendOtp - HTTP error status", func(t *testing.T) {
server := httptest.NewTLSServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
defer server.Close()
// Replace domain to pass validation
testURL := strings.Replace(
server.URL, server.URL[8:], "api.razorpay.com/v1/payments/otp", 1)
err := sendOtp(testURL)
if err == nil {
t.Error("Expected error for HTTP error status")
}
if !strings.Contains(err.Error(), "OTP generation failed with HTTP status") {
t.Logf("Got expected error: %s", err.Error())
}
})
// More aggressive tests - hitting every error path!
t.Run("sendOtp - request creation error", func(t *testing.T) {
// Test with malformed URL that passes parsing but fails request creation
err := sendOtp("https://api.razorpay.com:99999/invalid")
if err == nil {
t.Error("Expected error for malformed URL")
}
if !strings.Contains(err.Error(), "failed to create OTP request") &&
!strings.Contains(err.Error(), "OTP generation failed") {
t.Logf("Got expected error: %s", err.Error())
}
})
// Test with extremely long URL to trigger different error paths
t.Run("sendOtp - extreme URL", func(t *testing.T) {
longPath := strings.Repeat("a", 10000)
testURL := "https://api.razorpay.com/v1/payments/" + longPath + "/otp"
err := sendOtp(testURL)
if err == nil {
t.Error("Expected error for extreme URL")
}
})
}
// contextKey is a type for context keys to avoid collisions
type contextKey string
// TestPayments100PercentCoverage_ContextErrors tests context error paths
func TestPayments100PercentCoverage_ContextErrors(t *testing.T) {
// Test getClientFromContextOrDefault error path
t.Run("SubmitOtp - client context error", func(t *testing.T) {
// Create context with invalid client
ctx := context.WithValue(
context.Background(), contextKey("invalid_key"), "invalid_value")
tool := SubmitOtp(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"payment_id": "pay_test123",
"otp_string": "123456",
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for invalid client context")
}
})
// Test sendOtp with actual HTTP client failure
t.Run("sendOtp - HTTP client failure", func(t *testing.T) {
// Test with a URL that will fail at the HTTP client level
err := sendOtp("https://api.razorpay.com:99999/invalid/path/that/will/fail")
if err == nil {
t.Error("Expected error for HTTP client failure")
}
if !strings.Contains(err.Error(), "OTP generation failed") {
t.Logf("Got expected error: %s", err.Error())
}
})
}
// TestPayments100PercentCoverage_ContextErrors2 tests more context error paths
func TestPayments100PercentCoverage_ContextErrors2(t *testing.T) {
// Test InitiatePayment - getClientFromContextOrDefault error
t.Run("InitiatePayment - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := InitiatePayment(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"amount": 1000,
"order_id": "order_test123",
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Simple working test
t.Run("InitiatePayment - basic test", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "basic test",
Request: map[string]interface{}{
"amount": 1000,
"order_id": "order_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/create/json",
Method: "POST",
Response: map[string]interface{}{
"id": "pay_test123",
"entity": "payment",
"amount": 1000,
"currency": "INR",
"status": "created",
"invalid": make(chan int), // This causes JSON marshal to fail
"created_at": 1234567890,
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "failed",
}
runToolTest(t, testCase, InitiatePayment, "Payment")
})
}
// TestPayments100PercentCoverage_ContextErrors3 tests remaining
// context error paths
func TestPayments100PercentCoverage_ContextErrors3(t *testing.T) {
// Test FetchPayment - getClientFromContextOrDefault error
t.Run("FetchPayment - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := FetchPayment(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"payment_id": "pay_test123",
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Test FetchPaymentCardDetails - getClientFromContextOrDefault error
t.Run("FetchPaymentCardDetails - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := FetchPaymentCardDetails(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"payment_id": "pay_test123",
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Test FetchPaymentCardDetails - JSON marshal error
// (using channel in response)
t.Run("FetchPaymentCardDetails - JSON marshal error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "JSON marshal error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/card",
Method: "GET",
Response: map[string]interface{}{
"id": "card_test123",
"entity": "card",
"name": "Test User",
"last4": "1234",
"network": "Visa",
"type": "credit",
"invalid": make(chan int), // This causes JSON marshal to fail
"created_at": 1234567890,
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "failed",
}
runToolTest(t, testCase, FetchPaymentCardDetails, "Card Details")
})
}
// TestPayments100PercentCoverage_ContextErrors4 tests final context error paths
func TestPayments100PercentCoverage_ContextErrors4(t *testing.T) {
// Test CapturePayment - getClientFromContextOrDefault error
t.Run("CapturePayment - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := CapturePayment(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"payment_id": "pay_test123",
"amount": 1000,
"currency": "INR",
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Test CapturePayment - JSON marshal error (using channel in response)
t.Run("CapturePayment - JSON marshal error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "JSON marshal error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
"amount": 1000,
"currency": "INR",
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123/capture",
Method: "POST",
Response: map[string]interface{}{
"id": "pay_test123",
"entity": "payment",
"amount": 1000,
"currency": "INR",
"status": "captured",
"invalid": make(chan int), // This causes JSON marshal to fail
"created_at": 1234567890,
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "failed",
}
runToolTest(t, testCase, CapturePayment, "Payment")
})
// Test UpdatePayment - getClientFromContextOrDefault error
t.Run("UpdatePayment - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := UpdatePayment(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"payment_id": "pay_test123",
"notes": map[string]interface{}{
"key": "value",
},
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Test UpdatePayment - JSON marshal error (using channel in response)
t.Run("UpdatePayment - JSON marshal error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "JSON marshal error",
Request: map[string]interface{}{
"payment_id": "pay_test123",
"notes": map[string]interface{}{
"key": "value",
},
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments/pay_test123",
Method: "PATCH",
Response: map[string]interface{}{
"id": "pay_test123",
"entity": "payment",
"amount": 1000,
"currency": "INR",
"status": "authorized",
"invalid": make(chan int), // This causes JSON marshal to fail
"notes": map[string]interface{}{
"key": "value",
},
"created_at": 1234567890,
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "failed",
}
runToolTest(t, testCase, UpdatePayment, "Payment")
})
// Test FetchAllPayments - getClientFromContextOrDefault error
t.Run("FetchAllPayments - client context error", func(t *testing.T) {
// Create context without client
ctx := context.Background()
tool := FetchAllPayments(nil, nil)
request := mcpgo.CallToolRequest{
Arguments: map[string]interface{}{
"count": 10,
},
}
result, err := tool.GetHandler()(ctx, request)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil || !result.IsError {
t.Error("Expected error result for missing client context")
}
if !strings.Contains(result.Text, "no client found in context") {
t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
}
})
// Test FetchAllPayments - JSON marshal error (using channel in response)
t.Run("FetchAllPayments - JSON marshal error", func(t *testing.T) {
testCase := RazorpayToolTestCase{
Name: "JSON marshal error",
Request: map[string]interface{}{
"count": 10,
},
MockHttpClient: func() (*http.Client, *httptest.Server) {
return mock.NewHTTPClient(
mock.Endpoint{
Path: "/v1/payments",
Method: "GET",
Response: map[string]interface{}{
"entity": "collection",
"count": 1,
"items": []interface{}{
map[string]interface{}{
"id": "pay_test123",
"invalid": make(chan int), // This causes JSON marshal to fail
"entity": "payment",
"amount": 1000,
"currency": "INR",
"status": "created",
"created_at": 1234567890,
},
},
},
},
)
},
ExpectError: true,
ExpectedErrMsg: "failed",
}
runToolTest(t, testCase, FetchAllPayments, "Collection")
})
}
```