UNPKG

@defra-fish/sales-api-service

Version:
203 lines (185 loc) • 8.5 kB
import { dynamicsClient, executeQuery, findById, findDueRecurringPayments, findRecurringPaymentsByAgreementId, persist, RecurringPayment, findRecurringPaymentByPermissionId, retrieveGlobalOptionSets } from '@defra-fish/dynamics-lib' import { calculateEndDate, generatePermissionNumber } from './permissions.service.js' import { getObfuscatedDob } from './contacts.service.js' import { createHash } from 'node:crypto' import { PAYMENT_JOURNAL_STATUS_CODES, PAYMENT_TYPE, TRANSACTION_SOURCE } from '@defra-fish/business-rules-lib' import { TRANSACTION_STAGING_TABLE, TRANSACTION_QUEUE } from '../config.js' import { TRANSACTION_STATUS } from '../services/transactions/constants.js' import { retrieveStagedTransaction } from '../services/transactions/retrieve-transaction.js' import { createPaymentJournal, getPaymentJournal, updatePaymentJournal } from '../services/paymentjournals/payment-journals.service.js' import { getGlobalOptionSetValue } from './reference-data.service.js' import moment from 'moment' import { AWS, govUkPayApi } from '@defra-fish/connectors-lib' import db from 'debug' const debug = db('sales:recurring') const { sqs, docClient } = AWS() export const getRecurringPayments = date => executeQuery(findDueRecurringPayments(date)) const getNextDueDate = (startDate, issueDate, endDate) => { const mStart = moment(startDate) if (mStart.isAfter(moment(issueDate))) { if (mStart.isSame(moment(issueDate), 'day')) { return moment(startDate).add(1, 'year').subtract(10, 'days').startOf('day').toISOString() } if (mStart.isBefore(moment(issueDate).add(10, 'days'), 'day')) { return moment(endDate).subtract(10, 'days').startOf('day').toISOString() } return moment(issueDate).add(1, 'year').startOf('day').toISOString() } throw new Error('Invalid dates provided for permission') } export const generateRecurringPaymentRecord = async (transactionRecord, permission) => { if (transactionRecord.recurringPayment?.agreementId) { const agreementResponse = await getRecurringPaymentAgreement(transactionRecord.recurringPayment.agreementId) const lastDigitsCardNumbers = agreementResponse.payment_instrument?.card_details?.last_digits_card_number const [{ startDate, issueDate, endDate }] = transactionRecord.permissions return { payment: { recurring: { name: '', nextDueDate: getNextDueDate(startDate, issueDate, endDate), cancelledDate: null, cancelledReason: null, endDate, agreementId: transactionRecord.recurringPayment.agreementId, status: 1, last_digits_card_number: lastDigitsCardNumbers } }, permissions: [permission] } } return { payment: { recurring: false } } } /** * Process a recurring payment instruction * @param transactionRecord * @returns {Promise<{recurringPayment: RecurringPayment | null}>} */ export const processRecurringPayment = async (transactionRecord, contact) => { const hash = createHash('sha256') if (transactionRecord.payment?.recurring) { const recurringPayment = new RecurringPayment() hash.update(recurringPayment.uniqueContentId) recurringPayment.name = determineRecurringPaymentName(transactionRecord, contact) recurringPayment.nextDueDate = transactionRecord.payment.recurring.nextDueDate recurringPayment.cancelledDate = transactionRecord.payment.recurring.cancelledDate recurringPayment.cancelledReason = transactionRecord.payment.recurring.cancelledReason recurringPayment.endDate = transactionRecord.payment.recurring.endDate recurringPayment.agreementId = transactionRecord.payment.recurring.agreementId recurringPayment.publicId = hash.digest('base64') recurringPayment.status = transactionRecord.payment.recurring.status recurringPayment.lastDigitsCardNumbers = transactionRecord.payment.recurring.last_digits_card_number const [permission] = transactionRecord.permissions recurringPayment.bindToEntity(RecurringPayment.definition.relationships.activePermission, permission) recurringPayment.bindToEntity(RecurringPayment.definition.relationships.contact, contact) return { recurringPayment } } return { recurringPayment: null } } export const getRecurringPaymentAgreement = async agreementId => { const response = await govUkPayApi.getRecurringPaymentAgreementInformation(agreementId) if (response.ok) { const resBody = await response.json() const resBodyNoCardDetails = structuredClone(resBody) if (resBodyNoCardDetails.payment_instrument?.card_details) { delete resBodyNoCardDetails.payment_instrument.card_details } debug('Successfully got recurring payment agreement information: %o', resBodyNoCardDetails) return resBody } else { throw new Error('Failure getting agreement in the GOV.UK API service') } } export const processRPResult = async (transactionId, paymentId, createdDate) => { const transactionRecord = await retrieveStagedTransaction(transactionId) if (await getPaymentJournal(transactionId)) { await updatePaymentJournal(transactionId, { paymentReference: paymentId, paymentTimestamp: createdDate, paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.InProgress }) } else { await createPaymentJournal(transactionId, { paymentReference: paymentId, paymentTimestamp: createdDate, paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.InProgress }) } const [permission] = transactionRecord.permissions permission.issueDate = new Date().toISOString() permission.endDate = await calculateEndDate(permission) permission.referenceNumber = await generatePermissionNumber(permission, transactionRecord.dataSource) permission.licensee.obfuscatedDob = await getObfuscatedDob(permission.licensee) await docClient.update({ TableName: TRANSACTION_STAGING_TABLE.TableName, Key: { id: transactionId }, ...docClient.createUpdateExpression({ payload: permission, permissions: transactionRecord.permissions, status: { id: TRANSACTION_STATUS.FINALISED }, payment: { amount: transactionRecord.cost, method: PAYMENT_TYPE.debit, source: TRANSACTION_SOURCE.govPay, timestamp: new Date().toISOString() } }), ReturnValues: 'ALL_NEW' }) await sqs.sendMessage({ QueueUrl: TRANSACTION_QUEUE.Url, MessageGroupId: transactionId, MessageDeduplicationId: transactionId, MessageBody: JSON.stringify({ id: transactionId }) }) return { permission } } export const findNewestExistingRecurringPaymentInCrm = async agreementId => { const query = findRecurringPaymentsByAgreementId(agreementId) const response = await dynamicsClient.retrieveMultipleRequest(query.toRetrieveRequest()) if (response.value.length) { const [rcpResponseData] = response.value.sort((a, b) => Date.parse(b.defra_enddate) - Date.parse(a.defra_enddate)) return RecurringPayment.fromResponse(rcpResponseData) } return false } export const cancelRecurringPayment = async (id, reason) => { const recurringPayment = await findById(RecurringPayment, id) if (recurringPayment) { const data = recurringPayment const isUserCancelled = reason === 'User Cancelled' if (!isUserCancelled) { data.cancelledDate = new Date().toISOString().split('T')[0] } data.cancelledReason = await getGlobalOptionSetValue(RecurringPayment.definition.mappings.cancelledReason.ref, reason) const updatedRecurringPayment = Object.assign(new RecurringPayment(), data) await persist([updatedRecurringPayment]) return updatedRecurringPayment } else { throw new Error('Invalid id provided for recurring payment cancellation') } } const determineRecurringPaymentName = (transactionRecord, contact) => { const [dueYear] = transactionRecord.payment.recurring.nextDueDate.split('-') return [contact.firstName, contact.lastName, dueYear].join(' ') } export const findLinkedRecurringPayment = async permissionId => { const query = findRecurringPaymentByPermissionId(permissionId) const response = await dynamicsClient.retrieveMultipleRequest(query.toRetrieveRequest()) if (response.value.length) { const [rcpResponseData] = response.value.sort((a, b) => Date.parse(b.defra_enddate) - Date.parse(a.defra_enddate)) const definition = await retrieveGlobalOptionSets().cached() return RecurringPayment.fromResponse(rcpResponseData, definition) } return false }