/// <inheritdoc />
        public async Task RefreshBalanceAsync(IAgentContext agentContext, PaymentAddressRecord paymentAddress = null)
        {
            if (paymentAddress == null)
            {
                var provisioning = await provisioningService.GetProvisioningAsync(agentContext.Wallet);

                if (provisioning.DefaultPaymentAddressId == null)
                {
                    throw new AgentFrameworkException(ErrorCode.RecordNotFound, "Default PaymentAddressRecord not found");
                }

                paymentAddress = await recordService.GetAsync <PaymentAddressRecord>(agentContext.Wallet, provisioning.DefaultPaymentAddressId);
            }

            // Cache sources data in record for one hour
            var request = await IndyPayments.BuildGetPaymentSourcesAsync(agentContext.Wallet, null, paymentAddress.Address);

            var response = await Ledger.SubmitRequestAsync(await agentContext.Pool, request.Result);

            var sourcesJson = await Indy.Payments.ParseGetPaymentSourcesAsync(paymentAddress.Method, response);

            var sources = sourcesJson.ToObject <IList <IndyPaymentInputSource> >();

            paymentAddress.Sources         = sources;
            paymentAddress.SourcesSyncedAt = DateTime.Now;

            await recordService.UpdateAsync(agentContext.Wallet, paymentAddress);
        }
        private async Task <IDictionary <string, ulong> > GetTransactionFeesAsync(IAgentContext agentContext)
        {
            if (_transactionFees == null)
            {
                var feesRequest = await IndyPayments.BuildGetTxnFeesRequestAsync(agentContext.Wallet, null, TokenConfiguration.MethodName);

                var feesResponse = await Ledger.SubmitRequestAsync(await agentContext.Pool, feesRequest);

                var feesParsed = await IndyPayments.ParseGetTxnFeesResponseAsync(TokenConfiguration.MethodName, feesResponse);

                _transactionFees = feesParsed.ToObject <IDictionary <string, ulong> >();
            }
            return(_transactionFees);
        }
        /// <inheritdoc />
        public async Task <PaymentAddressRecord> CreatePaymentAddressAsync(IAgentContext agentContext, AddressOptions configuration = null)
        {
            var address = await IndyPayments.CreatePaymentAddressAsync(agentContext.Wallet, TokenConfiguration.MethodName,
                                                                       new { seed = configuration?.Seed }.ToJson());

            var addressRecord = new PaymentAddressRecord
            {
                Id              = Guid.NewGuid().ToString("N"),
                Method          = TokenConfiguration.MethodName,
                Address         = address,
                SourcesSyncedAt = DateTime.MinValue
            };

            await recordService.AddAsync(agentContext.Wallet, addressRecord);

            return(addressRecord);
        }
        public async Task <bool> VerifyPaymentAsync(IAgentContext context, PaymentRecord paymentRecord)
        {
            if (paymentRecord.State != PaymentState.Paid && paymentRecord.State != PaymentState.ReceiptReceived)
            {
                throw new AgentFrameworkException(ErrorCode.RecordInInvalidState,
                                                  "Payment record must be in state Paid or ReceiptReceived to verify it");
            }

            var req = await IndyPayments.BuildVerifyPaymentRequestAsync(context.Wallet, null, paymentRecord.ReceiptId);

            var res = await Ledger.SubmitRequestAsync(await context.Pool, req.Result);

            var resParsed = JObject.Parse(await IndyPayments.ParseVerifyPaymentResponseAsync("sov", res));
            var receipts  = resParsed["receipts"].ToObject <IList <IndyPaymentOutputSource> >()
                            .Where(x => x.Recipient == paymentRecord.Address)
                            .ToList();

            if (receipts.Any() && receipts.Select(x => x.Amount).Aggregate((x, y) => x + y) == paymentRecord.Amount)
            {
                return(true);
            }
            return(false);
        }
        /// <inheritdoc />
        public async Task MakePaymentAsync(IAgentContext agentContext, PaymentRecord paymentRecord,
                                           PaymentAddressRecord addressFromRecord = null)
        {
            if (paymentRecord.Amount == 0)
            {
                throw new AgentFrameworkException(ErrorCode.InvalidRecordData, "Cannot make a payment with 0 amount");
            }

            await paymentRecord.TriggerAsync(PaymentTrigger.ProcessPayment);

            if (paymentRecord.Address == null)
            {
                throw new AgentFrameworkException(ErrorCode.InvalidRecordData, "Payment record is missing an address");
            }

            var provisioning = await provisioningService.GetProvisioningAsync(agentContext.Wallet);

            if (addressFromRecord == null)
            {
                addressFromRecord = await GetDefaultPaymentAddressAsync(agentContext);
            }

            await RefreshBalanceAsync(agentContext, addressFromRecord);

            if (addressFromRecord.Balance < paymentRecord.Amount)
            {
                throw new AgentFrameworkException(ErrorCode.PaymentInsufficientFunds,
                                                  "Address doesn't have enough funds to make this payment");
            }
            var txnFee = await GetTransactionFeeAsync(agentContext, TransactionTypes.XFER_PUBLIC);

            var paymentResult = await IndyPayments.BuildPaymentRequestAsync(
                wallet : agentContext.Wallet,
                submitterDid : null,
                inputsJson : addressFromRecord.Sources.Select(x => x.Source).ToJson(),
                outputsJson : new[]
            {
                new IndyPaymentOutputSource
                {
                    Amount    = paymentRecord.Amount,
                    Recipient = paymentRecord.Address
                },
                new IndyPaymentOutputSource
                {
                    Recipient = addressFromRecord.Address,
                    Amount    = addressFromRecord.Balance - paymentRecord.Amount - txnFee
                }
            }.ToJson(),
                extra : null);

            var response = await Ledger.SignAndSubmitRequestAsync(await agentContext.Pool, agentContext.Wallet,
                                                                  provisioning.Endpoint.Did, paymentResult.Result);

            var paymentResponse = await IndyPayments.ParsePaymentResponseAsync(TokenConfiguration.MethodName, response);

            var paymentOutputs = paymentResponse.ToObject <IList <IndyPaymentOutputSource> >();
            var paymentOutput  = paymentOutputs.SingleOrDefault(x => x.Recipient == paymentRecord.Address);

            paymentRecord.ReceiptId   = paymentOutput.Receipt;
            addressFromRecord.Sources = paymentOutputs
                                        .Where(x => x.Recipient == addressFromRecord.Address)
                                        .Select(x => new IndyPaymentInputSource
            {
                Amount         = x.Amount,
                PaymentAddress = x.Recipient,
                Source         = x.Receipt
            })
                                        .ToList();

            await recordService.UpdateAsync(agentContext.Wallet, paymentRecord);

            await recordService.UpdateAsync(agentContext.Wallet, addressFromRecord);
        }