diff --git a/documents/dispute.md b/documents/dispute.md new file mode 100644 index 00000000..79b7c0e2 --- /dev/null +++ b/documents/dispute.md @@ -0,0 +1,286 @@ +## Document + +### Fetch All Disputes + +```py +client.dispute.all() +``` + +**Response:** +```json +{ + "entity": "collection", + "count": 1, + "items": [ + { + "id": "disp_Esz7KAitoYM7PJ", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "pre_arbitration", + "respond_by": 1590604200, + "status": "open", + "phase": "pre_arbitration", + "created_at": 1590059211, + "evidence": { + "amount": 10000, + "summary": null, + "shipping_proof": null, + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": null + } + } + ] +} +``` +------------------------------------------------------------------------------------------------------- + +### Fetch a Dispute + +```py +disputeId = "disp_0000000000000"; + +client.dispute.fetch(disputeId); +``` + +**Parameters:** + +| Name | Type | Description | +|-------|-----------|--------------------------------------------------| +| disputeId* | string | The unique identifier of the dispute. | + +**Response:** +```json +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "pre_arbitration", + "respond_by": 1590604200, + "status": "open", + "phase": "pre_arbitration", + "created_at": 1590059211, + "evidence": { + "amount": 10000, + "summary": "goods delivered", + "shipping_proof": null, + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": null + } +} +``` +------------------------------------------------------------------------------------------------------- + +### Fetch a Dispute + +```py +disputeId = "disp_0000000000000"; + +client.dispute.accept(disputeId); +``` + +**Parameters:** + +| Name | Type | Description | +|-------|-----------|--------------------------------------------------| +| disputeId* | string | The unique identifier of the dispute. | + +**Response:** +```json +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 10000, + "reason_code": "pre_arbitration", + "respond_by": 1590604200, + "status": "lost", + "phase": "pre_arbitration", + "created_at": 1590059211, + "evidence": { + "amount": 10000, + "summary": null, + "shipping_proof": null, + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": null + } +} +``` +------------------------------------------------------------------------------------------------------- +### Contest a Dispute + +```py +# Use this API sample code for draft + +disputeId = "disp_0000000000000"; + +client.dispute.contest(disputeId,{ + "amount": 5000, + "summary": "goods delivered", + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "others": [ + { + "type": "receipt_signed_by_customer", + "document_ids": [ + "doc_EFtmUsbwpXwBH1", + "doc_EFtmUsbwpXwBH7" + ] + } + ], + "action": "draft" +}) +``` + +**Parameters:** + +| Name | Type | Description | +|-------|-----------|--------------------------------------------------| +| disputeId* | string | The unique identifier of the dispute. | +| amount | integer | The amount being contested. If the contest amount is not mentioned, we will assume it to be a full dispute contest. | +| summary | string | The explanation provided by you for contesting the dispute. It can have a maximum length of 1000 characters. | +| shipping_proof | array | List of document ids which serves as proof that the product was shipped to the customer at their provided address. It should show their complete shipping address, if possible. | +| others | array | All keys listed [here](https://razorpay.com/docs/api/disputes/contest) are supported | + +```py +# Use this API sample code for submit + +client.dispute.contest(disputeId,{ + "billing_proof": [ + "doc_EFtmUsbwpXwBG9", + "doc_EFtmUsbwpXwBG8" + ], + "action": "submit" +}) +``` + +**Response:** +```json +// Draft +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "chargeback", + "respond_by": 1590604200, + "status": "open", + "phase": "chargeback", + "created_at": 1590059211, + "evidence": { + "amount": 5000, + "summary": "goods delivered", + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": [ + { + "type": "receipt_signed_by_customer", + "document_ids": [ + "doc_EFtmUsbwpXwBH1", + "doc_EFtmUsbwpXwBH7" + ] + } + ], + "submitted_at": null + } +} + +//Submit +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "chargeback", + "respond_by": 1590604200, + "status": "under_review", + "phase": "chargeback", + "created_at": 1590059211, + "evidence": { + "amount": 5000, + "summary": "goods delivered", + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "billing_proof": [ + "doc_EFtmUsbwpXwBG9", + "doc_EFtmUsbwpXwBG8" + ], + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": [ + { + "type": "receipt_signed_by_customer", + "document_ids": [ + "doc_EFtmUsbwpXwBH1", + "doc_EFtmUsbwpXwBH7" + ] + } + ], + "submitted_at": 1590603200 + } +} +``` +------------------------------------------------------------------------------------------------------- +**PN: * indicates mandatory fields** +
+
+**For reference click [here](https://razorpay.com/docs/api/documents)** \ No newline at end of file diff --git a/razorpay/__init__.py b/razorpay/__init__.py index 92fcd45d..7f601b97 100644 --- a/razorpay/__init__.py +++ b/razorpay/__init__.py @@ -25,6 +25,7 @@ from .resources import Product from .resources import Iin from .resources import Webhook +from .resources import Dispute __all__ = [ 'Payment', @@ -53,5 +54,6 @@ 'Stakeholder', 'Product', 'Iin', - 'Webhook' + 'Webhook', + 'Dispute', ] diff --git a/razorpay/constants/url.py b/razorpay/constants/url.py index e9ae8dea..6e3811e6 100644 --- a/razorpay/constants/url.py +++ b/razorpay/constants/url.py @@ -26,3 +26,4 @@ class URL(object): TOKEN = "/tokens" IIN = "/iins" WEBHOOK = "/webhooks" + DISPUTE= "/disputes" diff --git a/razorpay/resources/__init__.py b/razorpay/resources/__init__.py index 68917d25..27338296 100644 --- a/razorpay/resources/__init__.py +++ b/razorpay/resources/__init__.py @@ -21,6 +21,7 @@ from .product import Product from .iin import Iin from .webhook import Webhook +from .dispute import Dispute __all__ = [ 'Payment', @@ -45,5 +46,6 @@ 'Stakeholder', 'Product', 'Iin', - 'Webhook' + 'Webhook', + 'Dispute', ] diff --git a/razorpay/resources/dispute.py b/razorpay/resources/dispute.py new file mode 100644 index 00000000..64d251fb --- /dev/null +++ b/razorpay/resources/dispute.py @@ -0,0 +1,46 @@ +from .base import Resource +from ..constants.url import URL + + +class Dispute(Resource): + def __init__(self, client=None): + super(Dispute, self).__init__(client) + self.base_url = URL.V1 + URL.DISPUTE + + def fetch(self, dispute_id, data={}, **kwargs): + """ + Fetch dispute for given Id + + Returns: + dispute dict for given dispute Id + """ + return super(Dispute, self).fetch(dispute_id, data, **kwargs) + + def accept(self, dispute_id, data={}, **kwargs): + """ + Accept a dispute + + Returns: + Dictionary of disputes + """ + url = f"{self.base_url}/{dispute_id}/accept" + return self.post_url(url, data, **kwargs) + + def contest(self, dispute_id, data={}, **kwargs): + """ + Contest a Dispute + + Returns: + Dictionary of disputes + """ + url = f"{self.base_url}/{dispute_id}/contest" + return self.patch_url(url, data, **kwargs) + + def all(self, data={}, **kwargs): + """ + Fetch all disputes + + Returns: + Dictionary of disputes + """ + return super(Dispute, self).all(data, **kwargs) diff --git a/tests/mocks/dispute.json b/tests/mocks/dispute.json new file mode 100644 index 00000000..92778f96 --- /dev/null +++ b/tests/mocks/dispute.json @@ -0,0 +1,15 @@ +{ + "id": "disp_9GTZ2XXXXXXXXX", + "entity": "dispute", + "payment_id": "pay_9GMNcXXXXXXXXX", + "amount": 5000, + "currency": "INR", + "amount_deducted": 0, + "gateway_dispute_id": "test", + "reason_code": "goods_or_services_not_received_or_partially_received", + "respond_by": 1514658600, + "status": "under_review", + "phase": "chargeback", + "comments": null, + "created_at": 1513965738 +} diff --git a/tests/mocks/dispute_accept.json b/tests/mocks/dispute_accept.json new file mode 100644 index 00000000..dfc91e36 --- /dev/null +++ b/tests/mocks/dispute_accept.json @@ -0,0 +1,29 @@ +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 10000, + "reason_code": "pre_arbitration", + "respond_by": 1590604200, + "status": "lost", + "phase": "pre_arbitration", + "created_at": 1590059211, + "evidence": { + "amount": 10000, + "summary": null, + "shipping_proof": null, + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": null + } + } diff --git a/tests/mocks/dispute_collection.json b/tests/mocks/dispute_collection.json new file mode 100644 index 00000000..791cd97e --- /dev/null +++ b/tests/mocks/dispute_collection.json @@ -0,0 +1,67 @@ +{ + "entity": "collection", + "count": 2, + "items": [ + { + "id": "disp_Esz7KAitoYM7PJ", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "pre_arbitration", + "respond_by": 1590604200, + "status": "open", + "phase": "pre_arbitration", + "created_at": 1590059211, + "evidence": { + "amount": 10000, + "summary": null, + "shipping_proof": null, + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": null + } + }, + { + "id": "disp_Esyvk3kZj0isXk", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 5000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "warning_bulletin_or_exception_file", + "respond_by": 1590604200, + "status": "won", + "phase": "chargeback", + "created_at": 1590058554, + "evidence": { + "amount": 5000, + "summary": null, + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": null, + "submitted_at": 1590604100 + } + } + ] + } diff --git a/tests/mocks/dispute_contest.json b/tests/mocks/dispute_contest.json new file mode 100644 index 00000000..6e2bf03a --- /dev/null +++ b/tests/mocks/dispute_contest.json @@ -0,0 +1,40 @@ +{ + "id": "disp_AHfqOvkldwsbqt", + "entity": "dispute", + "payment_id": "pay_EsyWjHrfzb59eR", + "amount": 10000, + "currency": "INR", + "amount_deducted": 0, + "reason_code": "chargeback", + "respond_by": 1590604200, + "status": "open", + "phase": "chargeback", + "created_at": 1590059211, + "evidence": { + "amount": 5000, + "summary": "goods delivered", + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "billing_proof": null, + "cancellation_proof": null, + "customer_communication": null, + "proof_of_service": null, + "explanation_letter": null, + "refund_confirmation": null, + "access_activity_log": null, + "refund_cancellation_policy": null, + "term_and_conditions": null, + "others": [ + { + "type": "receipt_signed_by_customer", + "document_ids": [ + "doc_EFtmUsbwpXwBH1", + "doc_EFtmUsbwpXwBH7" + ] + } + ], + "submitted_at": null + } + } diff --git a/tests/test_client_dispute.py b/tests/test_client_dispute.py new file mode 100644 index 00000000..bcf2a74c --- /dev/null +++ b/tests/test_client_dispute.py @@ -0,0 +1,65 @@ +import responses +import json + +from .helpers import mock_file, ClientTestCase + + +class TestClientDispute(ClientTestCase): + + def setUp(self): + super(TestClientDispute, self).setUp() + self.base_url = '{}/disputes'.format(self.base_url) + + @responses.activate + def test_dispute_fetch_all(self): + result = mock_file('dispute_collection') + url = self.base_url + responses.add(responses.GET, url, status=200, + body=json.dumps(result), match_querystring=True) + self.assertEqual(self.client.dispute.all(), result) + + @responses.activate + def test_fetch_dispute(self): + result = mock_file('dispute') + dispute_id = 'fake_dispute_id' + url = f"{self.base_url}/{dispute_id}" + responses.add(responses.GET, url, status=200, body=json.dumps(result), + match_querystring=True) + self.assertEqual(self.client.dispute.fetch(dispute_id), result) + + @responses.activate + def test_dispute_accept(self): + result = mock_file('dispute_accept') + dispute_id = 'fake_dispute_id' + url = f"{self.base_url}/{dispute_id}/accept" + responses.add(responses.POST, url, status=200, body=json.dumps(result), + match_querystring=True) + self.assertEqual(self.client.dispute.accept(dispute_id), result) + + @responses.activate + def test_dispute_contest(self): + request = { + "amount": 5000, + "summary": "goods delivered", + "shipping_proof": [ + "doc_EFtmUsbwpXwBH9", + "doc_EFtmUsbwpXwBH8" + ], + "others": [ + { + "type": "receipt_signed_by_customer", + "document_ids": [ + "doc_EFtmUsbwpXwBH1", + "doc_EFtmUsbwpXwBH7" + ] + } + ], + "action": "draft" + } + + result = mock_file('dispute_contest') + dispute_id = 'fake_dispute_id' + url = f"{self.base_url}/{dispute_id}/contest" + responses.add(responses.PATCH, url, status=200, body=json.dumps(result), + match_querystring=True) + self.assertEqual(self.client.dispute.contest(dispute_id, request), result)