public override async Task <ApiResult> RefundPaymentAsync(PaymentProviderContext <BamboraCheckoutSettings> ctx) { try { var clientConfig = GetBamboraClientConfig(ctx.Settings); var client = new BamboraClient(clientConfig); var transactionResp = await client.CreditTransactionAsync(ctx.Order.TransactionInfo.TransactionId, new BamboraAmountRequest { Amount = (int)AmountToMinorUnits(ctx.Order.TransactionInfo.AmountAuthorized.Value) }); if (transactionResp.Meta.Result) { return(new ApiResult { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = PaymentStatus.Refunded } }); } } catch (Exception ex) { _logger.Error(ex, "Bambora - RefundPayment"); } return(ApiResult.Empty); }
public override async Task <ApiResult> CancelPaymentAsync(PaymentProviderContext <BamboraCheckoutSettings> ctx) { try { var clientConfig = GetBamboraClientConfig(ctx.Settings); var client = new BamboraClient(clientConfig); var transactionResp = await client.DeleteTransactionAsync(ctx.Order.TransactionInfo.TransactionId); if (transactionResp.Meta.Result) { return(new ApiResult { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = PaymentStatus.Cancelled } }); } } catch (Exception ex) { _logger.Error(ex, "Bambora - CancelPayment"); } return(ApiResult.Empty); }
public override string GetContinueUrl(PaymentProviderContext <DibsSettings> ctx) { ctx.Settings.MustNotBeNull("ctx.Settings"); ctx.Settings.ContinueUrl.MustNotBeNull("ctx.Settings.ContinueUrl"); return(ctx.Settings.ContinueUrl); }
public override string GetErrorUrl(PaymentProviderContext <TSettings> ctx) { ctx.Settings.MustNotBeNull("settings"); ctx.Settings.ErrorUrl.MustNotBeNull("settings.ErrorUrl"); return(ctx.Settings.ErrorUrl); }
public override async Task <ApiResult> RefundPaymentAsync(PaymentProviderContext <DibsSettings> ctx) { try { var currency = Vendr.Services.CurrencyService.GetCurrency(ctx.Order.CurrencyId); var currencyCode = currency.Code.ToUpperInvariant(); // Ensure currency has valid ISO 4217 code if (!Iso4217.CurrencyCodes.ContainsKey(currencyCode)) { throw new Exception("Currency must be a valid ISO 4217 currency code: " + currency.Name); } var strCurrency = Iso4217.CurrencyCodes[currencyCode].ToString(CultureInfo.InvariantCulture); var strAmount = AmountToMinorUnits(ctx.Order.TransactionInfo.AmountAuthorized.Value).ToString("0", CultureInfo.InvariantCulture); // MD5(key2 + MD5(key1 + "merchant=<merchant>&ctx.Orderid=<ctx.Orderid>&transact=<transact>&amount=<amount>")) var md5Check = $"merchant={ctx.Settings.MerchantId}&ctx.Orderid={ctx.Order.OrderNumber}&transact={ctx.Order.TransactionInfo.TransactionId}&amount={strAmount}"; var response = await $"https://payment.architrade.com/cgi-adm/refund.cgi" .WithBasicAuth(ctx.Settings.ApiUsername, ctx.Settings.ApiPassword) .PostUrlEncodedAsync(new { merchant = ctx.Settings.MerchantId, orderid = ctx.Order.OrderNumber, transact = ctx.Order.TransactionInfo.TransactionId, amount = strAmount, currency = strCurrency, textreply = "1", md5key = MD5Hash(ctx.Settings.MD5Key2 + MD5Hash(ctx.Settings.MD5Key1 + md5Check)) }) .ReceiveString(); var responseParams = HttpUtility.ParseQueryString(response); var result = responseParams["result"]; if (result == "0") // 0 == Accepted { return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = PaymentStatus.Refunded } }); } else { _logger.Warn($"Dibs [{ctx.Order.OrderNumber}] - Error making API request - error message: {result}"); } } catch (Exception ex) { _logger.Error(ex, "Dibs - CapturePayment"); } return(ApiResult.Empty); }
public override async Task <ApiResult> RefundPaymentAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { try { // We can only refund a captured charge, so make sure we have one // otherwise there is nothing we can do var chargeId = ctx.Order.Properties["stripeChargeId"]; if (string.IsNullOrWhiteSpace(chargeId)) { return(null); } var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; ConfigureStripe(secretKey); var refundService = new RefundService(); var refundCreateOptions = new RefundCreateOptions() { Charge = chargeId }; var refund = refundService.Create(refundCreateOptions); var charge = refund.Charge ?? await new ChargeService().GetAsync(refund.ChargeId); // If we have a subscription then we'll cancel it as refunding an ctx.Order // should effecitvely undo any purchase if (!string.IsNullOrWhiteSpace(ctx.Order.Properties["stripeSubscriptionId"])) { var subscriptionService = new SubscriptionService(); var subscription = await subscriptionService.GetAsync(ctx.Order.Properties["stripeSubscriptionId"]); if (subscription != null) { subscriptionService.Cancel(ctx.Order.Properties["stripeSubscriptionId"], new SubscriptionCancelOptions { InvoiceNow = false, Prorate = false }); } } return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = GetTransactionId(charge), PaymentStatus = GetPaymentStatus(charge) } }); } catch (Exception ex) { _logger.Error(ex, "Stripe - RefundPayment"); } return(ApiResult.Empty); }
public override async Task <ApiResult> FetchPaymentStatusAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { try { var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; ConfigureStripe(secretKey); // See if we have a payment intent to work from var paymentIntentId = ctx.Order.Properties["stripePaymentIntentId"]; if (!string.IsNullOrWhiteSpace(paymentIntentId)) { var paymentIntentService = new PaymentIntentService(); var paymentIntent = await paymentIntentService.GetAsync(paymentIntentId, new PaymentIntentGetOptions { Expand = new List <string>(new[] { "review" }) }); return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = GetTransactionId(paymentIntent), PaymentStatus = GetPaymentStatus(paymentIntent) } }); } // No payment intent, so look for a charge var chargeId = ctx.Order.Properties["stripeChargeId"]; if (!string.IsNullOrWhiteSpace(chargeId)) { var chargeService = new ChargeService(); var charge = await chargeService.GetAsync(chargeId); return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = GetTransactionId(charge), PaymentStatus = GetPaymentStatus(charge) } }); } } catch (Exception ex) { _logger.Error(ex, "Stripe - FetchPaymentStatus"); } return(ApiResult.Empty); }
protected StripeTaxRate GetOrCreateStripeTaxRate(PaymentProviderContext <TSettings> ctx, string taxName, decimal percentage, bool inclusive) { var taxRateService = new TaxRateService(); var stripeTaxRates = new List <StripeTaxRate>(); if (ctx.AdditionalData.ContainsKey("Vendr_StripeTaxRates")) { stripeTaxRates = (List <StripeTaxRate>)ctx.AdditionalData["Vendr_StripeTaxRates"]; } if (stripeTaxRates.Count > 0) { var taxRate = GetStripeTaxRate(stripeTaxRates, taxName, percentage, inclusive); if (taxRate != null) { return(taxRate); } } stripeTaxRates = taxRateService.List(new TaxRateListOptions { Active = true }).ToList(); if (ctx.AdditionalData.ContainsKey("Vendr_StripeTaxRates")) { ctx.AdditionalData["Vendr_StripeTaxRates"] = stripeTaxRates; } else { ctx.AdditionalData.Add("Vendr_StripeTaxRates", stripeTaxRates); } if (stripeTaxRates.Count > 0) { var taxRate = GetStripeTaxRate(stripeTaxRates, taxName, percentage, inclusive); if (taxRate != null) { return(taxRate); } } var newTaxRate = taxRateService.Create(new TaxRateCreateOptions { DisplayName = taxName, Percentage = percentage, Inclusive = inclusive, }); stripeTaxRates.Add(newTaxRate); ctx.AdditionalData["Vendr_StripeTaxRates"] = stripeTaxRates; return(newTaxRate); }
public override async Task <ApiResult> FetchPaymentStatusAsync(PaymentProviderContext <DibsSettings> ctx) { try { var response = await $"https://@payment.architrade.com/cgi-adm/payinfo.cgi" .WithBasicAuth(ctx.Settings.ApiUsername, ctx.Settings.ApiPassword) .PostUrlEncodedAsync(new { transact = ctx.Order.TransactionInfo.TransactionId }) .ReceiveString(); var responseParams = HttpUtility.ParseQueryString(response); var status = responseParams["status"]; var paymentStatus = PaymentStatus.Initialized; switch (status) { case "2": paymentStatus = PaymentStatus.Authorized; break; case "5": paymentStatus = PaymentStatus.Captured; break; case "6": paymentStatus = PaymentStatus.Cancelled; break; case "11": paymentStatus = PaymentStatus.Refunded; break; } return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = paymentStatus } }); } catch (Exception ex) { _logger.Error(ex, "Dibs - FetchPaymentStatus"); } return(ApiResult.Empty); }
public override async Task <ApiResult> CapturePaymentAsync(PaymentProviderContext <DibsSettings> ctx) { try { var strAmount = AmountToMinorUnits(ctx.Order.TransactionInfo.AmountAuthorized.Value).ToString("0", CultureInfo.InvariantCulture); // MD5(key2 + MD5(key1 + "merchant=<merchant>&ctx.Orderid=<ctx.Orderid>&transact=<transact>&amount=<amount>")) var md5Check = $"merchant={ctx.Settings.MerchantId}&ctx.Orderid={ctx.Order.OrderNumber}&transact={ctx.Order.TransactionInfo.TransactionId}&amount={strAmount}"; var response = await $"https://payment.architrade.com/cgi-bin/capture.cgi" .PostUrlEncodedAsync(new { merchant = ctx.Settings.MerchantId, orderid = ctx.Order.OrderNumber, transact = ctx.Order.TransactionInfo.TransactionId, amount = strAmount, textreply = "1", md5key = MD5Hash(ctx.Settings.MD5Key2 + MD5Hash(ctx.Settings.MD5Key1 + md5Check)) }) .ReceiveString(); var responseParams = HttpUtility.ParseQueryString(response); var result = responseParams["result"]; if (result == "0") // 0 == Accepted { return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = PaymentStatus.Captured } }); } else { _logger.Warn($"Dibs [{ctx.Order.OrderNumber}] - Error making API request - error message: {result}"); } } catch (Exception ex) { _logger.Error(ex, "Dibs - CapturePayment"); } return(ApiResult.Empty); }
public override async Task <ApiResult> CapturePaymentAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { // NOTE: Subscriptions aren't currently abled to be "authorized" so the capture // routine shouldn't be relevant for subscription payments at this point try { // We can only capture a payment intent, so make sure we have one // otherwise there is nothing we can do var paymentIntentId = ctx.Order.Properties["stripePaymentIntentId"]; if (string.IsNullOrWhiteSpace(paymentIntentId)) { return(null); } var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; ConfigureStripe(secretKey); var paymentIntentService = new PaymentIntentService(); var paymentIntentOptions = new PaymentIntentCaptureOptions { AmountToCapture = AmountToMinorUnits(ctx.Order.TransactionInfo.AmountAuthorized.Value) }; var paymentIntent = await paymentIntentService.CaptureAsync(paymentIntentId, paymentIntentOptions); return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = GetTransactionId(paymentIntent), PaymentStatus = GetPaymentStatus(paymentIntent) }, MetaData = new Dictionary <string, string> { { "stripeChargeId", GetTransactionId(paymentIntent) }, { "stripeCardCountry", paymentIntent.Charges?.Data?.FirstOrDefault()?.PaymentMethodDetails?.Card?.Country } } }); } catch (Exception ex) { _logger.Error(ex, "Stripe - CapturePayment"); } return(ApiResult.Empty); }
public override Task <PaymentFormResult> GenerateFormAsync(PaymentProviderContext <DibsSettings> ctx) { var currency = Vendr.Services.CurrencyService.GetCurrency(ctx.Order.CurrencyId); var currencyCode = currency.Code.ToUpperInvariant(); // Ensure currency has valid ISO 4217 code if (!Iso4217.CurrencyCodes.ContainsKey(currencyCode)) { throw new Exception("Currency must be a valid ISO 4217 currency code: " + currency.Name); } var strCurrency = Iso4217.CurrencyCodes[currencyCode].ToString(CultureInfo.InvariantCulture); var orderAmount = AmountToMinorUnits(ctx.Order.TransactionAmount.Value).ToString("0", CultureInfo.InvariantCulture); var payTypes = ctx.Settings.PayTypes?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(s => s.Trim()) .ToArray(); // MD5(key2 + MD5(key1 + "merchant=<merchant>&ctx.Orderid=<ctx.Orderid> ¤cy=<cur>&amount=<amount>")) var md5Check = $"merchant={ctx.Settings.MerchantId}&ctx.Orderid={ctx.Order.OrderNumber}¤cy={strCurrency}&amount={orderAmount}"; var md5Hash = MD5Hash(ctx.Settings.MD5Key2 + MD5Hash(ctx.Settings.MD5Key1 + md5Check)); // Parse language - default language is Danish. Enum.TryParse(ctx.Settings.Lang, true, out DibsLang lang); return(Task.FromResult(new PaymentFormResult() { Form = new PaymentForm("https://payment.architrade.com/paymentweb/start.action", PaymentFormMethod.Post) .WithInput("ctx.Orderid", ctx.Order.OrderNumber) .WithInput("merchant", ctx.Settings.MerchantId) .WithInput("amount", orderAmount) .WithInput("currency", strCurrency) .WithInput("accepturl", ctx.Urls.ContinueUrl) .WithInput("cancelurl", ctx.Urls.CancelUrl) .WithInput("callbackurl", ctx.Urls.CallbackUrl) .WithInput("lang", lang.ToString()) .WithInputIf("paytype", payTypes != null && payTypes.Length > 0, string.Join(",", payTypes)) .WithInputIf("capturenow", ctx.Settings.Capture, "1") .WithInputIf("calcfee", ctx.Settings.CalcFee, "1") .WithInputIf("decorator", !string.IsNullOrWhiteSpace(ctx.Settings.Decorator), ctx.Settings.Decorator) .WithInputIf("test", ctx.Settings.TestMode, "1") .WithInput("md5key", md5Hash) })); }
public override async Task <ApiResult> CancelPaymentAsync(PaymentProviderContext <DibsSettings> ctx) { try { // MD5(key2 + MD5(key1 + "merchant=<merchant>&ctx.Orderid=<ctx.Orderid>&transact=<transact>")) var md5Check = $"merchant={ctx.Settings.MerchantId}&ctx.Orderid={ctx.Order.OrderNumber}&transact={ctx.Order.TransactionInfo.TransactionId}"; var response = await $"https://payment.architrade.com/cgi-adm/cancel.cgi" .WithBasicAuth(ctx.Settings.ApiUsername, ctx.Settings.ApiPassword) .PostUrlEncodedAsync(new { merchant = ctx.Settings.MerchantId, orderid = ctx.Order.OrderNumber, transact = ctx.Order.TransactionInfo.TransactionId, textreply = "1", md5key = MD5Hash(ctx.Settings.MD5Key2 + MD5Hash(ctx.Settings.MD5Key1 + md5Check)) }) .ReceiveString(); var responseParams = HttpUtility.ParseQueryString(response); var result = responseParams["result"]; if (result == "0") // 0 == Accepted { return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = ctx.Order.TransactionInfo.TransactionId, PaymentStatus = PaymentStatus.Cancelled } }); } else { _logger.Warn($"Dibs [{ctx.Order.OrderNumber}] - Error making API request - error message: {result}"); } } catch (Exception ex) { _logger.Error(ex, "Dibs - CancelPayment"); } return(ApiResult.Empty); }
public override async Task <CallbackResult> ProcessCallbackAsync(PaymentProviderContext <BamboraCheckoutSettings> ctx) { try { var clientConfig = GetBamboraClientConfig(ctx.Settings); var client = new BamboraClient(clientConfig); if (client.ValidateRequest(ctx.Request, out var qs)) { var txnId = qs["txnid"]; var orderId = qs["orderid"]; var amount = int.Parse("0" + qs["amount"]); var txnFee = int.Parse("0" + qs["txnfee"]); // Validate params if (!string.IsNullOrWhiteSpace(txnId) && !string.IsNullOrWhiteSpace(orderId) && orderId == BamboraSafeOrderId(ctx.Order.OrderNumber) && amount > 0) { // Fetch the transaction details so that we can work out // the status of the transaction as the querystring params // are not enough on their own var transactionResp = await client.GetTransactionAsync(txnId); if (transactionResp.Meta.Result) { return(CallbackResult.Ok(new TransactionInfo { TransactionId = transactionResp.Transaction.Id, AmountAuthorized = AmountFromMinorUnits(amount + txnFee), TransactionFee = AmountFromMinorUnits(txnFee), PaymentStatus = GetPaymentStatus(transactionResp.Transaction) })); } } } } catch (Exception ex) { _logger.Error(ex, "Bambora - ProcessCallback"); } return(CallbackResult.BadRequest()); }
public override async Task <ApiResult> CancelPaymentAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { // NOTE: Subscriptions aren't currently abled to be "authorized" so the cancel // routine shouldn't be relevant for subscription payments at this point try { // See if there is a payment intent to cancel var stripePaymentIntentId = ctx.Order.Properties["stripePaymentIntentId"]; if (!string.IsNullOrWhiteSpace(stripePaymentIntentId)) { var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; ConfigureStripe(secretKey); var paymentIntentService = new PaymentIntentService(); var intent = await paymentIntentService.CancelAsync(stripePaymentIntentId); return(new ApiResult() { TransactionInfo = new TransactionInfoUpdate() { TransactionId = GetTransactionId(intent), PaymentStatus = GetPaymentStatus(intent) } }); } // If there is a charge, then it's too late to cancel // so we attempt to refund it instead var chargeId = ctx.Order.Properties["stripeChargeId"]; if (chargeId != null) { return(await RefundPaymentAsync(ctx)); } } catch (Exception ex) { _logger.Error(ex, "Stripe - CancelPayment"); } return(ApiResult.Empty); }
public override async Task <CallbackResult> ProcessCallbackAsync(PaymentProviderContext <DibsSettings> ctx) { try { var formData = await ctx.Request.Content.ReadAsFormDataAsync(); var authkey = formData["authkey"]; var transaction = formData["transact"]; var currencyCode = formData["currency"]; var strAmount = formData["amount"]; var strFee = formData["fee"] ?? "0"; // Not always in the return data var captured = formData["capturenow"] == "1"; var totalAmount = decimal.Parse(strAmount, CultureInfo.InvariantCulture) + decimal.Parse(strFee, CultureInfo.InvariantCulture); var md5Check = $"transact={transaction}&amount={totalAmount.ToString("0", CultureInfo.InvariantCulture)}¤cy={currencyCode}"; // authkey = MD5(key2 + MD5(key1 + "transact=<transact>&amount=<amount>¤cy=<currency>")) if (MD5Hash(ctx.Settings.MD5Key2 + MD5Hash(ctx.Settings.MD5Key1 + md5Check)) == authkey) { return(new CallbackResult { TransactionInfo = new TransactionInfo { AmountAuthorized = AmountFromMinorUnits((long)totalAmount), TransactionId = transaction, PaymentStatus = !captured ? PaymentStatus.Authorized : PaymentStatus.Captured } }); } else { _logger.Warn($"Dibs [{ctx.Order.OrderNumber}] - MD5Sum security check failed"); } } catch (Exception ex) { _logger.Error(ex, "Dibs - ProcessCallback"); } return(CallbackResult.Empty); }
public override async Task <OrderReference> GetOrderReferenceAsync(PaymentProviderContext <TSettings> ctx) { try { var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; var webhookSigningSecret = ctx.Settings.TestMode ? ctx.Settings.TestWebhookSigningSecret : ctx.Settings.LiveWebhookSigningSecret; ConfigureStripe(secretKey); var stripeEvent = await GetWebhookStripeEventAsync(ctx, webhookSigningSecret); if (stripeEvent != null && stripeEvent.Type == Events.CheckoutSessionCompleted) { if (stripeEvent.Data?.Object?.Instance is Session stripeSession && !string.IsNullOrWhiteSpace(stripeSession.ClientReferenceId)) { return(OrderReference.Parse(stripeSession.ClientReferenceId)); } } else if (stripeEvent != null && stripeEvent.Type == Events.ReviewClosed) { if (stripeEvent.Data?.Object?.Instance is Review stripeReview && !string.IsNullOrWhiteSpace(stripeReview.PaymentIntentId)) { var paymentIntentService = new PaymentIntentService(); var paymentIntent = paymentIntentService.Get(stripeReview.PaymentIntentId); if (paymentIntent != null && paymentIntent.Metadata.ContainsKey("orderReference")) { return(OrderReference.Parse(paymentIntent.Metadata["orderReference"])); } } } } catch (Exception ex) { _logger.Error(ex, "Stripe - GetOrderReference"); } return(await base.GetOrderReferenceAsync(ctx)); }
public override async Task <CallbackResult> ProcessCallbackAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { // The ProcessCallback method is only intendid to be called via a Stripe Webhook and so // it's job is to process the webhook event and finalize / update the ctx.Order accordingly try { var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; var webhookSigningSecret = ctx.Settings.TestMode ? ctx.Settings.TestWebhookSigningSecret : ctx.Settings.LiveWebhookSigningSecret; ConfigureStripe(secretKey); var stripeEvent = await GetWebhookStripeEventAsync(ctx, webhookSigningSecret); if (stripeEvent != null && stripeEvent.Type == Events.CheckoutSessionCompleted) { if (stripeEvent.Data?.Object?.Instance is Session stripeSession) { if (stripeSession.Mode == "payment") { var paymentIntentService = new PaymentIntentService(); var paymentIntent = await paymentIntentService.GetAsync(stripeSession.PaymentIntentId, new PaymentIntentGetOptions { Expand = new List <string>(new[] { "review" }) }); return(CallbackResult.Ok(new TransactionInfo { TransactionId = GetTransactionId(paymentIntent), AmountAuthorized = AmountFromMinorUnits(paymentIntent.Amount), PaymentStatus = GetPaymentStatus(paymentIntent) }, new Dictionary <string, string> { { "stripeSessionId", stripeSession.Id }, { "stripeCustomerId", stripeSession.CustomerId }, { "stripePaymentIntentId", stripeSession.PaymentIntentId }, { "stripeSubscriptionId", stripeSession.SubscriptionId }, { "stripeChargeId", GetTransactionId(paymentIntent) }, { "stripeCardCountry", paymentIntent.Charges?.Data?.FirstOrDefault()?.PaymentMethodDetails?.Card?.Country } })); } else if (stripeSession.Mode == "subscription") { var subscriptionService = new SubscriptionService(); var subscription = await subscriptionService.GetAsync(stripeSession.SubscriptionId, new SubscriptionGetOptions { Expand = new List <string>(new[] { "latest_invoice", "latest_invoice.charge", "latest_invoice.charge.review", "latest_invoice.payment_intent", "latest_invoice.payment_intent.review" }) }); var invoice = subscription.LatestInvoice; return(CallbackResult.Ok(new TransactionInfo { TransactionId = GetTransactionId(invoice), AmountAuthorized = AmountFromMinorUnits(invoice.PaymentIntent.Amount), PaymentStatus = GetPaymentStatus(invoice) }, new Dictionary <string, string> { { "stripeSessionId", stripeSession.Id }, { "stripeCustomerId", stripeSession.CustomerId }, { "stripePaymentIntentId", invoice.PaymentIntentId }, { "stripeSubscriptionId", stripeSession.SubscriptionId }, { "stripeChargeId", invoice.ChargeId }, { "stripeCardCountry", invoice.Charge?.PaymentMethodDetails?.Card?.Country } })); } } else if (stripeEvent != null && stripeEvent.Type == Events.ReviewClosed) { if (stripeEvent.Data?.Object?.Instance is Review stripeReview && !string.IsNullOrWhiteSpace(stripeReview.PaymentIntentId)) { var paymentIntentService = new PaymentIntentService(); var paymentIntent = paymentIntentService.Get(stripeReview.PaymentIntentId, new PaymentIntentGetOptions { Expand = new List <string>(new[] { "review" }) }); return(CallbackResult.Ok(new TransactionInfo { TransactionId = GetTransactionId(paymentIntent), AmountAuthorized = AmountFromMinorUnits(paymentIntent.Amount), PaymentStatus = GetPaymentStatus(paymentIntent) })); } } } } catch (Exception ex) { _logger.Error(ex, "Stripe - ProcessCallback"); } return(CallbackResult.BadRequest()); }
protected async Task <StripeWebhookEvent> GetWebhookStripeEventAsync(PaymentProviderContext <TSettings> ctx, string webhookSigningSecret) { StripeWebhookEvent stripeEvent = null; if (ctx.AdditionalData.ContainsKey("Vendr_StripeEvent")) { stripeEvent = (StripeWebhookEvent)ctx.AdditionalData["Vendr_StripeEvent"]; } else { try { var json = await ctx.Request.Content.ReadAsStringAsync(); var stripeSignature = ctx.Request.Headers.GetValues("Stripe-Signature").FirstOrDefault(); // Just validate the webhook signature EventUtility.ValidateSignature(json, stripeSignature, webhookSigningSecret); // Parse the event ourselves to our custom webhook event model // as it only captures minimal object information. stripeEvent = JsonConvert.DeserializeObject <StripeWebhookEvent>(json); // We manually fetch the event object type ourself as it means it will be fetched // using the same API version as the payment providers is coded against. // NB: Only supports a number of object types we are likely to be interested in. if (stripeEvent?.Data?.Object != null) { switch (stripeEvent.Data.Object.Type) { case "checkout.session": var sessionService = new SessionService(); stripeEvent.Data.Object.Instance = sessionService.Get(stripeEvent.Data.Object.Id); break; case "charge": var chargeService = new ChargeService(); stripeEvent.Data.Object.Instance = chargeService.Get(stripeEvent.Data.Object.Id); break; case "payment_intent": var paymentIntentService = new PaymentIntentService(); stripeEvent.Data.Object.Instance = paymentIntentService.Get(stripeEvent.Data.Object.Id); break; case "subscription": var subscriptionService = new SubscriptionService(); stripeEvent.Data.Object.Instance = subscriptionService.Get(stripeEvent.Data.Object.Id); break; case "invoice": var invoiceService = new InvoiceService(); stripeEvent.Data.Object.Instance = invoiceService.Get(stripeEvent.Data.Object.Id); break; case "review": var reviewService = new ReviewService(); stripeEvent.Data.Object.Instance = reviewService.Get(stripeEvent.Data.Object.Id); break; } } ctx.AdditionalData.Add("Vendr_StripeEvent", stripeEvent); } catch (Exception ex) { _logger.Error(ex, "Stripe - GetWebhookStripeEvent"); } } return(stripeEvent); }
public override string GetCancelUrl(PaymentProviderContext <TSettings> ctx) { return(ctx.Settings.CancelUrl); }
public override string GetContinueUrl(PaymentProviderContext <TSettings> ctx) { return(ctx.Settings.ContinueUrl); // + (settings.ContinueUrl.Contains("?") ? "&" : "?") + "session_id={CHECKOUT_SESSION_ID}"; }
public override async Task <PaymentFormResult> GenerateFormAsync(PaymentProviderContext <BamboraCheckoutSettings> ctx) { var currency = Vendr.Services.CurrencyService.GetCurrency(ctx.Order.CurrencyId); var currencyCode = currency.Code.ToUpperInvariant(); // Ensure currency has valid ISO 4217 code if (!Iso4217.CurrencyCodes.ContainsKey(currencyCode)) { throw new Exception("Currency must be a valid ISO 4217 currency code: " + currency.Name); } var orderAmount = (int)AmountToMinorUnits(ctx.Order.TransactionAmount.Value); var clientConfig = GetBamboraClientConfig(ctx.Settings); var client = new BamboraClient(clientConfig); // Configure checkout session var checkoutSessionRequest = new BamboraCreateCheckoutSessionRequest { InstantCaptureAmount = ctx.Settings.Capture ? orderAmount : 0, Customer = new BamboraCustomer { Email = ctx.Order.CustomerInfo.Email }, Order = new BamboraOrder { Id = BamboraSafeOrderId(ctx.Order.OrderNumber), Amount = orderAmount, Currency = currencyCode }, Urls = new BamboraUrls { Accept = ctx.Urls.ContinueUrl, Cancel = ctx.Urls.CancelUrl, Callbacks = new[] { new BamboraUrl { Url = ctx.Urls.CallbackUrl } } }, PaymentWindow = new BamboraPaymentWindow { Id = 1, Language = ctx.Settings.Language } }; // Exclude payment methods if (!string.IsNullOrWhiteSpace(ctx.Settings.ExcludedPaymentMethods)) { checkoutSessionRequest.PaymentWindow.PaymentMethods = ctx.Settings.ExcludedPaymentMethods .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => new BamboraPaymentFilter { Id = x.Trim(), Action = BamboraPaymentFilter.Actions.Exclude }) .ToArray(); } // Exclude payment groups if (!string.IsNullOrWhiteSpace(ctx.Settings.ExcludedPaymentGroups)) { checkoutSessionRequest.PaymentWindow.PaymentGroups = ctx.Settings.ExcludedPaymentGroups .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => new BamboraPaymentFilter { Id = x.Trim(), Action = BamboraPaymentFilter.Actions.Exclude }) .ToArray(); } // Exclude payment types if (!string.IsNullOrWhiteSpace(ctx.Settings.ExcludedPaymentTypes)) { checkoutSessionRequest.PaymentWindow.PaymentTypes = ctx.Settings.ExcludedPaymentTypes .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => new BamboraPaymentFilter { Id = x.Trim(), Action = BamboraPaymentFilter.Actions.Exclude }) .ToArray(); } var checkoutSession = await client.CreateCheckoutSessionAsync(checkoutSessionRequest); if (checkoutSession.Meta.Result) { return(new PaymentFormResult() { Form = new PaymentForm(checkoutSession.Url, PaymentFormMethod.Get) }); } throw new ApplicationException(checkoutSession.Meta.Message.EndUser); }
public override string GetErrorUrl(PaymentProviderContext <TSettings> ctx) { return(ctx.Settings.ErrorUrl); }
public override async Task <PaymentFormResult> GenerateFormAsync(PaymentProviderContext <StripeCheckoutSettings> ctx) { var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey; var publicKey = ctx.Settings.TestMode ? ctx.Settings.TestPublicKey : ctx.Settings.LivePublicKey; ConfigureStripe(secretKey); var currency = Vendr.Services.CurrencyService.GetCurrency(ctx.Order.CurrencyId); var billingCountry = ctx.Order.PaymentInfo.CountryId.HasValue ? Vendr.Services.CountryService.GetCountry(ctx.Order.PaymentInfo.CountryId.Value) : null; Customer customer; var customerService = new CustomerService(); // If we've created a customer already, keep using it but update it incase // any of the billing details have changed if (!string.IsNullOrWhiteSpace(ctx.Order.Properties["stripeCustomerId"])) { var customerOptions = new CustomerUpdateOptions { Name = $"{ctx.Order.CustomerInfo.FirstName} {ctx.Order.CustomerInfo.LastName}", Email = ctx.Order.CustomerInfo.Email, Description = ctx.Order.OrderNumber, Address = new AddressOptions { Line1 = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressLine1PropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressLine1PropertyAlias] : "", Line2 = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressLine2PropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressLine2PropertyAlias] : "", City = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressCityPropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressCityPropertyAlias] : "", State = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressStatePropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressStatePropertyAlias] : "", PostalCode = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressZipCodePropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressZipCodePropertyAlias] : "", Country = billingCountry?.Code } }; // Pass billing country / zipcode as meta data as currently // this is the only way it can be validated via Radar // Block if ::customer:billingCountry:: != :card_country: customerOptions.Metadata = new Dictionary <string, string> { { "billingCountry", customerOptions.Address.Country }, { "billingZipCode", customerOptions.Address.PostalCode } }; customer = customerService.Update(ctx.Order.Properties["stripeCustomerId"].Value, customerOptions); } else { var customerOptions = new CustomerCreateOptions { Name = $"{ctx.Order.CustomerInfo.FirstName} {ctx.Order.CustomerInfo.LastName}", Email = ctx.Order.CustomerInfo.Email, Description = ctx.Order.OrderNumber, Address = new AddressOptions { Line1 = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressLine1PropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressLine1PropertyAlias] : "", Line2 = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressLine2PropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressLine2PropertyAlias] : "", City = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressCityPropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressCityPropertyAlias] : "", State = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressStatePropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressStatePropertyAlias] : "", PostalCode = !string.IsNullOrWhiteSpace(ctx.Settings.BillingAddressZipCodePropertyAlias) ? ctx.Order.Properties[ctx.Settings.BillingAddressZipCodePropertyAlias] : "", Country = billingCountry?.Code } }; // Pass billing country / zipcode as meta data as currently // this is the only way it can be validated via Radar // Block if ::customer:billingCountry:: != :card_country: customerOptions.Metadata = new Dictionary <string, string> { { "billingCountry", customerOptions.Address.Country }, { "billingZipCode", customerOptions.Address.PostalCode } }; customer = customerService.Create(customerOptions); } var metaData = new Dictionary <string, string> { { "ctx.OrderReference", ctx.Order.GenerateOrderReference() }, { "ctx.OrderId", ctx.Order.Id.ToString("D") }, { "ctx.OrderNumber", ctx.Order.OrderNumber } }; if (!string.IsNullOrWhiteSpace(ctx.Settings.OrderProperties)) { foreach (var alias in ctx.Settings.OrderProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x))) { if (!string.IsNullOrWhiteSpace(ctx.Order.Properties[alias])) { metaData.Add(alias, ctx.Order.Properties[alias]); } } } var hasRecurringItems = false; long recurringTotalPrice = 0; long orderTotalPrice = AmountToMinorUnits(ctx.Order.TransactionAmount.Value); var lineItems = new List <SessionLineItemOptions>(); foreach (var orderLine in ctx.Order.OrderLines.Where(IsRecurringOrderLine)) { var orderLineTaxRate = orderLine.TaxRate * 100; var lineItemOpts = new SessionLineItemOptions(); if (orderLine.Properties.ContainsKey("stripePriceId") && !string.IsNullOrWhiteSpace(orderLine.Properties["stripePriceId"])) { // NB: When using stripe prices there is an inherit risk that values may not // actually be in sync and so the price displayed on the site might not match // that in stripe and so this may cause inconsistant payments lineItemOpts.Price = orderLine.Properties["stripePriceId"].Value; // If we are using a stripe price, then assume the quantity of the line item means // the quantity of the stripe price you want to buy. lineItemOpts.Quantity = (long)orderLine.Quantity; // Because we are in charge of what taxes apply, we need to setup a tax rate // to ensure the price defined in stripe has the relevant taxes applied var stripePricesIncludeTax = PropertyIsTrue(orderLine.Properties, "stripePriceIncludesTax"); var stripeTaxRate = GetOrCreateStripeTaxRate(ctx, "Subscription Tax", orderLineTaxRate, stripePricesIncludeTax); if (stripeTaxRate != null) { lineItemOpts.TaxRates = new List <string>(new[] { stripeTaxRate.Id }); } } else { // We don't have a stripe price defined on the ctx.Order line // so we'll create one on the fly using the ctx.Order lines total // value var priceData = new SessionLineItemPriceDataOptions { Currency = currency.Code, UnitAmount = AmountToMinorUnits(orderLine.TotalPrice.Value.WithoutTax / orderLine.Quantity), // Without tax as Stripe will apply the tax Recurring = new SessionLineItemPriceDataRecurringOptions { Interval = orderLine.Properties["stripeRecurringInterval"].Value.ToLower(), IntervalCount = long.TryParse(orderLine.Properties["stripeRecurringIntervalCount"], out var intervalCount) ? intervalCount : 1 } }; if (orderLine.Properties.ContainsKey("stripeProductId") && !string.IsNullOrWhiteSpace(orderLine.Properties["stripeProductId"])) { priceData.Product = orderLine.Properties["stripeProductId"].Value; } else { priceData.ProductData = new SessionLineItemPriceDataProductDataOptions { Name = orderLine.Name, Metadata = new Dictionary <string, string> { { "ProductReference", orderLine.ProductReference } } }; } lineItemOpts.PriceData = priceData; // For dynamic subscriptions, regardless of line item quantity, treat the line // as a single subscription item with one price being the line items total price lineItemOpts.Quantity = (long)orderLine.Quantity; // If we define the price, then create tax rates that are set to be inclusive // as this means that we can pass prices inclusive of tax and Stripe works out // the pre-tax price which would be less suseptable to rounding inconsistancies var stripeTaxRate = GetOrCreateStripeTaxRate(ctx, "Subscription Tax", orderLineTaxRate, false); if (stripeTaxRate != null) { lineItemOpts.TaxRates = new List <string>(new[] { stripeTaxRate.Id }); } } lineItems.Add(lineItemOpts); recurringTotalPrice += AmountToMinorUnits(orderLine.TotalPrice.Value.WithTax); hasRecurringItems = true; } if (recurringTotalPrice < orderTotalPrice) { // If the total value of the ctx.Order is not covered by the subscription items // then we add another line item for the remainder of the ctx.Order value var lineItemOpts = new SessionLineItemOptions { PriceData = new SessionLineItemPriceDataOptions { Currency = currency.Code, UnitAmount = orderTotalPrice - recurringTotalPrice, ProductData = new SessionLineItemPriceDataProductDataOptions { Name = hasRecurringItems ? !string.IsNullOrWhiteSpace(ctx.Settings.OneTimeItemsHeading) ? ctx.Settings.OneTimeItemsHeading : "One time items (inc Tax)" : !string.IsNullOrWhiteSpace(ctx.Settings.OrderHeading) ? ctx.Settings.OrderHeading : "#" + ctx.Order.OrderNumber, Description = hasRecurringItems || !string.IsNullOrWhiteSpace(ctx.Settings.OrderHeading) ? "#" + ctx.Order.OrderNumber : null, } }, Quantity = 1 }; lineItems.Add(lineItemOpts); } // Add image to the first item (only if it's not a product link) if (!string.IsNullOrWhiteSpace(ctx.Settings.OrderImage) && lineItems.Count > 0 && lineItems[0].PriceData?.ProductData != null) { lineItems[0].PriceData.ProductData.Images = new[] { ctx.Settings.OrderImage }.ToList(); } var sessionOptions = new SessionCreateOptions { Customer = customer.Id, PaymentMethodTypes = !string.IsNullOrWhiteSpace(ctx.Settings.PaymentMethodTypes) ? ctx.Settings.PaymentMethodTypes.Split(',') .Select(tag => tag.Trim()) .Where(tag => !string.IsNullOrEmpty(tag)) .ToList() : new List <string> { "card", }, LineItems = lineItems, Mode = hasRecurringItems ? "subscription" : "payment", ClientReferenceId = ctx.Order.GenerateOrderReference(), SuccessUrl = ctx.Urls.ContinueUrl, CancelUrl = ctx.Urls.CancelUrl }; if (hasRecurringItems) { sessionOptions.SubscriptionData = new SessionSubscriptionDataOptions { Metadata = metaData }; } else { sessionOptions.PaymentIntentData = new SessionPaymentIntentDataOptions { CaptureMethod = ctx.Settings.Capture ? "automatic" : "manual", Metadata = metaData }; } if (ctx.Settings.SendStripeReceipt) { sessionOptions.PaymentIntentData.ReceiptEmail = ctx.Order.CustomerInfo.Email; } var sessionService = new SessionService(); var session = await sessionService.CreateAsync(sessionOptions); return(new PaymentFormResult() { MetaData = new Dictionary <string, string> { { "stripeSessionId", session.Id }, { "stripeCustomerId", session.CustomerId } }, Form = new PaymentForm(ctx.Urls.ContinueUrl, PaymentFormMethod.Post) .WithAttribute("onsubmit", "return handleStripeCheckout(event)") .WithJsFile("https://js.stripe.com/v3/") .WithJs(@" var stripe = Stripe('" + publicKey + @"'); window.handleStripeCheckout = function (e) { e.preventDefault(); stripe.redirectToCheckout({ sessionId: '" + session.Id + @"' }).then(function (result) { // If `redirectToCheckout` fails due to a browser or network // error, display the localized error message to your customer // using `result.error.message`. }); return false; } ") }); }