From f341db4ea7f3e6b539c8946f046eb0833ec534bd Mon Sep 17 00:00:00 2001 From: Ankit Das <89454448+ankitdas13@users.noreply.github.com> Date: Tue, 7 May 2024 18:32:02 +0530 Subject: [PATCH] feat: Added Dispute entity (#391) --- documents/disputes.md | 241 ++++++++++++++++++++++++++++++++ lib/razorpay.d.ts | 6 + lib/razorpay.js | 3 +- lib/resources/disputes.js | 43 ++++++ lib/types/disputes.d.ts | 185 ++++++++++++++++++++++++ test/resources/disputes.spec.js | 96 +++++++++++++ 6 files changed, 573 insertions(+), 1 deletion(-) create mode 100644 documents/disputes.md create mode 100644 lib/resources/disputes.js create mode 100644 lib/types/disputes.d.ts create mode 100644 test/resources/disputes.spec.js diff --git a/documents/disputes.md b/documents/disputes.md new file mode 100644 index 0000000..a5ffff8 --- /dev/null +++ b/documents/disputes.md @@ -0,0 +1,241 @@ +## Disputes + +### Fetch All Disputes + +```js +var options = { + count: 1 +} + +instance.disputes.all(options) +``` + +**Parameters:** + +| Name | Type | Description | +|---------------|-------------|---------------------------------------------| +| count | integer | number of dispute to fetch (default: 10) | +| skip | integer | number of dispute to be skipped (default: 0) | + +**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 + +```js +var disputeId = "disp_0000000000000"; + +instance.disputes.fetch(disputeId); +``` + +**Parameters:** + +| Name | Type | Description | +|-------|-----------|--------------------------------------------------| +| id* | 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 + } +} +``` +------------------------------------------------------------------------------------------------------- + +### Contest a Dispute + +```js + +//Use this API sample code for draft + +var disputeId = "disp_0000000000000"; + +instance.disputes.contest(disputeId,{ + "billing_proof": [ + "doc_EFtmUsbwpXwBG9", + "doc_EFtmUsbwpXwBG8" + ], + "action": "submit" +}) + + +//Use this API sample code for submit + +instance.disputes.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" +}) +``` + +**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/lib/razorpay.d.ts b/lib/razorpay.d.ts index 83d0639..5698d1c 100644 --- a/lib/razorpay.d.ts +++ b/lib/razorpay.d.ts @@ -22,6 +22,7 @@ import webhooks from './types/webhooks' import products from './types/products' import tokens from './types/tokens' import iins from './types/iins' +import disputes from './types/disputes' interface IRazorpayConfig { key_id: string; @@ -144,6 +145,11 @@ declare class Razorpay { * @see https://razorpay.com/docs/api/payments/cards/iin-api/#iin-entity */ iins: ReturnType + /** + * Dispute Entity + * @see https://razorpay.com/docs/api/disputes + */ + disputes: ReturnType } export = Razorpay diff --git a/lib/razorpay.js b/lib/razorpay.js index dbbd1d8..3738bd1 100644 --- a/lib/razorpay.js +++ b/lib/razorpay.js @@ -57,7 +57,8 @@ class Razorpay { fundAccount : require('./resources/fundAccount')(this.api), items : require('./resources/items')(this.api), cards : require('./resources/cards')(this.api), - webhooks : require('./resources/webhooks')(this.api) + webhooks : require('./resources/webhooks')(this.api), + disputes : require('./resources/disputes')(this.api) }) } } diff --git a/lib/resources/disputes.js b/lib/resources/disputes.js new file mode 100644 index 0000000..6c821aa --- /dev/null +++ b/lib/resources/disputes.js @@ -0,0 +1,43 @@ +'use strict'; + +module.exports = function (api) { + + const BASE_URL = "/disputes"; + + return { + + fetch(disputeId, callback) { + return api.get({ + url: `${BASE_URL}/${disputeId}`, + }, callback); + }, + + all(params = {}, callback) { + let { count, skip } = params + + count = Number(count) || 10 + skip = Number(skip) || 0 + + return api.get({ + url: `${BASE_URL}`, + data: { + count, + skip + } + }, callback) + }, + + accept(disputeId, callback) { + return api.post({ + url: `${BASE_URL}/${disputeId}/accept`, + }, callback); + }, + + contest(disputeId, param, callback) { + return api.patch({ + url: `${BASE_URL}/${disputeId}/contest`, + data: param + }, callback); + } + } +} \ No newline at end of file diff --git a/lib/types/disputes.d.ts b/lib/types/disputes.d.ts new file mode 100644 index 0000000..28b92cb --- /dev/null +++ b/lib/types/disputes.d.ts @@ -0,0 +1,185 @@ +import { IMap, INormalizeError, PartialOptional, RazorpayPaginationOptions } from "./api"; + +export declare namespace Disputes { + interface RazorpayDisputesBaseRequestBody { + + } + + interface RazorpayDisputesContestBaseRequestBody { + /** + * The contested amount in currency subunits + */ + amount: number; + /** + * The explanation provided by you for contesting the dispute. max length 1000 char + */ + summary: string; + /** + * List of document ids which serves as proof that the product was shipped to the + * customer at their provided address. + */ + shipping_proof: string[]; + /** + * List of document ids which serves as proof of order confirmation, such as a receipt. + */ + billing_proof: string[]; + /** + * List of document ids that serves as proof that this product/service was cancelled. + */ + cancellation_proof: string[]; + /** + * List of document ids listing any written/email communication from the customer + * confirming that the customer received the product/service or is satisfied with the + * product/service. + */ + customer_communication: string[]; + /*** + * List of document ids showing proof of service provided to the customer. + */ + proof_of_service: string[]; + explanation_letter: string[]; + /** + * List of document ids showing proof that the refund had been provided to the customer. + */ + refund_confirmation: string[]; + /** + * List of document ids of any server or activity logs which prove that the customer accessed + * or downloaded the purchased digital product. + */ + access_activity_log: string[]; + /** + * List of document ids listing your refund and/or cancellation policy, as shown to the customer. + */ + refund_cancellation_policy: string[]; + /** + * List of document ids listing your sales terms and conditions, as shown to the customer. + */ + term_and_conditions: string[]; + /** + * Specifies the evidence documents to be uploaded as a part of contesting a dispute. + */ + others: { + /** + * Describes the custom type of evidence document(s) provided. + */ + type: string + /** + * List of document ids corresponding to the customer evidence type. + */ + document_ids: string[] + }[] + /** + * The action to be taken for this contest. Possible value is `draft` or `submit`. + */ + action: string; + /** + * Unix timestamp when the dispute was last submitted by you to Razorpay. The default value is `null`. + */ + submitted_at: any; + } + + interface RazorpayDisputesContest { + + } + + interface RazorpayDisputeEvidence extends RazorpayDisputesContestBaseRequestBody{} + + interface RazorpayDispute { + /** + * The unique identifier of the dispute generated by Razorpay + */ + id: string; + /** + * Indicates the type of entity. + */ + entity: string; + /** + * The unique identifier of the payment against which the dispute was created. + */ + payment_id: string; + /** + * Amount, in currency subunits, for which the dispute was created. + */ + amount: number; + /** + * 3-letter ISO currency code associated with the amount. + */ + currency: string; + /** + * The amount, in currency subunits, deducted from your Razorpay current + * balance when the dispute is `lost`. + */ + amount_deducted: number; + /** + * Code associated with the reason for the dispute. + */ + reason_code: string; + /** + * Unix timestamp by which a response should be sent to the customer. + */ + respond_by: number; + /** + * The status of the dispute. + */ + status: string; + /** + * Phase associated with the dispute + */ + phase: string; + /** + * Unix timestamp when the dispute was created. + */ + created_at: number; + /** + * Provides details of the evidence submitted/saved for contesting a + * dispute. + */ + evidence: RazorpayDisputeEvidence; + } +} + +declare function disputes(api: any): { + /** + * Fetches a dispute given Dispute ID + * + * @param disputeId - The unique identifier of the dispute. + * + */ + fetch(disputeId: string): Promise + fetch(disputeId: string, callback: (err: INormalizeError | null, data: Disputes.RazorpayDispute) => void): void; + /** + * Get all disputes + * + * @param params - Check [doc](https://razorpay.com/docs/api/disputes/fetch-all) for required params + * + */ + all(params?: RazorpayPaginationOptions): Promise<{ + entity: string, + count: number, + items: Array + }> + all(params: RazorpayPaginationOptions, callback: (err: INormalizeError | null, data: { + entity: string, + count: number, + items: Array + }) => void): void; + /** + * Update an account + * + * @param disputeId - The unique identifier of the dispute. + * + */ + accept(disputeId: string): Promise + accept(disputeId: string, callback: (err: INormalizeError | null, data: Disputes.RazorpayDispute) => void): void; + /** + * Contest a dispute + * + * @param disputeId - The unique identifier of the dispute. + * @param params - Check [doc](https://razorpay.com/docs/api/disputes/contest) for required params + * + */ + contest(accountId: string, param: Partial): Promise + contest(accountId: string, param: Partial, callback: (err: INormalizeError | null, data: Promise) => void): void; +} + +export default disputes \ No newline at end of file diff --git a/test/resources/disputes.spec.js b/test/resources/disputes.spec.js new file mode 100644 index 0000000..614384d --- /dev/null +++ b/test/resources/disputes.spec.js @@ -0,0 +1,96 @@ +'use strict' + +const chai = require('chai') +const { assert } = chai +const rzpInstance = require('../razorpay') +const mocker = require('../mocker') +const equal = require('deep-equal') + +let mockRequest = { + "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" +} + +const BASE_URL = '/disputes', + TEST_DISPUTE_ID = 'disp_AHfqOvkldwsbqt'; + +describe('DISPUTE', () => { + + it('Dispute fetch', (done) => { + mocker.mock({ + url: `/${BASE_URL}/${TEST_DISPUTE_ID}` + }) + + rzpInstance.disputes.fetch(TEST_DISPUTE_ID).then((response) => { + assert.equal( + response.__JUST_FOR_TESTS__.url, + `/v1/disputes/${TEST_DISPUTE_ID}`, + 'Fetch dispute url formed correctly' + ) + done() + }) + }) + + it('Fetch all dispute', (done) => { + + mocker.mock({ + url: `/${BASE_URL}`, + }) + + rzpInstance.disputes.all({count:10, skip: 0}).then((response) => { + console.log(response.__JUST_FOR_TESTS__) + assert.equal( + response.__JUST_FOR_TESTS__.url, + `/v1/disputes?count=10&skip=0`, + 'fetch all disputes url formed correctly' + ) + done() + }) + }) + + it('Accept a dispute ', (done) => { + mocker.mock({ + url: `/${BASE_URL}/${TEST_DISPUTE_ID}/accept`, + method: "POST" + }) + + rzpInstance.disputes.accept(TEST_DISPUTE_ID).then((response) => { + assert.equal( + response.__JUST_FOR_TESTS__.url, + `/v1/disputes/${TEST_DISPUTE_ID}/accept`, + 'accept a dispute url formed correctly' + ) + done() + }) + }) + + it('contest a dispute ', (done) => { + mocker.mock({ + url: `/${BASE_URL}/${TEST_DISPUTE_ID}/contest`, + method: "PATCH" + }) + + rzpInstance.disputes.contest(TEST_DISPUTE_ID, mockRequest).then((response) => { + assert.equal( + response.__JUST_FOR_TESTS__.url, + `/v1/disputes/${TEST_DISPUTE_ID}/contest`, + 'accept a dispute url formed correctly' + ) + done() + }) + }) +})