UNPKG

@wepublish/api

Version:
297 lines 16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BexioPaymentProvider = void 0; const tslib_1 = require("tslib"); const client_1 = require("@prisma/client"); const api_1 = require("../../../../../utils-api/src"); const bexio_1 = tslib_1.__importDefault(require("bexio")); const mapped_replacer_1 = require("mapped-replacer"); const payment_provider_1 = require("../payment-provider"); const bexio_errors_1 = require("./bexio-errors"); const bexio_utils_1 = require("./bexio-utils"); const node_fetch_1 = tslib_1.__importDefault(require("node-fetch")); class BexioPaymentProvider extends payment_provider_1.BasePaymentProvider { constructor(props) { super(props); this.apiKey = props.apiKey; this.userId = props.userId; this.countryId = props.countryId; this.invoiceTemplateNewMembership = props.invoiceTemplateNewMembership; this.invoiceTemplateRenewalMembership = props.invoiceTemplateRenewalMembership; this.unitId = props.unitId; this.taxId = props.taxId; this.accountId = props.accountId; this.invoiceTitleNewMembership = props.invoiceTitleNewMembership; this.invoiceTitleRenewalMembership = props.invoiceTitleRenewalMembership; this.invoiceMailSubjectNewMembership = props.invoiceMailSubjectNewMembership; this.invoiceMailBodyNewMembership = props.invoiceMailBodyNewMembership; this.invoiceMailSubjectRenewalMembership = props.invoiceMailSubjectRenewalMembership; this.invoiceMailBodyRenewalMembership = props.invoiceMailBodyRenewalMembership; this.markInvoiceAsOpen = props.markInvoiceAsOpen; this.prisma = props.prisma; this.bexio = new bexio_1.default(this.apiKey); } webhookForPaymentIntent(props) { return tslib_1.__awaiter(this, void 0, void 0, function* () { throw new bexio_errors_1.WebhookNotImplementedError(); }); } /** * Creates a payment intent based on the provided invoice details. * If the invoice has an associated subscription ID, it throws an error. * Otherwise, it proceeds to create a payment intent using Bexio's API. * * @param {CreatePaymentIntentProps} props - The properties required to create the payment intent. * @returns {Promise<Intent>} A promise that resolves to the created payment intent object. * @throws {NoSubscriptionIdInInvoice} Throws an error if the invoice contains a subscription ID. */ createIntent(props) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!props.invoice.subscriptionID) { throw new bexio_errors_1.NoSubscriptionIdInInvoice(); } return yield this.bexioCreate(props.invoice.id, false, props.successURL); }); } /** * This function calls the `bexioCreate` method to create an invoice in Bexio. * It is designed to handle the creation of remote invoices, specifically for renewals. * * @async * @function * @param {CreateRemoteInvoiceProps} props - The properties required to create the remote invoice. * @param {string} props.invoice.id - The ID of the invoice. * * @returns {Promise<void>} Resolves when the remote invoice is successfully created. */ createRemoteInvoice(props) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.bexioCreate(props.invoice.id, true); }); } /** * Checks the status of a payment intent in Bexio. * It makes a direct fetch request to the Bexio API because the Bexio library * does not return the status when querying for an invoice. * * @param {CheckIntentProps} CheckIntentProps - An object with the intentID to check the status. * @returns {Promise<IntentState>} A promise that resolves to the status of the intent. * @throws {ResponseNOK} Throws an error if the response status from Bexio is not 200. * @throws {UnknownIntentState} Throws an error if the intent status is not recognized. */ checkIntentStatus({ intentID }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // currently the bexio library we use doesn't return the status (kb_item_status_id) // when querying for invoice, so we need to do it "manually" using fetch api const bexioBaseUrl = 'https://api.bexio.com/2.0'; const headers = { Method: 'GET', Accept: 'application/json', Authorization: `Bearer ${this.apiKey}` }; const response = yield (0, node_fetch_1.default)(`${bexioBaseUrl}/kb_invoice/${intentID}`, { headers }); if (response.status !== 200) { (0, api_1.logger)('bexioPaymentProvider').error('Bexio response for intent %s is NOK with status %s', intentID, response.status); throw new bexio_errors_1.ResponseNOK(response.status); } const bexioResponse = yield response.json(); const intentStatus = (0, bexio_utils_1.mapBexioStatusToPaymentStatus)(bexioResponse.kb_item_status_id); if (!intentStatus) { (0, api_1.logger)('bexioPaymentProvider').error('Bexio intent with ID: %s for paymentProvider %s returned with an unknown state %s', intentID, this.id, intentStatus); throw new bexio_errors_1.UnknownIntentState(); } // Get payment to return it const payment = yield this.prisma.payment.findFirst({ where: { invoiceID: bexioResponse.api_reference, intentID } }); if (!payment) { (0, api_1.logger)('bexioPaymentProvider').error('No payment with intentID <%s> and invoiceID <%s> found!', intentID, bexioResponse.api_reference); throw new bexio_errors_1.PaymentNotFound(); } return { state: intentStatus, paymentID: payment.id, paymentData: JSON.stringify(bexioResponse), customerID: bexioResponse.contact_id ? bexioResponse.contact_id.toString() : '' }; }); } /** * Creates an invoice in Bexio based on a provided invoice ID and optionally marks it as a renewal. * * This function performs several operations: * 1. Fetches the invoice details from the database using Prisma. * 2. Searches for the associated contact in Bexio or creates/updates the contact. * 3. Creates a new invoice in Bexio for the contact. * * @async * @function * @param {string} invoiceId - The ID of the invoice in the local database. * @param {boolean} isRenewal - Indicates whether the invoice is a renewal or a new invoice. * @param {string} successURL - The url the client gets redirected after successful invoice creation * * @throws {InvoiceNotFoundError} If the invoice or related data cannot be found in the local database. * @throws {Error} For any other generic error. * * @returns {Promise<Intent>} An object containing: * - intentID: The ID of the newly created invoice in Bexio. * - intentSecret: An empty string (as there's no secret provided in the function). * - intentData: A JSON string representation of the new invoice from Bexio. * - state: The state of the payment, set as 'submitted'. */ bexioCreate(invoiceId, isRenewal, successURL = '') { return tslib_1.__awaiter(this, void 0, void 0, function* () { try { const invoice = yield this.prisma.invoice.findUnique({ where: { id: invoiceId }, include: { subscription: { include: { memberPlan: true, user: { include: { address: true } } } } } }); if (!invoice || !invoice.subscription || !invoice.subscription.user) { throw new bexio_errors_1.InvoiceNotFoundError(invoice); } const contact = yield (0, bexio_utils_1.searchForContact)(this.bexio, invoice.subscription.user); const updatedContact = yield this.createOrUpdateContact(contact, invoice.subscription.user); const newInvoice = yield this.createInvoice(updatedContact, invoice, isRenewal); return { intentID: newInvoice.id.toString(), intentSecret: successURL, intentData: JSON.stringify(newInvoice), state: client_1.PaymentState.submitted }; } catch (e) { console.error(e); throw e; } }); } /** * Creates a new contact in Bexio or updates an existing contact based on provided data. * * The function determines whether to create or update a contact based on the presence * of the `contact` parameter. If a contact is provided, it will be updated; otherwise, * a new contact will be created. The function utilizes user data, especially the user's * address, to populate or overwrite contact details in Bexio. * * @async * @function * @param {ContactsStatic.ContactSmall} contact - The existing contact details from Bexio. * If this parameter is not provided, a new contact will be created. * @param {User & {address: UserAddress | null}} user - The user's details, including the associated address. * * @returns {Promise<ContactsStatic.ContactFull>} The created or updated contact object from Bexio. */ createOrUpdateContact(contact, user) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; return tslib_1.__awaiter(this, void 0, void 0, function* () { const upsertContact = { nr: '', name_1: ((_a = user === null || user === void 0 ? void 0 : user.address) === null || _a === void 0 ? void 0 : _a.company) ? user === null || user === void 0 ? void 0 : user.address.company : user.name, name_2: ((_b = user === null || user === void 0 ? void 0 : user.address) === null || _b === void 0 ? void 0 : _b.company) ? '' : (_c = user.firstName) !== null && _c !== void 0 ? _c : undefined, mail: user.email, user_id: this.userId, contact_type_id: ((_d = user === null || user === void 0 ? void 0 : user.address) === null || _d === void 0 ? void 0 : _d.company) ? 1 : 2, country_id: this.countryId, owner_id: this.userId, contact_group_ids: [], postcode: (_f = (_e = user === null || user === void 0 ? void 0 : user.address) === null || _e === void 0 ? void 0 : _e.zipCode) !== null && _f !== void 0 ? _f : undefined, city: (_h = (_g = user === null || user === void 0 ? void 0 : user.address) === null || _g === void 0 ? void 0 : _g.city) !== null && _h !== void 0 ? _h : undefined, address: (_k = (_j = user === null || user === void 0 ? void 0 : user.address) === null || _j === void 0 ? void 0 : _j.streetAddress) !== null && _k !== void 0 ? _k : undefined }; if (!contact) { return yield this.bexio.contacts.create(upsertContact); } else { upsertContact.nr = contact.nr; return yield this.bexio.contacts.edit(contact.id, upsertContact); } }); } /** * Creates and sends an invoice in Bexio based on provided contact, invoice, and renewal status. * * This function performs several operations: * 1. Validates the provided invoice and associated data. * 2. Creates a string replace map based on the subscription, user, and memberPlan. * 3. Constructs a new Bexio invoice object and creates the invoice in Bexio. * 4. Sends the created Bexio invoice to the associated contact. * * @async * @function * @param {ContactsStatic.ContactFull} contact - The contact associated with the invoice in Bexio. * @param {Invoice & {subscription: (Subscription & {memberPlan: MemberPlan; user: User}) | null}} invoice - * The invoice data and its associated subscription, member plan, and user details. * @param {boolean} isRenewal - Indicates whether the invoice is a renewal or a new invoice. * * @throws {InvoiceNotFoundError} If the provided invoice or related data is not valid. * @throws {SendingInvoiceError} If there's an issue sending the created invoice. * * @returns {Promise<InvoicesStatic.Invoice>} The created invoice object from Bexio. */ createInvoice(contact, invoice, isRenewal) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!invoice || !invoice.subscription || !invoice.subscription.user) { throw new bexio_errors_1.InvoiceNotFoundError(invoice); } const stringReplaceMap = new mapped_replacer_1.MappedReplacer(); (0, bexio_utils_1.addToStringReplaceMap)(stringReplaceMap, 'subscription', invoice.subscription); (0, bexio_utils_1.addToStringReplaceMap)(stringReplaceMap, 'user', invoice.subscription.user); (0, bexio_utils_1.addToStringReplaceMap)(stringReplaceMap, 'memberPlan', invoice.subscription.memberPlan); const bexioInvoice = { title: isRenewal ? this.invoiceTitleRenewalMembership : this.invoiceTitleNewMembership, contact_id: contact.id, user_id: this.userId, mwst_type: 0, mwst_is_net: false, api_reference: invoice.id, template_slug: isRenewal ? this.invoiceTemplateRenewalMembership : this.invoiceTemplateNewMembership, positions: [ { amount: '1', unit_id: this.unitId, account_id: this.accountId, tax_id: this.taxId, text: invoice.subscription.memberPlan.name, unit_price: `${(invoice.subscription.monthlyAmount * (0, api_1.mapPaymentPeriodToMonths)(invoice.subscription.paymentPeriodicity)) / 100}`, type: 'KbPositionCustom' } ] }; const invoiceUpdated = yield this.bexio.invoices.create(bexioInvoice); const sentInvoice = yield this.bexio.invoices.sent(invoiceUpdated.id, { recipient_email: contact.mail, subject: stringReplaceMap.replace(isRenewal ? this.invoiceMailSubjectRenewalMembership : this.invoiceMailSubjectNewMembership), message: stringReplaceMap.replace(isRenewal ? this.invoiceMailBodyRenewalMembership : this.invoiceMailBodyNewMembership), mark_as_open: this.markInvoiceAsOpen, attach_pdf: true }); (0, api_1.logger)('bexioPaymentProvider').info('Created Bexio invoice with ID: %s for paymentProvider %s', invoiceUpdated.id, this.id); if (!sentInvoice.success) { throw new bexio_errors_1.SendingInvoiceError(sentInvoice); } return invoiceUpdated; }); } } exports.BexioPaymentProvider = BexioPaymentProvider; //# sourceMappingURL=bexio-payment-provider.js.map