This is page 5 of 5. Use http://codebase.md/razorpay/razorpay-mcp-server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cursor
│ └── rules
│ └── new-tool-from-docs.mdc
├── .cursorignore
├── .dockerignore
├── .github
│ ├── 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
1 | package razorpay
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/razorpay/razorpay-go/constants"
12 |
13 | "github.com/razorpay/razorpay-mcp-server/pkg/mcpgo"
14 | "github.com/razorpay/razorpay-mcp-server/pkg/razorpay/mock"
15 | )
16 |
17 | func Test_FetchPayment(t *testing.T) {
18 | fetchPaymentPathFmt := fmt.Sprintf(
19 | "/%s%s/%%s",
20 | constants.VERSION_V1,
21 | constants.PAYMENT_URL,
22 | )
23 |
24 | paymentResp := map[string]interface{}{
25 | "id": "pay_MT48CvBhIC98MQ",
26 | "amount": float64(1000),
27 | "status": "captured",
28 | }
29 |
30 | paymentNotFoundResp := map[string]interface{}{
31 | "error": map[string]interface{}{
32 | "code": "BAD_REQUEST_ERROR",
33 | "description": "payment not found",
34 | },
35 | }
36 |
37 | tests := []RazorpayToolTestCase{
38 | {
39 | Name: "successful payment fetch",
40 | Request: map[string]interface{}{
41 | "payment_id": "pay_MT48CvBhIC98MQ",
42 | },
43 | MockHttpClient: func() (*http.Client, *httptest.Server) {
44 | return mock.NewHTTPClient(
45 | mock.Endpoint{
46 | Path: fmt.Sprintf(fetchPaymentPathFmt, "pay_MT48CvBhIC98MQ"),
47 | Method: "GET",
48 | Response: paymentResp,
49 | },
50 | )
51 | },
52 | ExpectError: false,
53 | ExpectedResult: paymentResp,
54 | },
55 | {
56 | Name: "payment not found",
57 | Request: map[string]interface{}{
58 | "payment_id": "pay_invalid",
59 | },
60 | MockHttpClient: func() (*http.Client, *httptest.Server) {
61 | return mock.NewHTTPClient(
62 | mock.Endpoint{
63 | Path: fmt.Sprintf(fetchPaymentPathFmt, "pay_invalid"),
64 | Method: "GET",
65 | Response: paymentNotFoundResp,
66 | },
67 | )
68 | },
69 | ExpectError: true,
70 | ExpectedErrMsg: "fetching payment failed: payment not found",
71 | },
72 | {
73 | Name: "missing payment_id parameter",
74 | Request: map[string]interface{}{},
75 | MockHttpClient: nil, // No HTTP client needed for validation error
76 | ExpectError: true,
77 | ExpectedErrMsg: "missing required parameter: payment_id",
78 | },
79 | }
80 |
81 | for _, tc := range tests {
82 | t.Run(tc.Name, func(t *testing.T) {
83 | runToolTest(t, tc, FetchPayment, "Payment")
84 | })
85 | }
86 | }
87 |
88 | func Test_FetchPaymentCardDetails(t *testing.T) {
89 | fetchCardDetailsPathFmt := fmt.Sprintf(
90 | "/%s%s/%%s/card",
91 | constants.VERSION_V1,
92 | constants.PAYMENT_URL,
93 | )
94 |
95 | cardDetailsResp := map[string]interface{}{
96 | "id": "card_JXPULjlKqC5j0i",
97 | "entity": "card",
98 | "name": "Gaurav Kumar",
99 | "last4": "4366",
100 | "network": "Visa",
101 | "type": "credit",
102 | "issuer": "UTIB",
103 | "international": false,
104 | "emi": false,
105 | "sub_type": "consumer",
106 | "token_iin": nil,
107 | }
108 |
109 | paymentNotFoundResp := map[string]interface{}{
110 | "error": map[string]interface{}{
111 | "code": "BAD_REQUEST_ERROR",
112 | "description": "The id provided does not exist",
113 | },
114 | }
115 |
116 | tests := []RazorpayToolTestCase{
117 | {
118 | Name: "successful card details fetch",
119 | Request: map[string]interface{}{
120 | "payment_id": "pay_DtFYPi3IfUTgsL",
121 | },
122 | MockHttpClient: func() (*http.Client, *httptest.Server) {
123 | return mock.NewHTTPClient(
124 | mock.Endpoint{
125 | Path: fmt.Sprintf(fetchCardDetailsPathFmt, "pay_DtFYPi3IfUTgsL"),
126 | Method: "GET",
127 | Response: cardDetailsResp,
128 | },
129 | )
130 | },
131 | ExpectError: false,
132 | ExpectedResult: cardDetailsResp,
133 | },
134 | {
135 | Name: "payment not found",
136 | Request: map[string]interface{}{
137 | "payment_id": "pay_invalid",
138 | },
139 | MockHttpClient: func() (*http.Client, *httptest.Server) {
140 | return mock.NewHTTPClient(
141 | mock.Endpoint{
142 | Path: fmt.Sprintf(fetchCardDetailsPathFmt, "pay_invalid"),
143 | Method: "GET",
144 | Response: paymentNotFoundResp,
145 | },
146 | )
147 | },
148 | ExpectError: true,
149 | ExpectedErrMsg: "fetching card details failed: " +
150 | "The id provided does not exist",
151 | },
152 | {
153 | Name: "missing payment_id parameter",
154 | Request: map[string]interface{}{},
155 | MockHttpClient: nil, // No HTTP client needed for validation error
156 | ExpectError: true,
157 | ExpectedErrMsg: "missing required parameter: payment_id",
158 | },
159 | }
160 |
161 | for _, tc := range tests {
162 | t.Run(tc.Name, func(t *testing.T) {
163 | runToolTest(t, tc, FetchPaymentCardDetails, "Card Details")
164 | })
165 | }
166 | }
167 |
168 | func Test_CapturePayment(t *testing.T) {
169 | capturePaymentPathFmt := fmt.Sprintf(
170 | "/%s%s/%%s/capture",
171 | constants.VERSION_V1,
172 | constants.PAYMENT_URL,
173 | )
174 |
175 | successfulCaptureResp := map[string]interface{}{
176 | "id": "pay_G3P9vcIhRs3NV4",
177 | "entity": "payment",
178 | "amount": float64(1000),
179 | "currency": "INR",
180 | "status": "captured",
181 | "order_id": "order_GjCr5oKh4AVC51",
182 | "invoice_id": nil,
183 | "international": false,
184 | "method": "card",
185 | "amount_refunded": float64(0),
186 | "refund_status": nil,
187 | "captured": true,
188 | "description": "Payment for Adidas shoes",
189 | "card_id": "card_KOdY30ajbuyOYN",
190 | "bank": nil,
191 | "wallet": nil,
192 | "vpa": nil,
193 | "email": "[email protected]",
194 | "contact": "9000090000",
195 | "customer_id": "cust_K6fNE0WJZWGqtN",
196 | "token_id": "token_KOdY$DBYQOv08n",
197 | "notes": []interface{}{},
198 | "fee": float64(1),
199 | "tax": float64(0),
200 | "error_code": nil,
201 | "error_description": nil,
202 | "error_source": nil,
203 | "error_step": nil,
204 | "error_reason": nil,
205 | "acquirer_data": map[string]interface{}{
206 | "authentication_reference_number": "100222021120200000000742753928",
207 | },
208 | "created_at": float64(1605871409),
209 | }
210 |
211 | alreadyCapturedResp := map[string]interface{}{
212 | "error": map[string]interface{}{
213 | "code": "BAD_REQUEST_ERROR",
214 | "description": "This payment has already been captured",
215 | },
216 | }
217 |
218 | tests := []RazorpayToolTestCase{
219 | {
220 | Name: "successful payment capture",
221 | Request: map[string]interface{}{
222 | "payment_id": "pay_G3P9vcIhRs3NV4",
223 | "amount": float64(1000),
224 | "currency": "INR",
225 | },
226 | MockHttpClient: func() (*http.Client, *httptest.Server) {
227 | return mock.NewHTTPClient(
228 | mock.Endpoint{
229 | Path: fmt.Sprintf(capturePaymentPathFmt, "pay_G3P9vcIhRs3NV4"),
230 | Method: "POST",
231 | Response: successfulCaptureResp,
232 | },
233 | )
234 | },
235 | ExpectError: false,
236 | ExpectedResult: successfulCaptureResp,
237 | },
238 | {
239 | Name: "payment already captured",
240 | Request: map[string]interface{}{
241 | "payment_id": "pay_G3P9vcIhRs3NV4",
242 | "amount": float64(1000),
243 | "currency": "INR",
244 | },
245 | MockHttpClient: func() (*http.Client, *httptest.Server) {
246 | return mock.NewHTTPClient(
247 | mock.Endpoint{
248 | Path: fmt.Sprintf(capturePaymentPathFmt, "pay_G3P9vcIhRs3NV4"),
249 | Method: "POST",
250 | Response: alreadyCapturedResp,
251 | },
252 | )
253 | },
254 | ExpectError: true,
255 | ExpectedErrMsg: "capturing payment failed: This payment has already been " +
256 | "captured",
257 | },
258 | {
259 | Name: "missing payment_id parameter",
260 | Request: map[string]interface{}{
261 | "amount": float64(1000),
262 | "currency": "INR",
263 | },
264 | MockHttpClient: nil, // No HTTP client needed for validation error
265 | ExpectError: true,
266 | ExpectedErrMsg: "missing required parameter: payment_id",
267 | },
268 | {
269 | Name: "missing amount parameter",
270 | Request: map[string]interface{}{
271 | "payment_id": "pay_G3P9vcIhRs3NV4",
272 | "currency": "INR",
273 | },
274 | MockHttpClient: nil, // No HTTP client needed for validation error
275 | ExpectError: true,
276 | ExpectedErrMsg: "missing required parameter: amount",
277 | },
278 | {
279 | Name: "missing currency parameter",
280 | Request: map[string]interface{}{
281 | "payment_id": "pay_G3P9vcIhRs3NV4",
282 | "amount": float64(1000),
283 | },
284 | MockHttpClient: nil, // No HTTP client needed for validation error
285 | ExpectError: true,
286 | ExpectedErrMsg: "missing required parameter: currency",
287 | },
288 | {
289 | Name: "multiple validation errors",
290 | Request: map[string]interface{}{
291 | // All required parameters missing
292 | },
293 | MockHttpClient: nil, // No HTTP client needed for validation error
294 | ExpectError: true,
295 | ExpectedErrMsg: "Validation errors:\n- " +
296 | "missing required parameter: payment_id\n- " +
297 | "missing required parameter: amount\n- " +
298 | "missing required parameter: currency",
299 | },
300 | }
301 |
302 | for _, tc := range tests {
303 | t.Run(tc.Name, func(t *testing.T) {
304 | runToolTest(t, tc, CapturePayment, "Payment")
305 | })
306 | }
307 | }
308 |
309 | func Test_UpdatePayment(t *testing.T) {
310 | updatePaymentPathFmt := fmt.Sprintf(
311 | "/%s%s/%%s",
312 | constants.VERSION_V1,
313 | constants.PAYMENT_URL,
314 | )
315 |
316 | successfulUpdateResp := map[string]interface{}{
317 | "id": "pay_KbCVlLqUbb3VhA",
318 | "entity": "payment",
319 | "amount": float64(400000),
320 | "currency": "INR",
321 | "status": "authorized",
322 | "order_id": nil,
323 | "invoice_id": nil,
324 | "international": false,
325 | "method": "emi",
326 | "amount_refunded": float64(0),
327 | "refund_status": nil,
328 | "captured": false,
329 | "description": "Test Transaction",
330 | "card_id": "card_KbCVlPnxWRlOpH",
331 | "bank": "HDFC",
332 | "wallet": nil,
333 | "vpa": nil,
334 | "email": "[email protected]",
335 | "contact": "+919000090000",
336 | "notes": map[string]interface{}{
337 | "key1": "value1",
338 | "key2": "value2",
339 | },
340 | "fee": nil,
341 | "tax": nil,
342 | "error_code": nil,
343 | "error_description": nil,
344 | "error_source": nil,
345 | "error_step": nil,
346 | "error_reason": nil,
347 | "acquirer_data": map[string]interface{}{
348 | "auth_code": "205480",
349 | },
350 | "emi_plan": map[string]interface{}{
351 | "issuer": "HDFC",
352 | "type": "credit",
353 | "rate": float64(1500),
354 | "duration": float64(24),
355 | },
356 | "created_at": float64(1667398779),
357 | }
358 |
359 | paymentNotFoundResp := map[string]interface{}{
360 | "error": map[string]interface{}{
361 | "code": "BAD_REQUEST_ERROR",
362 | "description": "The id provided does not exist",
363 | },
364 | }
365 |
366 | tests := []RazorpayToolTestCase{
367 | {
368 | Name: "successful payment notes update",
369 | Request: map[string]interface{}{
370 | "payment_id": "pay_KbCVlLqUbb3VhA",
371 | "notes": map[string]interface{}{
372 | "key1": "value1",
373 | "key2": "value2",
374 | },
375 | },
376 | MockHttpClient: func() (*http.Client, *httptest.Server) {
377 | return mock.NewHTTPClient(
378 | mock.Endpoint{
379 | Path: fmt.Sprintf(updatePaymentPathFmt, "pay_KbCVlLqUbb3VhA"),
380 | Method: "PATCH",
381 | Response: successfulUpdateResp,
382 | },
383 | )
384 | },
385 | ExpectError: false,
386 | ExpectedResult: successfulUpdateResp,
387 | },
388 | {
389 | Name: "payment not found",
390 | Request: map[string]interface{}{
391 | "payment_id": "pay_invalid",
392 | "notes": map[string]interface{}{
393 | "key1": "value1",
394 | },
395 | },
396 | MockHttpClient: func() (*http.Client, *httptest.Server) {
397 | return mock.NewHTTPClient(
398 | mock.Endpoint{
399 | Path: fmt.Sprintf(updatePaymentPathFmt, "pay_invalid"),
400 | Method: "PATCH",
401 | Response: paymentNotFoundResp,
402 | },
403 | )
404 | },
405 | ExpectError: true,
406 | ExpectedErrMsg: "updating payment failed: The id provided does not exist",
407 | },
408 | {
409 | Name: "missing payment_id parameter",
410 | Request: map[string]interface{}{
411 | "notes": map[string]interface{}{
412 | "key1": "value1",
413 | },
414 | },
415 | MockHttpClient: nil, // No HTTP client needed for validation error
416 | ExpectError: true,
417 | ExpectedErrMsg: "missing required parameter: payment_id",
418 | },
419 | {
420 | Name: "missing notes parameter",
421 | Request: map[string]interface{}{
422 | "payment_id": "pay_KbCVlLqUbb3VhA",
423 | },
424 | MockHttpClient: nil, // No HTTP client needed for validation error
425 | ExpectError: true,
426 | ExpectedErrMsg: "missing required parameter: notes",
427 | },
428 | {
429 | Name: "multiple validation errors",
430 | Request: map[string]interface{}{
431 | // All required parameters missing
432 | },
433 | MockHttpClient: nil, // No HTTP client needed for validation error
434 | ExpectError: true,
435 | ExpectedErrMsg: "Validation errors:\n- " +
436 | "missing required parameter: payment_id\n- " +
437 | "missing required parameter: notes",
438 | },
439 | }
440 |
441 | for _, tc := range tests {
442 | t.Run(tc.Name, func(t *testing.T) {
443 | runToolTest(t, tc, UpdatePayment, "Payment")
444 | })
445 | }
446 | }
447 |
448 | func Test_FetchAllPayments(t *testing.T) {
449 | fetchAllPaymentsPath := fmt.Sprintf(
450 | "/%s%s",
451 | constants.VERSION_V1,
452 | constants.PAYMENT_URL,
453 | )
454 |
455 | // Sample response for successful fetch
456 | paymentsListResp := map[string]interface{}{
457 | "entity": "collection",
458 | "count": float64(2),
459 | "items": []interface{}{
460 | map[string]interface{}{
461 | "id": "pay_KbCFyQ0t9Lmi1n",
462 | "entity": "payment",
463 | "amount": float64(1000),
464 | "currency": "INR",
465 | "status": "authorized",
466 | "order_id": nil,
467 | "invoice_id": nil,
468 | "international": false,
469 | "method": "netbanking",
470 | "amount_refunded": float64(0),
471 | "refund_status": nil,
472 | "captured": false,
473 | "description": "Test Transaction",
474 | "card_id": nil,
475 | "bank": "IBKL",
476 | "wallet": nil,
477 | "vpa": nil,
478 | "email": "[email protected]",
479 | "contact": "+919000090000",
480 | "notes": map[string]interface{}{
481 | "address": "Razorpay Corporate Office",
482 | },
483 | "fee": nil,
484 | "tax": nil,
485 | "error_code": nil,
486 | "error_description": nil,
487 | "error_source": nil,
488 | "error_step": nil,
489 | "error_reason": nil,
490 | "acquirer_data": map[string]interface{}{
491 | "bank_transaction_id": "5733649",
492 | },
493 | "created_at": float64(1667397881),
494 | },
495 | map[string]interface{}{
496 | "id": "pay_KbCEDHh1IrU4RJ",
497 | "entity": "payment",
498 | "amount": float64(1000),
499 | "currency": "INR",
500 | "status": "authorized",
501 | "order_id": nil,
502 | "invoice_id": nil,
503 | "international": false,
504 | "method": "upi",
505 | "amount_refunded": float64(0),
506 | "refund_status": nil,
507 | "captured": false,
508 | "description": "Test Transaction",
509 | "card_id": nil,
510 | "bank": nil,
511 | "wallet": nil,
512 | "vpa": "gaurav.kumar@okhdfcbank",
513 | "email": "[email protected]",
514 | "contact": "+919000090000",
515 | "notes": map[string]interface{}{
516 | "address": "Razorpay Corporate Office",
517 | },
518 | "fee": nil,
519 | "tax": nil,
520 | "error_code": nil,
521 | "error_description": nil,
522 | "error_source": nil,
523 | "error_step": nil,
524 | "error_reason": nil,
525 | "acquirer_data": map[string]interface{}{
526 | "rrn": "230901495295",
527 | "upi_transaction_id": "6935B87A72C2A7BC83FA927AA264AD53",
528 | },
529 | "created_at": float64(1667397781),
530 | },
531 | },
532 | }
533 |
534 | // Error response when parameters are invalid
535 | invalidParamsResp := map[string]interface{}{
536 | "error": map[string]interface{}{
537 | "code": "BAD_REQUEST_ERROR",
538 | "description": "from must be between 946684800 and 4765046400",
539 | },
540 | }
541 |
542 | tests := []RazorpayToolTestCase{
543 | {
544 | Name: "successful payments fetch with all parameters",
545 | Request: map[string]interface{}{
546 | "from": float64(1593320020),
547 | "to": float64(1624856020),
548 | "count": float64(2),
549 | "skip": float64(1),
550 | },
551 | MockHttpClient: func() (*http.Client, *httptest.Server) {
552 | return mock.NewHTTPClient(
553 | mock.Endpoint{
554 | Path: fetchAllPaymentsPath,
555 | Method: "GET",
556 | Response: paymentsListResp,
557 | },
558 | )
559 | },
560 | ExpectError: false,
561 | ExpectedResult: paymentsListResp,
562 | },
563 | {
564 | Name: "payments fetch with invalid timestamp",
565 | Request: map[string]interface{}{
566 | "from": float64(900000000), // Invalid timestamp (too early)
567 | "to": float64(1624856020),
568 | },
569 | MockHttpClient: func() (*http.Client, *httptest.Server) {
570 | return mock.NewHTTPClient(
571 | mock.Endpoint{
572 | Path: fetchAllPaymentsPath,
573 | Method: "GET",
574 | Response: invalidParamsResp,
575 | },
576 | )
577 | },
578 | ExpectError: true,
579 | ExpectedErrMsg: "fetching payments failed: from must be between " +
580 | "946684800 and 4765046400",
581 | },
582 | {
583 | Name: "multiple validation errors with wrong types",
584 | Request: map[string]interface{}{
585 | "count": "not_a_number",
586 | "skip": "not_a_number",
587 | "from": "not_a_number",
588 | "to": "not_a_number",
589 | },
590 | MockHttpClient: nil, // No HTTP client needed for validation error
591 | ExpectError: true,
592 | ExpectedErrMsg: "Validation errors:\n- " +
593 | "invalid parameter type: count\n- " +
594 | "invalid parameter type: skip\n- " +
595 | "invalid parameter type: from\n- " +
596 | "invalid parameter type: to",
597 | },
598 | }
599 |
600 | for _, tc := range tests {
601 | t.Run(tc.Name, func(t *testing.T) {
602 | runToolTest(t, tc, FetchAllPayments, "Payments List")
603 | })
604 | }
605 | }
606 |
607 | func Test_InitiatePayment(t *testing.T) {
608 | initiatePaymentPath := fmt.Sprintf(
609 | "/%s%s/create/json",
610 | constants.VERSION_V1,
611 | constants.PAYMENT_URL,
612 | )
613 |
614 | createCustomerPath := fmt.Sprintf(
615 | "/%s%s",
616 | constants.VERSION_V1,
617 | constants.CUSTOMER_URL,
618 | )
619 |
620 | customerResp := map[string]interface{}{
621 | "id": "cust_1Aa00000000003",
622 | "entity": "customer",
623 | "name": "",
624 | "email": "",
625 | "contact": "9876543210",
626 | "gstin": nil,
627 | "notes": map[string]interface{}{},
628 | "created_at": float64(1234567890),
629 | }
630 |
631 | successPaymentWithRedirectResp := map[string]interface{}{
632 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
633 | "status": "created",
634 | "amount": float64(10000),
635 | "currency": "INR",
636 | "order_id": "order_129837127313912",
637 | "next": []interface{}{
638 | map[string]interface{}{
639 | "action": "redirect",
640 | "url": "https://api.razorpay.com/v1/payments/" +
641 | "pay_MT48CvBhIC98MQ/authenticate",
642 | },
643 | },
644 | }
645 |
646 | successPaymentWithoutNextResp := map[string]interface{}{
647 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
648 | "status": "captured",
649 | "amount": float64(10000),
650 | "currency": "INR",
651 | "order_id": "order_129837127313912",
652 | }
653 |
654 | paymentErrorResp := map[string]interface{}{
655 | "error": map[string]interface{}{
656 | "code": "BAD_REQUEST_ERROR",
657 | "description": "Invalid token",
658 | },
659 | }
660 |
661 | tests := []RazorpayToolTestCase{
662 | {
663 | Name: "successful payment initiation without next actions",
664 | Request: map[string]interface{}{
665 | "amount": 10000,
666 | "currency": "INR",
667 | "token": "token_MT48CvBhIC98MQ",
668 | "order_id": "order_129837127313912",
669 | "email": "[email protected]",
670 | "contact": "9876543210",
671 | },
672 | MockHttpClient: func() (*http.Client, *httptest.Server) {
673 | return mock.NewHTTPClient(
674 | mock.Endpoint{
675 | Path: createCustomerPath,
676 | Method: "POST",
677 | Response: customerResp,
678 | },
679 | mock.Endpoint{
680 | Path: initiatePaymentPath,
681 | Method: "POST",
682 | Response: successPaymentWithoutNextResp,
683 | },
684 | )
685 | },
686 | ExpectError: false,
687 | ExpectedResult: map[string]interface{}{
688 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
689 | "payment_details": successPaymentWithoutNextResp,
690 | "status": "payment_initiated",
691 | "message": "Payment initiated successfully using " +
692 | "S2S JSON v1 flow",
693 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
694 | "'submit_otp' to proceed to enter OTP if " +
695 | "OTP authentication is required.",
696 | "next_tool": "resend_otp",
697 | "next_tool_params": map[string]interface{}{
698 | "payment_id": "pay_MT48CvBhIC98MQ",
699 | },
700 | },
701 | },
702 | {
703 | Name: "successful payment initiation with redirect",
704 | Request: map[string]interface{}{
705 | "amount": 10000,
706 | "token": "token_MT48CvBhIC98MQ",
707 | "order_id": "order_129837127313912",
708 | },
709 | MockHttpClient: func() (*http.Client, *httptest.Server) {
710 | return mock.NewHTTPClient(
711 | mock.Endpoint{
712 | Path: initiatePaymentPath,
713 | Method: "POST",
714 | Response: successPaymentWithRedirectResp,
715 | },
716 | )
717 | },
718 | ExpectError: false,
719 | ExpectedResult: map[string]interface{}{
720 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
721 | "payment_details": successPaymentWithRedirectResp,
722 | "status": "payment_initiated",
723 | "message": "Payment initiated. Redirect authentication is available. " +
724 | "Use the redirect URL provided in available_actions.",
725 | "available_actions": []interface{}{
726 | map[string]interface{}{
727 | "action": "redirect",
728 | "url": "https://api.razorpay.com/v1/payments/" +
729 | "pay_MT48CvBhIC98MQ/authenticate",
730 | },
731 | },
732 | },
733 | },
734 | {
735 | Name: "successful payment initiation with contact only",
736 | Request: map[string]interface{}{
737 | "amount": 10000,
738 | "token": "token_MT48CvBhIC98MQ",
739 | "order_id": "order_129837127313912",
740 | "contact": "9876543210",
741 | },
742 | MockHttpClient: func() (*http.Client, *httptest.Server) {
743 | return mock.NewHTTPClient(
744 | mock.Endpoint{
745 | Path: createCustomerPath,
746 | Method: "POST",
747 | Response: customerResp,
748 | },
749 | mock.Endpoint{
750 | Path: initiatePaymentPath,
751 | Method: "POST",
752 | Response: successPaymentWithoutNextResp,
753 | },
754 | )
755 | },
756 | ExpectError: false,
757 | ExpectedResult: map[string]interface{}{
758 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
759 | "payment_details": successPaymentWithoutNextResp,
760 | "status": "payment_initiated",
761 | "message": "Payment initiated successfully using " +
762 | "S2S JSON v1 flow",
763 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
764 | "'submit_otp' to proceed to enter OTP if " +
765 | "OTP authentication is required.",
766 | "next_tool": "resend_otp",
767 | "next_tool_params": map[string]interface{}{
768 | "payment_id": "pay_MT48CvBhIC98MQ",
769 | },
770 | },
771 | },
772 | {
773 | Name: "payment initiation with API error",
774 | Request: map[string]interface{}{
775 | "amount": 10000,
776 | "token": "token_invalid",
777 | "order_id": "order_129837127313912",
778 | },
779 | MockHttpClient: func() (*http.Client, *httptest.Server) {
780 | return mock.NewHTTPClient(
781 | mock.Endpoint{
782 | Path: initiatePaymentPath,
783 | Method: "POST",
784 | Response: paymentErrorResp,
785 | },
786 | )
787 | },
788 | ExpectError: true,
789 | ExpectedErrMsg: "initiating payment failed:",
790 | },
791 | {
792 | Name: "missing required amount parameter",
793 | Request: map[string]interface{}{
794 | "token": "token_MT48CvBhIC98MQ",
795 | "order_id": "order_129837127313912",
796 | },
797 | MockHttpClient: nil,
798 | ExpectError: true,
799 | ExpectedErrMsg: "missing required parameter: amount",
800 | },
801 | {
802 | Name: "missing required order_id parameter",
803 | Request: map[string]interface{}{
804 | "amount": 10000,
805 | "token": "token_MT48CvBhIC98MQ",
806 | },
807 | MockHttpClient: nil,
808 | ExpectError: true,
809 | ExpectedErrMsg: "missing required parameter: order_id",
810 | },
811 | {
812 | Name: "invalid amount parameter type",
813 | Request: map[string]interface{}{
814 | "amount": "not_a_number",
815 | "token": "token_MT48CvBhIC98MQ",
816 | "order_id": "order_129837127313912",
817 | },
818 | MockHttpClient: nil,
819 | ExpectError: true,
820 | ExpectedErrMsg: "invalid parameter type: amount",
821 | },
822 | {
823 | Name: "multiple validation errors",
824 | Request: map[string]interface{}{
825 | "amount": "not_a_number",
826 | "token": 123,
827 | "email": 456,
828 | },
829 | MockHttpClient: nil,
830 | ExpectError: true,
831 | ExpectedErrMsg: "Validation errors:\n- invalid parameter type: amount\n- " +
832 | "invalid parameter type: token\n- " +
833 | "missing required parameter: order_id\n- " +
834 | "invalid parameter type: email",
835 | },
836 | {
837 | Name: "successful UPI collect flow payment initiation",
838 | Request: map[string]interface{}{
839 | "amount": 10000,
840 | "currency": "INR",
841 | "order_id": "order_129837127313912",
842 | "email": "[email protected]",
843 | "contact": "9876543210",
844 | "customer_id": "cust_RGCgP2osfPKFq2",
845 | "save": true,
846 | "vpa": "9876543210@ptsbi",
847 | },
848 | MockHttpClient: func() (*http.Client, *httptest.Server) {
849 | successUpiCollectResp := map[string]interface{}{
850 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
851 | "status": "created",
852 | "amount": float64(10000),
853 | "currency": "INR",
854 | "order_id": "order_129837127313912",
855 | "method": "upi",
856 | "next": []interface{}{
857 | map[string]interface{}{
858 | "action": "upi_collect",
859 | "url": "https://api.razorpay.com/v1/payments/" +
860 | "pay_MT48CvBhIC98MQ/authenticate",
861 | },
862 | },
863 | }
864 | return mock.NewHTTPClient(
865 | mock.Endpoint{
866 | Path: createCustomerPath,
867 | Method: "POST",
868 | Response: customerResp,
869 | },
870 | mock.Endpoint{
871 | Path: initiatePaymentPath,
872 | Method: "POST",
873 | Response: successUpiCollectResp,
874 | },
875 | )
876 | },
877 | ExpectError: false,
878 | ExpectedResult: map[string]interface{}{
879 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
880 | "payment_details": map[string]interface{}{
881 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
882 | "status": "created",
883 | "amount": float64(10000),
884 | "currency": "INR",
885 | "order_id": "order_129837127313912",
886 | "method": "upi",
887 | "next": []interface{}{
888 | map[string]interface{}{
889 | "action": "upi_collect",
890 | "url": "https://api.razorpay.com/v1/payments/" +
891 | "pay_MT48CvBhIC98MQ/authenticate",
892 | },
893 | },
894 | },
895 | "status": "payment_initiated",
896 | "message": "Payment initiated. Available actions: [upi_collect]",
897 | "available_actions": []interface{}{
898 | map[string]interface{}{
899 | "action": "upi_collect",
900 | "url": "https://api.razorpay.com/v1/payments/" +
901 | "pay_MT48CvBhIC98MQ/authenticate",
902 | },
903 | },
904 | },
905 | },
906 | {
907 | Name: "successful UPI collect flow without token",
908 | Request: map[string]interface{}{
909 | "amount": 10000,
910 | "order_id": "order_129837127313912",
911 | "contact": "9876543210",
912 | "vpa": "9876543210@ptsbi",
913 | },
914 | MockHttpClient: func() (*http.Client, *httptest.Server) {
915 | successUpiCollectResp := map[string]interface{}{
916 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
917 | "status": "created",
918 | "amount": float64(10000),
919 | "currency": "INR",
920 | "order_id": "order_129837127313912",
921 | "method": "upi",
922 | }
923 | return mock.NewHTTPClient(
924 | mock.Endpoint{
925 | Path: createCustomerPath,
926 | Method: "POST",
927 | Response: customerResp,
928 | },
929 | mock.Endpoint{
930 | Path: initiatePaymentPath,
931 | Method: "POST",
932 | Response: successUpiCollectResp,
933 | },
934 | )
935 | },
936 | ExpectError: false,
937 | ExpectedResult: map[string]interface{}{
938 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
939 | "payment_details": map[string]interface{}{
940 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
941 | "status": "created",
942 | "amount": float64(10000),
943 | "currency": "INR",
944 | "order_id": "order_129837127313912",
945 | "method": "upi",
946 | },
947 | "status": "payment_initiated",
948 | "message": "Payment initiated successfully using " +
949 | "S2S JSON v1 flow",
950 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
951 | "'submit_otp' to proceed to enter OTP if " +
952 | "OTP authentication is required.",
953 | "next_tool": "resend_otp",
954 | "next_tool_params": map[string]interface{}{
955 | "payment_id": "pay_MT48CvBhIC98MQ",
956 | },
957 | },
958 | },
959 | {
960 | Name: "UPI collect flow with all optional parameters",
961 | Request: map[string]interface{}{
962 | "amount": 10000,
963 | "currency": "INR",
964 | "order_id": "order_129837127313912",
965 | "email": "[email protected]",
966 | "contact": "9876543210",
967 | "customer_id": "cust_RGCgP2osfPKFq2",
968 | "save": false,
969 | "vpa": "test@paytm",
970 | },
971 | MockHttpClient: func() (*http.Client, *httptest.Server) {
972 | successUpiCollectResp := map[string]interface{}{
973 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
974 | "status": "created",
975 | "amount": float64(10000),
976 | "currency": "INR",
977 | "order_id": "order_129837127313912",
978 | "method": "upi",
979 | }
980 | return mock.NewHTTPClient(
981 | mock.Endpoint{
982 | Path: createCustomerPath,
983 | Method: "POST",
984 | Response: customerResp,
985 | },
986 | mock.Endpoint{
987 | Path: initiatePaymentPath,
988 | Method: "POST",
989 | Response: successUpiCollectResp,
990 | },
991 | )
992 | },
993 | ExpectError: false,
994 | ExpectedResult: map[string]interface{}{
995 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
996 | "payment_details": map[string]interface{}{
997 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
998 | "status": "created",
999 | "amount": float64(10000),
1000 | "currency": "INR",
1001 | "order_id": "order_129837127313912",
1002 | "method": "upi",
1003 | },
1004 | "status": "payment_initiated",
1005 | "message": "Payment initiated successfully using " +
1006 | "S2S JSON v1 flow",
1007 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
1008 | "'submit_otp' to proceed to enter OTP if " +
1009 | "OTP authentication is required.",
1010 | "next_tool": "resend_otp",
1011 | "next_tool_params": map[string]interface{}{
1012 | "payment_id": "pay_MT48CvBhIC98MQ",
1013 | },
1014 | },
1015 | },
1016 | {
1017 | Name: "invalid save parameter type",
1018 | Request: map[string]interface{}{
1019 | "amount": 10000,
1020 | "order_id": "order_129837127313912",
1021 | "save": "invalid_string_instead_of_bool",
1022 | },
1023 | MockHttpClient: nil,
1024 | ExpectError: true,
1025 | ExpectedErrMsg: "invalid parameter type: save",
1026 | },
1027 | {
1028 | Name: "invalid customer_id parameter type",
1029 | Request: map[string]interface{}{
1030 | "amount": 10000,
1031 | "order_id": "order_129837127313912",
1032 | "customer_id": 123,
1033 | },
1034 | MockHttpClient: nil,
1035 | ExpectError: true,
1036 | ExpectedErrMsg: "invalid parameter type: customer_id",
1037 | },
1038 | {
1039 | Name: "successful UPI intent flow payment initiation",
1040 | Request: map[string]interface{}{
1041 | "amount": 12000,
1042 | "currency": "INR",
1043 | "order_id": "order_INTENT123",
1044 | "email": "[email protected]",
1045 | "contact": "9876543210",
1046 | "upi_intent": true,
1047 | },
1048 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1049 | successUpiIntentResp := map[string]interface{}{
1050 | "razorpay_payment_id": "pay_INTENT123",
1051 | "status": "created",
1052 | "amount": float64(12000),
1053 | "currency": "INR",
1054 | "order_id": "order_INTENT123",
1055 | "method": "upi",
1056 | "upi": map[string]interface{}{
1057 | "flow": "intent",
1058 | },
1059 | "next": []interface{}{
1060 | map[string]interface{}{
1061 | "action": "upi_intent",
1062 | "url": "https://api.razorpay.com/v1/payments/" +
1063 | "pay_INTENT123/upi_intent",
1064 | },
1065 | },
1066 | }
1067 | return mock.NewHTTPClient(
1068 | mock.Endpoint{
1069 | Path: createCustomerPath,
1070 | Method: "POST",
1071 | Response: customerResp,
1072 | },
1073 | mock.Endpoint{
1074 | Path: initiatePaymentPath,
1075 | Method: "POST",
1076 | Response: successUpiIntentResp,
1077 | },
1078 | )
1079 | },
1080 | ExpectError: false,
1081 | ExpectedResult: map[string]interface{}{
1082 | "razorpay_payment_id": "pay_INTENT123",
1083 | "payment_details": map[string]interface{}{
1084 | "razorpay_payment_id": "pay_INTENT123",
1085 | "status": "created",
1086 | "amount": float64(12000),
1087 | "currency": "INR",
1088 | "order_id": "order_INTENT123",
1089 | "method": "upi",
1090 | "upi": map[string]interface{}{
1091 | "flow": "intent",
1092 | },
1093 | "next": []interface{}{
1094 | map[string]interface{}{
1095 | "action": "upi_intent",
1096 | "url": "https://api.razorpay.com/v1/payments/" +
1097 | "pay_INTENT123/upi_intent",
1098 | },
1099 | },
1100 | },
1101 | "status": "payment_initiated",
1102 | "message": "Payment initiated. Available actions: [upi_intent]",
1103 | "available_actions": []interface{}{
1104 | map[string]interface{}{
1105 | "action": "upi_intent",
1106 | "url": "https://api.razorpay.com/v1/payments/" +
1107 | "pay_INTENT123/upi_intent",
1108 | },
1109 | },
1110 | },
1111 | },
1112 | {
1113 | Name: "invalid upi_intent parameter type",
1114 | Request: map[string]interface{}{
1115 | "amount": 10000,
1116 | "order_id": "order_129837127313912",
1117 | "upi_intent": "invalid_string",
1118 | },
1119 | MockHttpClient: nil,
1120 | ExpectError: true,
1121 | ExpectedErrMsg: "invalid parameter type: upi_intent",
1122 | },
1123 | {
1124 | Name: "successful payment initiation with force_terminal_id " +
1125 | "for single block multiple debit",
1126 | Request: map[string]interface{}{
1127 | "amount": 10000,
1128 | "currency": "INR",
1129 | "order_id": "order_129837127313912",
1130 | "email": "[email protected]",
1131 | "contact": "9876543210",
1132 | "customer_id": "cust_RGCgP2osfPKFq2",
1133 | "recurring": true,
1134 | "force_terminal_id": "term_ABCD1234256732",
1135 | },
1136 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1137 | successPaymentWithTerminalResp := map[string]interface{}{
1138 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1139 | "status": "created",
1140 | "amount": float64(10000),
1141 | "currency": "INR",
1142 | "order_id": "order_129837127313912",
1143 | "method": "upi",
1144 | "force_terminal_id": "term_ABCD1234256732",
1145 | }
1146 | return mock.NewHTTPClient(
1147 | mock.Endpoint{
1148 | Path: initiatePaymentPath,
1149 | Method: "POST",
1150 | Response: successPaymentWithTerminalResp,
1151 | },
1152 | )
1153 | },
1154 | ExpectError: false,
1155 | ExpectedResult: map[string]interface{}{
1156 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1157 | "payment_details": map[string]interface{}{
1158 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1159 | "status": "created",
1160 | "amount": float64(10000),
1161 | "currency": "INR",
1162 | "order_id": "order_129837127313912",
1163 | "method": "upi",
1164 | "force_terminal_id": "term_ABCD1234256732",
1165 | },
1166 | "status": "payment_initiated",
1167 | "message": "Payment initiated successfully using S2S JSON v1 flow",
1168 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
1169 | "'submit_otp' to proceed to enter OTP if " +
1170 | "OTP authentication is required.",
1171 | "next_tool": "resend_otp",
1172 | "next_tool_params": map[string]interface{}{
1173 | "payment_id": "pay_MT48CvBhIC98MQ",
1174 | },
1175 | },
1176 | },
1177 | {
1178 | Name: "invalid force_terminal_id parameter type",
1179 | Request: map[string]interface{}{
1180 | "amount": 10000,
1181 | "order_id": "order_129837127313912",
1182 | "force_terminal_id": 123,
1183 | },
1184 | MockHttpClient: nil,
1185 | ExpectError: true,
1186 | ExpectedErrMsg: "invalid parameter type: force_terminal_id",
1187 | },
1188 | }
1189 |
1190 | for _, tc := range tests {
1191 | t.Run(tc.Name, func(t *testing.T) {
1192 | runToolTest(t, tc, InitiatePayment, "Payment Initiation")
1193 | })
1194 | }
1195 | }
1196 |
1197 | func Test_SubmitOtp(t *testing.T) {
1198 | submitOtpPathFmt := fmt.Sprintf(
1199 | "/%s%s/%%s/otp/submit",
1200 | constants.VERSION_V1,
1201 | constants.PAYMENT_URL,
1202 | )
1203 |
1204 | successOtpSubmitResp := map[string]interface{}{
1205 | "id": "pay_MT48CvBhIC98MQ",
1206 | "entity": "payment",
1207 | "amount": float64(10000),
1208 | "currency": "INR",
1209 | "status": "authorized",
1210 | "order_id": "order_129837127313912",
1211 | "description": "Test payment",
1212 | "method": "card",
1213 | "amount_refunded": float64(0),
1214 | "refund_status": nil,
1215 | "captured": false,
1216 | "email": "[email protected]",
1217 | "contact": "9876543210",
1218 | "fee": float64(236),
1219 | "tax": float64(36),
1220 | "error_code": nil,
1221 | "error_description": nil,
1222 | "created_at": float64(1234567890),
1223 | }
1224 |
1225 | otpVerificationFailedResp := map[string]interface{}{
1226 | "error": map[string]interface{}{
1227 | "code": "BAD_REQUEST_ERROR",
1228 | "description": "Invalid OTP provided",
1229 | "field": "otp",
1230 | },
1231 | }
1232 |
1233 | paymentNotFoundResp := map[string]interface{}{
1234 | "error": map[string]interface{}{
1235 | "code": "BAD_REQUEST_ERROR",
1236 | "description": "Payment not found",
1237 | },
1238 | }
1239 |
1240 | tests := []RazorpayToolTestCase{
1241 | {
1242 | Name: "successful OTP submission",
1243 | Request: map[string]interface{}{
1244 | "payment_id": "pay_MT48CvBhIC98MQ",
1245 | "otp_string": "123456",
1246 | },
1247 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1248 | return mock.NewHTTPClient(
1249 | mock.Endpoint{
1250 | Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
1251 | Method: "POST",
1252 | Response: successOtpSubmitResp,
1253 | },
1254 | )
1255 | },
1256 | ExpectError: false,
1257 | ExpectedResult: map[string]interface{}{
1258 | "payment_id": "pay_MT48CvBhIC98MQ",
1259 | "status": "success",
1260 | "message": "OTP verified successfully.",
1261 | "response_data": successOtpSubmitResp,
1262 | },
1263 | },
1264 | {
1265 | Name: "OTP verification failed - invalid OTP",
1266 | Request: map[string]interface{}{
1267 | "payment_id": "pay_MT48CvBhIC98MQ",
1268 | "otp_string": "000000",
1269 | },
1270 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1271 | return mock.NewHTTPClient(
1272 | mock.Endpoint{
1273 | Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
1274 | Method: "POST",
1275 | Response: otpVerificationFailedResp,
1276 | },
1277 | )
1278 | },
1279 | ExpectError: true,
1280 | ExpectedErrMsg: "OTP verification failed: Invalid OTP provided",
1281 | },
1282 | {
1283 | Name: "payment not found",
1284 | Request: map[string]interface{}{
1285 | "payment_id": "pay_invalid",
1286 | "otp_string": "123456",
1287 | },
1288 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1289 | return mock.NewHTTPClient(
1290 | mock.Endpoint{
1291 | Path: fmt.Sprintf(submitOtpPathFmt, "pay_invalid"),
1292 | Method: "POST",
1293 | Response: paymentNotFoundResp,
1294 | },
1295 | )
1296 | },
1297 | ExpectError: true,
1298 | ExpectedErrMsg: "OTP verification failed: Payment not found",
1299 | },
1300 | {
1301 | Name: "missing payment_id parameter",
1302 | Request: map[string]interface{}{
1303 | "otp_string": "123456",
1304 | },
1305 | MockHttpClient: nil, // No HTTP client needed for validation error
1306 | ExpectError: true,
1307 | ExpectedErrMsg: "missing required parameter: payment_id",
1308 | },
1309 | {
1310 | Name: "missing otp_string parameter",
1311 | Request: map[string]interface{}{
1312 | "payment_id": "pay_MT48CvBhIC98MQ",
1313 | },
1314 | MockHttpClient: nil, // No HTTP client needed for validation error
1315 | ExpectError: true,
1316 | ExpectedErrMsg: "missing required parameter: otp_string",
1317 | },
1318 | {
1319 | Name: "missing both required parameters",
1320 | Request: map[string]interface{}{},
1321 | MockHttpClient: nil, // No HTTP client needed for validation error
1322 | ExpectError: true,
1323 | ExpectedErrMsg: "missing required parameter: otp_string",
1324 | },
1325 | {
1326 | Name: "empty otp_string",
1327 | Request: map[string]interface{}{
1328 | "payment_id": "pay_MT48CvBhIC98MQ",
1329 | "otp_string": "",
1330 | },
1331 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1332 | return mock.NewHTTPClient(
1333 | mock.Endpoint{
1334 | Path: fmt.Sprintf(submitOtpPathFmt, "pay_MT48CvBhIC98MQ"),
1335 | Method: "POST",
1336 | Response: map[string]interface{}{
1337 | "error": map[string]interface{}{
1338 | "code": "BAD_REQUEST_ERROR",
1339 | "description": "Authentication failed",
1340 | },
1341 | },
1342 | },
1343 | )
1344 | },
1345 | ExpectError: true,
1346 | ExpectedErrMsg: "OTP verification failed: Authentication failed",
1347 | },
1348 | {
1349 | Name: "empty payment_id",
1350 | Request: map[string]interface{}{
1351 | "payment_id": "",
1352 | "otp_string": "123456",
1353 | },
1354 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1355 | return mock.NewHTTPClient(
1356 | mock.Endpoint{
1357 | Path: fmt.Sprintf(submitOtpPathFmt, ""),
1358 | Method: "POST",
1359 | Response: map[string]interface{}{
1360 | "error": map[string]interface{}{
1361 | "code": "BAD_REQUEST_ERROR",
1362 | "description": "",
1363 | },
1364 | },
1365 | },
1366 | )
1367 | },
1368 | ExpectError: true,
1369 | ExpectedErrMsg: "OTP verification failed:",
1370 | },
1371 | }
1372 |
1373 | for _, tc := range tests {
1374 | t.Run(tc.Name, func(t *testing.T) {
1375 | runToolTest(t, tc, SubmitOtp, "OTP Submission")
1376 | })
1377 | }
1378 | }
1379 |
1380 | func Test_InitiatePaymentWithVPA(t *testing.T) {
1381 | initiatePaymentPath := fmt.Sprintf(
1382 | "/%s%s/create/json",
1383 | constants.VERSION_V1,
1384 | constants.PAYMENT_URL,
1385 | )
1386 |
1387 | createCustomerPath := fmt.Sprintf(
1388 | "/%s%s",
1389 | constants.VERSION_V1,
1390 | constants.CUSTOMER_URL,
1391 | )
1392 |
1393 | customerResp := map[string]interface{}{
1394 | "id": "cust_1Aa00000000003",
1395 | "entity": "customer",
1396 | "name": "",
1397 | "email": "",
1398 | "contact": "9876543210",
1399 | "gstin": nil,
1400 | "notes": map[string]interface{}{},
1401 | "created_at": float64(1234567890),
1402 | }
1403 |
1404 | testCases := []RazorpayToolTestCase{
1405 | {
1406 | Name: "successful UPI payment with VPA parameter",
1407 | Request: map[string]interface{}{
1408 | "amount": 10000,
1409 | "order_id": "order_129837127313912",
1410 | "vpa": "9876543210@ptsbi",
1411 | "email": "[email protected]",
1412 | "contact": "9876543210",
1413 | },
1414 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1415 | successUpiVpaResp := map[string]interface{}{
1416 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1417 | "status": "created",
1418 | "amount": float64(10000),
1419 | "currency": "INR",
1420 | "order_id": "order_129837127313912",
1421 | "method": "upi",
1422 | "email": "[email protected]",
1423 | "contact": "9876543210",
1424 | "upi_transaction_id": nil,
1425 | "upi": map[string]interface{}{
1426 | "flow": "collect",
1427 | "expiry_time": "6",
1428 | "vpa": "9876543210@ptsbi",
1429 | },
1430 | "next": []interface{}{
1431 | map[string]interface{}{
1432 | "action": "upi_collect",
1433 | "url": "https://api.razorpay.com/v1/payments/" +
1434 | "pay_MT48CvBhIC98MQ/otp_generate",
1435 | },
1436 | },
1437 | }
1438 | return mock.NewHTTPClient(
1439 | mock.Endpoint{
1440 | Path: createCustomerPath,
1441 | Method: "POST",
1442 | Response: customerResp,
1443 | },
1444 | mock.Endpoint{
1445 | Path: initiatePaymentPath,
1446 | Method: "POST",
1447 | Response: successUpiVpaResp,
1448 | },
1449 | )
1450 | },
1451 | ExpectError: false,
1452 | ExpectedResult: map[string]interface{}{
1453 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1454 | "payment_details": map[string]interface{}{
1455 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1456 | "status": "created",
1457 | "amount": float64(10000),
1458 | "currency": "INR",
1459 | "order_id": "order_129837127313912",
1460 | "method": "upi",
1461 | "email": "[email protected]",
1462 | "contact": "9876543210",
1463 | "upi_transaction_id": nil,
1464 | "upi": map[string]interface{}{
1465 | "flow": "collect",
1466 | "expiry_time": "6",
1467 | "vpa": "9876543210@ptsbi",
1468 | },
1469 | "next": []interface{}{
1470 | map[string]interface{}{
1471 | "action": "upi_collect",
1472 | "url": "https://api.razorpay.com/v1/payments/" +
1473 | "pay_MT48CvBhIC98MQ/otp_generate",
1474 | },
1475 | },
1476 | },
1477 | "status": "payment_initiated",
1478 | "message": "Payment initiated. Available actions: [upi_collect]",
1479 | "available_actions": []interface{}{
1480 | map[string]interface{}{
1481 | "action": "upi_collect",
1482 | "url": "https://api.razorpay.com/v1/payments/" +
1483 | "pay_MT48CvBhIC98MQ/otp_generate",
1484 | },
1485 | },
1486 | },
1487 | },
1488 | {
1489 | Name: "UPI payment with VPA and custom currency",
1490 | Request: map[string]interface{}{
1491 | "amount": 20000,
1492 | "currency": "INR",
1493 | "order_id": "order_ABC123XYZ456",
1494 | "vpa": "test@upi",
1495 | },
1496 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1497 | successUpiVpaResp := map[string]interface{}{
1498 | "razorpay_payment_id": "pay_ABC123XYZ456",
1499 | "status": "created",
1500 | "amount": float64(20000),
1501 | "currency": "INR",
1502 | "order_id": "order_ABC123XYZ456",
1503 | "method": "upi",
1504 | "upi": map[string]interface{}{
1505 | "flow": "collect",
1506 | "expiry_time": "6",
1507 | "vpa": "test@upi",
1508 | },
1509 | }
1510 | return mock.NewHTTPClient(
1511 | mock.Endpoint{
1512 | Path: initiatePaymentPath,
1513 | Method: "POST",
1514 | Response: successUpiVpaResp,
1515 | },
1516 | )
1517 | },
1518 | ExpectError: false,
1519 | ExpectedResult: map[string]interface{}{
1520 | "razorpay_payment_id": "pay_ABC123XYZ456",
1521 | "payment_details": map[string]interface{}{
1522 | "razorpay_payment_id": "pay_ABC123XYZ456",
1523 | "status": "created",
1524 | "amount": float64(20000),
1525 | "currency": "INR",
1526 | "order_id": "order_ABC123XYZ456",
1527 | "method": "upi",
1528 | "upi": map[string]interface{}{
1529 | "flow": "collect",
1530 | "expiry_time": "6",
1531 | "vpa": "test@upi",
1532 | },
1533 | },
1534 | "status": "payment_initiated",
1535 | "message": "Payment initiated successfully using " +
1536 | "S2S JSON v1 flow",
1537 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
1538 | "'submit_otp' to proceed to enter OTP if " +
1539 | "OTP authentication is required.",
1540 | "next_tool": "resend_otp",
1541 | "next_tool_params": map[string]interface{}{
1542 | "payment_id": "pay_ABC123XYZ456",
1543 | },
1544 | },
1545 | },
1546 | {
1547 | Name: "missing VPA parameter value",
1548 | Request: map[string]interface{}{
1549 | "amount": 10000,
1550 | "order_id": "order_129837127313912",
1551 | "vpa": "",
1552 | },
1553 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1554 | successRegularResp := map[string]interface{}{
1555 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1556 | "status": "created",
1557 | "amount": float64(10000),
1558 | "currency": "INR",
1559 | "order_id": "order_129837127313912",
1560 | }
1561 | return mock.NewHTTPClient(
1562 | mock.Endpoint{
1563 | Path: initiatePaymentPath,
1564 | Method: "POST",
1565 | Response: successRegularResp,
1566 | },
1567 | )
1568 | },
1569 | ExpectError: false, // Empty VPA should not trigger UPI logic
1570 | ExpectedResult: map[string]interface{}{
1571 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1572 | "payment_details": map[string]interface{}{
1573 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1574 | "status": "created",
1575 | "amount": float64(10000),
1576 | "currency": "INR",
1577 | "order_id": "order_129837127313912",
1578 | },
1579 | "status": "payment_initiated",
1580 | "message": "Payment initiated successfully using " +
1581 | "S2S JSON v1 flow",
1582 | "next_step": "Use 'resend_otp' to regenerate OTP or " +
1583 | "'submit_otp' to proceed to enter OTP if " +
1584 | "OTP authentication is required.",
1585 | "next_tool": "resend_otp",
1586 | "next_tool_params": map[string]interface{}{
1587 | "payment_id": "pay_MT48CvBhIC98MQ",
1588 | },
1589 | },
1590 | },
1591 | {
1592 | Name: "VPA parameter automatically sets UPI method",
1593 | Request: map[string]interface{}{
1594 | "amount": 15000,
1595 | "order_id": "order_OVERRIDE123",
1596 | "vpa": "new@upi",
1597 | },
1598 | MockHttpClient: func() (*http.Client, *httptest.Server) {
1599 | successUpiOverrideResp := map[string]interface{}{
1600 | "razorpay_payment_id": "pay_OVERRIDE123",
1601 | "status": "created",
1602 | "amount": float64(15000),
1603 | "currency": "INR",
1604 | "order_id": "order_OVERRIDE123",
1605 | "method": "upi", // Should be set to UPI by VPA
1606 | "upi": map[string]interface{}{
1607 | "flow": "collect", // Default flow
1608 | "expiry_time": "6", // Default expiry
1609 | "vpa": "new@upi", // VPA from parameter
1610 | },
1611 | "next": []interface{}{
1612 | map[string]interface{}{
1613 | "action": "upi_collect",
1614 | "url": "https://api.razorpay.com/v1/payments/" +
1615 | "pay_OVERRIDE123/otp_generate",
1616 | },
1617 | },
1618 | }
1619 | return mock.NewHTTPClient(
1620 | mock.Endpoint{
1621 | Path: initiatePaymentPath,
1622 | Method: "POST",
1623 | Response: successUpiOverrideResp,
1624 | },
1625 | )
1626 | },
1627 | ExpectError: false,
1628 | ExpectedResult: map[string]interface{}{
1629 | "razorpay_payment_id": "pay_OVERRIDE123",
1630 | "payment_details": map[string]interface{}{
1631 | "razorpay_payment_id": "pay_OVERRIDE123",
1632 | "status": "created",
1633 | "amount": float64(15000),
1634 | "currency": "INR",
1635 | "order_id": "order_OVERRIDE123",
1636 | "method": "upi",
1637 | "upi": map[string]interface{}{
1638 | "flow": "collect",
1639 | "expiry_time": "6",
1640 | "vpa": "new@upi",
1641 | },
1642 | "next": []interface{}{
1643 | map[string]interface{}{
1644 | "action": "upi_collect",
1645 | "url": "https://api.razorpay.com/v1/payments/" +
1646 | "pay_OVERRIDE123/otp_generate",
1647 | },
1648 | },
1649 | },
1650 | "status": "payment_initiated",
1651 | "message": "Payment initiated. Available actions: [upi_collect]",
1652 | "available_actions": []interface{}{
1653 | map[string]interface{}{
1654 | "action": "upi_collect",
1655 | "url": "https://api.razorpay.com/v1/payments/" +
1656 | "pay_OVERRIDE123/otp_generate",
1657 | },
1658 | },
1659 | },
1660 | },
1661 | }
1662 |
1663 | for _, tc := range testCases {
1664 | t.Run(tc.Name, func(t *testing.T) {
1665 | runToolTest(t, tc, InitiatePayment, "Payment")
1666 | })
1667 | }
1668 | }
1669 |
1670 | // Test helper functions for better coverage
1671 | func Test_extractPaymentID(t *testing.T) {
1672 | tests := []struct {
1673 | name string
1674 | payment map[string]interface{}
1675 | expected string
1676 | }{
1677 | {
1678 | name: "valid payment ID",
1679 | payment: map[string]interface{}{
1680 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
1681 | "status": "created",
1682 | },
1683 | expected: "pay_MT48CvBhIC98MQ",
1684 | },
1685 | {
1686 | name: "missing payment ID",
1687 | payment: map[string]interface{}{
1688 | "status": "created",
1689 | },
1690 | expected: "",
1691 | },
1692 | {
1693 | name: "nil payment ID",
1694 | payment: map[string]interface{}{
1695 | "razorpay_payment_id": nil,
1696 | "status": "created",
1697 | },
1698 | expected: "",
1699 | },
1700 | {
1701 | name: "empty payment map",
1702 | payment: map[string]interface{}{},
1703 | expected: "",
1704 | },
1705 | }
1706 |
1707 | for _, tt := range tests {
1708 | t.Run(tt.name, func(t *testing.T) {
1709 | result := extractPaymentID(tt.payment)
1710 | if result != tt.expected {
1711 | t.Errorf("extractPaymentID() = %v, want %v", result, tt.expected)
1712 | }
1713 | })
1714 | }
1715 | }
1716 |
1717 | func Test_buildInitiatePaymentResponse(t *testing.T) {
1718 | tests := []struct {
1719 | name string
1720 | payment map[string]interface{}
1721 | paymentID string
1722 | actions []map[string]interface{}
1723 | expectedMsg string
1724 | expectedOtpURL string
1725 | }{
1726 | {
1727 | name: "payment with OTP action",
1728 | payment: map[string]interface{}{
1729 | "id": "pay_MT48CvBhIC98MQ",
1730 | "status": "created",
1731 | },
1732 | paymentID: "pay_MT48CvBhIC98MQ",
1733 | actions: []map[string]interface{}{
1734 | {
1735 | "action": "otp_generate",
1736 | "url": "https://api.razorpay.com/v1/payments/" +
1737 | "pay_MT48CvBhIC98MQ/otp_generate",
1738 | },
1739 | },
1740 | expectedMsg: "Payment initiated. OTP authentication is available. " +
1741 | "Use the 'submit_otp' tool to submit OTP received by the customer " +
1742 | "for authentication.",
1743 | expectedOtpURL: "https://api.razorpay.com/v1/payments/" +
1744 | "pay_MT48CvBhIC98MQ/otp_generate",
1745 | },
1746 | {
1747 | name: "payment with redirect action",
1748 | payment: map[string]interface{}{
1749 | "id": "pay_MT48CvBhIC98MQ",
1750 | "status": "created",
1751 | },
1752 | paymentID: "pay_MT48CvBhIC98MQ",
1753 | actions: []map[string]interface{}{
1754 | {
1755 | "action": "redirect",
1756 | "url": "https://api.razorpay.com/v1/payments/" +
1757 | "pay_MT48CvBhIC98MQ/authenticate",
1758 | },
1759 | },
1760 | expectedMsg: "Payment initiated. Redirect authentication is available. " +
1761 | "Use the redirect URL provided in available_actions.",
1762 | expectedOtpURL: "",
1763 | },
1764 | {
1765 | name: "payment with UPI collect action",
1766 | payment: map[string]interface{}{
1767 | "id": "pay_MT48CvBhIC98MQ",
1768 | "status": "created",
1769 | },
1770 | paymentID: "pay_MT48CvBhIC98MQ",
1771 | actions: []map[string]interface{}{
1772 | {
1773 | "action": "upi_collect",
1774 | "url": "https://api.razorpay.com/v1/payments/" +
1775 | "pay_MT48CvBhIC98MQ/authenticate",
1776 | },
1777 | },
1778 | expectedMsg: "Payment initiated. Available actions: [upi_collect]",
1779 | expectedOtpURL: "",
1780 | },
1781 | {
1782 | name: "payment with multiple actions including OTP",
1783 | payment: map[string]interface{}{
1784 | "id": "pay_MT48CvBhIC98MQ",
1785 | "status": "created",
1786 | },
1787 | paymentID: "pay_MT48CvBhIC98MQ",
1788 | actions: []map[string]interface{}{
1789 | {
1790 | "action": "otp_generate",
1791 | "url": "https://api.razorpay.com/v1/payments/" +
1792 | "pay_MT48CvBhIC98MQ/otp_generate",
1793 | },
1794 | {
1795 | "action": "redirect",
1796 | "url": "https://api.razorpay.com/v1/payments/" +
1797 | "pay_MT48CvBhIC98MQ/authenticate",
1798 | },
1799 | },
1800 | expectedMsg: "Payment initiated. OTP authentication is available. " +
1801 | "Use the 'submit_otp' tool to submit OTP received by the customer " +
1802 | "for authentication.",
1803 | expectedOtpURL: "https://api.razorpay.com/v1/payments/" +
1804 | "pay_MT48CvBhIC98MQ/otp_generate",
1805 | },
1806 | {
1807 | name: "payment with no actions",
1808 | payment: map[string]interface{}{
1809 | "id": "pay_MT48CvBhIC98MQ",
1810 | "status": "captured",
1811 | },
1812 | paymentID: "pay_MT48CvBhIC98MQ",
1813 | actions: []map[string]interface{}{},
1814 | expectedMsg: "Payment initiated successfully using S2S JSON v1 flow",
1815 | expectedOtpURL: "",
1816 | },
1817 | {
1818 | name: "payment with unknown action",
1819 | payment: map[string]interface{}{
1820 | "id": "pay_MT48CvBhIC98MQ",
1821 | "status": "created",
1822 | },
1823 | paymentID: "pay_MT48CvBhIC98MQ",
1824 | actions: []map[string]interface{}{
1825 | {
1826 | "action": "unknown_action",
1827 | "url": "https://api.razorpay.com/v1/payments/" +
1828 | "pay_MT48CvBhIC98MQ/unknown",
1829 | },
1830 | },
1831 | expectedMsg: "Payment initiated. Available actions: [unknown_action]",
1832 | expectedOtpURL: "",
1833 | },
1834 | }
1835 |
1836 | for _, tt := range tests {
1837 | t.Run(tt.name, func(t *testing.T) {
1838 | response, otpURL := buildInitiatePaymentResponse(
1839 | tt.payment, tt.paymentID, tt.actions)
1840 |
1841 | // Check basic response structure
1842 | if response["razorpay_payment_id"] != tt.paymentID {
1843 | t.Errorf("Expected payment ID %s, got %v", tt.paymentID,
1844 | response["razorpay_payment_id"])
1845 | }
1846 |
1847 | if response["status"] != "payment_initiated" {
1848 | t.Errorf("Expected status 'payment_initiated', got %v", response["status"])
1849 | }
1850 |
1851 | // Check message
1852 | if response["message"] != tt.expectedMsg {
1853 | t.Errorf("Expected message %s, got %v", tt.expectedMsg, response["message"])
1854 | }
1855 |
1856 | // Check OTP URL
1857 | if otpURL != tt.expectedOtpURL {
1858 | t.Errorf("Expected OTP URL %s, got %s", tt.expectedOtpURL, otpURL)
1859 | }
1860 |
1861 | // Check actions are included when present
1862 | if len(tt.actions) > 0 {
1863 | if _, exists := response["available_actions"]; !exists {
1864 | t.Error("Expected available_actions to be present in response")
1865 | }
1866 | }
1867 |
1868 | // Check next step instructions for OTP case
1869 | if tt.paymentID != "" && len(tt.actions) == 0 {
1870 | if _, exists := response["next_step"]; !exists {
1871 | t.Error("Expected next_step to be present for fallback case")
1872 | }
1873 | }
1874 | })
1875 | }
1876 | }
1877 |
1878 | func Test_addNextStepInstructions(t *testing.T) {
1879 | tests := []struct {
1880 | name string
1881 | paymentID string
1882 | expected bool // whether next_step should be added
1883 | }{
1884 | {
1885 | name: "valid payment ID",
1886 | paymentID: "pay_MT48CvBhIC98MQ",
1887 | expected: true,
1888 | },
1889 | {
1890 | name: "empty payment ID",
1891 | paymentID: "",
1892 | expected: false,
1893 | },
1894 | }
1895 |
1896 | for _, tt := range tests {
1897 | t.Run(tt.name, func(t *testing.T) {
1898 | response := make(map[string]interface{})
1899 | addNextStepInstructions(response, tt.paymentID)
1900 |
1901 | if tt.expected {
1902 | if _, exists := response["next_step"]; !exists {
1903 | t.Error("Expected next_step to be added")
1904 | }
1905 | if _, exists := response["next_tool"]; !exists {
1906 | t.Error("Expected next_tool to be added")
1907 | }
1908 | if _, exists := response["next_tool_params"]; !exists {
1909 | t.Error("Expected next_tool_params to be added")
1910 | }
1911 |
1912 | // Check specific values
1913 | if response["next_tool"] != "resend_otp" {
1914 | t.Errorf("Expected next_tool to be 'resend_otp', got %v",
1915 | response["next_tool"])
1916 | }
1917 |
1918 | params, ok := response["next_tool_params"].(map[string]interface{})
1919 | if !ok || params["payment_id"] != tt.paymentID {
1920 | t.Errorf("Expected next_tool_params to contain payment_id %s",
1921 | tt.paymentID)
1922 | }
1923 | } else {
1924 | if _, exists := response["next_step"]; exists {
1925 | t.Error("Expected next_step NOT to be added for empty payment ID")
1926 | }
1927 | }
1928 | })
1929 | }
1930 | }
1931 |
1932 | func Test_sendOtp_validation(t *testing.T) {
1933 | tests := []struct {
1934 | name string
1935 | otpURL string
1936 | expectedErr string
1937 | }{
1938 | {
1939 | name: "empty URL",
1940 | otpURL: "",
1941 | expectedErr: "OTP URL is empty",
1942 | },
1943 | {
1944 | name: "invalid URL",
1945 | otpURL: "not-a-valid-url",
1946 | expectedErr: "OTP URL must use HTTPS",
1947 | },
1948 | {
1949 | name: "non-HTTPS URL",
1950 | otpURL: "http://api.razorpay.com/v1/payments/pay_123/otp_generate",
1951 | expectedErr: "OTP URL must use HTTPS",
1952 | },
1953 | {
1954 | name: "non-Razorpay domain",
1955 | otpURL: "https://malicious.com/v1/payments/pay_123/otp_generate",
1956 | expectedErr: "OTP URL must be from Razorpay domain",
1957 | },
1958 | {
1959 | name: "valid Razorpay URL - should fail at HTTP call",
1960 | otpURL: "https://api.razorpay.com/v1/payments/pay_123/otp_generate",
1961 | expectedErr: "OTP generation failed",
1962 | },
1963 | }
1964 |
1965 | for _, tt := range tests {
1966 | t.Run(tt.name, func(t *testing.T) {
1967 | err := sendOtp(tt.otpURL)
1968 | if err == nil {
1969 | t.Error("Expected error but got nil")
1970 | return
1971 | }
1972 |
1973 | if tt.expectedErr != "" && !strings.Contains(err.Error(), tt.expectedErr) {
1974 | t.Errorf("Expected error to contain '%s', got '%s'",
1975 | tt.expectedErr, err.Error())
1976 | }
1977 | })
1978 | }
1979 | }
1980 |
1981 | func Test_extractOtpSubmitURL(t *testing.T) {
1982 | tests := []struct {
1983 | name string
1984 | payment map[string]interface{}
1985 | expected string
1986 | }{
1987 | {
1988 | name: "payment with next actions containing otp_submit",
1989 | payment: map[string]interface{}{
1990 | "next": []interface{}{
1991 | map[string]interface{}{
1992 | "action": "otp_submit",
1993 | "url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
1994 | },
1995 | },
1996 | },
1997 | expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
1998 | },
1999 | {
2000 | name: "payment with multiple next actions",
2001 | payment: map[string]interface{}{
2002 | "next": []interface{}{
2003 | map[string]interface{}{
2004 | "action": "redirect",
2005 | "url": "https://api.razorpay.com/v1/payments/pay_123/authenticate",
2006 | },
2007 | map[string]interface{}{
2008 | "action": "otp_submit",
2009 | "url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
2010 | },
2011 | },
2012 | },
2013 | expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
2014 | },
2015 | {
2016 | name: "payment with no next actions",
2017 | payment: map[string]interface{}{
2018 | "status": "captured",
2019 | },
2020 | expected: "",
2021 | },
2022 | {
2023 | name: "payment with next actions but no otp_submit",
2024 | payment: map[string]interface{}{
2025 | "next": []interface{}{
2026 | map[string]interface{}{
2027 | "action": "redirect",
2028 | "url": "https://api.razorpay.com/v1/payments/pay_123/authenticate",
2029 | },
2030 | },
2031 | },
2032 | expected: "",
2033 | },
2034 | {
2035 | name: "payment with empty next array",
2036 | payment: map[string]interface{}{
2037 | "next": []interface{}{},
2038 | },
2039 | expected: "",
2040 | },
2041 | {
2042 | name: "payment with invalid next structure",
2043 | payment: map[string]interface{}{
2044 | "next": "invalid_structure",
2045 | },
2046 | expected: "",
2047 | },
2048 | {
2049 | name: "payment with otp_submit action but nil URL",
2050 | payment: map[string]interface{}{
2051 | "next": []interface{}{
2052 | map[string]interface{}{
2053 | "action": "otp_submit",
2054 | "url": nil, // nil URL should return empty string
2055 | },
2056 | },
2057 | },
2058 | expected: "",
2059 | },
2060 | {
2061 | name: "payment with otp_submit action but non-string URL",
2062 | payment: map[string]interface{}{
2063 | "next": []interface{}{
2064 | map[string]interface{}{
2065 | "action": "otp_submit",
2066 | "url": 123, // non-string URL should cause type assertion to fail
2067 | },
2068 | },
2069 | },
2070 | expected: "",
2071 | },
2072 | {
2073 | name: "payment with otp_submit action but missing URL field",
2074 | payment: map[string]interface{}{
2075 | "next": []interface{}{
2076 | map[string]interface{}{
2077 | "action": "otp_submit",
2078 | // no url field
2079 | },
2080 | },
2081 | },
2082 | expected: "",
2083 | },
2084 | {
2085 | name: "payment with mixed valid and invalid items in next array",
2086 | payment: map[string]interface{}{
2087 | "next": []interface{}{
2088 | "invalid_item", // This should be skipped
2089 | map[string]interface{}{
2090 | "action": "redirect",
2091 | "url": "https://example.com/redirect",
2092 | },
2093 | 123, // Another invalid item that should be skipped
2094 | map[string]interface{}{
2095 | "action": "otp_submit",
2096 | "url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
2097 | },
2098 | },
2099 | },
2100 | expected: "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
2101 | },
2102 | {
2103 | name: "payment with otp_submit action but missing action field",
2104 | payment: map[string]interface{}{
2105 | "next": []interface{}{
2106 | map[string]interface{}{
2107 | // no action field
2108 | "url": "https://api.razorpay.com/v1/payments/pay_123/otp/submit",
2109 | },
2110 | },
2111 | },
2112 | expected: "",
2113 | },
2114 | }
2115 |
2116 | for _, tt := range tests {
2117 | t.Run(tt.name, func(t *testing.T) {
2118 | result := extractOtpSubmitURL(tt.payment)
2119 | if result != tt.expected {
2120 | t.Errorf("extractOtpSubmitURL() = %v, want %v", result, tt.expected)
2121 | }
2122 | })
2123 | }
2124 | }
2125 |
2126 | // Test_extractOtpSubmitURL_invalidInput tests the
2127 | // function with invalid input types
2128 | func Test_extractOtpSubmitURL_invalidInput(t *testing.T) {
2129 | tests := []struct {
2130 | name string
2131 | input interface{}
2132 | expected string
2133 | }{
2134 | {
2135 | name: "nil input",
2136 | input: nil,
2137 | expected: "",
2138 | },
2139 | {
2140 | name: "string input instead of map",
2141 | input: "invalid_input",
2142 | expected: "",
2143 | },
2144 | {
2145 | name: "integer input instead of map",
2146 | input: 123,
2147 | expected: "",
2148 | },
2149 | {
2150 | name: "slice input instead of map",
2151 | input: []string{"invalid"},
2152 | expected: "",
2153 | },
2154 | }
2155 |
2156 | for _, tt := range tests {
2157 | t.Run(tt.name, func(t *testing.T) {
2158 | result := extractOtpSubmitURL(tt.input)
2159 | if result != tt.expected {
2160 | t.Errorf("extractOtpSubmitURL() = %v, want %v", result, tt.expected)
2161 | }
2162 | })
2163 | }
2164 | }
2165 |
2166 | func Test_ResendOtp(t *testing.T) {
2167 | resendOtpPathFmt := fmt.Sprintf(
2168 | "/%s%s/%%s/otp/resend",
2169 | constants.VERSION_V1,
2170 | constants.PAYMENT_URL,
2171 | )
2172 |
2173 | successResendOtpResp := map[string]interface{}{
2174 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
2175 | "next": []interface{}{
2176 | map[string]interface{}{
2177 | "action": "otp_submit",
2178 | "url": "https://api.razorpay.com/v1/payments/" +
2179 | "pay_MT48CvBhIC98MQ/otp/submit",
2180 | },
2181 | },
2182 | }
2183 |
2184 | paymentNotFoundResp := map[string]interface{}{
2185 | "error": map[string]interface{}{
2186 | "code": "BAD_REQUEST_ERROR",
2187 | "description": "Payment not found",
2188 | },
2189 | }
2190 |
2191 | tests := []RazorpayToolTestCase{
2192 | {
2193 | Name: "successful OTP resend",
2194 | Request: map[string]interface{}{
2195 | "payment_id": "pay_MT48CvBhIC98MQ",
2196 | },
2197 | MockHttpClient: func() (*http.Client, *httptest.Server) {
2198 | return mock.NewHTTPClient(
2199 | mock.Endpoint{
2200 | Path: fmt.Sprintf(resendOtpPathFmt, "pay_MT48CvBhIC98MQ"),
2201 | Method: "POST",
2202 | Response: successResendOtpResp,
2203 | },
2204 | )
2205 | },
2206 | ExpectError: false,
2207 | ExpectedResult: map[string]interface{}{
2208 | "payment_id": "pay_MT48CvBhIC98MQ",
2209 | "status": "success",
2210 | "message": "OTP sent successfully. Please enter the OTP received on your " +
2211 | "mobile number to complete the payment.",
2212 | "next_step": "Use 'submit_otp' tool with the OTP code received " +
2213 | "from user to complete payment authentication.",
2214 | "next_tool": "submit_otp",
2215 | "next_tool_params": map[string]interface{}{
2216 | "payment_id": "pay_MT48CvBhIC98MQ",
2217 | "otp_string": "{OTP_CODE_FROM_USER}",
2218 | },
2219 | "otp_submit_url": "https://api.razorpay.com/v1/payments/" +
2220 | "pay_MT48CvBhIC98MQ/otp/submit",
2221 | "response_data": successResendOtpResp,
2222 | },
2223 | },
2224 | {
2225 | Name: "payment not found for OTP resend",
2226 | Request: map[string]interface{}{
2227 | "payment_id": "pay_invalid",
2228 | },
2229 | MockHttpClient: func() (*http.Client, *httptest.Server) {
2230 | return mock.NewHTTPClient(
2231 | mock.Endpoint{
2232 | Path: fmt.Sprintf(resendOtpPathFmt, "pay_invalid"),
2233 | Method: "POST",
2234 | Response: paymentNotFoundResp,
2235 | },
2236 | )
2237 | },
2238 | ExpectError: true,
2239 | ExpectedErrMsg: "OTP resend failed: Payment not found",
2240 | },
2241 | {
2242 | Name: "missing payment_id parameter for resend",
2243 | Request: map[string]interface{}{
2244 | // No payment_id provided
2245 | },
2246 | MockHttpClient: nil, // No HTTP client needed for validation error
2247 | ExpectError: true,
2248 | ExpectedErrMsg: "missing required parameter: payment_id",
2249 | },
2250 | {
2251 | Name: "OTP resend without next actions",
2252 | Request: map[string]interface{}{
2253 | "payment_id": "pay_MT48CvBhIC98MQ",
2254 | },
2255 | MockHttpClient: func() (*http.Client, *httptest.Server) {
2256 | return mock.NewHTTPClient(
2257 | mock.Endpoint{
2258 | Path: fmt.Sprintf(resendOtpPathFmt, "pay_MT48CvBhIC98MQ"),
2259 | Method: "POST",
2260 | Response: map[string]interface{}{
2261 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
2262 | "status": "created",
2263 | },
2264 | },
2265 | )
2266 | },
2267 | ExpectError: false,
2268 | ExpectedResult: map[string]interface{}{
2269 | "payment_id": "pay_MT48CvBhIC98MQ",
2270 | "status": "success",
2271 | "message": "OTP sent successfully. Please enter the OTP received on your " +
2272 | "mobile number to complete the payment.",
2273 | "next_step": "Use 'submit_otp' tool with the OTP code received " +
2274 | "from user to complete payment authentication.",
2275 | "next_tool": "submit_otp",
2276 | "next_tool_params": map[string]interface{}{
2277 | "payment_id": "pay_MT48CvBhIC98MQ",
2278 | "otp_string": "{OTP_CODE_FROM_USER}",
2279 | },
2280 | "response_data": map[string]interface{}{
2281 | "razorpay_payment_id": "pay_MT48CvBhIC98MQ",
2282 | "status": "created",
2283 | },
2284 | },
2285 | },
2286 | }
2287 |
2288 | for _, tc := range tests {
2289 | t.Run(tc.Name, func(t *testing.T) {
2290 | runToolTest(t, tc, ResendOtp, "OTP Resend")
2291 | })
2292 | }
2293 | }
2294 |
2295 | // Test_sendOtp_additionalCases tests additional cases for sendOtp function
2296 | func Test_sendOtp_additionalCases(t *testing.T) {
2297 | tests := []struct {
2298 | name string
2299 | otpURL string
2300 | expectedErr string
2301 | }{
2302 | {
2303 | name: "URL with invalid characters",
2304 | otpURL: "https://api.razorpay.com/v1/payments/pay_123/" +
2305 | "otp_generate?param=value with spaces",
2306 | expectedErr: "OTP generation failed",
2307 | },
2308 | {
2309 | name: "URL with special characters in domain",
2310 | otpURL: "https://api-test.razorpay.com/v1/payments/" +
2311 | "pay_123/otp_generate",
2312 | expectedErr: "OTP generation failed",
2313 | },
2314 | }
2315 |
2316 | for _, tt := range tests {
2317 | t.Run(tt.name, func(t *testing.T) {
2318 | err := sendOtp(tt.otpURL)
2319 | if err == nil {
2320 | t.Error("Expected error but got nil")
2321 | return
2322 | }
2323 | if tt.expectedErr != "" && !strings.Contains(err.Error(), tt.expectedErr) {
2324 | t.Errorf(
2325 | "Expected error to contain '%s', got '%s'", tt.expectedErr, err.Error(),
2326 | )
2327 | }
2328 | })
2329 | }
2330 | }
2331 |
2332 | // Test_buildPaymentData_edgeCases tests edge cases for
2333 | // buildPaymentData function
2334 | func Test_buildPaymentData_edgeCases(t *testing.T) {
2335 | tests := []struct {
2336 | name string
2337 | params map[string]interface{}
2338 | currency string
2339 | customerID string
2340 | expectedError string
2341 | shouldContain map[string]interface{}
2342 | }{
2343 | {
2344 | name: "payment data with valid customer ID",
2345 | params: map[string]interface{}{
2346 | "amount": 10000,
2347 | "order_id": "order_123",
2348 | },
2349 | currency: "INR",
2350 | customerID: "cust_123456789",
2351 | shouldContain: map[string]interface{}{
2352 | "amount": 10000,
2353 | "currency": "INR",
2354 | "order_id": "order_123",
2355 | "customer_id": "cust_123456789",
2356 | },
2357 | },
2358 | {
2359 | name: "payment data with empty token",
2360 | params: map[string]interface{}{
2361 | "amount": 10000,
2362 | "order_id": "order_123",
2363 | "token": "", // Empty token should not be added
2364 | },
2365 | currency: "INR",
2366 | customerID: "cust_123456789",
2367 | shouldContain: map[string]interface{}{
2368 | "amount": 10000,
2369 | "currency": "INR",
2370 | "order_id": "order_123",
2371 | "customer_id": "cust_123456789",
2372 | },
2373 | },
2374 | {
2375 | name: "payment data with empty customer ID",
2376 | params: map[string]interface{}{
2377 | "amount": 10000,
2378 | "order_id": "order_123",
2379 | "token": "token_123",
2380 | },
2381 | currency: "INR",
2382 | customerID: "",
2383 | shouldContain: map[string]interface{}{
2384 | "amount": 10000,
2385 | "currency": "INR",
2386 | "order_id": "order_123",
2387 | "token": "token_123",
2388 | },
2389 | },
2390 | {
2391 | name: "payment data with all parameters",
2392 | params: map[string]interface{}{
2393 | "amount": 10000,
2394 | "order_id": "order_123",
2395 | "token": "token_123",
2396 | "email": "[email protected]",
2397 | "contact": "9876543210",
2398 | "method": "upi",
2399 | "save": true,
2400 | "upi": map[string]interface{}{
2401 | "flow": "collect",
2402 | "expiry_time": "6",
2403 | "vpa": "test@upi",
2404 | },
2405 | },
2406 | currency: "INR",
2407 | customerID: "cust_123456789",
2408 | shouldContain: map[string]interface{}{
2409 | "amount": 10000,
2410 | "currency": "INR",
2411 | "order_id": "order_123",
2412 | "customer_id": "cust_123456789",
2413 | "token": "token_123",
2414 | "email": "[email protected]",
2415 | "contact": "9876543210",
2416 | "method": "upi",
2417 | "save": true,
2418 | },
2419 | },
2420 | }
2421 |
2422 | for _, tt := range tests {
2423 | t.Run(tt.name, func(t *testing.T) {
2424 | result := buildPaymentData(tt.params, tt.currency, tt.customerID)
2425 |
2426 | if result == nil {
2427 | t.Error("Expected result but got nil")
2428 | return
2429 | }
2430 |
2431 | // Check that all expected fields are present
2432 | for key, expectedValue := range tt.shouldContain {
2433 | actualValue, exists := (*result)[key]
2434 | if !exists {
2435 | t.Errorf("Expected key '%s' to exist in result", key)
2436 | } else if actualValue != expectedValue {
2437 | t.Errorf(
2438 | "Expected '%s' to be %v, got %v", key, expectedValue, actualValue,
2439 | )
2440 | }
2441 | }
2442 |
2443 | // Check that empty token is not added
2444 | if tt.params["token"] == "" {
2445 | if _, exists := (*result)["token"]; exists {
2446 | t.Error("Empty token should not be added to payment data")
2447 | }
2448 | }
2449 |
2450 | // Check that customer_id is not added when customerID is empty
2451 | if tt.customerID == "" {
2452 | if _, exists := (*result)["customer_id"]; exists {
2453 | t.Error("customer_id should not be added when customerID is empty")
2454 | }
2455 | }
2456 |
2457 | })
2458 | }
2459 | }
2460 |
2461 | // Test_createOrGetCustomer_scenarios tests
2462 | // createOrGetCustomer function scenarios
2463 | func Test_createOrGetCustomer_scenarios(t *testing.T) {
2464 | tests := []struct {
2465 | name string
2466 | params map[string]interface{}
2467 | mockSetup func() (*http.Client, *httptest.Server)
2468 | expectedError string
2469 | expectedResult map[string]interface{}
2470 | }{
2471 | {
2472 | name: "successful customer creation with contact",
2473 | params: map[string]interface{}{
2474 | "contact": "9876543210",
2475 | },
2476 | mockSetup: func() (*http.Client, *httptest.Server) {
2477 | return mock.NewHTTPClient(
2478 | mock.Endpoint{
2479 | Path: "/v1/customers",
2480 | Method: "POST",
2481 | Response: map[string]interface{}{
2482 | "id": "cust_123456789",
2483 | "contact": "9876543210",
2484 | "email": "[email protected]",
2485 | },
2486 | },
2487 | )
2488 | },
2489 | expectedResult: map[string]interface{}{
2490 | "id": "cust_123456789",
2491 | "contact": "9876543210",
2492 | "email": "[email protected]",
2493 | },
2494 | },
2495 | {
2496 | name: "no contact provided - returns nil",
2497 | params: map[string]interface{}{
2498 | "amount": 10000,
2499 | },
2500 | mockSetup: func() (*http.Client, *httptest.Server) {
2501 | return mock.NewHTTPClient()
2502 | },
2503 | expectedResult: nil,
2504 | },
2505 | {
2506 | name: "empty contact provided - returns nil",
2507 | params: map[string]interface{}{
2508 | "contact": "",
2509 | },
2510 | mockSetup: func() (*http.Client, *httptest.Server) {
2511 | return mock.NewHTTPClient()
2512 | },
2513 | expectedResult: nil,
2514 | },
2515 | {
2516 | name: "customer creation API error",
2517 | params: map[string]interface{}{
2518 | "contact": "9876543210",
2519 | },
2520 | mockSetup: func() (*http.Client, *httptest.Server) {
2521 | return mock.NewHTTPClient(
2522 | mock.Endpoint{
2523 | Path: "/v1/customers",
2524 | Method: "POST",
2525 | Response: map[string]interface{}{
2526 | "error": map[string]interface{}{
2527 | "code": "BAD_REQUEST_ERROR",
2528 | "description": "Invalid contact number",
2529 | },
2530 | },
2531 | },
2532 | )
2533 | },
2534 | expectedError: "failed to create/fetch customer with contact 9876543210",
2535 | },
2536 | }
2537 |
2538 | for _, tt := range tests {
2539 | t.Run(tt.name, func(t *testing.T) {
2540 | client, server := newMockRzpClient(tt.mockSetup)
2541 | if server != nil {
2542 | defer server.Close()
2543 | }
2544 |
2545 | result, err := createOrGetCustomer(client, tt.params)
2546 |
2547 | if tt.expectedError != "" {
2548 | if err == nil {
2549 | t.Error("Expected error but got nil")
2550 | return
2551 | }
2552 | if !strings.Contains(err.Error(), tt.expectedError) {
2553 | t.Errorf(
2554 | "Expected error to contain '%s', got '%s'", tt.expectedError, err.Error(),
2555 | )
2556 | }
2557 | } else {
2558 | if err != nil {
2559 | t.Errorf("Unexpected error: %v", err)
2560 | return
2561 | }
2562 |
2563 | switch {
2564 | case tt.expectedResult == nil && result != nil:
2565 | t.Errorf("Expected nil result but got %v", result)
2566 | case tt.expectedResult != nil && result == nil:
2567 | t.Error("Expected result but got nil")
2568 | case tt.expectedResult != nil && result != nil:
2569 | if result["id"] != tt.expectedResult["id"] {
2570 | t.Errorf(
2571 | "Expected customer ID '%s', got '%s'", tt.expectedResult["id"],
2572 | result["id"],
2573 | )
2574 | }
2575 | }
2576 | }
2577 | })
2578 | }
2579 | }
2580 |
2581 | // Test_processVPAParameters_scenarios tests
2582 | //
2583 | // processUPIParameters function scenarios
2584 | func Test_processUPIParameters_scenarios(t *testing.T) {
2585 | tests := []struct {
2586 | name string
2587 | inputParams map[string]interface{}
2588 | expectedParams map[string]interface{}
2589 | }{
2590 | {
2591 | name: "VPA parameter provided - sets UPI parameters",
2592 | inputParams: map[string]interface{}{
2593 | "amount": 10000,
2594 | "order_id": "order_123",
2595 | "vpa": "9876543210@paytm",
2596 | },
2597 | expectedParams: map[string]interface{}{
2598 | "amount": 10000,
2599 | "order_id": "order_123",
2600 | "vpa": "9876543210@paytm",
2601 | "method": "upi",
2602 | "upi": map[string]interface{}{
2603 | "flow": "collect",
2604 | "expiry_time": "6",
2605 | "vpa": "9876543210@paytm",
2606 | },
2607 | },
2608 | },
2609 | {
2610 | name: "empty VPA parameter - no changes",
2611 | inputParams: map[string]interface{}{
2612 | "amount": 10000,
2613 | "order_id": "order_123",
2614 | "vpa": "",
2615 | },
2616 | expectedParams: map[string]interface{}{
2617 | "amount": 10000,
2618 | "order_id": "order_123",
2619 | "vpa": "",
2620 | },
2621 | },
2622 | {
2623 | name: "no VPA parameter - no changes",
2624 | inputParams: map[string]interface{}{
2625 | "amount": 10000,
2626 | "order_id": "order_123",
2627 | },
2628 | expectedParams: map[string]interface{}{
2629 | "amount": 10000,
2630 | "order_id": "order_123",
2631 | },
2632 | },
2633 | {
2634 | name: "UPI intent parameter provided - sets UPI intent parameters",
2635 | inputParams: map[string]interface{}{
2636 | "amount": 15000,
2637 | "order_id": "order_456",
2638 | "upi_intent": true,
2639 | },
2640 | expectedParams: map[string]interface{}{
2641 | "amount": 15000,
2642 | "order_id": "order_456",
2643 | "upi_intent": true,
2644 | "method": "upi",
2645 | "upi": map[string]interface{}{
2646 | "flow": "intent",
2647 | },
2648 | },
2649 | },
2650 | {
2651 | name: "UPI intent false - no changes",
2652 | inputParams: map[string]interface{}{
2653 | "amount": 10000,
2654 | "order_id": "order_123",
2655 | "upi_intent": false,
2656 | },
2657 | expectedParams: map[string]interface{}{
2658 | "amount": 10000,
2659 | "order_id": "order_123",
2660 | "upi_intent": false,
2661 | },
2662 | },
2663 | {
2664 | name: "both VPA and UPI intent provided - UPI intent takes precedence",
2665 | inputParams: map[string]interface{}{
2666 | "amount": 20000,
2667 | "order_id": "order_789",
2668 | "vpa": "test@upi",
2669 | "upi_intent": true,
2670 | },
2671 | expectedParams: map[string]interface{}{
2672 | "amount": 20000,
2673 | "order_id": "order_789",
2674 | "vpa": "test@upi",
2675 | "upi_intent": true,
2676 | "method": "upi",
2677 | "upi": map[string]interface{}{
2678 | "flow": "intent",
2679 | },
2680 | },
2681 | },
2682 | }
2683 |
2684 | for _, tt := range tests {
2685 | t.Run(tt.name, func(t *testing.T) {
2686 | params := make(map[string]interface{})
2687 | for k, v := range tt.inputParams {
2688 | params[k] = v
2689 | }
2690 |
2691 | processUPIParameters(params)
2692 |
2693 | for key, expectedValue := range tt.expectedParams {
2694 | actualValue, exists := params[key]
2695 | if !exists {
2696 | t.Errorf("Expected key '%s' to exist in params", key)
2697 | continue
2698 | }
2699 |
2700 | if key == "upi" {
2701 | expectedUPI := expectedValue.(map[string]interface{})
2702 | actualUPI, ok := actualValue.(map[string]interface{})
2703 | if !ok {
2704 | t.Errorf("Expected UPI to be map[string]interface{}, got %T", actualValue)
2705 | continue
2706 | }
2707 | for upiKey, upiValue := range expectedUPI {
2708 | if actualUPI[upiKey] != upiValue {
2709 | t.Errorf(
2710 | "Expected UPI[%s] to be '%v', got '%v'",
2711 | upiKey, upiValue, actualUPI[upiKey],
2712 | )
2713 | }
2714 | }
2715 | } else if actualValue != expectedValue {
2716 | t.Errorf(
2717 | "Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
2718 | )
2719 | }
2720 | }
2721 | })
2722 | }
2723 | }
2724 |
2725 | // Test_addAdditionalPaymentParameters_scenarios
2726 | // tests addAdditionalPaymentParameters function scenarios
2727 | // Note: method parameter is set internally by VPA processing, not by user input
2728 | func Test_addAdditionalPaymentParameters_scenarios(t *testing.T) {
2729 | tests := []struct {
2730 | name string
2731 | paymentData map[string]interface{}
2732 | params map[string]interface{}
2733 | expectedResult map[string]interface{}
2734 | }{
2735 | {
2736 | name: "all additional parameters provided (method set internally)",
2737 | paymentData: map[string]interface{}{
2738 | "amount": 10000,
2739 | "currency": "INR",
2740 | },
2741 | params: map[string]interface{}{
2742 | "method": "upi",
2743 | "save": true,
2744 | "upi": map[string]interface{}{
2745 | "flow": "collect",
2746 | "vpa": "test@upi",
2747 | },
2748 | },
2749 | expectedResult: map[string]interface{}{
2750 | "amount": 10000,
2751 | "currency": "INR",
2752 | "method": "upi",
2753 | "save": true,
2754 | "upi": map[string]interface{}{
2755 | "flow": "collect",
2756 | "vpa": "test@upi",
2757 | },
2758 | },
2759 | },
2760 | {
2761 | name: "empty method parameter - not added (internal processing)",
2762 | paymentData: map[string]interface{}{
2763 | "amount": 10000,
2764 | },
2765 | params: map[string]interface{}{
2766 | "method": "",
2767 | "save": false,
2768 | },
2769 | expectedResult: map[string]interface{}{
2770 | "amount": 10000,
2771 | "save": false,
2772 | },
2773 | },
2774 | {
2775 | name: "nil UPI parameters - not added (method set internally)",
2776 | paymentData: map[string]interface{}{
2777 | "amount": 10000,
2778 | },
2779 | params: map[string]interface{}{
2780 | "method": "card",
2781 | "upi": nil,
2782 | },
2783 | expectedResult: map[string]interface{}{
2784 | "amount": 10000,
2785 | "method": "card",
2786 | },
2787 | },
2788 | {
2789 | name: "invalid UPI parameter type - not added (method set internally)",
2790 | paymentData: map[string]interface{}{
2791 | "amount": 10000,
2792 | },
2793 | params: map[string]interface{}{
2794 | "method": "upi",
2795 | "upi": "invalid_type",
2796 | },
2797 | expectedResult: map[string]interface{}{
2798 | "amount": 10000,
2799 | "method": "upi",
2800 | },
2801 | },
2802 | {
2803 | name: "recurring parameter provided",
2804 | paymentData: map[string]interface{}{
2805 | "amount": 10000,
2806 | },
2807 | params: map[string]interface{}{
2808 | "recurring": true,
2809 | },
2810 | expectedResult: map[string]interface{}{
2811 | "amount": 10000,
2812 | "recurring": true,
2813 | },
2814 | },
2815 | {
2816 | name: "recurring parameter false",
2817 | paymentData: map[string]interface{}{
2818 | "amount": 10000,
2819 | },
2820 | params: map[string]interface{}{
2821 | "recurring": false,
2822 | },
2823 | expectedResult: map[string]interface{}{
2824 | "amount": 10000,
2825 | "recurring": false,
2826 | },
2827 | },
2828 | }
2829 |
2830 | for _, tt := range tests {
2831 | t.Run(tt.name, func(t *testing.T) {
2832 | paymentData := make(map[string]interface{})
2833 | for k, v := range tt.paymentData {
2834 | paymentData[k] = v
2835 | }
2836 |
2837 | addAdditionalPaymentParameters(paymentData, tt.params)
2838 |
2839 | for key, expectedValue := range tt.expectedResult {
2840 | actualValue, exists := paymentData[key]
2841 | if !exists {
2842 | t.Errorf("Expected key '%s' to exist in paymentData", key)
2843 | continue
2844 | }
2845 |
2846 | if key == "upi" {
2847 | expectedUPI := expectedValue.(map[string]interface{})
2848 | actualUPI, ok := actualValue.(map[string]interface{})
2849 | if !ok {
2850 | t.Errorf("Expected UPI to be map[string]interface{}, got %T", actualValue)
2851 | continue
2852 | }
2853 | for upiKey, upiValue := range expectedUPI {
2854 | if actualUPI[upiKey] != upiValue {
2855 | t.Errorf(
2856 | "Expected UPI[%s] to be '%v', got '%v'", upiKey, upiValue,
2857 | actualUPI[upiKey],
2858 | )
2859 | }
2860 | }
2861 | } else if actualValue != expectedValue {
2862 | t.Errorf(
2863 | "Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
2864 | )
2865 | }
2866 | }
2867 |
2868 | // Check that no unexpected keys were added
2869 | for key := range paymentData {
2870 | if _, expected := tt.expectedResult[key]; !expected {
2871 | t.Errorf("Unexpected key '%s' found in paymentData", key)
2872 | }
2873 | }
2874 | })
2875 | }
2876 | }
2877 |
2878 | // Test_addContactAndEmailToPaymentData_scenarios
2879 | // tests addContactAndEmailToPaymentData function scenarios
2880 | func Test_addContactAndEmailToPaymentData_scenarios(t *testing.T) {
2881 | tests := []struct {
2882 | name string
2883 | paymentData map[string]interface{}
2884 | params map[string]interface{}
2885 | expectedResult map[string]interface{}
2886 | }{
2887 | {
2888 | name: "both contact and email provided",
2889 | paymentData: map[string]interface{}{
2890 | "amount": 10000,
2891 | },
2892 | params: map[string]interface{}{
2893 | "contact": "9876543210",
2894 | "email": "[email protected]",
2895 | },
2896 | expectedResult: map[string]interface{}{
2897 | "amount": 10000,
2898 | "contact": "9876543210",
2899 | "email": "[email protected]",
2900 | },
2901 | },
2902 | {
2903 | name: "only contact provided - email generated",
2904 | paymentData: map[string]interface{}{
2905 | "amount": 10000,
2906 | },
2907 | params: map[string]interface{}{
2908 | "contact": "9876543210",
2909 | },
2910 | expectedResult: map[string]interface{}{
2911 | "amount": 10000,
2912 | "contact": "9876543210",
2913 | "email": "[email protected]",
2914 | },
2915 | },
2916 | {
2917 | name: "empty contact and email - nothing added",
2918 | paymentData: map[string]interface{}{
2919 | "amount": 10000,
2920 | },
2921 | params: map[string]interface{}{
2922 | "contact": "",
2923 | "email": "",
2924 | },
2925 | expectedResult: map[string]interface{}{
2926 | "amount": 10000,
2927 | },
2928 | },
2929 | {
2930 | name: "contact provided but email is empty - email generated",
2931 | paymentData: map[string]interface{}{
2932 | "amount": 10000,
2933 | },
2934 | params: map[string]interface{}{
2935 | "contact": "9876543210",
2936 | "email": "",
2937 | },
2938 | expectedResult: map[string]interface{}{
2939 | "amount": 10000,
2940 | "contact": "9876543210",
2941 | "email": "[email protected]",
2942 | },
2943 | },
2944 | }
2945 |
2946 | for _, tt := range tests {
2947 | t.Run(tt.name, func(t *testing.T) {
2948 | paymentData := make(map[string]interface{})
2949 | for k, v := range tt.paymentData {
2950 | paymentData[k] = v
2951 | }
2952 |
2953 | addContactAndEmailToPaymentData(paymentData, tt.params)
2954 |
2955 | for key, expectedValue := range tt.expectedResult {
2956 | actualValue, exists := paymentData[key]
2957 | if !exists {
2958 | t.Errorf("Expected key '%s' to exist in paymentData", key)
2959 | continue
2960 | }
2961 | if actualValue != expectedValue {
2962 | t.Errorf(
2963 | "Expected '%s' to be '%v', got '%v'", key, expectedValue, actualValue,
2964 | )
2965 | }
2966 | }
2967 |
2968 | // Check that no unexpected keys were added
2969 | for key := range paymentData {
2970 | if _, expected := tt.expectedResult[key]; !expected {
2971 | t.Errorf("Unexpected key '%s' found in paymentData", key)
2972 | }
2973 | }
2974 | })
2975 | }
2976 | }
2977 |
2978 | // Test_processPaymentResult_edgeCases
2979 | // tests edge cases for processPaymentResult function
2980 | func Test_processPaymentResult_edgeCases(t *testing.T) {
2981 | tests := []struct {
2982 | name string
2983 | payment map[string]interface{}
2984 | expectedError string
2985 | shouldProcess bool
2986 | }{
2987 | {
2988 | name: "payment with OTP URL that causes sendOtp to fail",
2989 | payment: map[string]interface{}{
2990 | "razorpay_payment_id": "pay_123456789",
2991 | "next": []interface{}{
2992 | map[string]interface{}{
2993 | "action": "otp_generate",
2994 | "url": "http://invalid-url", // Invalid URL
2995 | },
2996 | },
2997 | },
2998 | expectedError: "OTP generation failed",
2999 | },
3000 | {
3001 | name: "payment with empty OTP URL",
3002 | payment: map[string]interface{}{
3003 | "razorpay_payment_id": "pay_123456789",
3004 | "next": []interface{}{
3005 | map[string]interface{}{
3006 | "action": "otp_generate",
3007 | "url": "", // Empty URL should not trigger sendOtp
3008 | },
3009 | },
3010 | },
3011 | shouldProcess: true,
3012 | },
3013 | {
3014 | name: "payment without next actions",
3015 | payment: map[string]interface{}{
3016 | "razorpay_payment_id": "pay_123456789",
3017 | },
3018 | shouldProcess: true,
3019 | },
3020 | }
3021 |
3022 | for _, tt := range tests {
3023 | t.Run(tt.name, func(t *testing.T) {
3024 | result, err := processPaymentResult(tt.payment)
3025 |
3026 | if tt.expectedError != "" {
3027 | if err == nil {
3028 | t.Error("Expected error but got nil")
3029 | return
3030 | }
3031 | if !strings.Contains(err.Error(), tt.expectedError) {
3032 | t.Errorf(
3033 | "Expected error to contain '%s', got '%s'", tt.expectedError, err.Error())
3034 | }
3035 | } else if tt.shouldProcess {
3036 | if err != nil {
3037 | t.Errorf("Unexpected error: %v", err)
3038 | }
3039 | if result == nil {
3040 | t.Error("Expected result but got nil")
3041 | } else {
3042 | // Verify the response structure
3043 | if paymentID, exists := result["razorpay_payment_id"]; !exists ||
3044 | paymentID == "" {
3045 | t.Error("Expected razorpay_payment_id in result")
3046 | }
3047 | if status, exists := result["status"]; !exists ||
3048 | status != "payment_initiated" {
3049 | t.Error("Expected status to be 'payment_initiated'")
3050 | }
3051 | }
3052 | }
3053 | })
3054 | }
3055 | }
3056 |
3057 | // Test for sendOtp function - comprehensive coverage
3058 | func TestSendOtp(t *testing.T) {
3059 | t.Run("empty OTP URL", func(t *testing.T) {
3060 | err := sendOtp("")
3061 | if err == nil {
3062 | t.Error("Expected error for empty OTP URL")
3063 | }
3064 | if err.Error() != "OTP URL is empty" {
3065 | t.Errorf("Expected 'OTP URL is empty', got '%s'", err.Error())
3066 | }
3067 | })
3068 |
3069 | t.Run("invalid URL format", func(t *testing.T) {
3070 | err := sendOtp("invalid-url")
3071 | if err == nil {
3072 | t.Error("Expected error for invalid URL")
3073 | }
3074 | // The URL parsing succeeds but fails on HTTPS check
3075 | if !strings.Contains(err.Error(), "OTP URL must use HTTPS") {
3076 | t.Errorf("Expected 'OTP URL must use HTTPS' error, got '%s'", err.Error())
3077 | }
3078 | })
3079 |
3080 | t.Run("non-HTTPS URL", func(t *testing.T) {
3081 | err := sendOtp("http://api.razorpay.com/v1/payments/otp")
3082 | if err == nil {
3083 | t.Error("Expected error for non-HTTPS URL")
3084 | }
3085 | if err.Error() != "OTP URL must use HTTPS" {
3086 | t.Errorf("Expected 'OTP URL must use HTTPS', got '%s'", err.Error())
3087 | }
3088 | })
3089 |
3090 | t.Run("non-Razorpay domain", func(t *testing.T) {
3091 | err := sendOtp("https://example.com/otp")
3092 | if err == nil {
3093 | t.Error("Expected error for non-Razorpay domain")
3094 | }
3095 | if err.Error() != "OTP URL must be from Razorpay domain" {
3096 | t.Errorf("Expected 'OTP URL must be from Razorpay domain', got '%s'",
3097 | err.Error())
3098 | }
3099 | })
3100 |
3101 | t.Run("successful OTP request", func(t *testing.T) {
3102 | // Since we can't actually call external APIs in tests, we'll test the
3103 | // validation logic by testing with a URL that would fail at HTTP call stage
3104 | err := sendOtp(
3105 | "https://api.razorpay.com/v1/payments/invalid-endpoint-for-test")
3106 | if err == nil {
3107 | t.Error("Expected error for invalid endpoint")
3108 | }
3109 | // This should fail at the HTTP request stage, which is expected
3110 | if !strings.Contains(err.Error(), "OTP generation failed") {
3111 | t.Logf("Got expected error: %s", err.Error())
3112 | }
3113 | })
3114 |
3115 | t.Run("HTTP request creation failure", func(t *testing.T) {
3116 | // Test with invalid characters that would cause http.NewRequest to fail
3117 | // This is difficult to trigger in practice, so we'll test URL validation
3118 | err := sendOtp("https://api.razorpay.com/v1/payments\x00/otp")
3119 | if err == nil {
3120 | t.Error("Expected error for invalid URL characters")
3121 | }
3122 | })
3123 | }
3124 |
3125 | // Test for extractPaymentID function
3126 | func TestExtractPaymentID(t *testing.T) {
3127 | t.Run("payment ID exists", func(t *testing.T) {
3128 | payment := map[string]interface{}{
3129 | "razorpay_payment_id": "pay_test123",
3130 | "other_field": "value",
3131 | }
3132 | result := extractPaymentID(payment)
3133 | if result != "pay_test123" {
3134 | t.Errorf("Expected 'pay_test123', got '%s'", result)
3135 | }
3136 | })
3137 |
3138 | t.Run("payment ID missing", func(t *testing.T) {
3139 | payment := map[string]interface{}{
3140 | "other_field": "value",
3141 | }
3142 | result := extractPaymentID(payment)
3143 | if result != "" {
3144 | t.Errorf("Expected empty string, got '%s'", result)
3145 | }
3146 | })
3147 |
3148 | t.Run("payment ID is nil", func(t *testing.T) {
3149 | payment := map[string]interface{}{
3150 | "razorpay_payment_id": nil,
3151 | }
3152 | result := extractPaymentID(payment)
3153 | if result != "" {
3154 | t.Errorf("Expected empty string, got '%s'", result)
3155 | }
3156 | })
3157 | }
3158 |
3159 | // Test for extractNextActions function
3160 | func TestExtractNextActions(t *testing.T) {
3161 | t.Run("next actions exist", func(t *testing.T) {
3162 | payment := map[string]interface{}{
3163 | "next": []interface{}{
3164 | map[string]interface{}{
3165 | "action": "redirect",
3166 | "url": "https://example.com",
3167 | },
3168 | map[string]interface{}{
3169 | "action": "otp",
3170 | "url": "https://otp.example.com",
3171 | },
3172 | },
3173 | }
3174 | result := extractNextActions(payment)
3175 | if len(result) != 2 {
3176 | t.Errorf("Expected 2 actions, got %d", len(result))
3177 | }
3178 | if result[0]["action"] != "redirect" {
3179 | t.Errorf("Expected first action to be 'redirect', got '%s'",
3180 | result[0]["action"])
3181 | }
3182 | })
3183 |
3184 | t.Run("next field missing", func(t *testing.T) {
3185 | payment := map[string]interface{}{
3186 | "other_field": "value",
3187 | }
3188 | result := extractNextActions(payment)
3189 | if len(result) != 0 {
3190 | t.Errorf("Expected empty slice, got %d actions", len(result))
3191 | }
3192 | })
3193 |
3194 | t.Run("next field is nil", func(t *testing.T) {
3195 | payment := map[string]interface{}{
3196 | "next": nil,
3197 | }
3198 | result := extractNextActions(payment)
3199 | if len(result) != 0 {
3200 | t.Errorf("Expected empty slice, got %d actions", len(result))
3201 | }
3202 | })
3203 |
3204 | t.Run("next field is not a slice", func(t *testing.T) {
3205 | payment := map[string]interface{}{
3206 | "next": "invalid_type",
3207 | }
3208 | result := extractNextActions(payment)
3209 | if len(result) != 0 {
3210 | t.Errorf("Expected empty slice, got %d actions", len(result))
3211 | }
3212 | })
3213 |
3214 | t.Run("next field contains non-map items", func(t *testing.T) {
3215 | payment := map[string]interface{}{
3216 | "next": []interface{}{
3217 | "invalid_item",
3218 | map[string]interface{}{
3219 | "action": "valid_action",
3220 | },
3221 | },
3222 | }
3223 | result := extractNextActions(payment)
3224 | if len(result) != 1 {
3225 | t.Errorf("Expected 1 valid action, got %d", len(result))
3226 | }
3227 | if result[0]["action"] != "valid_action" {
3228 | t.Errorf("Expected action to be 'valid_action', got '%s'",
3229 | result[0]["action"])
3230 | }
3231 | })
3232 | }
3233 |
3234 | // Test for addNextStepInstructions function
3235 | func TestAddNextStepInstructions(t *testing.T) {
3236 | t.Run("add next step instructions with payment ID", func(t *testing.T) {
3237 | result := make(map[string]interface{})
3238 | paymentID := "pay_test123"
3239 |
3240 | addNextStepInstructions(result, paymentID)
3241 |
3242 | nextStep, exists := result["next_step"]
3243 | if !exists {
3244 | t.Error("Expected next_step to be added")
3245 | }
3246 |
3247 | nextStepStr, ok := nextStep.(string)
3248 | if !ok {
3249 | t.Error("Expected next_step to be a string")
3250 | }
3251 |
3252 | if !strings.Contains(nextStepStr, "resend_otp") {
3253 | t.Error("Expected instructions to contain 'resend_otp'")
3254 | }
3255 | if !strings.Contains(nextStepStr, "submit_otp") {
3256 | t.Error("Expected instructions to contain 'submit_otp'")
3257 | }
3258 |
3259 | // Check next_tool
3260 | nextTool, exists := result["next_tool"]
3261 | if !exists {
3262 | t.Error("Expected next_tool to be added")
3263 | }
3264 | if nextTool != "resend_otp" {
3265 | t.Errorf("Expected next_tool to be 'resend_otp', got '%s'", nextTool)
3266 | }
3267 |
3268 | // Check next_tool_params
3269 | params, exists := result["next_tool_params"]
3270 | if !exists {
3271 | t.Error("Expected next_tool_params to be added")
3272 | }
3273 | paramsMap, ok := params.(map[string]interface{})
3274 | if !ok {
3275 | t.Error("Expected next_tool_params to be a map")
3276 | }
3277 | if paramsMap["payment_id"] != paymentID {
3278 | t.Errorf("Expected payment_id to be '%s', got '%s'",
3279 | paymentID, paramsMap["payment_id"])
3280 | }
3281 | })
3282 |
3283 | t.Run("empty payment ID", func(t *testing.T) {
3284 | result := make(map[string]interface{})
3285 |
3286 | addNextStepInstructions(result, "")
3287 |
3288 | // Should not add anything when payment ID is empty
3289 | if len(result) != 0 {
3290 | t.Error("Expected no fields to be added for empty payment ID")
3291 | }
3292 | })
3293 | }
3294 |
3295 | // Test for processUPIParameters function
3296 | func TestProcessUPIParameters(t *testing.T) {
3297 | t.Run("processUPIParameters", func(t *testing.T) {
3298 | // Test with VPA
3299 | params := map[string]interface{}{
3300 | "vpa": "test@upi",
3301 | }
3302 | processUPIParameters(params)
3303 |
3304 | if params["method"] != "upi" {
3305 | t.Errorf("Expected method to be 'upi', got '%s'", params["method"])
3306 | }
3307 |
3308 | upi, exists := params["upi"]
3309 | if !exists {
3310 | t.Error("Expected upi field to be added")
3311 | }
3312 |
3313 | upiMap, ok := upi.(map[string]interface{})
3314 | if !ok {
3315 | t.Error("Expected upi to be a map")
3316 | }
3317 |
3318 | if upiMap["vpa"] != "test@upi" {
3319 | t.Errorf("Expected vpa to be 'test@upi', got '%s'", upiMap["vpa"])
3320 | }
3321 | if upiMap["flow"] != "collect" {
3322 | t.Errorf("Expected flow to be 'collect', got '%s'", upiMap["flow"])
3323 | }
3324 | })
3325 |
3326 | t.Run("processUPIParameters - UPI intent", func(t *testing.T) {
3327 | // Test with UPI intent
3328 | params := map[string]interface{}{
3329 | "upi_intent": true,
3330 | }
3331 | processUPIParameters(params)
3332 |
3333 | if params["method"] != "upi" {
3334 | t.Errorf("Expected method to be 'upi', got '%s'", params["method"])
3335 | }
3336 |
3337 | upi, exists := params["upi"]
3338 | if !exists {
3339 | t.Error("Expected upi field to be added")
3340 | }
3341 |
3342 | upiMap, ok := upi.(map[string]interface{})
3343 | if !ok {
3344 | t.Error("Expected upi to be a map")
3345 | }
3346 |
3347 | if upiMap["flow"] != "intent" {
3348 | t.Errorf("Expected flow to be 'intent', got '%s'", upiMap["flow"])
3349 | }
3350 | })
3351 |
3352 | t.Run("processUPIParameters - no UPI params", func(t *testing.T) {
3353 | // Test with no UPI parameters
3354 | params := map[string]interface{}{
3355 | "amount": 1000,
3356 | }
3357 | processUPIParameters(params)
3358 |
3359 | // Should not modify params when no UPI parameters are present
3360 | if _, exists := params["method"]; exists {
3361 | t.Error("Expected method not to be added when no UPI params")
3362 | }
3363 | if _, exists := params["upi"]; exists {
3364 | t.Error("Expected upi not to be added when no UPI params")
3365 | }
3366 | })
3367 |
3368 | }
3369 |
3370 | // Test for createOrGetCustomer function
3371 | func TestCreateOrGetCustomer(t *testing.T) {
3372 | t.Run("createOrGetCustomer - no contact", func(t *testing.T) {
3373 | // Test with no contact parameter
3374 | params := map[string]interface{}{
3375 | "amount": 1000,
3376 | }
3377 |
3378 | // This should return nil, nil since no contact is provided
3379 | result, err := createOrGetCustomer(nil, params)
3380 |
3381 | if result != nil {
3382 | t.Error("Expected nil result when no contact provided")
3383 | }
3384 | if err != nil {
3385 | t.Errorf("Expected no error when no contact provided, got %v", err)
3386 | }
3387 | })
3388 | }
3389 |
3390 | // Test for buildPaymentData function
3391 | func TestBuildPaymentData(t *testing.T) {
3392 | t.Run("buildPaymentData", func(t *testing.T) {
3393 | params := map[string]interface{}{
3394 | "amount": 1000,
3395 | "order_id": "order_test123",
3396 | }
3397 | currency := "INR"
3398 | customerId := "cust_test123"
3399 |
3400 | result := buildPaymentData(params, currency, customerId)
3401 |
3402 | if (*result)["amount"] != 1000 {
3403 | t.Errorf("Expected amount to be 1000, got %v", (*result)["amount"])
3404 | }
3405 | if (*result)["currency"] != "INR" {
3406 | t.Errorf("Expected currency to be 'INR', got '%s'", (*result)["currency"])
3407 | }
3408 | if (*result)["customer_id"] != customerId {
3409 | t.Errorf("Expected customer_id to be '%s', got '%s'",
3410 | customerId, (*result)["customer_id"])
3411 | }
3412 | })
3413 |
3414 | t.Run("buildPaymentData - no customer ID", func(t *testing.T) {
3415 | params := map[string]interface{}{
3416 | "amount": 1000,
3417 | "order_id": "order_test123",
3418 | }
3419 | currency := "INR"
3420 | customerId := ""
3421 |
3422 | result := buildPaymentData(params, currency, customerId)
3423 |
3424 | if (*result)["amount"] != 1000 {
3425 | t.Errorf("Expected amount to be 1000, got %v", (*result)["amount"])
3426 | }
3427 | if (*result)["currency"] != "INR" {
3428 | t.Errorf("Expected currency to be 'INR', got '%s'", (*result)["currency"])
3429 | }
3430 | // Should not have customer_id when empty
3431 | if _, exists := (*result)["customer_id"]; exists {
3432 | t.Error("Expected no customer_id when empty string provided")
3433 | }
3434 | })
3435 |
3436 | }
3437 |
3438 | // Test for processPaymentResult function
3439 | func TestProcessPaymentResult(t *testing.T) {
3440 | t.Run("processPaymentResult", func(t *testing.T) {
3441 | paymentResult := map[string]interface{}{
3442 | "razorpay_payment_id": "pay_test123",
3443 | "status": "created",
3444 | "next": []interface{}{
3445 | map[string]interface{}{
3446 | "action": "redirect",
3447 | "url": "https://example.com",
3448 | },
3449 | },
3450 | }
3451 |
3452 | result, err := processPaymentResult(paymentResult)
3453 |
3454 | if err != nil {
3455 | t.Errorf("Expected no error, got %v", err)
3456 | }
3457 |
3458 | if result["razorpay_payment_id"] != "pay_test123" {
3459 | t.Errorf("Expected payment ID, got %v", result["razorpay_payment_id"])
3460 | }
3461 |
3462 | if result["status"] != "payment_initiated" {
3463 | t.Errorf("Expected status to be 'payment_initiated', got '%s'",
3464 | result["status"])
3465 | }
3466 | })
3467 |
3468 | t.Run("processPaymentResult - with error", func(t *testing.T) {
3469 | // Test with payment result that might cause an error
3470 | paymentResult := map[string]interface{}{
3471 | "error": map[string]interface{}{
3472 | "code": "BAD_REQUEST_ERROR",
3473 | "description": "Invalid payment data",
3474 | },
3475 | }
3476 |
3477 | result, err := processPaymentResult(paymentResult)
3478 |
3479 | // The function should handle this gracefully
3480 | if err != nil && result == nil {
3481 | t.Logf("Expected behavior - got error: %v", err)
3482 | } else if result != nil {
3483 | // If no error, result should be properly processed
3484 | if result["status"] != "payment_initiated" {
3485 | t.Errorf("Expected status to be 'payment_initiated', got '%s'",
3486 | result["status"])
3487 | }
3488 | }
3489 | })
3490 |
3491 | }
3492 |
3493 | // Test for extractOtpSubmitURL function
3494 | func TestExtractOtpSubmitURL(t *testing.T) {
3495 | t.Run("extractOtpSubmitURL", func(t *testing.T) {
3496 | // Test with valid response data containing OTP submit URL
3497 | responseData := map[string]interface{}{
3498 | "next": []interface{}{
3499 | map[string]interface{}{
3500 | "action": "redirect",
3501 | "url": "https://example.com/redirect",
3502 | },
3503 | map[string]interface{}{
3504 | "action": "otp_submit",
3505 | "url": "https://example.com/otp/submit",
3506 | },
3507 | },
3508 | }
3509 |
3510 | result := extractOtpSubmitURL(responseData)
3511 | if result != "https://example.com/otp/submit" {
3512 | t.Errorf("Expected OTP submit URL, got '%s'", result)
3513 | }
3514 | })
3515 |
3516 | t.Run("extractOtpSubmitURL - no OTP action", func(t *testing.T) {
3517 | responseData := map[string]interface{}{
3518 | "next": []interface{}{
3519 | map[string]interface{}{
3520 | "action": "redirect",
3521 | "url": "https://example.com/redirect",
3522 | },
3523 | },
3524 | }
3525 |
3526 | result := extractOtpSubmitURL(responseData)
3527 | if result != "" {
3528 | t.Errorf("Expected empty string, got '%s'", result)
3529 | }
3530 | })
3531 |
3532 | t.Run("extractOtpSubmitURL - invalid input", func(t *testing.T) {
3533 | // Test with invalid input type
3534 | result := extractOtpSubmitURL("invalid_input")
3535 | if result != "" {
3536 | t.Errorf("Expected empty string for invalid input, got '%s'", result)
3537 | }
3538 | })
3539 |
3540 | t.Run("extractOtpSubmitURL - no next field", func(t *testing.T) {
3541 | responseData := map[string]interface{}{
3542 | "other_field": "value",
3543 | }
3544 |
3545 | result := extractOtpSubmitURL(responseData)
3546 | if result != "" {
3547 | t.Errorf("Expected empty string when no next field, got '%s'", result)
3548 | }
3549 | })
3550 | }
3551 |
3552 | // TestPayments100PercentCoverage_FetchPayment tests FetchPayment coverage
3553 | func TestPayments100PercentCoverage_FetchPayment(t *testing.T) {
3554 | // Test FetchPayment with SDK errors
3555 | t.Run("FetchPayment - SDK error", func(t *testing.T) {
3556 | testCase := RazorpayToolTestCase{
3557 | Name: "SDK error",
3558 | Request: map[string]interface{}{
3559 | "payment_id": "pay_test123",
3560 | },
3561 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3562 | return mock.NewHTTPClient(
3563 | mock.Endpoint{
3564 | Path: "/v1/payments/pay_test123",
3565 | Method: "GET",
3566 | Response: map[string]interface{}{
3567 | "error": map[string]interface{}{
3568 | "code": "BAD_REQUEST_ERROR",
3569 | "description": "Invalid payment ID",
3570 | },
3571 | },
3572 | },
3573 | )
3574 | },
3575 | ExpectError: true,
3576 | ExpectedErrMsg: "fetching payment failed",
3577 | }
3578 | runToolTest(t, testCase, FetchPayment, "Payment")
3579 | })
3580 |
3581 | }
3582 |
3583 | // TestPayments100PercentCoverage_FetchPaymentCardDetails tests
3584 | // FetchPaymentCardDetails coverage
3585 | func TestPayments100PercentCoverage_FetchPaymentCardDetails(t *testing.T) {
3586 | // Test FetchPaymentCardDetails with SDK errors
3587 | t.Run("FetchPaymentCardDetails - SDK error", func(t *testing.T) {
3588 | testCase := RazorpayToolTestCase{
3589 | Name: "SDK error",
3590 | Request: map[string]interface{}{
3591 | "payment_id": "pay_test123",
3592 | },
3593 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3594 | return mock.NewHTTPClient(
3595 | mock.Endpoint{
3596 | Path: "/v1/payments/pay_test123/card",
3597 | Method: "GET",
3598 | Response: map[string]interface{}{
3599 | "error": map[string]interface{}{
3600 | "code": "BAD_REQUEST_ERROR",
3601 | "description": "Card details not available",
3602 | },
3603 | },
3604 | },
3605 | )
3606 | },
3607 | ExpectError: true,
3608 | ExpectedErrMsg: "fetching card details failed",
3609 | }
3610 | runToolTest(t, testCase, FetchPaymentCardDetails, "PaymentCardDetails")
3611 | })
3612 |
3613 | }
3614 |
3615 | // TestPayments100PercentCoverage_UpdatePayment tests UpdatePayment coverage
3616 | func TestPayments100PercentCoverage_UpdatePayment(t *testing.T) {
3617 | // Test UpdatePayment with SDK errors
3618 | t.Run("UpdatePayment - SDK error", func(t *testing.T) {
3619 | testCase := RazorpayToolTestCase{
3620 | Name: "SDK error",
3621 | Request: map[string]interface{}{
3622 | "payment_id": "pay_test123",
3623 | "notes": map[string]interface{}{
3624 | "key": "value",
3625 | },
3626 | },
3627 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3628 | return mock.NewHTTPClient(
3629 | mock.Endpoint{
3630 | Path: "/v1/payments/pay_test123",
3631 | Method: "PATCH",
3632 | Response: map[string]interface{}{
3633 | "error": map[string]interface{}{
3634 | "code": "BAD_REQUEST_ERROR",
3635 | "description": "Invalid notes",
3636 | },
3637 | },
3638 | },
3639 | )
3640 | },
3641 | ExpectError: true,
3642 | ExpectedErrMsg: "updating payment failed",
3643 | }
3644 | runToolTest(t, testCase, UpdatePayment, "Payment")
3645 | })
3646 |
3647 | }
3648 |
3649 | // TestPayments100PercentCoverage_CapturePayment tests CapturePayment coverage
3650 | func TestPayments100PercentCoverage_CapturePayment(t *testing.T) {
3651 | // Test CapturePayment with SDK errors
3652 | t.Run("CapturePayment - SDK error", func(t *testing.T) {
3653 | testCase := RazorpayToolTestCase{
3654 | Name: "SDK error",
3655 | Request: map[string]interface{}{
3656 | "payment_id": "pay_test123",
3657 | "amount": 1000,
3658 | "currency": "INR",
3659 | },
3660 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3661 | return mock.NewHTTPClient(
3662 | mock.Endpoint{
3663 | Path: "/v1/payments/pay_test123/capture",
3664 | Method: "POST",
3665 | Response: map[string]interface{}{
3666 | "error": map[string]interface{}{
3667 | "code": "BAD_REQUEST_ERROR",
3668 | "description": "Payment cannot be captured",
3669 | },
3670 | },
3671 | },
3672 | )
3673 | },
3674 | ExpectError: true,
3675 | ExpectedErrMsg: "capturing payment failed",
3676 | }
3677 | runToolTest(t, testCase, CapturePayment, "Payment")
3678 | })
3679 |
3680 | }
3681 |
3682 | // TestPayments100PercentCoverage_FetchAllPayments tests
3683 | // FetchAllPayments coverage
3684 | func TestPayments100PercentCoverage_FetchAllPayments(t *testing.T) {
3685 | // Test FetchAllPayments with SDK errors
3686 | t.Run("FetchAllPayments - SDK error", func(t *testing.T) {
3687 | testCase := RazorpayToolTestCase{
3688 | Name: "SDK error",
3689 | Request: map[string]interface{}{
3690 | "count": 10,
3691 | },
3692 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3693 | return mock.NewHTTPClient(
3694 | mock.Endpoint{
3695 | Path: "/v1/payments",
3696 | Method: "GET",
3697 | Response: map[string]interface{}{
3698 | "error": map[string]interface{}{
3699 | "code": "BAD_REQUEST_ERROR",
3700 | "description": "Invalid request",
3701 | },
3702 | },
3703 | },
3704 | )
3705 | },
3706 | ExpectError: true,
3707 | ExpectedErrMsg: "fetching payments failed",
3708 | }
3709 | runToolTest(t, testCase, FetchAllPayments, "Collection")
3710 | })
3711 |
3712 | }
3713 |
3714 | // TestPayments100PercentCoverage_ResendOtp tests ResendOtp coverage
3715 | func TestPayments100PercentCoverage_ResendOtp(t *testing.T) {
3716 | // Test ResendOtp with SDK errors
3717 | t.Run("ResendOtp - SDK error", func(t *testing.T) {
3718 | testCase := RazorpayToolTestCase{
3719 | Name: "SDK error",
3720 | Request: map[string]interface{}{
3721 | "payment_id": "pay_test123",
3722 | },
3723 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3724 | return mock.NewHTTPClient(
3725 | mock.Endpoint{
3726 | Path: "/v1/payments/pay_test123/otp/resend",
3727 | Method: "POST",
3728 | Response: map[string]interface{}{
3729 | "error": map[string]interface{}{
3730 | "code": "BAD_REQUEST_ERROR",
3731 | "description": "Cannot resend OTP",
3732 | },
3733 | },
3734 | },
3735 | )
3736 | },
3737 | ExpectError: true,
3738 | ExpectedErrMsg: "OTP resend failed",
3739 | }
3740 | runToolTest(t, testCase, ResendOtp, "ResendOtp")
3741 | })
3742 |
3743 | }
3744 |
3745 | // TestPayments100PercentCoverage_SubmitOtp tests SubmitOtp coverage
3746 | func TestPayments100PercentCoverage_SubmitOtp(t *testing.T) {
3747 | // Test SubmitOtp with SDK errors
3748 | t.Run("SubmitOtp - SDK error", func(t *testing.T) {
3749 | testCase := RazorpayToolTestCase{
3750 | Name: "SDK error",
3751 | Request: map[string]interface{}{
3752 | "payment_id": "pay_test123",
3753 | "otp_string": "123456",
3754 | },
3755 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3756 | return mock.NewHTTPClient(
3757 | mock.Endpoint{
3758 | Path: "/v1/payments/pay_test123/otp/submit",
3759 | Method: "POST",
3760 | Response: map[string]interface{}{
3761 | "error": map[string]interface{}{
3762 | "code": "BAD_REQUEST_ERROR",
3763 | "description": "Invalid OTP",
3764 | },
3765 | },
3766 | },
3767 | )
3768 | },
3769 | ExpectError: true,
3770 | ExpectedErrMsg: "OTP verification failed",
3771 | }
3772 | runToolTest(t, testCase, SubmitOtp, "SubmitOtp")
3773 | })
3774 |
3775 | }
3776 |
3777 | // TestPayments100PercentCoverage_InitiatePayment tests InitiatePayment coverage
3778 | func TestPayments100PercentCoverage_InitiatePayment(t *testing.T) {
3779 |
3780 | // Test InitiatePayment with errors
3781 | t.Run("InitiatePayment - SDK error", func(t *testing.T) {
3782 | testCase := RazorpayToolTestCase{
3783 | Name: "SDK error",
3784 | Request: map[string]interface{}{
3785 | "amount": 1000,
3786 | "currency": "INR",
3787 | "order_id": "order_test123",
3788 | },
3789 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3790 | return mock.NewHTTPClient(
3791 | mock.Endpoint{
3792 | Path: "/v1/payments/create/json",
3793 | Method: "POST",
3794 | Response: map[string]interface{}{
3795 | "error": map[string]interface{}{
3796 | "code": "BAD_REQUEST_ERROR",
3797 | "description": "Invalid payment data",
3798 | },
3799 | },
3800 | },
3801 | )
3802 | },
3803 | ExpectError: true,
3804 | ExpectedErrMsg: "initiating payment failed",
3805 | }
3806 | runToolTest(t, testCase, InitiatePayment, "InitiatePayment")
3807 | })
3808 |
3809 | // Test sendOtp with HTTP error status
3810 | t.Run("sendOtp - HTTP error status", func(t *testing.T) {
3811 | server := httptest.NewTLSServer(
3812 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3813 | w.WriteHeader(http.StatusBadRequest)
3814 | }))
3815 | defer server.Close()
3816 |
3817 | // Replace domain to pass validation
3818 | testURL := strings.Replace(
3819 | server.URL, server.URL[8:], "api.razorpay.com/v1/payments/otp", 1)
3820 | err := sendOtp(testURL)
3821 | if err == nil {
3822 | t.Error("Expected error for HTTP error status")
3823 | }
3824 | if !strings.Contains(err.Error(), "OTP generation failed with HTTP status") {
3825 | t.Logf("Got expected error: %s", err.Error())
3826 | }
3827 | })
3828 |
3829 | // More aggressive tests - hitting every error path!
3830 | t.Run("sendOtp - request creation error", func(t *testing.T) {
3831 | // Test with malformed URL that passes parsing but fails request creation
3832 | err := sendOtp("https://api.razorpay.com:99999/invalid")
3833 | if err == nil {
3834 | t.Error("Expected error for malformed URL")
3835 | }
3836 | if !strings.Contains(err.Error(), "failed to create OTP request") &&
3837 | !strings.Contains(err.Error(), "OTP generation failed") {
3838 | t.Logf("Got expected error: %s", err.Error())
3839 | }
3840 | })
3841 |
3842 | // Test with extremely long URL to trigger different error paths
3843 | t.Run("sendOtp - extreme URL", func(t *testing.T) {
3844 | longPath := strings.Repeat("a", 10000)
3845 | testURL := "https://api.razorpay.com/v1/payments/" + longPath + "/otp"
3846 | err := sendOtp(testURL)
3847 | if err == nil {
3848 | t.Error("Expected error for extreme URL")
3849 | }
3850 | })
3851 |
3852 | }
3853 |
3854 | // contextKey is a type for context keys to avoid collisions
3855 | type contextKey string
3856 |
3857 | // TestPayments100PercentCoverage_ContextErrors tests context error paths
3858 | func TestPayments100PercentCoverage_ContextErrors(t *testing.T) {
3859 | // Test getClientFromContextOrDefault error path
3860 | t.Run("SubmitOtp - client context error", func(t *testing.T) {
3861 | // Create context with invalid client
3862 | ctx := context.WithValue(
3863 | context.Background(), contextKey("invalid_key"), "invalid_value")
3864 |
3865 | tool := SubmitOtp(nil, nil)
3866 | request := mcpgo.CallToolRequest{
3867 | Arguments: map[string]interface{}{
3868 | "payment_id": "pay_test123",
3869 | "otp_string": "123456",
3870 | },
3871 | }
3872 |
3873 | result, err := tool.GetHandler()(ctx, request)
3874 | if err != nil {
3875 | t.Errorf("Expected no error, got %v", err)
3876 | }
3877 | if result == nil || !result.IsError {
3878 | t.Error("Expected error result for invalid client context")
3879 | }
3880 | })
3881 |
3882 | // Test sendOtp with actual HTTP client failure
3883 | t.Run("sendOtp - HTTP client failure", func(t *testing.T) {
3884 | // Test with a URL that will fail at the HTTP client level
3885 | err := sendOtp("https://api.razorpay.com:99999/invalid/path/that/will/fail")
3886 | if err == nil {
3887 | t.Error("Expected error for HTTP client failure")
3888 | }
3889 | if !strings.Contains(err.Error(), "OTP generation failed") {
3890 | t.Logf("Got expected error: %s", err.Error())
3891 | }
3892 | })
3893 |
3894 | }
3895 |
3896 | // TestPayments100PercentCoverage_ContextErrors2 tests more context error paths
3897 | func TestPayments100PercentCoverage_ContextErrors2(t *testing.T) {
3898 | // Test InitiatePayment - getClientFromContextOrDefault error
3899 | t.Run("InitiatePayment - client context error", func(t *testing.T) {
3900 | // Create context without client
3901 | ctx := context.Background()
3902 |
3903 | tool := InitiatePayment(nil, nil)
3904 | request := mcpgo.CallToolRequest{
3905 | Arguments: map[string]interface{}{
3906 | "amount": 1000,
3907 | "order_id": "order_test123",
3908 | },
3909 | }
3910 |
3911 | result, err := tool.GetHandler()(ctx, request)
3912 | if err != nil {
3913 | t.Errorf("Expected no error, got %v", err)
3914 | }
3915 | if result == nil || !result.IsError {
3916 | t.Error("Expected error result for missing client context")
3917 | }
3918 | if !strings.Contains(result.Text, "no client found in context") {
3919 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
3920 | }
3921 | })
3922 |
3923 | // Simple working test
3924 | t.Run("InitiatePayment - basic test", func(t *testing.T) {
3925 | testCase := RazorpayToolTestCase{
3926 | Name: "basic test",
3927 | Request: map[string]interface{}{
3928 | "amount": 1000,
3929 | "order_id": "order_test123",
3930 | },
3931 | MockHttpClient: func() (*http.Client, *httptest.Server) {
3932 | return mock.NewHTTPClient(
3933 | mock.Endpoint{
3934 | Path: "/v1/payments/create/json",
3935 | Method: "POST",
3936 | Response: map[string]interface{}{
3937 | "id": "pay_test123",
3938 | "entity": "payment",
3939 | "amount": 1000,
3940 | "currency": "INR",
3941 | "status": "created",
3942 | "invalid": make(chan int), // This causes JSON marshal to fail
3943 | "created_at": 1234567890,
3944 | },
3945 | },
3946 | )
3947 | },
3948 | ExpectError: true,
3949 | ExpectedErrMsg: "failed",
3950 | }
3951 | runToolTest(t, testCase, InitiatePayment, "Payment")
3952 | })
3953 |
3954 | }
3955 |
3956 | // TestPayments100PercentCoverage_ContextErrors3 tests remaining
3957 | // context error paths
3958 | func TestPayments100PercentCoverage_ContextErrors3(t *testing.T) {
3959 | // Test FetchPayment - getClientFromContextOrDefault error
3960 | t.Run("FetchPayment - client context error", func(t *testing.T) {
3961 | // Create context without client
3962 | ctx := context.Background()
3963 |
3964 | tool := FetchPayment(nil, nil)
3965 | request := mcpgo.CallToolRequest{
3966 | Arguments: map[string]interface{}{
3967 | "payment_id": "pay_test123",
3968 | },
3969 | }
3970 |
3971 | result, err := tool.GetHandler()(ctx, request)
3972 | if err != nil {
3973 | t.Errorf("Expected no error, got %v", err)
3974 | }
3975 | if result == nil || !result.IsError {
3976 | t.Error("Expected error result for missing client context")
3977 | }
3978 | if !strings.Contains(result.Text, "no client found in context") {
3979 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
3980 | }
3981 | })
3982 |
3983 | // Test FetchPaymentCardDetails - getClientFromContextOrDefault error
3984 | t.Run("FetchPaymentCardDetails - client context error", func(t *testing.T) {
3985 | // Create context without client
3986 | ctx := context.Background()
3987 |
3988 | tool := FetchPaymentCardDetails(nil, nil)
3989 | request := mcpgo.CallToolRequest{
3990 | Arguments: map[string]interface{}{
3991 | "payment_id": "pay_test123",
3992 | },
3993 | }
3994 |
3995 | result, err := tool.GetHandler()(ctx, request)
3996 | if err != nil {
3997 | t.Errorf("Expected no error, got %v", err)
3998 | }
3999 | if result == nil || !result.IsError {
4000 | t.Error("Expected error result for missing client context")
4001 | }
4002 | if !strings.Contains(result.Text, "no client found in context") {
4003 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
4004 | }
4005 | })
4006 |
4007 | // Test FetchPaymentCardDetails - JSON marshal error
4008 | // (using channel in response)
4009 | t.Run("FetchPaymentCardDetails - JSON marshal error", func(t *testing.T) {
4010 | testCase := RazorpayToolTestCase{
4011 | Name: "JSON marshal error",
4012 | Request: map[string]interface{}{
4013 | "payment_id": "pay_test123",
4014 | },
4015 | MockHttpClient: func() (*http.Client, *httptest.Server) {
4016 | return mock.NewHTTPClient(
4017 | mock.Endpoint{
4018 | Path: "/v1/payments/pay_test123/card",
4019 | Method: "GET",
4020 | Response: map[string]interface{}{
4021 | "id": "card_test123",
4022 | "entity": "card",
4023 | "name": "Test User",
4024 | "last4": "1234",
4025 | "network": "Visa",
4026 | "type": "credit",
4027 | "invalid": make(chan int), // This causes JSON marshal to fail
4028 | "created_at": 1234567890,
4029 | },
4030 | },
4031 | )
4032 | },
4033 | ExpectError: true,
4034 | ExpectedErrMsg: "failed",
4035 | }
4036 | runToolTest(t, testCase, FetchPaymentCardDetails, "Card Details")
4037 | })
4038 |
4039 | }
4040 |
4041 | // TestPayments100PercentCoverage_ContextErrors4 tests final context error paths
4042 | func TestPayments100PercentCoverage_ContextErrors4(t *testing.T) {
4043 | // Test CapturePayment - getClientFromContextOrDefault error
4044 | t.Run("CapturePayment - client context error", func(t *testing.T) {
4045 | // Create context without client
4046 | ctx := context.Background()
4047 |
4048 | tool := CapturePayment(nil, nil)
4049 | request := mcpgo.CallToolRequest{
4050 | Arguments: map[string]interface{}{
4051 | "payment_id": "pay_test123",
4052 | "amount": 1000,
4053 | "currency": "INR",
4054 | },
4055 | }
4056 |
4057 | result, err := tool.GetHandler()(ctx, request)
4058 | if err != nil {
4059 | t.Errorf("Expected no error, got %v", err)
4060 | }
4061 | if result == nil || !result.IsError {
4062 | t.Error("Expected error result for missing client context")
4063 | }
4064 | if !strings.Contains(result.Text, "no client found in context") {
4065 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
4066 | }
4067 | })
4068 |
4069 | // Test CapturePayment - JSON marshal error (using channel in response)
4070 | t.Run("CapturePayment - JSON marshal error", func(t *testing.T) {
4071 | testCase := RazorpayToolTestCase{
4072 | Name: "JSON marshal error",
4073 | Request: map[string]interface{}{
4074 | "payment_id": "pay_test123",
4075 | "amount": 1000,
4076 | "currency": "INR",
4077 | },
4078 | MockHttpClient: func() (*http.Client, *httptest.Server) {
4079 | return mock.NewHTTPClient(
4080 | mock.Endpoint{
4081 | Path: "/v1/payments/pay_test123/capture",
4082 | Method: "POST",
4083 | Response: map[string]interface{}{
4084 | "id": "pay_test123",
4085 | "entity": "payment",
4086 | "amount": 1000,
4087 | "currency": "INR",
4088 | "status": "captured",
4089 | "invalid": make(chan int), // This causes JSON marshal to fail
4090 | "created_at": 1234567890,
4091 | },
4092 | },
4093 | )
4094 | },
4095 | ExpectError: true,
4096 | ExpectedErrMsg: "failed",
4097 | }
4098 | runToolTest(t, testCase, CapturePayment, "Payment")
4099 | })
4100 |
4101 | // Test UpdatePayment - getClientFromContextOrDefault error
4102 | t.Run("UpdatePayment - client context error", func(t *testing.T) {
4103 | // Create context without client
4104 | ctx := context.Background()
4105 |
4106 | tool := UpdatePayment(nil, nil)
4107 | request := mcpgo.CallToolRequest{
4108 | Arguments: map[string]interface{}{
4109 | "payment_id": "pay_test123",
4110 | "notes": map[string]interface{}{
4111 | "key": "value",
4112 | },
4113 | },
4114 | }
4115 |
4116 | result, err := tool.GetHandler()(ctx, request)
4117 | if err != nil {
4118 | t.Errorf("Expected no error, got %v", err)
4119 | }
4120 | if result == nil || !result.IsError {
4121 | t.Error("Expected error result for missing client context")
4122 | }
4123 | if !strings.Contains(result.Text, "no client found in context") {
4124 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
4125 | }
4126 | })
4127 |
4128 | // Test UpdatePayment - JSON marshal error (using channel in response)
4129 | t.Run("UpdatePayment - JSON marshal error", func(t *testing.T) {
4130 | testCase := RazorpayToolTestCase{
4131 | Name: "JSON marshal error",
4132 | Request: map[string]interface{}{
4133 | "payment_id": "pay_test123",
4134 | "notes": map[string]interface{}{
4135 | "key": "value",
4136 | },
4137 | },
4138 | MockHttpClient: func() (*http.Client, *httptest.Server) {
4139 | return mock.NewHTTPClient(
4140 | mock.Endpoint{
4141 | Path: "/v1/payments/pay_test123",
4142 | Method: "PATCH",
4143 | Response: map[string]interface{}{
4144 | "id": "pay_test123",
4145 | "entity": "payment",
4146 | "amount": 1000,
4147 | "currency": "INR",
4148 | "status": "authorized",
4149 | "invalid": make(chan int), // This causes JSON marshal to fail
4150 | "notes": map[string]interface{}{
4151 | "key": "value",
4152 | },
4153 | "created_at": 1234567890,
4154 | },
4155 | },
4156 | )
4157 | },
4158 | ExpectError: true,
4159 | ExpectedErrMsg: "failed",
4160 | }
4161 | runToolTest(t, testCase, UpdatePayment, "Payment")
4162 | })
4163 |
4164 | // Test FetchAllPayments - getClientFromContextOrDefault error
4165 | t.Run("FetchAllPayments - client context error", func(t *testing.T) {
4166 | // Create context without client
4167 | ctx := context.Background()
4168 |
4169 | tool := FetchAllPayments(nil, nil)
4170 | request := mcpgo.CallToolRequest{
4171 | Arguments: map[string]interface{}{
4172 | "count": 10,
4173 | },
4174 | }
4175 |
4176 | result, err := tool.GetHandler()(ctx, request)
4177 | if err != nil {
4178 | t.Errorf("Expected no error, got %v", err)
4179 | }
4180 | if result == nil || !result.IsError {
4181 | t.Error("Expected error result for missing client context")
4182 | }
4183 | if !strings.Contains(result.Text, "no client found in context") {
4184 | t.Errorf("Expected 'no client found in context', got '%s'", result.Text)
4185 | }
4186 | })
4187 |
4188 | // Test FetchAllPayments - JSON marshal error (using channel in response)
4189 | t.Run("FetchAllPayments - JSON marshal error", func(t *testing.T) {
4190 | testCase := RazorpayToolTestCase{
4191 | Name: "JSON marshal error",
4192 | Request: map[string]interface{}{
4193 | "count": 10,
4194 | },
4195 | MockHttpClient: func() (*http.Client, *httptest.Server) {
4196 | return mock.NewHTTPClient(
4197 | mock.Endpoint{
4198 | Path: "/v1/payments",
4199 | Method: "GET",
4200 | Response: map[string]interface{}{
4201 | "entity": "collection",
4202 | "count": 1,
4203 | "items": []interface{}{
4204 | map[string]interface{}{
4205 | "id": "pay_test123",
4206 | "invalid": make(chan int), // This causes JSON marshal to fail
4207 | "entity": "payment",
4208 | "amount": 1000,
4209 | "currency": "INR",
4210 | "status": "created",
4211 | "created_at": 1234567890,
4212 | },
4213 | },
4214 | },
4215 | },
4216 | )
4217 | },
4218 | ExpectError: true,
4219 | ExpectedErrMsg: "failed",
4220 | }
4221 | runToolTest(t, testCase, FetchAllPayments, "Collection")
4222 | })
4223 | }
4224 |
```