From 9def0c742384dd5c43c76ef6b70d34ba2f634fe6 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 25 Nov 2024 09:51:05 -0800 Subject: [PATCH] Updates for customizing paypal transactions --- .../Interfaces/ICreditCardProcessor.cs | 34 +++-- .../BlueSnapService.cs | 7 +- .../CardConnectService.cs | 7 +- .../Mappers/PayPalOrderPaymentMapper.cs | 128 +++++++++++++++--- .../Models/PayPalOrder.cs | 33 +++++ .../PayPalClient.cs | 75 ++++++---- .../PayPalConfig.cs | 19 ++- .../PayPalService.cs | 20 ++- .../StripeService.cs | 7 +- 9 files changed, 266 insertions(+), 64 deletions(-) diff --git a/OrderCloud.Catalyst/Integrations/Interfaces/ICreditCardProcessor.cs b/OrderCloud.Catalyst/Integrations/Interfaces/ICreditCardProcessor.cs index 511f303..80b8ced 100644 --- a/OrderCloud.Catalyst/Integrations/Interfaces/ICreditCardProcessor.cs +++ b/OrderCloud.Catalyst/Integrations/Interfaces/ICreditCardProcessor.cs @@ -16,28 +16,32 @@ public interface ICreditCardProcessor /// Task GetIFrameCredentialAsync(OCIntegrationConfig overrideConfig = null); /// - /// Create the payment request to initialize payment processing. + /// Create the payment request to initialize payment processing. Optionally define whether the intent is authorization only, or capture. /// - Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null); - /// - /// Attempt to verify the user can pay by placing a hold on a credit card. Funds will be captured later. Typically used as a verification step directly before order submit. - /// - Task AuthorizeOnlyAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null); + Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false); /// - /// Attempt to capture funds from a credit card. A prior authorization is required. Typically used when a shipment is created, at the end of the day, or a defined time period after submit. + /// Attempt to verify the user can pay by placing a hold on a credit card. Funds will be captured later. Typically used as a verification step directly before order submit. /// - Task CapturePriorAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); + Task AuthorizeOnlyAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null); /// - /// Remove an authorization hold previously placed on a credit card. Use if order submit fails, or if order is canceled/returned before capture. + /// Attempt to capture funds from a credit card. A prior authorization is required. Typically used when a shipment is created, at the end of the day, or a defined time period after submit. /// - Task VoidAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); + Task CapturePriorAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); + /// + /// Capture funds at the time of the transaction without prior authorization. + /// + Task CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); + /// + /// Remove an authorization hold previously placed on a credit card. Use if order submit fails, or if order is canceled/returned before capture. + /// + Task VoidAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); /// /// Refund a previously captured amount. Used if an order is canceled/returned after capture. Refunding generally incures extra processing fees, whereas voiding does not. /// Task RefundCaptureAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null); } - public class AuthorizeCCTransaction + public class AuthorizeCCTransaction { /// /// The OrderCloud Order ID that this card transaction applies to. @@ -107,6 +111,14 @@ public class CCTransactionResult /// User readable text explaining the result. /// public string Message { get; set; } + /// + /// The ID of the merchant associated with the transaction + /// + public string MerchantID { get; set; } + /// + /// If there are multiple merchant captures processed, store each response in a nested CCTransactionResult + /// + public List InnerTransactions { get; set; } } /// diff --git a/OrderCloud.Integrations.Payment.BlueSnap/BlueSnapService.cs b/OrderCloud.Integrations.Payment.BlueSnap/BlueSnapService.cs index 84b6114..88600e2 100644 --- a/OrderCloud.Integrations.Payment.BlueSnap/BlueSnapService.cs +++ b/OrderCloud.Integrations.Payment.BlueSnap/BlueSnapService.cs @@ -18,7 +18,12 @@ public async Task GetIFrameCredentialAsync(OCIntegrationConfig overrideC return token; } - public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null) + public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false) + { + throw new NotImplementedException(); + } + + public Task CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) { throw new NotImplementedException(); } diff --git a/OrderCloud.Integrations.Payment.CardConnect/CardConnectService.cs b/OrderCloud.Integrations.Payment.CardConnect/CardConnectService.cs index f353a9f..3e53171 100644 --- a/OrderCloud.Integrations.Payment.CardConnect/CardConnectService.cs +++ b/OrderCloud.Integrations.Payment.CardConnect/CardConnectService.cs @@ -19,7 +19,12 @@ public async Task GetIFrameCredentialAsync(OCIntegrationConfig overrideC return token; } - public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null) + public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false) + { + throw new NotImplementedException(); + } + + public Task CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) { throw new NotImplementedException(); } diff --git a/OrderCloud.Integrations.Payment.PayPal/Mappers/PayPalOrderPaymentMapper.cs b/OrderCloud.Integrations.Payment.PayPal/Mappers/PayPalOrderPaymentMapper.cs index f5c927d..b037654 100644 --- a/OrderCloud.Integrations.Payment.PayPal/Mappers/PayPalOrderPaymentMapper.cs +++ b/OrderCloud.Integrations.Payment.PayPal/Mappers/PayPalOrderPaymentMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using OrderCloud.Catalyst; @@ -8,36 +9,128 @@ namespace OrderCloud.Integrations.Payment.PayPal.Mappers { public class PayPalOrderPaymentMapper { - public PurchaseUnit MapToPurchaseUnit(AuthorizeCCTransaction transaction) => new PurchaseUnit() + public List MapToPurchaseUnit(AuthorizeCCTransaction transaction, PayPalConfig config) { - amount = new Amount() + PayPalAddress address = null; + if (transaction.AddressVerification != null) { - currency_code = transaction.Currency, - value = transaction.Amount.ToString(CultureInfo.InvariantCulture) ?? null + address = new PayPalAddress() + { + address_line_1 = transaction.AddressVerification?.Street1, + address_line_2 = transaction.AddressVerification?.Street2, + admin_area_1 = transaction.AddressVerification?.State, + admin_area_2 = transaction.AddressVerification?.City, + postal_code = transaction.AddressVerification?.Zip, + country_code = transaction.AddressVerification?.Country + }; } - }; + var purchaseUnits = new List(); + if (config.Merchants.Any()) + { + config.Merchants.ForEach(m => + { + var merchantLines = + transaction?.OrderWorksheet?.LineItems?.Where(li => li.Product.DefaultSupplierID == m.SupplierID).ToList(); + if (merchantLines != null && merchantLines.Any()) + { + var merchantUnit = new PurchaseUnit() + { + amount = new Amount() + { + currency_code = transaction.Currency, + value = merchantLines.Sum(li => li.LineTotal).ToString(CultureInfo.InvariantCulture) ?? + null // sum Amount for each merchant + }, + payee = new Payee() + { + merchant_id = m.MerchantID, + }, + description = transaction?.OrderWorksheet?.Order?.Comments, + reference_id = Guid.NewGuid().ToString(), + invoice_id = Guid.NewGuid().ToString(), + }; + if (address != null) + { + merchantUnit.shipping = new Shipping() + { + address = address + }; + } + + purchaseUnits.Add(merchantUnit); + } + }); + } + + var unit = new PurchaseUnit() + { + amount = new Amount() + { + currency_code = transaction.Currency, + value = transaction.Amount.ToString(CultureInfo.InvariantCulture) ?? null + } + }; + if (address != null) + { + unit.shipping = new Shipping() + { + address = address + }; + } + purchaseUnits.Add(unit); + + return purchaseUnits; + } public CCTransactionResult MapAuthorizedPaymentToCCTransactionResult(PayPalOrder authorizedOrder) { - var amount = ConvertStringAmountToDecimal(authorizedOrder.purchase_units.FirstOrDefault()?.payments.authorizations - .FirstOrDefault()?.amount.value); - var authorizationId = authorizedOrder.purchase_units.FirstOrDefault()?.payments.authorizations - .FirstOrDefault()?.id; + var innerTransactions = new List(); + authorizedOrder.purchase_units.ForEach(u => + { + var capture = u.payments.authorizations.FirstOrDefault(); + if (capture != null) + { + innerTransactions.Add(new CCTransactionResult() + { + TransactionID = capture.id, + Amount = ConvertStringAmountToDecimal(u.amount.value), + Succeeded = capture.status.ToLowerInvariant() == "completed", + MerchantID = u.payee.merchant_id + }); + } + }); var ccTransaction = new CCTransactionResult { - Succeeded = authorizedOrder.status.ToLowerInvariant() == "completed" && authorizationId != null, - Amount = amount, - TransactionID = authorizationId, // Authorization ID needed to Capture payment or Void Authorization + Succeeded = authorizedOrder.status.ToLowerInvariant() == "completed", + TransactionID = authorizedOrder.id, // Authorization ID needed to Capture payment or Void Authorization ResponseCode = authorizedOrder.processor_response.response_code, AuthorizationCode = null, AVSResponseCode = authorizedOrder.processor_response.avs_code, - Message = null + Message = null, + Amount = authorizedOrder.purchase_units.Sum(unit => ConvertStringAmountToDecimal(unit.amount.value)), + InnerTransactions = innerTransactions }; return ccTransaction; } - public CCTransactionResult MapCapturedPaymentToCCTransactionResult(PayPalOrder capturedOrder) => - new CCTransactionResult + public CCTransactionResult MapCapturedPaymentToCCTransactionResult(PayPalOrder capturedOrder) + { + var innerTransactions = new List(); + capturedOrder.purchase_units.ForEach(u => + { + var capture = u.payments.captures.FirstOrDefault(); + if (capture != null) + { + innerTransactions.Add(new CCTransactionResult() + { + TransactionID = capture.id, + Amount = ConvertStringAmountToDecimal(u.amount.value), + Succeeded = capture.status.ToLowerInvariant() == "completed", + MerchantID = u.payee.merchant_id + }); + } + }); + return new CCTransactionResult { TransactionID = capturedOrder.id, // Capture ID needed to Refund payment ResponseCode = capturedOrder.processor_response.response_code, @@ -45,8 +138,11 @@ public CCTransactionResult MapCapturedPaymentToCCTransactionResult(PayPalOrder c AVSResponseCode = capturedOrder.processor_response.avs_code, Message = null, Succeeded = capturedOrder.status.ToLowerInvariant() == "completed", - Amount = 0 + Amount = capturedOrder.purchase_units.Sum(unit => ConvertStringAmountToDecimal(unit.amount.value)), + InnerTransactions = innerTransactions }; + } + public CCTransactionResult MapRefundPaymentToCCTransactionResult(PayPalOrderReturn orderReturn) => new CCTransactionResult diff --git a/OrderCloud.Integrations.Payment.PayPal/Models/PayPalOrder.cs b/OrderCloud.Integrations.Payment.PayPal/Models/PayPalOrder.cs index 8531eb1..812b05b 100644 --- a/OrderCloud.Integrations.Payment.PayPal/Models/PayPalOrder.cs +++ b/OrderCloud.Integrations.Payment.PayPal/Models/PayPalOrder.cs @@ -26,6 +26,11 @@ public class Payer public string payer_id { get; set; } } + public class Payee + { + public string merchant_id { get; set; } + } + public class RelatedLink { public string href { get; set; } @@ -33,17 +38,38 @@ public class RelatedLink public string method { get; set; } } + public class PayPalAddress + { + public string address_line_1 { get; set; } + public string address_line_2 { get; set; } + + public string admin_area_1 { get; set; } + public string admin_area_2 { get; set; } + public string postal_code { get; set; } + public string country_code { get; set; } + } + + public class Shipping + { + public PayPalAddress address { get; set; } + } + public class PurchaseUnit { // The merchant ID for the purchase unit. public string reference_id { get; set; } + public string description { get; set; } + public string invoice_id { get; set; } public Amount amount { get; set; } public PurchaseUnitPayment payments { get; set; } + public Shipping shipping { get; set; } + public Payee payee { get; set; } } public class PurchaseUnitPayment { public List authorizations { get; set; } + public List captures { get; set; } } public class PurchaseUnitAuthorization @@ -53,6 +79,7 @@ public class PurchaseUnitAuthorization public Amount amount { get; set; } public List links { get; set; } } + public class Amount { // The three-character ISO-4217 currency code. @@ -72,11 +99,17 @@ public class PaymentSource public PaymentToken token { get; set; } } + public class ExperienceContext + { + public string shipping_preference { get; set; } + } + public class PayPal { public Name name { get; set; } public string email_address { get; set; } public string account_id { get; set; } + public ExperienceContext experience_context { get; set; } } public class Card diff --git a/OrderCloud.Integrations.Payment.PayPal/PayPalClient.cs b/OrderCloud.Integrations.Payment.PayPal/PayPalClient.cs index 342b0c2..2af930f 100644 --- a/OrderCloud.Integrations.Payment.PayPal/PayPalClient.cs +++ b/OrderCloud.Integrations.Payment.PayPal/PayPalClient.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using Flurl.Http; @@ -8,11 +9,24 @@ namespace OrderCloud.Integrations.Payment.PayPal { public class PayPalClient { - protected static IFlurlRequest BuildClient(PayPalConfig config) => config.BaseUrl.WithBasicAuth(config.ClientID, config.SecretKey); + protected static IFlurlRequest BuildClient(PayPalConfig config, string requestID) + { + var request = config.BaseUrl + .WithBasicAuth(config.ClientID, config.SecretKey) + .WithHeader("PayPal-Request-Id", requestID) + .WithHeader("Prefer", "return=representation"); + + if (config?.PartnerAttributionID != null) + { + request.WithHeader("PayPal-Partner-Attribution-Id", config.PartnerAttributionID); + } + + return request; + } #region Step 1: Create order with Authorize intent // https://developer.paypal.com/docs/api/orders/v2/#orders_create - public static async Task CreateAuthorizedOrderAsync(PayPalConfig config, PurchaseUnit purchaseUnit, AuthorizeCCTransaction transaction) + public static async Task CreateAuthorizedOrderAsync(PayPalConfig config, List purchaseUnits, AuthorizeCCTransaction transaction, bool isCapture) { var paymentSource = new PaymentSource(); if (transaction.CardDetails != null) @@ -25,19 +39,21 @@ public static async Task CreateAuthorizedOrderAsync(PayPalConfig co { paymentSource.card.single_use_token = transaction.CardDetails.Token; } + } else if (transaction.AddressVerification != null) + { + paymentSource.paypal = new Models.PayPal() + { + experience_context = new ExperienceContext() { shipping_preference = "SET_PROVIDED_ADDRESS" } + }; } - return await BuildClient(config) + return await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "checkout", "orders") - .WithHeader("PayPal-Request-Id", transaction.RequestID) .PostJsonAsync(new { - intent = "AUTHORIZE", - purchase_units = new List() - { - purchaseUnit - }, - payment_source = paymentSource.card != null ? paymentSource : null + intent = isCapture ? "CAPTURE" : "AUTHORIZE", + purchase_units = purchaseUnits, + payment_source = (paymentSource?.card != null || paymentSource.paypal != null) ? paymentSource : null }) .ReceiveJson(); } @@ -47,21 +63,30 @@ public static async Task CreateAuthorizedOrderAsync(PayPalConfig co // https://developer.paypal.com/docs/api/orders/v2/#orders_authorize public static async Task AuthorizePaymentForOrderAsync(PayPalConfig config, AuthorizeCCTransaction transaction) { - return await BuildClient(config) + return await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "checkout", "orders", transaction.OrderID, "authorize") - .WithHeader("PayPal-Request-Id", transaction.RequestID) .PostJsonAsync(new { }) .ReceiveJson(); } #endregion #region Step 3: Capture the order + // capture previously authorized payment // https://developer.paypal.com/docs/api/payments/v2/#authorizations_capture - public static async Task CapturePaymentAsync(PayPalConfig config, FollowUpCCTransaction transaction) + public static async Task CapturePriorAuthAsync(PayPalConfig config, FollowUpCCTransaction transaction) { - return await BuildClient(config) + return await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "payments", "authorizations", transaction.TransactionID, "capture") - .WithHeader("PayPal-Request-Id", transaction.RequestID) + .PostJsonAsync(new { }) + .ReceiveJson(); + } + + // OR capture payment immediately without prior auth + // https://developer.paypal.com/docs/api/orders/v2/#orders_capture + public static async Task CapturePaymentAsync(PayPalConfig config, FollowUpCCTransaction transaction) + { + return await BuildClient(config, transaction.RequestID) + .AppendPathSegments("v2", "checkout", "orders", transaction.TransactionID, "capture") .PostJsonAsync(new { }) .ReceiveJson(); } @@ -70,9 +95,8 @@ public static async Task CapturePaymentAsync(PayPalConfig config, F // https://developer.paypal.com/docs/api/payments/v2/#authorizations_void public static async Task VoidPaymentAsync(PayPalConfig config, FollowUpCCTransaction transaction) { - return await BuildClient(config) + return await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "payments", "authorizations", transaction.TransactionID, "void") - .WithHeader("PayPal-Request-Id", transaction.RequestID) .PostJsonAsync(new { }); } @@ -80,15 +104,12 @@ public static async Task VoidPaymentAsync(PayPalConfig config, F public static async Task RefundPaymentAsync(PayPalConfig config, FollowUpCCTransaction transaction) { // get capture details to get the currency - var captureDetails = await BuildClient(config) + var captureDetails = await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "payments", "captures", transaction.TransactionID) - .WithHeader("PayPal-Request-Id", transaction.RequestID) .GetJsonAsync(); - return await BuildClient(config) + return await BuildClient(config, transaction.RequestID) .AppendPathSegments("v2", "payments", "captures", transaction.TransactionID, "refund") - .WithHeader("PayPal-Request-Id", transaction.RequestID) - .WithHeader("Prefer", "return=representation") .PostJsonAsync(new { amount = new @@ -102,7 +123,7 @@ public static async Task RefundPaymentAsync(PayPalConfig conf public static async Task CreateVaultSetupToken(PayPalConfig config) { - var response = await BuildClient(config) + var response = await BuildClient(config, Guid.NewGuid().ToString()) .AppendPathSegments("v3", "vault", "setup-tokens") .PostJsonAsync(new { @@ -119,7 +140,7 @@ public static async Task CreateVaultSetupToken(PayPalConfig config) // https://developer.paypal.com/docs/api/payment-tokens/v3/#payment-tokens_create public static async Task CreatePaymentTokenAsync(PayPalConfig config, PCISafeCardDetails card, PaymentSystemCustomer customer) { - var response = await BuildClient(config) + var response = await BuildClient(config, Guid.NewGuid().ToString()) .AppendPathSegments("v3", "vault", "payment-tokens") .PostJsonAsync(new { @@ -139,7 +160,7 @@ public static async Task CreatePaymentTokenAsync(PayPalConfi // https://developer.paypal.com/docs/api/payment-tokens/v3/#customer_payment-tokens_get public static async Task ListPaymentTokensAsync(PayPalConfig config, string customerID) { - return await BuildClient(config) + return await BuildClient(config, Guid.NewGuid().ToString()) .AppendPathSegments("v3", "vault", "payment-tokens") .SetQueryParam("customer_id", customerID) .GetJsonAsync(); @@ -148,7 +169,7 @@ public static async Task ListPaymentTokensAsync(PayPalConf // https://developer.paypal.com/docs/api/payment-tokens/v3/#payment-tokens_get public static async Task GetPaymentTokenAsync(PayPalConfig config, string tokenID) { - return await BuildClient(config) + return await BuildClient(config, Guid.NewGuid().ToString()) .AppendPathSegments("v3", "vault", "payment-tokens", tokenID) .GetJsonAsync(); } @@ -156,7 +177,7 @@ public static async Task GetPaymentTokenAsync(PayPalConfig c // https://developer.paypal.com/docs/api/payment-tokens/v3/#payment-tokens_deletes public static async Task DeletePaymentTokenAsync(PayPalConfig config, string tokenID) { - await BuildClient(config) + await BuildClient(config, Guid.NewGuid().ToString()) .AppendPathSegments("v3", "vault", "payment-tokens", tokenID) .DeleteAsync(); } diff --git a/OrderCloud.Integrations.Payment.PayPal/PayPalConfig.cs b/OrderCloud.Integrations.Payment.PayPal/PayPalConfig.cs index f317cde..0f83d18 100644 --- a/OrderCloud.Integrations.Payment.PayPal/PayPalConfig.cs +++ b/OrderCloud.Integrations.Payment.PayPal/PayPalConfig.cs @@ -1,4 +1,5 @@ -using OrderCloud.Catalyst; +using System.Collections.Generic; +using OrderCloud.Catalyst; namespace OrderCloud.Integrations.Payment.PayPal { @@ -11,5 +12,21 @@ public class PayPalConfig : OCIntegrationConfig public string ClientID {get; set; } [RequiredIntegrationField] public string SecretKey { get; set; } + /// + /// Optional property. BN codes provide tracking on all transactions that originate or are associated with a particular partner. + /// If provided, it will be included in all request headers: https://developer.paypal.com/docs/multiparty/accept-payments/#link-bncode + /// + public string PartnerAttributionID { get; set; } + /// + /// Optional property. A list of paypal merchant IDs that correspond with OrderCloud Suppliers. + /// if provided, transactions will be split into multiple purchase_units by supplier line items. + /// + public List Merchants { get; set; } + } + + public class PayPalMerchantConfig + { + public string SupplierID { get; set; } + public string MerchantID { get; set; } } } diff --git a/OrderCloud.Integrations.Payment.PayPal/PayPalService.cs b/OrderCloud.Integrations.Payment.PayPal/PayPalService.cs index 6e34d94..d75067f 100644 --- a/OrderCloud.Integrations.Payment.PayPal/PayPalService.cs +++ b/OrderCloud.Integrations.Payment.PayPal/PayPalService.cs @@ -16,12 +16,12 @@ public Task GetIFrameCredentialAsync(OCIntegrationConfig overrideConfig throw new NotImplementedException("Not required for PayPal"); } - public async Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null) + public async Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false) { var config = ValidateConfig(overrideConfig ?? _defaultConfig); var purchaseUnitMapper = new PayPalOrderPaymentMapper(); - var purchaseUnit = purchaseUnitMapper.MapToPurchaseUnit(transaction); - var order = await PayPalClient.CreateAuthorizedOrderAsync(config, purchaseUnit, transaction); + var purchaseUnits = purchaseUnitMapper.MapToPurchaseUnit(transaction, config); + var order = await PayPalClient.CreateAuthorizedOrderAsync(config, purchaseUnits, transaction, isCapture); return new CCTransactionResult { Succeeded = new List{ "created", "completed" }.Contains(order.status.ToLowerInvariant()), @@ -37,19 +37,27 @@ public async Task AuthorizeOnlyAsync(AuthorizeCCTransaction var authorizedPaymentForOrder = await PayPalClient.AuthorizePaymentForOrderAsync(config, transaction); var ccTransactionMapper = new PayPalOrderPaymentMapper(); return ccTransactionMapper.MapAuthorizedPaymentToCCTransactionResult(authorizedPaymentForOrder); - // CCTransactionResult.TransactionID represents the PayPal Authorization ID } public async Task CapturePriorAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) { var config = ValidateConfig(overrideConfig ?? _defaultConfig); // FollowUpCCTransaction.TransactionID represents the PayPal Authorization ID - var capturedPaymentForOrder = await PayPalClient.CapturePaymentAsync(config, transaction); + var capturedPaymentForOrder = await PayPalClient.CapturePriorAuthAsync(config, transaction); var ccTransactionMapper = new PayPalOrderPaymentMapper(); var ccTransaction = ccTransactionMapper.MapCapturedPaymentToCCTransactionResult(capturedPaymentForOrder); ccTransaction.Amount = transaction.Amount; // PayPal Capture Authorized Payment doesn't return an amount in the response body return ccTransaction; - // CCTransactionResult.TransactionID represents the PayPal Capture ID + } + + public async Task CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) + { + var config = ValidateConfig(overrideConfig ?? _defaultConfig); + // FollowUpCCTransaction.TransactionID represents the PayPal Authorization ID + var capturedPaymentForOrder = await PayPalClient.CapturePaymentAsync(config, transaction); + var ccTransactionMapper = new PayPalOrderPaymentMapper(); + var ccTransaction = ccTransactionMapper.MapCapturedPaymentToCCTransactionResult(capturedPaymentForOrder); + return ccTransaction; } public async Task VoidAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) diff --git a/OrderCloud.Integrations.Payment.Stripe/StripeService.cs b/OrderCloud.Integrations.Payment.Stripe/StripeService.cs index b8bc8c5..bdc0fb3 100644 --- a/OrderCloud.Integrations.Payment.Stripe/StripeService.cs +++ b/OrderCloud.Integrations.Payment.Stripe/StripeService.cs @@ -19,7 +19,12 @@ public async Task GetIFrameCredentialAsync(OCIntegrationConfig overrideC return token; } - public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null) + public Task InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false) + { + throw new NotImplementedException(); + } + + public Task CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null) { throw new NotImplementedException(); }