Skip to content

Commit

Permalink
Updates for customizing paypal transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
erincdustin committed Nov 25, 2024
1 parent cd16ad7 commit 9def0c7
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 64 deletions.
34 changes: 23 additions & 11 deletions OrderCloud.Catalyst/Integrations/Interfaces/ICreditCardProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,32 @@ public interface ICreditCardProcessor
/// </summary>
Task<string> GetIFrameCredentialAsync(OCIntegrationConfig overrideConfig = null);
/// <summary>
/// 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.
/// </summary>
Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
/// <summary>
/// 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.
/// </summary>
Task<CCTransactionResult> AuthorizeOnlyAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false);
/// <summary>
/// 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.
/// </summary>
Task<CCTransactionResult> CapturePriorAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
Task<CCTransactionResult> AuthorizeOnlyAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
/// <summary>
/// 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.
/// </summary>
Task<CCTransactionResult> VoidAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
Task<CCTransactionResult> CapturePriorAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
/// <summary>
/// Capture funds at the time of the transaction without prior authorization.
/// </summary>
Task<CCTransactionResult> CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
/// <summary>
/// Remove an authorization hold previously placed on a credit card. Use if order submit fails, or if order is canceled/returned before capture.
/// </summary>
Task<CCTransactionResult> VoidAuthorizationAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
/// <summary>
/// Refund a previously captured amount. Used if an order is canceled/returned after capture. Refunding generally incures extra processing fees, whereas voiding does not.
/// </summary>
Task<CCTransactionResult> RefundCaptureAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null);
}

public class AuthorizeCCTransaction
public class AuthorizeCCTransaction
{
/// <summary>
/// The OrderCloud Order ID that this card transaction applies to.
Expand Down Expand Up @@ -107,6 +111,14 @@ public class CCTransactionResult
/// User readable text explaining the result.
/// </summary>
public string Message { get; set; }
/// <summary>
/// The ID of the merchant associated with the transaction
/// </summary>
public string MerchantID { get; set; }
/// <summary>
/// If there are multiple merchant captures processed, store each response in a nested CCTransactionResult
/// </summary>
public List<CCTransactionResult> InnerTransactions { get; set; }
}

/// <summary>
Expand Down
7 changes: 6 additions & 1 deletion OrderCloud.Integrations.Payment.BlueSnap/BlueSnapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public async Task<string> GetIFrameCredentialAsync(OCIntegrationConfig overrideC
return token;
}

public Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null)
public Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false)
{
throw new NotImplementedException();
}

public Task<CCTransactionResult> CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public async Task<string> GetIFrameCredentialAsync(OCIntegrationConfig overrideC
return token;
}

public Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null)
public Task<CCTransactionResult> InitializePaymentRequestAsync(AuthorizeCCTransaction transaction, OCIntegrationConfig overrideConfig = null, bool isCapture = false)
{
throw new NotImplementedException();
}

public Task<CCTransactionResult> CapturePaymentAsync(FollowUpCCTransaction transaction, OCIntegrationConfig overrideConfig = null)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using OrderCloud.Catalyst;
Expand All @@ -8,45 +9,140 @@ namespace OrderCloud.Integrations.Payment.PayPal.Mappers
{
public class PayPalOrderPaymentMapper
{
public PurchaseUnit MapToPurchaseUnit(AuthorizeCCTransaction transaction) => new PurchaseUnit()
public List<PurchaseUnit> 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<PurchaseUnit>();
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<CCTransactionResult>();
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<CCTransactionResult>();
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,
AuthorizationCode = null,
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
Expand Down
33 changes: 33 additions & 0 deletions OrderCloud.Integrations.Payment.PayPal/Models/PayPalOrder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,50 @@ 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; }
public string rel { get; set; }
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<PurchaseUnitAuthorization> authorizations { get; set; }
public List<PurchaseUnitAuthorization> captures { get; set; }
}

public class PurchaseUnitAuthorization
Expand All @@ -53,6 +79,7 @@ public class PurchaseUnitAuthorization
public Amount amount { get; set; }
public List<RelatedLink> links { get; set; }
}

public class Amount
{
// The three-character ISO-4217 currency code.
Expand All @@ -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
Expand Down
Loading

0 comments on commit 9def0c7

Please sign in to comment.