Example #1
0
        public async Task <PaymentRequest> ProcessPayment(PaymentConfirmPayload.Content request)
        {
            var payment = await Mongo.GetPaymentRequestByOtc(request.Otc);

            if (payment == null)
            {
                Logger.LogInformation(LoggingEvents.Operations, "Payment {0} not found", request.Otc);
                throw new ArgumentException("OTC code not valid");
            }
            if (!payment.Verified)
            {
                Logger.LogInformation(LoggingEvents.Operations, "Payment {0} not verified, cannot be performed", request.Otc);
                throw new ArgumentException("OTC code not verified");
            }
            if (!payment.IsPersistent && payment.Confirmations?.Count > 0)
            {
                Logger.LogInformation(LoggingEvents.Operations, "Payment {0} not persistent and already performed");
                throw new InvalidOperationException("Payment already performed");
            }
            if (!payment.Password.Equals(request.Password, StringComparison.Ordinal))
            {
                Logger.LogInformation(LoggingEvents.Operations, "Payment password does not match");
                throw new ArgumentException("Password does not match");
            }
            if (request.Vouchers.Length != payment.Amount)
            {
                Logger.LogInformation(LoggingEvents.Operations, "{0} vouchers given instead of {1}", request.Vouchers.Length, payment.Amount);
                throw new ArgumentException("Wrong number of vouchers");
            }

            int oldCount = await ProcessPaymentOldVouchers(request.Vouchers.Where(v => !v.Id.ToString().Contains('/')), payment.Filter);

            int newCount = await ProcessPaymentNewVouchers(request.Vouchers.Where(v => v.Id.Id.Contains('/')), payment.Filter);

            Logger.LogDebug("Old vouchers spent {0}, new vouchers spent {1}", oldCount, newCount);
            if (oldCount + newCount < payment.Amount)
            {
                Logger.LogInformation(LoggingEvents.Operations, "Found {0} valid vouchers, less than requested ({1})", newCount + oldCount, payment.Amount);
                throw new ArgumentException("Insufficient number of valid vouchers");
            }

            Logger.LogDebug(LoggingEvents.Operations, "Payment confirmed, vouchers updated");

            if (payment.Confirmations == null)
            {
                payment.Confirmations = new List <PaymentConfirmation>();
            }
            payment.Confirmations.Add(new PaymentConfirmation {
                PerformedAt = DateTime.UtcNow
            });
            await Mongo.UpdatePaymentRequest(payment);

            Logger.LogDebug(LoggingEvents.Operations, "Payment confirmation stored");

            return(payment);
        }
        public async Task <PaymentRequest> ProcessPayment(PaymentConfirmPayload.Content request)
        {
            (var payment, var filter) = await GetPaymentRequestInfo(request.Otc, request.Password);

            if (request.Vouchers.Length != payment.Amount)
            {
                Logger.LogInformation(LoggingEvents.DatabaseOperation, "{0} vouchers given instead of {1}", request.Vouchers.Length, payment.Amount);
                throw new ArgumentException("Wrong number of vouchers");
            }

            // Fetch non-spent vouchers from DB
            var voucherIds = request.Vouchers.Select(v => v.Id.ToLong()).ToArray();
            var voucherMap = (from v in Data.Vouchers
                              where voucherIds.Contains(v.Id)
                              where !v.Spent
                              select v).ToDictionary(v => v.Id);

            bool CheckVoucher(PaymentConfirmPayload.VoucherInfo vi)
            {
                if (!voucherMap.ContainsKey(vi.Id.ToLong()))
                {
                    // Voucher not found or already spent
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Voucher {0} not found or already spent", vi.Id);
                    return(false);
                }
                var voucher = voucherMap[vi.Id.ToLong()];

                if (!vi.Secret.FromBase64().SequenceEqual(voucher.Secret))
                {
                    // Secret does not match
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Secret for voucher {0} does not match", vi.Id);
                    return(false);
                }

                if (filter?.Simple?.Aim != null && !voucher.AimCode.StartsWith(filter.Simple.Aim))
                {
                    // Voucher does not match aim filter
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Voucher {0} does not match aim filter '{1}'", vi.Id, filter.Simple.Aim);
                    return(false);
                }
                if (filter?.Simple == null && voucher.AimCode.StartsWith("0"))
                {
                    // Voucher matches "demo aim" when without filter
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Voucher {0} matches 0 demo aim filter", vi.Id);
                    return(false);
                }

                if (filter?.Simple?.Bounds != null && !filter.Simple.Bounds.Contains(voucher.Latitude, voucher.Longitude))
                {
                    // Voucher not contained in geographical bounds
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Voucher {0} is outside geographical bounds", vi.Id);
                    return(false);
                }

                if (filter?.Simple?.MaxAge != null && DateTime.UtcNow.Subtract(voucher.Timestamp) > TimeSpan.FromDays(filter.Simple.MaxAge.Value))
                {
                    // Voucher too old
                    Logger.LogInformation(LoggingEvents.DatabaseOperation, "Voucher {0} is older than {1} days (age {2})", vi.Id, filter.Simple.MaxAge.Value, DateTime.UtcNow.Subtract(voucher.Timestamp));
                    return(false);
                }

                return(true);
            }

            // Check all voucher secrets
            if (!request.Vouchers.All(CheckVoucher))
            {
                throw new ArgumentException("One or more vouchers not valid for payment");
            }

            // Update payment status
            foreach (var v in voucherMap.Values)
            {
                v.Spent = true;
            }
            Data.PaymentConfirmations.Add(new PaymentConfirmations {
                PaymentRequestId = payment.Id,
                PerformedAt      = DateTime.UtcNow
            });
            await Data.SaveChangesAsync();

            return(payment);
        }