/// <summary> /// Validates a successful response. /// </summary> /// <param name="invoice"> /// The invoice. /// </param> /// <param name="payment"> /// The payment. /// </param> /// <param name="token"> /// The token. /// </param> /// <param name="payerId"> /// The payer id. /// </param> /// <param name="record"> /// The record. /// </param> /// <returns> /// The <see cref="ExpressCheckoutResponse"/>. /// </returns> /// <remarks> /// PayPal returns to the success URL even if the payment was declined. e.g. The success URL represents a successful transaction /// not a successful payment so we need to do another request to verify the payment was completed and get additional information /// such as the transaction id so we can do refunds etc. /// </remarks> internal PayPalExpressTransactionRecord Authorize(IInvoice invoice, IPayment payment, string token, string payerId, PayPalExpressTransactionRecord record) { // Now we have to get the transaction details for the successful payment ExpressCheckoutResponse result = null; try { // do authorization var service = GetPayPalService(); var doAuthorizationResponse = service.DoAuthorization(new DoAuthorizationReq { DoAuthorizationRequest = new DoAuthorizationRequestType { TransactionID = record.Data.CheckoutPaymentTransactionId, Amount = new BasicAmountType(PayPalApiHelper.GetPayPalCurrencyCode(record.Data.CurrencyId), record.Data.AuthorizedAmount) } }); result = _responseFactory.Build(doAuthorizationResponse, token); if (result.Success()) { record.Data.Authorized = true; record.Data.AuthorizationTransactionId = doAuthorizationResponse.TransactionID; } } catch (Exception ex) { result = _responseFactory.Build(ex); } record.DoAuthorization = result; return(record); }
/// <summary> /// Process payment when user checkout. /// </summary> /// <param name="cart">The current cart.</param> /// <param name="payment">The payment to process.</param> /// <returns>return false and set the message will make the WorkFlow activity raise PaymentExcetion(message)</returns> private PaymentProcessingResult ProcessPaymentCheckout(ICart cart, IPayment payment) { var orderNumberId = _orderNumberGenerator.GenerateOrderNumber(cart); if (string.IsNullOrEmpty(_paymentMethodConfiguration.ExpChkoutUrl) || string.IsNullOrEmpty(_paymentMethodConfiguration.PaymentAction)) { return(PaymentProcessingResult.CreateUnsuccessfulResult(Utilities.Translate("PayPalSettingsError"))); } var caller = PayPalApiHelper.GetPayPalApiCallerServices(_paymentMethodConfiguration); var setExpressChkOutReqType = new SetExpressCheckoutRequestType(SetupExpressCheckoutReqDetailsType(cart, payment, orderNumberId)); var setChkOutResponse = caller.SetExpressCheckout(new SetExpressCheckoutReq { SetExpressCheckoutRequest = setExpressChkOutReqType }); var errorCheck = _payPalApiHelper.CheckErrors(setChkOutResponse); if (!string.IsNullOrEmpty(errorCheck)) { _logger.Error(errorCheck); return(PaymentProcessingResult.CreateUnsuccessfulResult(string.Join("; ", setChkOutResponse.Errors.Select(e => e.LongMessage)))); } payment.Properties[PayPalOrderNumberPropertyName] = orderNumberId; payment.Properties[PayPalExpTokenPropertyName] = setChkOutResponse.Token; _orderRepository.Save(cart); // validation checking with PayPal OK (Server's PayPal API, Billing Address, Shipping Address, ... do redirect to PayPal.com var redirectUrl = CreateRedirectUrl(_paymentMethodConfiguration.ExpChkoutUrl, setChkOutResponse.Token); var message = $"---PayPal-SetExpressCheckout is successful. Redirect end user to {redirectUrl}"; _logger.Information(message); return(PaymentProcessingResult.CreateSuccessfulResult(message, redirectUrl)); }
/// <summary> /// Builds the <see cref="PaymentDetailsItemType"/>. /// </summary> /// <param name="invoice"> /// The invoice. /// </param> /// <param name="actionCode"> /// The <see cref="PaymentActionCodeType"/>. /// </param> /// <returns> /// The <see cref="PaymentDetailsType"/>. /// </returns> public PaymentDetailsType Build(IInvoice invoice, PaymentActionCodeType actionCode) { // Get the decimal configuration for the current currency var currencyCodeType = PayPalApiHelper.GetPayPalCurrencyCode(invoice.CurrencyCode); var basicAmountFactory = new PayPalBasicAmountTypeFactory(currencyCodeType); // Get the tax total var itemTotal = basicAmountFactory.Build(invoice.TotalItemPrice()); var shippingTotal = basicAmountFactory.Build(invoice.TotalShipping()); var taxTotal = basicAmountFactory.Build(invoice.TotalTax()); var invoiceTotal = basicAmountFactory.Build(invoice.Total); var items = BuildPaymentDetailsItemTypes(invoice.ProductLineItems(), basicAmountFactory); var paymentDetails = new PaymentDetailsType { PaymentDetailsItem = items.ToList(), ItemTotal = itemTotal, TaxTotal = taxTotal, ShippingTotal = shippingTotal, OrderTotal = invoiceTotal, PaymentAction = actionCode, InvoiceID = invoice.PrefixedInvoiceNumber() }; // ShipToAddress if (invoice.ShippingLineItems().Any()) { var addressTypeFactory = new PayPalAddressTypeFactory(); paymentDetails.ShipToAddress = addressTypeFactory.Build(invoice.GetShippingAddresses().FirstOrDefault()); } return(paymentDetails); }
/// <summary> /// Initializes the factory. /// </summary> private void Initialize() { if (!this.WebsiteUrl.IsNullOrWhiteSpace()) { return; } this.WebsiteUrl = PayPalApiHelper.GetBaseWebsiteUrl(); }
/// <summary> /// Refunds or partially refunds a payment. /// </summary> /// <param name="invoice"> /// The invoice. /// </param> /// <param name="payment"> /// The payment. /// </param> /// <param name="amount"> /// The amount of the refund. /// </param> /// <returns> /// The <see cref="PayPalExpressTransactionRecord"/>. /// </returns> public ExpressCheckoutResponse Refund(IInvoice invoice, IPayment payment, decimal amount) { var record = payment.GetPayPalTransactionRecord(); // Ensure the currency code if (record.Data.CurrencyCode.IsNullOrWhiteSpace()) { var ex = new PayPalApiException("CurrencyCode was not found in payment extended data PayPal transaction data record. Cannot perform refund."); return(_responseFactory.Build(ex)); } // Ensure the transaction id if (record.Data.CaptureTransactionId.IsNullOrWhiteSpace()) { var ex = new PayPalApiException("CaptureTransactionId was not found in payment extended data PayPal transaction data record. Cannot perform refund."); return(_responseFactory.Build(ex)); } // Get the decimal configuration for the current currency var currencyCodeType = PayPalApiHelper.GetPayPalCurrencyCode(record.Data.CurrencyCode); var basicAmountFactory = new PayPalBasicAmountTypeFactory(currencyCodeType); ExpressCheckoutResponse result = null; if (amount > payment.Amount) { amount = payment.Amount; } try { var request = new RefundTransactionRequestType { InvoiceID = invoice.PrefixedInvoiceNumber(), PayerID = record.Data.PayerId, RefundSource = RefundSourceCodeType.DEFAULT, Version = record.DoCapture.Version, TransactionID = record.Data.CaptureTransactionId, Amount = basicAmountFactory.Build(amount) }; var wrapper = new RefundTransactionReq { RefundTransactionRequest = request }; var refundTransactionResponse = GetPayPalService().RefundTransaction(wrapper); result = _responseFactory.Build(refundTransactionResponse, record.Data.Token); } catch (Exception ex) { result = _responseFactory.Build(ex); } return(result); }
/// <summary> /// Captures an authorized payment. /// </summary> /// <para> /// See API doc here https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/DoCapture_API_Operation_SOAP/ /// </para> /// <param name="orderGroup">The order group to process.</param> /// <param name="payment">The payment to process</param> /// <returns>return false and set the message will make the WorkFlow activity raise PaymentExcetion(message)</returns> private PaymentProcessingResult ProcessPaymentCapture(IOrderGroup orderGroup, IPayment payment) { // Implement refund feature logic for current payment gateway var captureAmount = payment.Amount; if (!(orderGroup is IPurchaseOrder purchaseOrder) || captureAmount <= 0) { return(PaymentProcessingResult.CreateUnsuccessfulResult("Nothing to capture")); } var captureRequest = new DoCaptureRequestType { AuthorizationID = payment.TransactionID, // original transactionID (which PayPal gave us when DoExpressCheckoutPayment, DoDirectPayment, or CheckOut) Amount = _payPalApiHelper.ToPayPalAmount(captureAmount, orderGroup.Currency), // if refund with Partial, we have to set the Amount CompleteType = payment.Amount >= purchaseOrder.GetTotal().Amount ? CompleteCodeType.COMPLETE : CompleteCodeType.NOTCOMPLETE, InvoiceID = purchaseOrder.OrderNumber }; captureRequest.Note = $"[{payment.PaymentMethodName}-{payment.TransactionID}] captured {captureAmount}{captureRequest.Amount.currencyID} for [PurchaseOrder-{purchaseOrder.OrderNumber}]"; var caller = PayPalApiHelper.GetPayPalApiCallerServices(_paymentMethodConfiguration); var doCaptureReq = new DoCaptureReq { DoCaptureRequest = captureRequest }; var captureResponse = caller.DoCapture(doCaptureReq); var errorCheck = _payPalApiHelper.CheckErrors(captureResponse); if (!string.IsNullOrEmpty(errorCheck)) { return(PaymentProcessingResult.CreateUnsuccessfulResult(errorCheck)); } var captureResponseDetails = captureResponse.DoCaptureResponseDetails; var paymentInfo = captureResponseDetails.PaymentInfo; // Extract the response details. payment.ProviderTransactionID = paymentInfo.TransactionID; payment.Status = paymentInfo.PaymentStatus.ToString(); var message = $"[{payment.PaymentMethodName}] [Capture payment-{paymentInfo.TransactionID}] [Status: {paymentInfo.PaymentStatus.ToString()}] " + $".Response: {captureResponse.Ack.ToString()} at Timestamp={captureResponse.Timestamp}: {paymentInfo.GrossAmount.value}{paymentInfo.GrossAmount.currencyID}"; // add a new order note about this capture AddNoteToPurchaseOrder("CAPTURE", message, purchaseOrder.CustomerId, purchaseOrder); _orderRepository.Save(purchaseOrder); return(PaymentProcessingResult.CreateSuccessfulResult(message)); }
public PayPalPaymentGateway( IInventoryProcessor inventoryProcessor, IOrderNumberGenerator orderNumberGenerator, IOrderRepository orderRepository, ITaxCalculator taxCalculator, PayPalApiHelper paypalApiHelper) { _inventoryProcessor = inventoryProcessor; _orderNumberGenerator = orderNumberGenerator; _orderRepository = orderRepository; _taxCalculator = taxCalculator; _payPalApiHelper = paypalApiHelper; // ReSharper disable once VirtualMemberCallInConstructor _paymentMethodConfiguration = new PayPalConfiguration(Settings); }
/// <summary> /// Process payment when a refund request happens. /// </summary> /// <remarks> /// <para> /// See API doc here https://www.x.com/developers/paypal/documentation-tools/api/refundtransaction-api-operation-soap /// </para> /// <para> /// You may offer a refund only for a limited time, usually 60 days. If you need to make a refund after that time, you will need to initiate a new PayPal payment to your buyer. /// If you offer the buyer a partial refund, she has 10 days to decline it if she wishes. (Full refunds are automatically processed.) /// </para> /// </remarks> /// <param name="payment">The payment to process.</param> /// <param name="orderGroup">The order group to process.</param> /// <returns>True if refund was completed, otherwise false and set the message will make the WorkFlow activity raise PaymentExcetion(message).</returns> private PaymentProcessingResult ProcessPaymentRefund(IOrderGroup orderGroup, IPayment payment) { // Implement refund feature logic for current payment gateway var refundAmount = payment.Amount; if (!(orderGroup is IPurchaseOrder purchaseOrder) || refundAmount <= 0) { return(PaymentProcessingResult.CreateUnsuccessfulResult(Utilities.Translate("PayPalRefundError"))); } // Call payment gateway API to do refund business // Create the Refund Request var refundRequest = new RefundTransactionRequestType { TransactionID = payment.ProviderTransactionID, // original transactionID (which payPal gave us when do ExpressCheckout) Memo = $"[{payment.PaymentMethodName}-{payment.TransactionID}] refunds {refundAmount}{purchaseOrder.Currency} for [PurchaseOrder-{purchaseOrder.OrderNumber}]", // NOTE: If RefundType is Full, do not set the amount. // refundRequest.RefundType = RefundType.Full; //refund a full or partial amount RefundType = RefundType.PARTIAL, //refund a partial amount Amount = _payPalApiHelper.ToPayPalAmount(refundAmount, orderGroup.Currency) // if refund with Partial, we have to set the Amount }; var caller = PayPalApiHelper.GetPayPalApiCallerServices(_paymentMethodConfiguration); var refundResponse = caller.RefundTransaction(new RefundTransactionReq { RefundTransactionRequest = refundRequest }); var errorCheck = _payPalApiHelper.CheckErrors(refundResponse); if (!string.IsNullOrEmpty(errorCheck)) { return(PaymentProcessingResult.CreateUnsuccessfulResult(errorCheck)); } // Extract the response details. payment.TransactionID = refundResponse.RefundTransactionID; var message = $"[{payment.PaymentMethodName}] [RefundTransaction-{refundResponse.RefundTransactionID}] " + $"Response: {refundResponse.Ack.ToString()} at Timestamp={refundResponse.Timestamp}: {refundResponse.GrossRefundAmount.value}{refundResponse.GrossRefundAmount.currencyID}"; // add a new order note about this refund AddNoteToPurchaseOrder("REFUND", message, purchaseOrder.CustomerId, purchaseOrder); _orderRepository.Save(purchaseOrder); return(PaymentProcessingResult.CreateSuccessfulResult(message)); }
/// <summary> /// The capture success. /// </summary> /// <param name="invoice"> /// The invoice. /// </param> /// <param name="payment"> /// The payment. /// </param> /// <param name="amount"> /// The amount. /// </param> /// <param name="isPartialPayment"> /// The is partial payment. /// </param> /// <returns> /// The <see cref="ExpressCheckoutResponse"/>. /// </returns> public PayPalExpressTransactionRecord Capture(IInvoice invoice, IPayment payment, decimal amount, bool isPartialPayment) { // Get the transaction record var record = payment.GetPayPalTransactionRecord(); ExpressCheckoutResponse result = null; try { var amountFactory = new PayPalBasicAmountTypeFactory(PayPalApiHelper.GetPayPalCurrencyCode(invoice.CurrencyCode)); var authorizationId = record.Data.AuthorizationTransactionId; // do express checkout var request = new DoCaptureRequestType() { AuthorizationID = authorizationId, Amount = amountFactory.Build(amount), CompleteType = isPartialPayment ? CompleteCodeType.NOTCOMPLETE : CompleteCodeType.COMPLETE }; var doCaptureReq = new DoCaptureReq() { DoCaptureRequest = request }; var service = GetPayPalService(); var doCaptureResponse = service.DoCapture(doCaptureReq); result = _responseFactory.Build(doCaptureResponse, record.Data.Token); if (result.Success()) { var transactionId = doCaptureResponse.DoCaptureResponseDetails.PaymentInfo.TransactionID; record.Data.CaptureTransactionId = transactionId; } } catch (Exception ex) { result = _responseFactory.Build(ex); } record.DoCapture = result; record.Success = result.Success(); return(record); }
/// <summary> /// Initializes a new instance of the <see cref="PayPalExpressCheckoutService"/> class. /// </summary> /// <param name="settings"> /// The settings. /// </param> public PayPalExpressCheckoutService(PayPalProviderSettings settings) : base(settings) { _websiteUrl = PayPalApiHelper.GetBaseWebsiteUrl(); _responseFactory = new ExpressCheckoutResponseFactory(); }
/// <summary> /// Processes the successful transaction, was called when PayPal.com redirect back. /// </summary> /// <param name="orderGroup">The order group that was processed.</param> /// <param name="payment">The order payment.</param> /// <param name="acceptUrl">The redirect url when finished.</param> /// <param name="cancelUrl">The redirect url when error happens.</param> /// <returns>The url redirection after process.</returns> public string ProcessSuccessfulTransaction(IOrderGroup orderGroup, IPayment payment, string acceptUrl, string cancelUrl) { if (HttpContext.Current == null) { return(cancelUrl); } if (HttpContext.Current.Session != null) { HttpContext.Current.Session.Remove("LastCouponCode"); } if (!(orderGroup is ICart cart)) { // return to the shopping cart page immediately and show error messages return(ProcessUnsuccessfulTransaction(cancelUrl, Utilities.Translate("CommitTranErrorCartNull"))); } string redirectionUrl; using (var scope = new TransactionScope()) { SetSecurityProtocolToTls12(); var getDetailRequest = new GetExpressCheckoutDetailsRequestType { Token = payment.Properties[PayPalExpTokenPropertyName] as string // Add request-specific fields to the request. }; // Execute the API operation and obtain the response. var caller = PayPalApiHelper.GetPayPalApiCallerServices(_paymentMethodConfiguration); var getDetailsResponse = caller.GetExpressCheckoutDetails(new GetExpressCheckoutDetailsReq { GetExpressCheckoutDetailsRequest = getDetailRequest }); var errorCheck = _payPalApiHelper.CheckErrors(getDetailsResponse); if (!string.IsNullOrEmpty(errorCheck)) { RestoreSecurityProtocol(); // unsuccessful get detail call return(ProcessUnsuccessfulTransaction(cancelUrl, errorCheck)); } var expressCheckoutDetailsResponse = getDetailsResponse.GetExpressCheckoutDetailsResponseDetails; // get commerceOrderId from what we put to PayPal instead of getting from cookie payment.Properties[PayPalOrderNumberPropertyName] = expressCheckoutDetailsResponse.InvoiceID; //process details sent from paypal, changing addresses if required string emptyAddressMsg; //process billing address var payPalBillingAddress = expressCheckoutDetailsResponse.BillingAddress; if (payPalBillingAddress != null && AddressHandling.IsAddressChanged(payment.BillingAddress, payPalBillingAddress)) { emptyAddressMsg = _payPalApiHelper.ProcessOrderAddress(expressCheckoutDetailsResponse.PayerInfo, payPalBillingAddress, payment.BillingAddress, CustomerAddressTypeEnum.Billing, "CommitTranErrorPayPalBillingAddressEmpty"); if (!string.IsNullOrEmpty(emptyAddressMsg)) { RestoreSecurityProtocol(); return(ProcessUnsuccessfulTransaction(cancelUrl, emptyAddressMsg)); } } //process shipping address var payPalShippingAddress = expressCheckoutDetailsResponse.PaymentDetails[0].ShipToAddress; if (payPalShippingAddress != null && AddressHandling.IsAddressChanged(cart.GetFirstShipment().ShippingAddress, payPalShippingAddress)) { //when address was changed on PayPal site, it might cause changing tax value changed and changing order value also. var taxValueBefore = _taxCalculator.GetTaxTotal(cart, cart.Market, cart.Currency); var shippingAddress = orderGroup.CreateOrderAddress("address"); emptyAddressMsg = _payPalApiHelper.ProcessOrderAddress(expressCheckoutDetailsResponse.PayerInfo, payPalShippingAddress, shippingAddress, CustomerAddressTypeEnum.Shipping, "CommitTranErrorPayPalShippingAddressEmpty"); if (!string.IsNullOrEmpty(emptyAddressMsg)) { RestoreSecurityProtocol(); return(ProcessUnsuccessfulTransaction(cancelUrl, emptyAddressMsg)); } cart.GetFirstShipment().ShippingAddress = shippingAddress; var taxValueAfter = _taxCalculator.GetTaxTotal(cart, cart.Market, cart.Currency); if (taxValueBefore != taxValueAfter) { RestoreSecurityProtocol(); _orderRepository.Save(cart); // Saving cart to submit order address changed. scope.Complete(); return(ProcessUnsuccessfulTransaction(cancelUrl, Utilities.Translate("ProcessPaymentTaxValueChangedWarning"))); } } // Add request-specific fields to the request. // Create the request details object. var doExpressChkOutPaymentReqDetails = CreateExpressCheckoutPaymentRequest(getDetailsResponse, orderGroup, payment); // Execute the API operation and obtain the response. var doCheckOutResponse = caller.DoExpressCheckoutPayment(new DoExpressCheckoutPaymentReq { DoExpressCheckoutPaymentRequest = new DoExpressCheckoutPaymentRequestType(doExpressChkOutPaymentReqDetails) }); errorCheck = _payPalApiHelper.CheckErrors(doCheckOutResponse); if (!string.IsNullOrEmpty(errorCheck)) { RestoreSecurityProtocol(); // unsuccessful doCheckout response return(ProcessUnsuccessfulTransaction(cancelUrl, errorCheck)); } // everything is fine, this is a flag to tell ProcessPayment know about this case: redirect back from PayPal with accepted payment var errorMessages = new List <string>(); var cartCompleted = DoCompletingCart(cart, errorMessages); if (!cartCompleted) { RestoreSecurityProtocol(); return(UriSupport.AddQueryString(cancelUrl, "message", string.Join(";", errorMessages.Distinct().ToArray()))); } // Place order var purchaseOrder = MakePurchaseOrder(doCheckOutResponse, cart, payment); // Commit changes scope.Complete(); redirectionUrl = CreateRedirectionUrl(purchaseOrder, acceptUrl, payment.BillingAddress.Email); RestoreSecurityProtocol(); } _logger.Information($"PayPal transaction succeeds, redirect end user to {redirectionUrl}"); return(redirectionUrl); }