diff --git a/examples/with_async/payment_method_v2.js b/examples/with_async/payment_method_v2.js new file mode 100644 index 0000000..b50cc49 --- /dev/null +++ b/examples/with_async/payment_method_v2.js @@ -0,0 +1,48 @@ +const x = require('../xendit'); + +const { PaymentMethodV2 } = x; +const pm = new PaymentMethodV2({}); + +(async function() { + try { + let createdPaymentMethod = await pm.createPaymentMethodV2({ + type: 'DIRECT_DEBIT', + reusability: 'ONE_TIME_USE', + customer_id: '16f72571-9b3a-43dc-b241-5b71f470202f', + country: 'ID', + direct_debit: { + channel_code: 'BRI', + channel_properties: { + mobile_number: '+6281299640904', + card_last_four: '8888', + card_expiry: '10/29', + email: 'dharma@xendit.co', + }, + }, + }); + // eslint-disable-next-line no-console + console.log('created payment method', createdPaymentMethod); + + const paymentMethodDetailsById = await pm.getPaymentMethodByIdV2({ + id: createdPaymentMethod.id, + }); + // eslint-disable-next-line no-console + console.log('retrieved payment method', paymentMethodDetailsById); + + const listOfPaymentMethod = await pm.listPaymentMethodV2({}); + // eslint-disable-next-line no-console + console.log('retrieved payment method list', listOfPaymentMethod); + + const authorizedPaymentMethod = await pm.authorizePaymentMethodV2({ + id: createdPaymentMethod.id, + auth_code: '333000', + }); + // eslint-disable-next-line no-console + console.log('authorized payment method', authorizedPaymentMethod); + + process.exit(0); + } catch (e) { + console.error(e); // eslint-disable-line no-console + process.exit(1); + } +})(); diff --git a/examples/with_promises/payment_methods_v2.js b/examples/with_promises/payment_methods_v2.js new file mode 100644 index 0000000..5b0ee02 --- /dev/null +++ b/examples/with_promises/payment_methods_v2.js @@ -0,0 +1,37 @@ +const x = require('../xendit'); + +const PaymentMethodV2 = x.PaymentMethodV2; +const pm = new PaymentMethodV2(); + +pm.createPaymentMethodV2({ + type: 'DIRECT_DEBIT', + reusability: 'ONE_TIME_USE', + customer_id: '16f72571-9b3a-43dc-b241-5b71f470202f', + country: 'ID', + direct_debit: { + channel_code: 'BRI', + channel_properties: { + mobile_number: '+6281299640904', + card_last_four: '8888', + card_expiry: '10/29', + email: 'dharma@xendit.co', + }, + }, +}) + .then(id => { + pm.authorizePaymentMethodV2({ + id, + auth_code: '333000', + }); + }) + .then(id => { + pm.getPaymentMethodByIdV2({ id }); + }) + .then(() => { + pm.listPaymentMethodV2({}); + }) + .catch(e => { + throw new Error( + `payment method integration tests failed with error: ${e.message}`, + ); + }); diff --git a/integration_test/index.js b/integration_test/index.js index 866bb75..d19e828 100644 --- a/integration_test/index.js +++ b/integration_test/index.js @@ -11,17 +11,17 @@ Promise.all([ // require('./ewallet.test')(), require('./qr_code.test')(), require('./platform.test')(), + require('./regional_retail_outlet.test'), require('./customer.test')(), require('./direct_debit.test')(), require('./report.test')(), require('./transaction.test')(), + require('./payment_method_v2.test'), // require('./refund.test')() //test disabled until refunds endpoint is fixed ]) .then(() => { - Promise.all([require('./regional_retail_outlet.test')()]).then(() => - // eslint-disable-next-line no-console - console.log('Successful Integration Test!'), - ); + // eslint-disable-next-line no-console + console.log('Successful Integration Test!'); }) .catch(e => { console.error(e); // eslint-disable-line no-console diff --git a/integration_test/payment_method_v2.test.js b/integration_test/payment_method_v2.test.js new file mode 100644 index 0000000..27687d9 --- /dev/null +++ b/integration_test/payment_method_v2.test.js @@ -0,0 +1,44 @@ +const x = require('./xendit.test'); + +const { PaymentMethodV2 } = x; +const pm = new PaymentMethodV2({}); + +module.exports = function() { + return pm + .createPaymentMethodV2({ + type: 'DIRECT_DEBIT', + reusability: 'ONE_TIME_USE', + customer_id: '16f72571-9b3a-43dc-b241-5b71f470202f', + country: 'ID', + direct_debit: { + channel_code: 'BRI', + channel_properties: { + mobile_number: '+6281299640904', + card_last_four: '8888', + card_expiry: '10/29', + email: 'dharma@xendit.co', + }, + }, + }) + .then(id => { + pm.authorizePaymentMethodV2({ + id, + auth_code: '333000', + }); + }) + .then(id => { + pm.getPaymentMethodByIdV2({ id }); + }) + .then(() => { + pm.listPaymentMethodV2({}); + }) + .then(() => { + // eslint-disable-next-line no-console + console.log('payment method integration test done...'); + }) + .catch(e => { + throw new Error( + `payment method integration tests failed with error: ${e.message}`, + ); + }); +}; diff --git a/package.json b/package.json index 064ac0a..7ed7fef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xendit-node", - "version": "1.21.8", + "version": "1.21.9", "description": "NodeJS client for Xendit API", "main": "index.js", "types": "index.d.ts", diff --git a/src/payment_method_v2/index.d.ts b/src/payment_method_v2/index.d.ts new file mode 100644 index 0000000..5364e16 --- /dev/null +++ b/src/payment_method_v2/index.d.ts @@ -0,0 +1,3 @@ +import PaymentMethodV2Service from './payment_method_v2'; + +export { PaymentMethodV2Service }; diff --git a/src/payment_method_v2/index.js b/src/payment_method_v2/index.js new file mode 100644 index 0000000..95c0c9e --- /dev/null +++ b/src/payment_method_v2/index.js @@ -0,0 +1,3 @@ +const PaymentMethodV2Service = require('./payment_method_v2'); + +module.exports = { PaymentMethodV2Service }; diff --git a/src/payment_method_v2/payment_method_v2.d.ts b/src/payment_method_v2/payment_method_v2.d.ts new file mode 100644 index 0000000..5955085 --- /dev/null +++ b/src/payment_method_v2/payment_method_v2.d.ts @@ -0,0 +1,239 @@ +import { XenditOptions } from '../xendit_opts'; + +enum PaymentMethodV2Types { + Card = 'CARD', + eWallet = 'EWALLET', + DirectDebit = 'DIRECT_DEBIT', + OverTheCounter = 'OVER_THE_COUNTER', + QRCODE = 'QR_CODE', + VitrualAccount = 'VIRTUAL_ACCOUNT', +} + +enum PaymentMenthodV2Reusabilities { + OneTimeUse = 'ONE_TIME_USE', + MultipleUse = 'MULTIPLE_USE', +} + +enum CreatePaymentMenthodV2Countries { + ID = 'ID', + PH = 'PH', +} + +enum EWalletChannelCodes { + Dana = 'DANA', + OVO = 'OVO', + LinkAja = 'LINKAJA', + Astrapay = 'ASTRAPAY', + JeniusPay = 'JENIUSPAY', + ShopeePay = 'SHOPEEPAY', + GrabPay = 'GRABPAY', + Paymaya = 'PAYMAYA', + GCash = 'GCASH', +} + +enum DirectDebitChannelCodes { + BRI = 'BRI', + Mandiri = 'MANDIRI', + BPI = 'BPI', + UBP = 'UBP', + RCBC = 'RCBC', + ChinaBank = 'CHINABANK', +} + +enum CardCurrencies { + IDR = 'IDR', + PHP = 'PHP', + USD = 'USD', +} + +enum OverTheCounterChannelCodes { + Alfamart = 'ALFAMART', + Indomaret = 'INDOMARET', + '7Eleven' = '7ELEVEN', + '7ElevenCLIQQ' = '7ELEVEN_CLIQQ', + Cebuana = 'CEBUANA', + ECPay = 'ECPAY', + Palawan = 'PALAWAN', + MLhuillier = 'MLHUILLIER', + DragonLoanECPAY = 'DRAGONLOAN_ECPAY', +} + +enum OverTheCounterCurrencies { + IDR = 'IDR', + PHP = 'PHP', +} + +enum VirtualAccountChannelCodes { + BCA = 'BCA', + BSI = 'BSI', + BJB = 'BJB', + CIMB = 'CIMB', + SahabatSampoerna = 'SAHABAT_SAMPOERNA', + Artajasa = 'ARTAJASA', + BRI = 'BRI', + BNI = 'BNI', + Mandiri = 'MANDIRI', + Permata = 'PERMATA', +} + +enum VirtualAccountCurrencies { + IDR = 'IDR', +} + +enum QRISChannelCodes { + QRIS = 'QRIS', +} + +enum QRISCurrencies { + IDR = 'IDR', +} + +enum PaymentMethodV2Statuses { + Succeeded = 'SUCCEEDED', + Failed = 'FAILED', + Pending = 'PENDING', +} + +interface BillingInformationItems { + country: string; + street_line1?: string; + street_line2?: string; + city?: string; + province_state?: string; + postal_code?: string; +} + +interface EwalletItems { + channel_code: EWalletChannelCodes; + channel_properties: object; +} + +interface DirectDebitItems { + channel_code: DirectDebitChannelCodes; + channel_properties: object; +} + +interface CardItems { + currency: CardCurrencies; + channel_properties: object; + card_information: { + card_number: string; + expiry_month: string; + expiry_year: string; + cardholder_name?: string; + }; +} + +interface OverTheCounterItems { + channel_code: OverTheCounterChannelCodes; + currency?: OverTheCounterCurrencies; + amount?: number; + channel_properties: object; +} + +interface VirtualAccountItems { + channel_code: VirtualAccountChannelCodes; + currency?: VirtualAccountCurrencies; + amount?: number; + channel_properties: object; +} + +interface QRISItems { + channel_code: QRISChannelCodes; + currency?: QRISCurrencies; + amount?: number; +} + +interface UpdateOverTheCounterItems { + amount?: number; + channel_properties: object; +} + +interface UpdateVirtualAccountItems { + amount?: number; + channel_properties: object; +} + +interface ListPaymentMethodV2StatusItems { + status?: PaymentMethodV2Statuses; +} + +export = class PaymentMethodV2 { + constructor({}); + static _constructorWithInjectedXenditOpts: ( + opts: XenditOptions, + ) => typeof PaymentMethodV2; + createPaymentMethodV2(data: { + type: PaymentMethodV2Types; + reusability: PaymentMenthodV2Reusabilities; + reference_id?: string; + customer_id?: string; + country?: CreatePaymentMenthodV2Countries; + description?: string; + billing_information?: BillingInformationItems; + metadata?: object; + ewallet?: EwalletItems; + direct_debit?: DirectDebitItems; + card?: CardItems; + over_the_counter?: OverTheCounterItems; + virtual_account?: VirtualAccountItems; + qr_code?: QRISItems; + for_user_id?: string; + idempotency_key?: string; + }): Promise; + + listPaymentMethodV2(data: { + id: string; + type?: PaymentMethodV2Types; + reusability?: PaymentMenthodV2Reusabilities; + reference_id?: string; + customer_id?: string; + limit?: string; + after_id?: string; + before_id?: string; + for_user_id?: string; + }): Promise; + + authorizePaymentMethodV2(data: { + id: string; + auth_code: string; + for_user_id?: string; + idempotency_key?: string; + }): Promise; + + getPaymentMethodByIdV2(data: { + id: string; + for_user_id?: string; + }): Promise; + + updatePaymentMethodV2(data: { + id: string; + reference_id?: string; + description?: string; + metadata?: object; + status?: object; + reusability?: PaymentMenthodV2Reusabilities; + over_the_counter?: UpdateOverTheCounterItems; + virtual_account?: UpdateVirtualAccountItems; + for_user_id?: string; + }): Promise; + + expirePaymentMethodV2(data: { + id: string; + for_user_id?: string; + idempotency_key?: string; + }): Promise; + + listPaymentsByPaymentMethodIdV2(data: { + id: string; + payment_request_id?: string; + reference_id?: string; + status?: ListPaymentMethodV2StatusItems; + limit?: number; + after_id?: string; + before_id?: string; + created?: string; + updated?: string; + for_user_id?: string; + }): Promise; +}; diff --git a/src/payment_method_v2/payment_method_v2.js b/src/payment_method_v2/payment_method_v2.js new file mode 100644 index 0000000..6f425f4 --- /dev/null +++ b/src/payment_method_v2/payment_method_v2.js @@ -0,0 +1,288 @@ +const { promWithJsErr, Validate, fetchWithHTTPErr, Auth, queryStringWithoutUndefined } = require('../utils'); + +const PAYMENT_METHOD_V2_PATH = '/v2/payment_methods'; + +function PaymentMethodV2(options) { + let aggOpts = options; + if ( + PaymentMethodV2._injectedOpts && + Object.keys(PaymentMethodV2._injectedOpts).length > 0 + ) { + aggOpts = Object.assign({}, options, PaymentMethodV2._injectedOpts); + } + + this.opts = aggOpts; + this.API_ENDPOINT = this.opts.xenditURL + PAYMENT_METHOD_V2_PATH; +} + +PaymentMethodV2._injectedOpts = {}; +PaymentMethodV2._constructorWithInjectedXenditOpts = function (options) { + PaymentMethodV2._injectedOpts = options; + return PaymentMethodV2; +}; + +PaymentMethodV2.prototype.createPaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields( + ['type', 'reusability'], + data, + reject, + ); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.for_user_id; + } + + if (data && data.idempotency_key) { + headers['idempotency-key'] = data.idempotency_key; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}`, { + method: 'POST', + headers, + body: JSON.stringify({ + type: data.type, + reusability: data.reusability, + reference_id: data.reference_id, + customer_id: data.customer_id, + country: data.country, + description: data.description, + billing_information: data.billing_information, + metadata: data.metadata, + ewallet: data.ewallet, + direct_debit: data.direct_debit, + card: data.card, + over_the_counter: data.over_the_counter, + virtual_account: data.virtual_account, + qr_code: data.qr_code + }), + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.listPaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields([], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.for_user_id; + } + + const queryStr = data + ? queryStringWithoutUndefined({ + id: data.id ? data.id : undefined, + type: data.type ? data.type : undefined, + reusability: data.reusability ? data.reusability : undefined, + reference_id: data.reference_id ? data.reference_id : undefined, + customer_id: data.customer_id ? data.customer_id : undefined, + limit: data.limit ? data.limit : undefined, + after_id: data.after_id ? data.after_id : undefined, + before_id: data.before_id ? data.before_id : undefined + }) + : ''; + + const queryStrWithQuestionMark = queryStr ? `?${queryStr}` : ''; + + + fetchWithHTTPErr(`${this.API_ENDPOINT}${queryStrWithQuestionMark}`, { + method: 'GET', + headers, + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.authorizePaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields(['id'], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.forUserID; + } + + if (data && data.idempotency_key) { + headers['idempotency-key'] = data.idempotency_key; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}/auth`, { + method: 'POST', + headers, + body: JSON.stringify({ + auth_code: data.auth_code + }), + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.getPaymentMethodByIdV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields(['id'], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.forUserID; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}`, { + method: 'GET', + headers + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.updatePaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields(['id'], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.forUserID; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}`, { + method: 'PATCH', + headers, + body: JSON.stringify({ + reference_id: data.reference_id, + description: data.description, + metadata: data.metadata, + status: data.status, + reusability: data.reusability, + over_the_counter: data.over_the_counter, + virtual_account: data.virtual_account + }), + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.expirePaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields(['id'], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.forUserID; + } + + + if (data && data.idempotency_key) { + headers['idempotency-key'] = data.idempotency_key; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}/expire`, { + method: 'POST', + headers, + body: JSON.stringify({ + auth_code: data.auth_code + }), + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.expirePaymentMethodV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields(['id'], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.forUserID; + } + + + if (data && data.idempotency_key) { + headers['idempotency-key'] = data.idempotency_key; + } + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}/expire`, { + method: 'POST', + headers, + body: JSON.stringify({ + auth_code: data.auth_code + }), + }) + .then(resolve) + .catch(reject); + }); +}; + +PaymentMethodV2.prototype.listPaymentsByPaymentMethodIdV2 = function (data) { + return promWithJsErr((resolve, reject) => { + Validate.rejectOnMissingFields([], data, reject); + + let headers = { + Authorization: Auth.basicAuthHeader(this.opts.secretKey), + 'Content-Type': 'application/json', + }; + + if (data && data.for_user_id) { + headers['for-user-id'] = data.for_user_id; + } + + const queryStr = data + ? queryStringWithoutUndefined({ + payment_request_id: data.payment_request_id ? data.payment_request_id : undefined, + reference_id: data.reference_id ? data.reference_id : undefined, + status: data.status ? data.status :undefined, + limit: data.limit ? data.limit : undefined, + after_id: data.after_id ? data.after_id : undefined, + before_id: data.before_id ? data.before_id : undefined, + created: data.created ? data.created : undefined, + updated: data.updated ? data.updated : undefined + }) + : ''; + + const queryStrWithQuestionMark = queryStr ? `?${queryStr}` : ''; + + + fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}/payments${queryStrWithQuestionMark}`, { + method: 'GET', + headers, + }) + .then(resolve) + .catch(reject); + }); +}; + +module.exports = PaymentMethodV2; diff --git a/src/xendit.d.ts b/src/xendit.d.ts index 8131e33..c62cd0a 100644 --- a/src/xendit.d.ts +++ b/src/xendit.d.ts @@ -16,6 +16,7 @@ import { CustomerService } from './customer'; import { DirectDebitService } from './direct_debit'; import { ReportService } from './report'; import { TransactionService } from './transaction'; +import { PaymentMethodV2Service } from './payment_method_v2'; import { RefundService } from './refund'; declare class Xendit { @@ -38,6 +39,7 @@ declare class Xendit { DirectDebit: typeof DirectDebitService; Report: typeof ReportService; Transaction: typeof TransactionService; + PaymentMethodV2: typeof PaymentMethodV2Service; Refund: typeof RefundService; } export = Xendit; diff --git a/src/xendit.js b/src/xendit.js index 569a36c..8d4ad7a 100644 --- a/src/xendit.js +++ b/src/xendit.js @@ -15,6 +15,7 @@ const { DirectDebitService } = require('./direct_debit'); const { RegionalRetailOutletService } = require('./regional_retail_outlet'); const { ReportService } = require('./report'); const { TransactionService } = require('./transaction'); +const { PaymentMethodV2Service } = require('./payment_method_v2'); const { RefundService } = require('./refund'); const Errors = require('./errors'); @@ -44,6 +45,9 @@ function Xendit(options) { this.RetailOutlet = RetailOutletService._constructorWithInjectedXenditOpts( this.opts, ); + this.PaymentMethodV2 = PaymentMethodV2Service._constructorWithInjectedXenditOpts( + this.opts, + ); // eslint-disable-next-line this.RegionalRetailOutlet = RegionalRetailOutletService._constructorWithInjectedXenditOpts( this.opts, diff --git a/test/payment_method_v2/constants.js b/test/payment_method_v2/constants.js new file mode 100644 index 0000000..0a4876b --- /dev/null +++ b/test/payment_method_v2/constants.js @@ -0,0 +1,291 @@ +const CREATE_PAYMENT_RESPONSE = { + id: "pm-77de3ae9-0795-4de4-a08f-b8a463ae8eb4", + type: "QR_CODE", + country: "ID", + business_id: "58cd618ba0464eb64acdb246", + customer_id: null, + reference_id: "117e6382-d612-4d7c-94b0-21b76b136f64", + reusability: "ONE_TIME_USE", + status: "ACTIVE", + actions: [], + description: null, + created: "2022-11-02T10:14:20.8306107Z", + updated: "2022-11-02T10:14:20.8306107Z", + metadata: null, + billing_information: null, + failure_code: null, + ewallet: null, + direct_bank_transfer: null, + direct_debit: null, + card: null, + over_the_counter: null, + qr_code: { + amount: 10000, + currency: "IDR", + channel_code: "QRIS", + channel_properties: { + qr_string: "00020101021226660014ID.LINKAJA.WWW011893600911002414220002152003260414220010303UME51450015ID.OR.GPNQR.WWW02150000000000000000303UME520454995802ID5909Xendit QR6007Jakarta61051216062380115WxFFAGZrEbCqdch0715WxFFAGZrEbCqdch53033605405100006304716E" + } + }, + virtual_account: null +} + +const CREATE_PAYMENT_MISSING_TYPE_RESPONSE = { + error_code: "API_VALIDATION_ERROR", + message: "Failed to validate the request, 1 error occurred.", + errors: [ + { + path: "body.type", + message: "Property 'type' is missing" + } + ] +} + +const LIST_PAYMENT_METHOD_RESPONSE = { + data: [ + { + id: "pm-77de3ae9-0795-4de4-a08f-b8a463ae8eb4", + type: "QR_CODE", + country: "ID", + business_id: "58cd618ba0464eb64acdb246", + customer_id: null, + reference_id: "117e6382-d612-4d7c-94b0-21b76b136f64", + reusability: "ONE_TIME_USE", + status: "ACTIVE", + actions: [], + description: null, + created: "2022-11-02T10:14:20.830611Z", + updated: "2022-11-02T10:14:20.830611Z", + metadata: null, + billing_information: null, + failure_code: null, + ewallet: null, + direct_bank_transfer: null, + direct_debit: null, + card: null, + over_the_counter: null, + qr_code: { + amount: 10000, + currency: "IDR", + channel_code: "QRIS", + channel_properties: { + qr_string: "00020101021226660014ID.LINKAJA.WWW011893600911002414220002152003260414220010303UME51450015ID.OR.GPNQR.WWW02150000000000000000303UME520454995802ID5909Xendit QR6007Jakarta61051216062380115WxFFAGZrEbCqdch0715WxFFAGZrEbCqdch53033605405100006304716E" + } + }, + virtual_account: null + } + ], + has_more: false +}; + +const PAYMENT_METHOD_AUTH_SUCCESS_RESPONSE = { + id: "pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a", + card: null, + type: "DIRECT_DEBIT", + status: "ACTIVE", + actions: [], + country: "ID", + created: "2022-08-12T13:30:26.579048Z", + ewallet: null, + qr_code: null, + updated: "2022-08-12T13:30:58.908220358Z", + metadata: null, + customer_id: "e2878b4c-d57e-4a2c-922d-c0313c2800a3", + description: null, + reusability: "MULTIPLE_USE", + direct_debit: { + type: "DEBIT_CARD", + debit_card: { + mobile_number: "+62818555988", + card_last_four: "8888", + card_expiry: "06/24", + email: "email@email.com" + }, + bank_account: null, + channel_code: "BRI", + channel_properties: { + mobile_number: "+62818555988", + card_last_four: "8888", + card_expiry: "06/24", + email: "test.email@xendit.co" + } + }, + failure_code: null, + reference_id: "620b9df4-fe69-4bfd-b9d4-5cba6861db8a", + virtual_account: null, + over_the_counter: null, + billing_information: null, + direct_bank_transfer: null, + business_id: "5f27a14a9bf05c73dd040bc8" +} + +const GET_PAYMENT_METHOD_LIST_BY_ID_SUCCESS_RESPONSE = { + id: "pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a", + card: null, + type: "DIRECT_DEBIT", + status: "ACTIVE", + actions: [], + country: "ID", + created: "2022-08-12T13:30:26.579048Z", + ewallet: null, + qr_code: null, + updated: "2022-08-12T13:30:58.908220358Z", + metadata: null, + customer_id: "e2878b4c-d57e-4a2c-922d-c0313c2800a3", + description: null, + reusability: "MULTIPLE_USE", + direct_debit: { + type: "DEBIT_CARD", + debit_card: { + mobile_number: "+62818555988", + card_last_four: "8888", + card_expiry: "06/24", + email: "email@email.com" + }, + bank_account: null, + channel_code: "BRI", + channel_properties: { + mobile_number: "+62818555988", + card_last_four: "8888", + card_expiry: "06/24", + email: "test.email@xendit.co" + } + }, + failure_code: null, + reference_id: "620b9df4-fe69-4bfd-b9d4-5cba6861db8a", + virtual_account: null, + over_the_counter: null, + billing_information: null, + direct_bank_transfer: null, + business_id: "5f27a14a9bf05c73dd040bc8" +} + +const UPDATE_PAYMENT_METHOD_SUCCESS_RESPONSE = { + actions: [], + billing_information: null, + business_id: "5f27a14a9bf05c73dd040bc8", + card: null, + country: "ID", + created: "2022-08-12T13:30:26.579048Z", + customer_id: "e2878b4c-d57e-4a2c-922d-c0313c2800a3", + description: null, + direct_bank_transfer: null, + direct_debit: { + bank_account: null, + channel_code: "BRI", + channel_properties: { + card_expiry: "06/24", + card_last_four: "8888", + email: "test.email@xendit.co", + mobile_number: "+62818555988" + }, + debit_card: { + card_expiry: "06/24", + card_last_four: "8888", + email: "email@email.com", + mobile_number: "+62818555988" + }, + type: "DEBIT_CARD" + }, + ewallet: null, + failure_code: null, + id: "pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a", + metadata: null, + over_the_counter: null, + qr_code: null, + reference_id: "620b9df4-fe69-4bfd-b9d4-5cba6861db8a", + reusability: "MULTIPLE_USE", + status: "ACTIVE", + type: "DIRECT_DEBIT", + updated: "2022-08-12T13:30:58.908220358Z", + virtual_account: null, +} + +const EXPIRE_PAYMENT_METHOD_SUCCESS_RESPONSE = { + id: "pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a", + card: null, + type: "DIRECT_DEBIT", + status: "EXPIRED", + actions: [], + country: "PH", + created: "2022-08-12T13:30:26.579048Z", + ewallet: null, + qr_code: null, + updated: "2022-08-12T13:30:58.908220358Z", + metadata: null, + customer_id: "e2878b4c-d57e-4a2c-922d-c0313c2800a3", + description: null, + reusability: "MULTIPLE_USE", + direct_debit: { + type: "BANK_ACCOUNT", + debit_card: null, + bank_account: { + bank_account_hash: "b4dfa99c9b60c77f2e3962b73c098945", + masked_bank_account_number: "XXXXXX1234" + }, + channel_code: "BPI", + channel_propertie: { + failure_return_url: "https://your-redirect-website.com/failure", + success_return_url: "https://your-redirect-website.com/success" + } + }, + failure_code: null, + reference_id: "620b9df4-fe69-4bfd-b9d4-5cba6861db8a", + virtual_account: null, + over_the_counter: null, + billing_information: null, + direct_bank_transfer: null, + business_id: "5f27a14a9bf05c73dd040bc8" +} + +const LIST_PAYMENTS_BY_PAYMENT_METHOD_ID_SUCCESS_RESPONSE = { + actions: [], + billing_information: null, + business_id: "5f27a14a9bf05c73dd040bc8", + card: null, + country: "ID", + created: "2022-08-12T13:30:26.579048Z", + customer_id: "e2878b4c-d57e-4a2c-922d-c0313c2800a3", + description: null, + direct_bank_transfer: null, + direct_debit: { + bank_account: null, + channel_code: "BRI", + channel_properties: { + card_expiry: "06/24", + card_last_four: "8888", + email: "test.email@xendit.co", + mobile_number: "+62818555988", + }, + debit_card: { + card_expiry: "06/24", + card_last_four: "8888", + email: "email@email.com", + mobile_number: "+62818555988", + }, + type: "DEBIT_CARD" + }, + ewallet: null, + failure_code: null, + id: "pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a", + metadata: null, + over_the_counter: null, + qr_code: null, + reference_id: "620b9df4-fe69-4bfd-b9d4-5cba6861db8a", + reusability: "MULTIPLE_USE", + status: "ACTIVE", + type: "DIRECT_DEBIT", + updated: "2022-08-12T13:30:58.908220358Z", + virtual_account: null, +} + +module.exports = { + CREATE_PAYMENT_RESPONSE, + CREATE_PAYMENT_MISSING_TYPE_RESPONSE, + LIST_PAYMENT_METHOD_RESPONSE, + PAYMENT_METHOD_AUTH_SUCCESS_RESPONSE, + GET_PAYMENT_METHOD_LIST_BY_ID_SUCCESS_RESPONSE, + UPDATE_PAYMENT_METHOD_SUCCESS_RESPONSE, + EXPIRE_PAYMENT_METHOD_SUCCESS_RESPONSE, + LIST_PAYMENTS_BY_PAYMENT_METHOD_ID_SUCCESS_RESPONSE +}; diff --git a/test/payment_method_v2/payment_method_v2.test.js b/test/payment_method_v2/payment_method_v2.test.js new file mode 100644 index 0000000..74bf193 --- /dev/null +++ b/test/payment_method_v2/payment_method_v2.test.js @@ -0,0 +1,163 @@ +const chai = require('chai'); +const chaiAsProm = require('chai-as-promised'); +const TestConstants = require('./constants'); +const { expect } = chai; +const nock = require('nock'); +const { Errors } = require('../../src/xendit'); +const Xendit = require('../../src/xendit'); + +const x = new Xendit({ + secretKey: + 'xnd_production_ypr0UI6148UVBDHMJsHCUgF0Yff4XEjRSAzBvM626qPzHEBo45IRCBdqEHmmql', +}); + +chai.use(chaiAsProm); + +const { PaymentMethodV2 } = x; +let p = new PaymentMethodV2({}); +beforeEach(function() { + p = new PaymentMethodV2({}); +}); +before(function() { + nock(x.opts.xenditURL) + .post('/v2/payment_methods', { + type: 'QR_CODE', + reusability: 'ONE_TIME_USE', + qr_code: { + channel_code: 'QRIS', + amount: 10000, + }, + }) + .reply(201, TestConstants.CREATE_PAYMENT_RESPONSE) + .get('/v2/payment_methods') + .reply(200, TestConstants.LIST_PAYMENT_METHOD_RESPONSE) + .post('/v2/payment_methods/pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a/auth') + .reply(200, TestConstants.PAYMENT_METHOD_AUTH_SUCCESS_RESPONSE) + .get('/v2/payment_methods/pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a') + .reply(200, TestConstants.GET_PAYMENT_METHOD_LIST_BY_ID_SUCCESS_RESPONSE) + .patch('/v2/payment_methods/pm-4c85fd2c-29da-4bc4-b642-064a42727d89') + .reply(200, TestConstants.UPDATE_PAYMENT_METHOD_SUCCESS_RESPONSE) + .post('/v2/payment_methods/pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a/expire') + .reply(200, TestConstants.EXPIRE_PAYMENT_METHOD_SUCCESS_RESPONSE) + .get( + '/v2/payment_methods/qrpy_0de1622b-677c-48c5-ac8c-ea1b9636c48f/payments', + ) + .reply( + 200, + TestConstants.LIST_PAYMENTS_BY_PAYMENT_METHOD_ID_SUCCESS_RESPONSE, + ); +}); + +describe('Payment Method V2 Service', () => { + describe('create payments', () => { + it('should get a response of payment created', done => { + expect( + p.createPaymentMethodV2({ + type: 'QR_CODE', + reusability: 'ONE_TIME_USE', + qr_code: { + channel_code: 'QRIS', + amount: 10000, + }, + }), + ) + .to.eventually.deep.equal(TestConstants.CREATE_PAYMENT_RESPONSE) + .and.notify(done); + }); + it('should reject with missing field', done => { + expect( + p.createPaymentMethodV2({ + reusability: 'ONE_TIME_USE', + qr_code: { + channel_code: 'QRIS', + amount: 10000, + }, + }), + ) + .to.eventually.deep.equal( + TestConstants.CREATE_PAYMENT_MISSING_TYPE_RESPONSE, + ) + .to.eventually.be.rejected.then(e => + Promise.all([ + expect(e).to.have.property('status', 400), + expect(e).to.have.property('code', Errors.API_VALIDATION_ERROR), + ]), + ) + .then(() => done()) + .catch(e => done(e)); + }); + }); + describe('list payments', () => { + it('should get a list of payment created', done => { + expect(p.listPaymentMethodV2({})) + .to.eventually.deep.equal(TestConstants.LIST_PAYMENT_METHOD_RESPONSE) + .and.notify(done); + }); + }); + describe('auth payments', () => { + it('should get a success response of payment authorized', done => { + expect( + p.authorizePaymentMethodV2({ + auth_code: '12345', + id: 'pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a', + }), + ) + .to.eventually.deep.equal( + TestConstants.PAYMENT_METHOD_AUTH_SUCCESS_RESPONSE, + ) + .and.notify(done); + }); + }); + describe('get payment method by id', () => { + it('should get a response of payment method by id', done => { + expect( + p.getPaymentMethodByIdV2({ + id: 'pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a', + }), + ) + .to.eventually.deep.equal( + TestConstants.PAYMENT_METHOD_AUTH_SUCCESS_RESPONSE, + ) + .and.notify(done); + }); + }); + describe('update payment method', () => { + it('should get a response of updated payment method by id', done => { + expect( + p.updatePaymentMethodV2({ + id: 'pm-4c85fd2c-29da-4bc4-b642-064a42727d89', + }), + ) + .to.eventually.deep.equal( + TestConstants.UPDATE_PAYMENT_METHOD_SUCCESS_RESPONSE, + ) + .and.notify(done); + }); + }); + describe('expire payment method', () => { + it('should get a response of expired payment method by id', done => { + expect( + p.expirePaymentMethodV2({ + id: 'pm-6ff0b6f2-f5de-457f-b08f-bc98fbae485a', + }), + ) + .to.eventually.deep.equal( + TestConstants.EXPIRE_PAYMENT_METHOD_SUCCESS_RESPONSE, + ) + .and.notify(done); + }); + }); + describe('list payments by payment method', () => { + it('should get a list of payments by payment method', done => { + expect( + p.listPaymentsByPaymentMethodIdV2({ + id: 'qrpy_0de1622b-677c-48c5-ac8c-ea1b9636c48f', + }), + ) + .to.eventually.deep.equal( + TestConstants.UPDATE_PAYMENT_METHOD_SUCCESS_RESPONSE, + ) + .and.notify(done); + }); + }); +});