@wepublish/api
Version:
API core for we.publish.
297 lines • 16 kB
JavaScript
"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