public async Task <CreatePaymentResponse> ReadCharge(string paymentId, string publicKey, string privateKey, bool sandboxMode)
        {
            bool       isLive         = !sandboxMode; // await this.GetIsLiveSetting();
            IAffirmAPI affirmAPI      = new AffirmAPI(_httpContextAccessor, _httpClient, isLive, _context);
            dynamic    affirmResponse = await affirmAPI.ReadChargeAsync(publicKey, privateKey, paymentId);

            return(affirmResponse);
        }
        /// <summary>
        /// Cancels a payment that was not yet approved or captured (settled).
        /// </summary>
        /// <param name="cancelPaymentRequest"></param>
        /// <returns></returns>
        public async Task <CancelPaymentResponse> CancelPayment(CancelPaymentRequest cancelPaymentRequest, string publicKey, string privateKey)
        {
            CancelPaymentResponse cancelPaymentResponse = new CancelPaymentResponse
            {
                cancellationId = null,
                code           = null,
                message        = "Empty",
                paymentId      = cancelPaymentRequest.paymentId,
                requestId      = cancelPaymentRequest.requestId
            };

            // Load request from storage for order id
            CreatePaymentRequest paymentRequest = await this._paymentRequestRepository.GetPaymentRequestAsync(cancelPaymentRequest.paymentId);

            if (paymentRequest == null)
            {
                cancelPaymentResponse.message = $"Could not load Payment Request for Payment Id {cancelPaymentRequest.paymentId}.";
                _context.Vtex.Logger.Warn("CancelPayment", null, $"Could not load Payment Request for Payment Id: {cancelPaymentRequest.paymentId}");
            }
            else
            {
                // Get Affirm id from storage
                cancelPaymentRequest.authorizationId = paymentRequest.transactionId;
            }

            if (!string.IsNullOrEmpty(cancelPaymentRequest.authorizationId))
            {
                bool       isLive         = !cancelPaymentRequest.sandboxMode; // await this.GetIsLiveSetting();
                IAffirmAPI affirmAPI      = new AffirmAPI(_httpContextAccessor, _httpClient, isLive, _context);
                dynamic    affirmResponse = await affirmAPI.VoidAsync(publicKey, privateKey, cancelPaymentRequest.authorizationId);

                // If affirmResponse.reference_id is null, assume the payment was never authorized.
                // TODO: Make a call to 'Read' to get token status.
                // This will require storing and loading the token from vbase.
                cancelPaymentResponse = new CancelPaymentResponse
                {
                    paymentId      = cancelPaymentRequest.paymentId,
                    cancellationId = affirmResponse.id ?? null,
                    code           = affirmResponse.type ?? affirmResponse.Error.Code,
                    message        = affirmResponse.created ?? affirmResponse.Error.Message,
                    requestId      = cancelPaymentRequest.requestId
                };
            }
            else
            {
                // Assume that the order was never authorized
                cancelPaymentResponse.cancellationId = "not_authorized";
            }

            return(cancelPaymentResponse);
        }
        /// <summary>
        /// Refunds a payment that was previously captured (settled). You can expect partial refunds.
        /// </summary>
        /// <param name="refundPaymentRequest"></param>
        /// <returns></returns>
        public async Task <RefundPaymentResponse> RefundPayment(RefundPaymentRequest refundPaymentRequest, string publicKey, string privateKey)
        {
            bool isLive = !refundPaymentRequest.sandboxMode; // await this.GetIsLiveSetting();
            CreatePaymentRequest paymentRequest = await this._paymentRequestRepository.GetPaymentRequestAsync(refundPaymentRequest.paymentId);

            // Get Affirm id from storage
            refundPaymentRequest.authorizationId = paymentRequest.transactionId;

            int amount = decimal.ToInt32(refundPaymentRequest.value * 100);

            IAffirmAPI affirmAPI      = new AffirmAPI(_httpContextAccessor, _httpClient, isLive, _context);
            dynamic    affirmResponse = await affirmAPI.RefundAsync(publicKey, privateKey, refundPaymentRequest.authorizationId, amount);

            RefundPaymentResponse refundPaymentResponse = new RefundPaymentResponse
            {
                paymentId = refundPaymentRequest.paymentId,
                refundId  = affirmResponse.reference_id ?? affirmResponse.id,
                value     = affirmResponse.amount == null ? 0m : (decimal)affirmResponse.amount / 100m,
                code      = affirmResponse.type ?? affirmResponse.Error.Code,
                message   = affirmResponse.id != null ? $"Id:{affirmResponse.id} Fee={(affirmResponse.fee_refunded > 0 ? (decimal)affirmResponse.fee_refunded / 100m : 0):F2}" : affirmResponse.Error.Message,
                requestId = refundPaymentRequest.requestId
            };

            if (refundPaymentRequest.authorizationId.StartsWith(AffirmConstants.KatapultIdPrefix))
            {
                // Need to get details from Katapult
                VtexSettings vtexSettings = await _paymentRequestRepository.GetAppSettings();

                if (vtexSettings.enableKatapult)
                {
                    KatapultFunding katapultResponse = await affirmAPI.KatapultFundingAsync(vtexSettings.katapultPrivateToken);

                    if (katapultResponse != null)
                    {
                        FundingObject fundingObject = katapultResponse.FundingReport.FundingObjects.Where(f => f.OrderId.Equals(paymentRequest.orderId)).FirstOrDefault();
                        if (fundingObject != null)
                        {
                            refundPaymentResponse.message = $"Id:{affirmResponse.id} Fee={(fundingObject.Discount):F2}";
                            _context.Vtex.Logger.Info("RefundPayment", null, $"RefundPayment {refundPaymentResponse}");
                        }
                    }
                }
            }

            return(refundPaymentResponse);
        }
        /// <summary>
        /// After completing the checkout flow and receiving the checkout token, authorize the charge.
        /// Authorizing generates a charge ID that you’ll use to reference the charge moving forward.
        /// You must authorize a charge to fully create it. A charge is not visible in the Read response,
        /// nor in the merchant dashboard until you authorize it.
        /// </summary>
        /// <param name="paymentIdentifier"></param>
        /// <param name="token"></param>
        /// <param name="publicKey"></param>
        /// <param name="privateKey"></param>
        /// <returns></returns>
        public async Task <CreatePaymentResponse> Authorize(string paymentIdentifier, string token, string publicKey, string privateKey, string callbackUrl, int amount, string orderId, bool sandboxMode, string transactionId)
        {
            bool isLive = !sandboxMode; // await this.GetIsLiveSetting();

            if (string.IsNullOrEmpty(orderId))
            {
                orderId = paymentIdentifier;
            }

            IAffirmAPI affirmAPI      = new AffirmAPI(_httpContextAccessor, _httpClient, isLive, _context);
            dynamic    affirmResponse = await affirmAPI.AuthorizeAsync(publicKey, privateKey, token, orderId, transactionId);

            string paymentStatus = AffirmConstants.Vtex.Denied;

            if (affirmResponse.amount != null && affirmResponse.amount == amount)
            {
                paymentStatus = AffirmConstants.Vtex.Approved;
                _context.Vtex.Logger.Info("CreatePaymentResponse", null, $"PaymentResponse: {paymentStatus}");
            }
            else //if (affirmResponse.status_code != null && affirmResponse.status_code == StatusCodes.Status403Forbidden.ToString() && affirmResponse.code != null && affirmResponse.code == AffirmConstants.TokenUsed)
            {
                if (affirmResponse.charge_id != null || affirmResponse.reference_id != null)
                {
                    string chargeId = affirmResponse.charge_id ?? affirmResponse.reference_id;
                    affirmResponse = await affirmAPI.ReadChargeAsync(publicKey, privateKey, chargeId);

                    if (affirmResponse.status != null && affirmResponse.status == AffirmConstants.SuccessResponseCode)
                    {
                        paymentStatus = AffirmConstants.Vtex.Approved;
                        _context.Vtex.Logger.Info("CreatePaymentResponse", null, $"PaymentResponse: {paymentStatus}");
                    }
                }
            }

            CreatePaymentResponse paymentResponse = new CreatePaymentResponse();

            paymentResponse.paymentId       = paymentIdentifier;
            paymentResponse.status          = paymentStatus;
            paymentResponse.tid             = affirmResponse.id ?? null;
            paymentResponse.authorizationId = affirmResponse.checkout_id ?? null;
            paymentResponse.code            = affirmResponse.status ?? affirmResponse.status_code ?? affirmResponse.Error?.Code;
            string message = string.Empty;

            if (affirmResponse.events != null)
            {
                message = JsonConvert.SerializeObject(affirmResponse.events);
            }
            else if (affirmResponse.message != null)
            {
                message = affirmResponse.message;
            }
            else if (affirmResponse.Error != null && affirmResponse.Error.Message != null)
            {
                message = affirmResponse.Error.Message;
            }

            if (affirmResponse.fields != null)
            {
                message = $"{message}: {affirmResponse.fields}";
            }

            paymentResponse.message = message;
            _context.Vtex.Logger.Info("PaymentResponse", null, $"PaymentResponse: {paymentResponse.message}");

            // Save the Affirm id & order number - will need for capture
            CreatePaymentRequest paymentRequest = new CreatePaymentRequest
            {
                transactionId = affirmResponse.id,
                orderId       = orderId
            };

            // Don't save if data is missing
            if (!string.IsNullOrEmpty(paymentRequest.transactionId) && !string.IsNullOrEmpty(paymentRequest.orderId))
            {
                await this._paymentRequestRepository.SavePaymentRequestAsync(paymentIdentifier, paymentRequest);

                await this._paymentRequestRepository.SavePaymentResponseAsync(paymentResponse);
            }

            await this._paymentRequestRepository.PostCallbackResponse(callbackUrl, paymentResponse);

            return(paymentResponse);
        }
        /// <summary>
        /// Captures (settle) a payment that was previously approved.
        /// </summary>
        /// <param name="capturePaymentRequest"></param>
        /// <returns></returns>
        public async Task <CapturePaymentResponse> CapturePayment(CapturePaymentRequest capturePaymentRequest, string publicKey, string privateKey)
        {
            bool isLive = !capturePaymentRequest.sandboxMode; // await this.GetIsLiveSetting();
            CapturePaymentResponse capturePaymentResponse = new CapturePaymentResponse
            {
                message = "Unknown Error."
            };

            // Load request from storage for order id
            CreatePaymentRequest paymentRequest = await this._paymentRequestRepository.GetPaymentRequestAsync(capturePaymentRequest.paymentId);

            if (paymentRequest == null)
            {
                capturePaymentResponse.message = "Could not load Payment Request.";
                _context.Vtex.Logger.Info("CapturePayment", null, $"{capturePaymentResponse.message}");
            }
            else
            {
                // Get Affirm id from storage
                capturePaymentRequest.authorizationId = paymentRequest.transactionId;

                if (string.IsNullOrEmpty(capturePaymentRequest.authorizationId))
                {
                    capturePaymentResponse.message = "Missing authorizationId.";
                }
                else
                {
                    IAffirmAPI affirmAPI = new AffirmAPI(_httpContextAccessor, _httpClient, isLive, _context);
                    try
                    {
                        dynamic affirmResponse = await affirmAPI.CaptureAsync(publicKey, privateKey, capturePaymentRequest.authorizationId, paymentRequest.orderId, capturePaymentRequest.value, capturePaymentRequest.transactionId);

                        if (affirmResponse == null)
                        {
                            capturePaymentResponse.message = "Null affirmResponse.";
                            _context.Vtex.Logger.Warn("CapturePaymentResponse", null, $"{capturePaymentResponse.message}");
                        }
                        else
                        {
                            // If "Already Captured" then fake a success response.
                            if (affirmResponse.type != null && affirmResponse.type == AffirmConstants.AlreadyCaptured)
                            {
                                capturePaymentResponse = new CapturePaymentResponse
                                {
                                    paymentId = capturePaymentRequest.paymentId,
                                    settleId  = affirmResponse.id ?? affirmResponse.type,
                                    value     = affirmResponse.amount == null ? capturePaymentRequest.value : (decimal)affirmResponse.amount / 100m,
                                    code      = affirmResponse.type ?? null,                                                                                                                                                   //affirmResponse.Error.Code,
                                    message   = affirmResponse.id != null ? $"Fee={((affirmResponse.fee != null && affirmResponse.fee > 0) ? (decimal)affirmResponse.fee / 100m : 0):F2}" : affirmResponse.message ?? "Error", //: affirmResponse.Error.Message,
                                    requestId = capturePaymentRequest.requestId
                                };
                            }
                            else
                            {
                                capturePaymentResponse = new CapturePaymentResponse
                                {
                                    paymentId = capturePaymentRequest.paymentId,
                                    settleId  = affirmResponse.id ?? null,
                                    value     = affirmResponse.amount == null ? 0m : (decimal)affirmResponse.amount / 100m,
                                    code      = affirmResponse.type ?? null,                                                                                                                                                   //affirmResponse.Error.Code,
                                    message   = affirmResponse.id != null ? $"Fee={((affirmResponse.fee != null && affirmResponse.fee > 0) ? (decimal)affirmResponse.fee / 100m : 0):F2}" : affirmResponse.message ?? "Error", //: affirmResponse.Error.Message,
                                    requestId = capturePaymentRequest.requestId
                                };
                            }

                            if (capturePaymentRequest.authorizationId.StartsWith(AffirmConstants.KatapultIdPrefix))
                            {
                                // Need to get details from Katapult
                                VtexSettings vtexSettings = await _paymentRequestRepository.GetAppSettings();

                                if (vtexSettings.enableKatapult)
                                {
                                    capturePaymentResponse.value = affirmResponse.amount == null ? capturePaymentRequest.value : (decimal)affirmResponse.amount / 100m;
                                    KatapultFunding katapultResponse = await affirmAPI.KatapultFundingAsync(vtexSettings.katapultPrivateToken);

                                    if (katapultResponse != null)
                                    {
                                        FundingObject fundingObject = katapultResponse.FundingReport.FundingObjects.Where(f => f.OrderId.Equals(paymentRequest.orderId)).FirstOrDefault();
                                        capturePaymentResponse.message = JsonConvert.SerializeObject(fundingObject);
                                        _context.Vtex.Logger.Info("CapturePayment", null, $"Katapult Funding Response  {capturePaymentResponse.message}");
                                    }
                                    else
                                    {
                                        _context.Vtex.Logger.Info("CapturePayment", null, $"Katapult Funding Response was null.  [{capturePaymentRequest.authorizationId}]");
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _context.Vtex.Logger.Error("CapturePaymentAsync", null, $"CapturePaymentResponse:{ex.Message}", ex);
                        capturePaymentResponse.message = $"CapturePaymentAsync Error: {ex.Message}";
                    };
                }
            }

            return(capturePaymentResponse);
        }