UNPKG

medusa-payment-pesapal

Version:

Pesapal payment provider plugin for Medusa v2 - Accept payments via Pesapal gateway with support for M-Pesa, Airtel Money, and other East African payment methods

350 lines 28.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PesapalProviderService = void 0; const utils_1 = require("@medusajs/framework/utils"); class PesapalProviderService extends utils_1.AbstractPaymentProvider { constructor(container, options) { super(container, options); this.authToken = null; this.tokenExpiry = null; this.options_ = options; this.logger_ = container.logger; // Validate required options if (!options.consumer_key || !options.consumer_secret) { throw new Error("Pesapal consumer_key and consumer_secret are required"); } } get baseUrl() { return this.options_.environment === "live" ? "https://pay.pesapal.com/v3" : "https://cybqa.pesapal.com/pesapalv3"; } async getAuthToken() { // Check if we have a valid token if (this.authToken && this.tokenExpiry && new Date() < this.tokenExpiry) { return this.authToken; } try { const response = await fetch(`${this.baseUrl}/api/Auth/RequestToken`, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ consumer_key: this.options_.consumer_key, consumer_secret: this.options_.consumer_secret, }), }); const data = await response.json(); if (data.error || !data.token) { throw new Error(`Pesapal authentication failed: ${data.message || data.error}`); } this.authToken = data.token; this.tokenExpiry = new Date(data.expiryDate); return this.authToken; } catch (error) { this.logger_.error("Failed to get Pesapal auth token", error); throw new Error("Failed to authenticate with Pesapal"); } } async makeAuthenticatedRequest(endpoint, options = {}) { const token = await this.getAuthToken(); const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json", "Accept": "application/json", ...options.headers, }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Pesapal API error: ${response.status} ${errorText}`); } return response.json(); } async initiatePayment(input) { try { const { currency_code, amount, context: sessionContext } = input; const { customer, billing_address } = (sessionContext || {}); // Generate a unique merchant reference const merchantReference = `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Prepare order data const orderData = { id: merchantReference, currency: currency_code.toUpperCase(), amount: Number(amount) / 100, // Convert from cents to major currency unit description: `Payment for order ${merchantReference}`, callback_url: this.options_.ipn_url || `${process.env.BACKEND_URL}/store/pesapal/webhook`, notification_id: await this.getIpnId(), billing_address: { email_address: customer?.email || billing_address?.email || "customer@example.com", phone_number: billing_address?.phone || customer?.phone || "", country_code: billing_address?.country_code || "KE", first_name: billing_address?.first_name || customer?.first_name || "Customer", middle_name: billing_address?.address_2 || "", last_name: billing_address?.last_name || customer?.last_name || "", line_1: billing_address?.address_1 || "", line_2: billing_address?.address_2 || "", city: billing_address?.city || "", state: billing_address?.province || "", postal_code: billing_address?.postal_code || "", zip_code: billing_address?.postal_code || "", }, }; const response = await this.makeAuthenticatedRequest("/api/Transactions/SubmitOrderRequest", { method: "POST", body: JSON.stringify(orderData), }); if (response.error) { throw new Error(`Pesapal order submission failed: ${response.message}`); } return { id: response.order_tracking_id, data: { order_tracking_id: response.order_tracking_id, merchant_reference: response.merchant_reference, redirect_url: response.redirect_url, amount: amount, currency: currency_code, status: "pending", }, }; } catch (error) { this.logger_.error("Failed to initiate Pesapal payment", error); throw new Error("Failed to initiate payment"); } } async authorizePayment(input) { try { const { order_tracking_id } = (input.data || {}); // Get transaction status from Pesapal const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`); const status = this.mapPesapalStatus(statusResponse.payment_status_description); return { status: status, data: { ...input.data, payment_status: statusResponse.payment_status_description, confirmation_code: statusResponse.confirmation_code, payment_method: statusResponse.payment_method, payment_account: statusResponse.payment_account, status: status, }, }; } catch (error) { this.logger_.error("Failed to authorize Pesapal payment", error); throw new Error("Failed to authorize payment"); } } async capturePayment(input) { try { // Pesapal doesn't have a separate capture step - payment is captured upon authorization const { order_tracking_id } = (input.data || {}); const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`); return { data: { ...input.data, payment_status: statusResponse.payment_status_description, confirmation_code: statusResponse.confirmation_code, status: "captured", }, }; } catch (error) { this.logger_.error("Failed to capture Pesapal payment", error); throw new Error("Failed to capture payment"); } } async refundPayment(input) { try { const { confirmation_code } = (input.data || {}); const { amount } = input; // Prepare refund data const refundData = { confirmation_code: confirmation_code, amount: Number(amount) / 100, // Convert from cents username: this.options_.merchant_name || "merchant", remarks: `Refund for payment ${confirmation_code}`, }; const response = await this.makeAuthenticatedRequest("/api/Transactions/RefundRequest", { method: "POST", body: JSON.stringify(refundData), }); return { data: { ...input.data, refund_status: "refunded", refund_amount: amount, refund_response: response, }, }; } catch (error) { this.logger_.error("Failed to refund Pesapal payment", error); throw new Error("Failed to refund payment"); } } async cancelPayment(input) { try { // Pesapal doesn't have explicit cancel - we just mark it as cancelled return { data: { ...input.data, status: "canceled", canceled_at: new Date().toISOString(), }, }; } catch (error) { this.logger_.error("Failed to cancel Pesapal payment", error); throw new Error("Failed to cancel payment"); } } async deletePayment(input) { // Return the session data as-is since Pesapal doesn't support deleting payments return { data: input.data, }; } async retrievePayment(input) { try { const { order_tracking_id } = (input.data || {}); // Get transaction status from Pesapal const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`); return { data: { ...input.data, payment_status: statusResponse.payment_status_description, confirmation_code: statusResponse.confirmation_code, payment_method: statusResponse.payment_method, payment_account: statusResponse.payment_account, amount: statusResponse.amount, currency: statusResponse.currency, created_date: statusResponse.created_date, }, }; } catch (error) { this.logger_.error("Failed to retrieve Pesapal payment", error); throw new Error("Failed to retrieve payment"); } } async updatePayment(input) { // Return the current session data since Pesapal doesn't support updating payments return { data: input.data, }; } async getPaymentStatus(input) { try { const { order_tracking_id } = (input.data || {}); // Get transaction status from Pesapal const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`); const status = this.mapPesapalStatus(statusResponse.payment_status_description); return { status: status, data: { ...input.data, payment_status: statusResponse.payment_status_description, confirmation_code: statusResponse.confirmation_code, status: status, }, }; } catch (error) { this.logger_.error("Failed to get Pesapal payment status", error); return { status: "pending", data: input.data, }; } } async getWebhookActionAndData(data) { try { const webhookData = data.data; const { OrderTrackingId, OrderMerchantReference } = webhookData; if (!OrderTrackingId) { return { action: "not_supported" }; } // Get the current status from Pesapal const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${OrderTrackingId}`); const status = this.mapPesapalStatus(statusResponse.payment_status_code); let action = "not_supported"; switch (status) { case "authorized": action = "authorized"; break; case "captured": action = "captured"; break; case "error": action = "failed"; break; default: action = "not_supported"; } return { action: action, data: { session_id: String(OrderMerchantReference), amount: statusResponse.amount * 100, // Convert to cents }, }; } catch (error) { this.logger_.error("Failed to process Pesapal webhook", error); return { action: "failed" }; } } mapPesapalStatus(pesapalStatus) { switch (pesapalStatus) { case "0": return "pending"; case "1": return "authorized"; case "2": return "captured"; case "3": return "canceled"; default: return "error"; } } async getIpnId() { try { // First, try to get existing IPN registrations const ipnList = await this.makeAuthenticatedRequest("/api/URLSetup/GetIpnList"); // Check if we already have an IPN registered for our URL const existingIpn = ipnList.find((ipn) => ipn.url === this.options_.ipn_url && ipn.status === "Active"); if (existingIpn) { return existingIpn.ipn_id; } // Register new IPN if none exists const ipnData = { url: this.options_.ipn_url || `${process.env.BACKEND_URL}/store/pesapal/webhook`, ipn_notification_type: "GET", }; const response = await this.makeAuthenticatedRequest("/api/URLSetup/RegisterIPN", { method: "POST", body: JSON.stringify(ipnData), }); return response.ipn_id; } catch (error) { this.logger_.error("Failed to get/register IPN", error); // Return a default IPN ID or throw error based on your preference throw new Error("Failed to setup IPN registration"); } } } exports.PesapalProviderService = PesapalProviderService; PesapalProviderService.identifier = "pesapal"; PesapalProviderService.displayName = "Pesapal"; exports.default = PesapalProviderService; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9wcm92aWRlcnMvcGVzYXBhbC9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFEQUFtRTtBQWtFbkUsTUFBYSxzQkFBdUIsU0FBUSwrQkFBdUM7SUFTakYsWUFDRSxTQUFjLEVBQ2QsT0FBdUI7UUFFdkIsS0FBSyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQTtRQVBuQixjQUFTLEdBQWtCLElBQUksQ0FBQTtRQUMvQixnQkFBVyxHQUFnQixJQUFJLENBQUE7UUFRckMsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7UUFDdkIsSUFBSSxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUMsTUFBTSxDQUFBO1FBRS9CLDRCQUE0QjtRQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksSUFBSSxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUN0RCxNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxDQUFDLENBQUE7UUFDMUUsQ0FBQztJQUNILENBQUM7SUFFRCxJQUFZLE9BQU87UUFDakIsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsS0FBSyxNQUFNO1lBQ3pDLENBQUMsQ0FBQyw0QkFBNEI7WUFDOUIsQ0FBQyxDQUFDLHFDQUFxQyxDQUFBO0lBQzNDLENBQUM7SUFFTyxLQUFLLENBQUMsWUFBWTtRQUN4QixpQ0FBaUM7UUFDakMsSUFBSSxJQUFJLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDeEUsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFBO1FBQ3ZCLENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPLHdCQUF3QixFQUFFO2dCQUNwRSxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUU7b0JBQ1AsY0FBYyxFQUFFLGtCQUFrQjtvQkFDbEMsUUFBUSxFQUFFLGtCQUFrQjtpQkFDN0I7Z0JBQ0QsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7b0JBQ25CLFlBQVksRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVk7b0JBQ3hDLGVBQWUsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWU7aUJBQy9DLENBQUM7YUFDSCxDQUFDLENBQUE7WUFFRixNQUFNLElBQUksR0FBd0IsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUE7WUFFdkQsSUFBSSxJQUFJLENBQUMsS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLGtDQUFrQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFBO1lBQ2pGLENBQUM7WUFFRCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUE7WUFDM0IsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUE7WUFFNUMsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFBO1FBQ3ZCLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0NBQWtDLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDN0QsTUFBTSxJQUFJLEtBQUssQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFBO1FBQ3hELENBQUM7SUFDSCxDQUFDO0lBRU8sS0FBSyxDQUFDLHdCQUF3QixDQUFDLFFBQWdCLEVBQUUsVUFBdUIsRUFBRTtRQUNoRixNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQTtRQUV2QyxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPLEdBQUcsUUFBUSxFQUFFLEVBQUU7WUFDekQsR0FBRyxPQUFPO1lBQ1YsT0FBTyxFQUFFO2dCQUNQLGVBQWUsRUFBRSxVQUFVLEtBQUssRUFBRTtnQkFDbEMsY0FBYyxFQUFFLGtCQUFrQjtnQkFDbEMsUUFBUSxFQUFFLGtCQUFrQjtnQkFDNUIsR0FBRyxPQUFPLENBQUMsT0FBTzthQUNuQjtTQUNGLENBQUMsQ0FBQTtRQUVGLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDakIsTUFBTSxTQUFTLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUE7WUFDdkMsTUFBTSxJQUFJLEtBQUssQ0FBQyxzQkFBc0IsUUFBUSxDQUFDLE1BQU0sSUFBSSxTQUFTLEVBQUUsQ0FBQyxDQUFBO1FBQ3ZFLENBQUM7UUFFRCxPQUFPLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtJQUN4QixDQUFDO0lBRUQsS0FBSyxDQUFDLGVBQWUsQ0FDbkIsS0FBMkI7UUFFM0IsSUFBSSxDQUFDO1lBQ0gsTUFBTSxFQUFFLGFBQWEsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLGNBQWMsRUFBRSxHQUFHLEtBQUssQ0FBQTtZQUNoRSxNQUFNLEVBQUUsUUFBUSxFQUFFLGVBQWUsRUFBRSxHQUFHLENBQUMsY0FBYyxJQUFJLEVBQUUsQ0FBUSxDQUFBO1lBRW5FLHVDQUF1QztZQUN2QyxNQUFNLGlCQUFpQixHQUFHLFNBQVMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFBO1lBRTFGLHFCQUFxQjtZQUNyQixNQUFNLFNBQVMsR0FBRztnQkFDaEIsRUFBRSxFQUFFLGlCQUFpQjtnQkFDckIsUUFBUSxFQUFFLGFBQWEsQ0FBQyxXQUFXLEVBQUU7Z0JBQ3JDLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsR0FBRyxFQUFFLDRDQUE0QztnQkFDMUUsV0FBVyxFQUFFLHFCQUFxQixpQkFBaUIsRUFBRTtnQkFDckQsWUFBWSxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLHdCQUF3QjtnQkFDekYsZUFBZSxFQUFFLE1BQU0sSUFBSSxDQUFDLFFBQVEsRUFBRTtnQkFDdEMsZUFBZSxFQUFFO29CQUNmLGFBQWEsRUFBRSxRQUFRLEVBQUUsS0FBSyxJQUFLLGVBQXVCLEVBQUUsS0FBSyxJQUFJLHNCQUFzQjtvQkFDM0YsWUFBWSxFQUFHLGVBQXVCLEVBQUUsS0FBSyxJQUFJLFFBQVEsRUFBRSxLQUFLLElBQUksRUFBRTtvQkFDdEUsWUFBWSxFQUFHLGVBQXVCLEVBQUUsWUFBWSxJQUFJLElBQUk7b0JBQzVELFVBQVUsRUFBRyxlQUF1QixFQUFFLFVBQVUsSUFBSSxRQUFRLEVBQUUsVUFBVSxJQUFJLFVBQVU7b0JBQ3RGLFdBQVcsRUFBRyxlQUF1QixFQUFFLFNBQVMsSUFBSSxFQUFFO29CQUN0RCxTQUFTLEVBQUcsZUFBdUIsRUFBRSxTQUFTLElBQUksUUFBUSxFQUFFLFNBQVMsSUFBSSxFQUFFO29CQUMzRSxNQUFNLEVBQUcsZUFBdUIsRUFBRSxTQUFTLElBQUksRUFBRTtvQkFDakQsTUFBTSxFQUFHLGVBQXVCLEVBQUUsU0FBUyxJQUFJLEVBQUU7b0JBQ2pELElBQUksRUFBRyxlQUF1QixFQUFFLElBQUksSUFBSSxFQUFFO29CQUMxQyxLQUFLLEVBQUcsZUFBdUIsRUFBRSxRQUFRLElBQUksRUFBRTtvQkFDL0MsV0FBVyxFQUFHLGVBQXVCLEVBQUUsV0FBVyxJQUFJLEVBQUU7b0JBQ3hELFFBQVEsRUFBRyxlQUF1QixFQUFFLFdBQVcsSUFBSSxFQUFFO2lCQUN0RDthQUNGLENBQUE7WUFFRCxNQUFNLFFBQVEsR0FBeUIsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ3hFLHNDQUFzQyxFQUN0QztnQkFDRSxNQUFNLEVBQUUsTUFBTTtnQkFDZCxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUM7YUFDaEMsQ0FDRixDQUFBO1lBRUQsSUFBSSxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsb0NBQW9DLFFBQVEsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO1lBQ3pFLENBQUM7WUFFRCxPQUFPO2dCQUNMLEVBQUUsRUFBRSxRQUFRLENBQUMsaUJBQWlCO2dCQUM5QixJQUFJLEVBQUU7b0JBQ0osaUJBQWlCLEVBQUUsUUFBUSxDQUFDLGlCQUFpQjtvQkFDN0Msa0JBQWtCLEVBQUUsUUFBUSxDQUFDLGtCQUFrQjtvQkFDL0MsWUFBWSxFQUFFLFFBQVEsQ0FBQyxZQUFZO29CQUNuQyxNQUFNLEVBQUUsTUFBTTtvQkFDZCxRQUFRLEVBQUUsYUFBYTtvQkFDdkIsTUFBTSxFQUFFLFNBQVM7aUJBQ2xCO2FBQ0YsQ0FBQTtRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0NBQW9DLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDL0QsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO1FBQy9DLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGdCQUFnQixDQUNwQixLQUE0QjtRQUU1QixJQUFJLENBQUM7WUFDSCxNQUFNLEVBQUUsaUJBQWlCLEVBQUUsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFRLENBQUE7WUFFdkQsc0NBQXNDO1lBQ3RDLE1BQU0sY0FBYyxHQUE2QixNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FDbEYsMERBQTBELGlCQUFpQixFQUFFLENBQzlFLENBQUE7WUFFRCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsY0FBYyxDQUFDLDBCQUEwQixDQUFDLENBQUE7WUFFL0UsT0FBTztnQkFDTCxNQUFNLEVBQUUsTUFBTTtnQkFDZCxJQUFJLEVBQUU7b0JBQ0osR0FBRyxLQUFLLENBQUMsSUFBSTtvQkFDYixjQUFjLEVBQUUsY0FBYyxDQUFDLDBCQUEwQjtvQkFDekQsaUJBQWlCLEVBQUUsY0FBYyxDQUFDLGlCQUFpQjtvQkFDbkQsY0FBYyxFQUFFLGNBQWMsQ0FBQyxjQUFjO29CQUM3QyxlQUFlLEVBQUUsY0FBYyxDQUFDLGVBQWU7b0JBQy9DLE1BQU0sRUFBRSxNQUFNO2lCQUNmO2FBQ0YsQ0FBQTtRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMscUNBQXFDLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDaEUsTUFBTSxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO1FBQ2hELENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGNBQWMsQ0FDbEIsS0FBMEI7UUFFMUIsSUFBSSxDQUFDO1lBQ0gsd0ZBQXdGO1lBQ3hGLE1BQU0sRUFBRSxpQkFBaUIsRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksSUFBSSxFQUFFLENBQVEsQ0FBQTtZQUVyRCxNQUFNLGNBQWMsR0FBNkIsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ2xGLDBEQUEwRCxpQkFBaUIsRUFBRSxDQUM5RSxDQUFBO1lBRUQsT0FBTztnQkFDUCxJQUFJLEVBQUU7b0JBQ0osR0FBRyxLQUFLLENBQUMsSUFBSTtvQkFDYixjQUFjLEVBQUUsY0FBYyxDQUFDLDBCQUEwQjtvQkFDekQsaUJBQWlCLEVBQUUsY0FBYyxDQUFDLGlCQUFpQjtvQkFDbkQsTUFBTSxFQUFFLFVBQVU7aUJBQ25CO2FBQ0YsQ0FBQTtRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsbUNBQW1DLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDOUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxDQUFBO1FBQzlDLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWEsQ0FDakIsS0FBeUI7UUFFekIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxFQUFFLGlCQUFpQixFQUFFLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBUSxDQUFBO1lBQ3ZELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxLQUFLLENBQUE7WUFFeEIsc0JBQXNCO1lBQ3RCLE1BQU0sVUFBVSxHQUFHO2dCQUNqQixpQkFBaUIsRUFBRSxpQkFBaUI7Z0JBQ3BDLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsR0FBRyxFQUFFLHFCQUFxQjtnQkFDbkQsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsYUFBYSxJQUFJLFVBQVU7Z0JBQ25ELE9BQU8sRUFBRSxzQkFBc0IsaUJBQWlCLEVBQUU7YUFDbkQsQ0FBQTtZQUVELE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNsRCxpQ0FBaUMsRUFDakM7Z0JBQ0UsTUFBTSxFQUFFLE1BQU07Z0JBQ2QsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDO2FBQ2pDLENBQ0YsQ0FBQTtZQUVELE9BQU87Z0JBQ0wsSUFBSSxFQUFFO29CQUNKLEdBQUcsS0FBSyxDQUFDLElBQUk7b0JBQ2YsYUFBYSxFQUFFLFVBQVU7b0JBQ3ZCLGFBQWEsRUFBRSxNQUFNO29CQUN2QixlQUFlLEVBQUUsUUFBUTtpQkFDeEI7YUFDRixDQUFBO1FBQ0gsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsRUFBRSxLQUFLLENBQUMsQ0FBQTtZQUM3RCxNQUFNLElBQUksS0FBSyxDQUFDLDBCQUEwQixDQUFDLENBQUE7UUFDN0MsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUNqQixLQUF5QjtRQUV6QixJQUFJLENBQUM7WUFDSCxzRUFBc0U7WUFDdEUsT0FBTztnQkFDTCxJQUFJLEVBQUU7b0JBQ0osR0FBRyxLQUFLLENBQUMsSUFBSTtvQkFDZixNQUFNLEVBQUUsVUFBVTtvQkFDaEIsV0FBVyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO2lCQUN0QzthQUNGLENBQUE7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxFQUFFLEtBQUssQ0FBQyxDQUFBO1lBQzdELE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtRQUM3QyxDQUFDO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQ2pCLEtBQXlCO1FBRXpCLGdGQUFnRjtRQUNoRixPQUFPO1lBQ0wsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1NBQ2pCLENBQUE7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGVBQWUsQ0FDbkIsS0FBMkI7UUFFM0IsSUFBSSxDQUFDO1lBQ0gsTUFBTSxFQUFFLGlCQUFpQixFQUFFLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBUSxDQUFBO1lBRXZELHNDQUFzQztZQUN0QyxNQUFNLGNBQWMsR0FBNkIsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ2xGLDBEQUEwRCxpQkFBaUIsRUFBRSxDQUM5RSxDQUFBO1lBRUQsT0FBTztnQkFDTCxJQUFJLEVBQUU7b0JBQ0osR0FBRyxLQUFLLENBQUMsSUFBSTtvQkFDYixjQUFjLEVBQUUsY0FBYyxDQUFDLDBCQUEwQjtvQkFDM0QsaUJBQWlCLEVBQUUsY0FBYyxDQUFDLGlCQUFpQjtvQkFDbkQsY0FBYyxFQUFFLGNBQWMsQ0FBQyxjQUFjO29CQUM3QyxlQUFlLEVBQUUsY0FBYyxDQUFDLGVBQWU7b0JBQzdDLE1BQU0sRUFBRSxjQUFjLENBQUMsTUFBTTtvQkFDL0IsUUFBUSxFQUFFLGNBQWMsQ0FBQyxRQUFRO29CQUMvQixZQUFZLEVBQUUsY0FBYyxDQUFDLFlBQVk7aUJBQzFDO2FBQ0YsQ0FBQTtRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0NBQW9DLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDL0QsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO1FBQy9DLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWEsQ0FDakIsS0FBeUI7UUFFekIsa0ZBQWtGO1FBQ2xGLE9BQU87WUFDTCxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUk7U0FDakIsQ0FBQTtJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQ3BCLEtBQTRCO1FBRTVCLElBQUksQ0FBQztZQUNILE1BQU0sRUFBRSxpQkFBaUIsRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksSUFBSSxFQUFFLENBQVEsQ0FBQTtZQUV2RCxzQ0FBc0M7WUFDdEMsTUFBTSxjQUFjLEdBQTZCLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNsRiwwREFBMEQsaUJBQWlCLEVBQUUsQ0FDOUUsQ0FBQTtZQUVELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxjQUFjLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtZQUUvRSxPQUFPO2dCQUNMLE1BQU0sRUFBRSxNQUFNO2dCQUNkLElBQUksRUFBRTtvQkFDSixHQUFHLEtBQUssQ0FBQyxJQUFJO29CQUNiLGNBQWMsRUFBRSxjQUFjLENBQUMsMEJBQTBCO29CQUN6RCxpQkFBaUIsRUFBRSxjQUFjLENBQUMsaUJBQWlCO29CQUNuRCxNQUFNLEVBQUUsTUFBTTtpQkFDZjthQUNGLENBQUE7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLHNDQUFzQyxFQUFFLEtBQUssQ0FBQyxDQUFBO1lBQ2pFLE9BQU87Z0JBQ0wsTUFBTSxFQUFFLFNBQVM7Z0JBQ2pCLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTthQUNqQixDQUFBO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsdUJBQXVCLENBQzNCLElBQXVDO1FBRXZDLElBQUksQ0FBQztZQUNILE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFXLENBQUE7WUFDcEMsTUFBTSxFQUFFLGVBQWUsRUFBRSxzQkFBc0IsRUFBRSxHQUFHLFdBQVcsQ0FBQTtZQUUvRCxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ3JCLE9BQU8sRUFBRSxNQUFNLEVBQUUsZUFBZSxFQUFFLENBQUE7WUFDcEMsQ0FBQztZQUVELHNDQUFzQztZQUN0QyxNQUFNLGNBQWMsR0FBNkIsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ2xGLDBEQUEwRCxlQUFlLEVBQUUsQ0FDNUUsQ0FBQTtZQUVELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxjQUFjLENBQUMsbUJBQW1CLENBQUMsQ0FBQTtZQUV4RSxJQUFJLE1BQU0sR0FBMkQsZUFBZSxDQUFBO1lBRXBGLFFBQVEsTUFBTSxFQUFFLENBQUM7Z0JBQ2YsS0FBSyxZQUFZO29CQUNmLE1BQU0sR0FBRyxZQUFZLENBQUE7b0JBQ3JCLE1BQUs7Z0JBQ1AsS0FBSyxVQUFVO29CQUNiLE1BQU0sR0FBRyxVQUFVLENBQUE7b0JBQ25CLE1BQUs7Z0JBQ1AsS0FBSyxPQUFPO29CQUNWLE1BQU0sR0FBRyxRQUFRLENBQUE7b0JBQ2pCLE1BQUs7Z0JBQ1A7b0JBQ0UsTUFBTSxHQUFHLGVBQWUsQ0FBQTtZQUM1QixDQUFDO1lBRUQsT0FBTztnQkFDTCxNQUFNLEVBQUUsTUFBTTtnQkFDZCxJQUFJLEVBQUU7b0JBQ0osVUFBVSxFQUFFLE1BQU0sQ0FBQyxzQkFBc0IsQ0FBQztvQkFDMUMsTUFBTSxFQUFFLGNBQWMsQ0FBQyxNQUFNLEdBQUcsR0FBRyxFQUFFLG1CQUFtQjtpQkFDekQ7YUFDRixDQUFBO1FBQ0gsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxtQ0FBbUMsRUFBRSxLQUFLLENBQUMsQ0FBQTtZQUM5RCxPQUFPLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxDQUFBO1FBQzdCLENBQUM7SUFDSCxDQUFDO0lBRU8sZ0JBQWdCLENBQUMsYUFBcUI7UUFDNUMsUUFBUSxhQUFhLEVBQUUsQ0FBQztZQUN0QixLQUFLLEdBQUc7Z0JBQ04sT0FBTyxTQUFTLENBQUE7WUFDbEIsS0FBSyxHQUFHO2dCQUNOLE9BQU8sWUFBWSxDQUFBO1lBQ3JCLEtBQUssR0FBRztnQkFDTixPQUFPLFVBQVUsQ0FBQTtZQUNuQixLQUFLLEdBQUc7Z0JBQ04sT0FBTyxVQUFVLENBQUE7WUFDbkI7Z0JBQ0UsT0FBTyxPQUFPLENBQUE7UUFDbEIsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsUUFBUTtRQUNwQixJQUFJLENBQUM7WUFDSCwrQ0FBK0M7WUFDL0MsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtZQUUvRSx5REFBeUQ7WUFDekQsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQVEsRUFBRSxFQUFFLENBQzVDLEdBQUcsQ0FBQyxHQUFHLEtBQUssSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxRQUFRLENBQzdELENBQUE7WUFFRCxJQUFJLFdBQVcsRUFBRSxDQUFDO2dCQUNoQixPQUFPLFdBQVcsQ0FBQyxNQUFNLENBQUE7WUFDM0IsQ0FBQztZQUVELGtDQUFrQztZQUNsQyxNQUFNLE9BQU8sR0FBRztnQkFDZCxHQUFHLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLElBQUksR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsd0JBQXdCO2dCQUNoRixxQkFBcUIsRUFBRSxLQUFLO2FBQzdCLENBQUE7WUFFRCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQywyQkFBMkIsRUFBRTtnQkFDaEYsTUFBTSxFQUFFLE1BQU07Z0JBQ2QsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDO2FBQzlCLENBQUMsQ0FBQTtZQUVGLE9BQU8sUUFBUSxDQUFDLE1BQU0sQ0FBQTtRQUN4QixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLDRCQUE0QixFQUFFLEtBQUssQ0FBQyxDQUFBO1lBQ3ZELGtFQUFrRTtZQUNsRSxNQUFNLElBQUksS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUE7UUFDckQsQ0FBQztJQUNILENBQUM7O0FBOWFILHdEQSthQztBQTlhUSxpQ0FBVSxHQUFHLFNBQVMsQUFBWixDQUFZO0FBQ3RCLGtDQUFXLEdBQUcsU0FBUyxBQUFaLENBQVk7QUErYWhDLGtCQUFlLHNCQUFzQixDQUFBIn0=