/// <summary>
        /// Create request parameters to charge transaction
        /// </summary>
        /// <param name="paymentRequest">Payment request parameters</param>
        /// <param name="isRecurringPayment">Whether it is a recurring payment</param>
        /// <returns>Charge request parameters</returns>
        private ExtendedChargeRequest CreateChargeRequest(ProcessPaymentRequest paymentRequest, bool isRecurringPayment)
        {
            //get customer
            var customer = _customerService.GetCustomerById(paymentRequest.CustomerId);

            if (customer == null)
            {
                throw new NopException("Customer cannot be loaded");
            }

            //get the primary store currency
            var currency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId);

            if (currency == null)
            {
                throw new NopException("Primary store currency cannot be loaded");
            }

            //whether the currency is supported by the Square
            if (!CheckSupportCurrency(currency))
            {
                throw new NopException($"The {currency.CurrencyCode} currency is not supported by the Square");
            }

            //check customer's billing address, shipping address and email,
            SquareModel.Address createAddress(Address address) => address == null ? null : new SquareModel.Address
            (
                AddressLine1: address.Address1,
                AddressLine2: address.Address2,
                AdministrativeDistrictLevel1: address.StateProvince?.Abbreviation,
                AdministrativeDistrictLevel2: address.County,
                Country: string.Equals(address.Country?.TwoLetterIsoCode, new RegionInfo(address.Country?.TwoLetterIsoCode).TwoLetterISORegionName, StringComparison.InvariantCultureIgnoreCase)
                    ? address.Country?.TwoLetterIsoCode : null,
                FirstName: address.FirstName,
                LastName: address.LastName,
                Locality: address.City,
                PostalCode: address.ZipPostalCode
            );

            var billingAddress  = createAddress(customer.BillingAddress);
            var shippingAddress = billingAddress == null?createAddress(customer.ShippingAddress) : null;

            var email = customer.BillingAddress != null ? customer.BillingAddress.Email : customer.ShippingAddress?.Email;

            //the transaction is ineligible for chargeback protection if they are not provided
            if ((billingAddress == null && shippingAddress == null) || string.IsNullOrEmpty(email))
            {
                _logger.Warning("Square payment warning: Address or email is not provided, so the transaction is ineligible for chargeback protection", customer: customer);
            }

            //the amount of money, in the smallest denomination of the currency indicated by currency. For example, when currency is USD, amount is in cents;
            //most currencies consist of 100 units of smaller denomination, so we multiply the total by 100
            var orderTotal  = (int)(paymentRequest.OrderTotal * 100);
            var amountMoney = new SquareModel.Money(Amount: orderTotal, Currency: currency.CurrencyCode);

            //create common charge request parameters
            var chargeRequest = new ExtendedChargeRequest
                                (
                amountMoney: amountMoney,
                billingAddress: billingAddress,
                buyerEmailAddress: email,
                delayCapture: _squarePaymentSettings.TransactionMode == TransactionMode.Authorize,
                idempotencyKey: Guid.NewGuid().ToString(),
                integrationId: !string.IsNullOrEmpty(SquarePaymentDefaults.IntegrationId) ? SquarePaymentDefaults.IntegrationId : null,
                note: string.Format(SquarePaymentDefaults.PaymentNote, paymentRequest.OrderGuid),
                referenceId: paymentRequest.OrderGuid.ToString(),
                shippingAddress: shippingAddress
                                );

            //try to get previously stored card details
            var storedCardKey = _localizationService.GetResource("Plugins.Payments.Square.Fields.StoredCard.Key");

            if (paymentRequest.CustomValues.TryGetValue(storedCardKey, out var storedCardId) && !storedCardId.ToString().Equals(Guid.Empty.ToString()))
            {
                //check whether customer exists
                var customerId     = _genericAttributeService.GetAttribute <string>(customer, SquarePaymentDefaults.CustomerIdAttribute);
                var squareCustomer = _squarePaymentManager.GetCustomer(customerId);
                if (squareCustomer == null)
                {
                    throw new NopException("Failed to retrieve customer");
                }

                //set 'card on file' to charge
                chargeRequest.CustomerId     = squareCustomer.Id;
                chargeRequest.CustomerCardId = storedCardId.ToString();
                return(chargeRequest);
            }

            //or try to get the card nonce
            var cardNonceKey = _localizationService.GetResource("Plugins.Payments.Square.Fields.CardNonce.Key");

            if (!paymentRequest.CustomValues.TryGetValue(cardNonceKey, out var cardNonce) || string.IsNullOrEmpty(cardNonce?.ToString()))
            {
                throw new NopException("Failed to get the card nonce");
            }

            //remove the card nonce from payment custom values, since it is no longer needed
            paymentRequest.CustomValues.Remove(cardNonceKey);

            //whether to save card details for the future purchasing
            var saveCardKey = _localizationService.GetResource("Plugins.Payments.Square.Fields.SaveCard.Key");

            if (paymentRequest.CustomValues.TryGetValue(saveCardKey, out var saveCardValue) && saveCardValue is bool saveCard && saveCard && !customer.IsGuest())
            {
                //remove the value from payment custom values, since it is no longer needed
                paymentRequest.CustomValues.Remove(saveCardKey);

                try
                {
                    //check whether customer exists
                    var customerId     = _genericAttributeService.GetAttribute <string>(customer, SquarePaymentDefaults.CustomerIdAttribute);
                    var squareCustomer = _squarePaymentManager.GetCustomer(customerId);

                    if (squareCustomer == null)
                    {
                        //try to create the new one, if not exists
                        var customerRequest = new SquareModel.CreateCustomerRequest
                                              (
                            EmailAddress: customer.Email,
                            Nickname: customer.Username,
                            GivenName: _genericAttributeService.GetAttribute <string>(customer, NopCustomerDefaults.FirstNameAttribute),
                            FamilyName: _genericAttributeService.GetAttribute <string>(customer, NopCustomerDefaults.LastNameAttribute),
                            PhoneNumber: _genericAttributeService.GetAttribute <string>(customer, NopCustomerDefaults.PhoneAttribute),
                            CompanyName: _genericAttributeService.GetAttribute <string>(customer, NopCustomerDefaults.CompanyAttribute),
                            ReferenceId: customer.CustomerGuid.ToString()
                                              );
                        squareCustomer = _squarePaymentManager.CreateCustomer(customerRequest);
                        if (squareCustomer == null)
                        {
                            throw new NopException("Failed to create customer. Error details in the log");
                        }

                        //save customer identifier as generic attribute
                        _genericAttributeService.SaveAttribute(customer, SquarePaymentDefaults.CustomerIdAttribute, squareCustomer.Id);
                    }

                    //create request parameters to create the new card
                    var cardRequest = new SquareModel.CreateCustomerCardRequest
                                      (
                        BillingAddress: chargeRequest.BillingAddress ?? chargeRequest.ShippingAddress,
                        CardNonce: cardNonce.ToString()
                                      );

                    //set postal code
                    var postalCodeKey = _localizationService.GetResource("Plugins.Payments.Square.Fields.PostalCode.Key");
                    if (paymentRequest.CustomValues.TryGetValue(postalCodeKey, out var postalCode) && !string.IsNullOrEmpty(postalCode.ToString()))
                    {
                        //remove the value from payment custom values, since it is no longer needed
                        paymentRequest.CustomValues.Remove(postalCodeKey);

                        cardRequest.BillingAddress            = cardRequest.BillingAddress ?? new SquareModel.Address();
                        cardRequest.BillingAddress.PostalCode = postalCode.ToString();
                    }

                    //try to create card
                    var card = _squarePaymentManager.CreateCustomerCard(squareCustomer.Id, cardRequest);
                    if (card == null)
                    {
                        throw new NopException("Failed to create card. Error details in the log");
                    }

                    //save card identifier to payment custom values for further purchasing
                    if (isRecurringPayment)
                    {
                        paymentRequest.CustomValues.Add(storedCardKey, card.Id);
                    }

                    //set 'card on file' to charge
                    chargeRequest.CustomerId     = squareCustomer.Id;
                    chargeRequest.CustomerCardId = card.Id;
                    return(chargeRequest);
                }
                catch (Exception exception)
                {
                    _logger.Warning(exception.Message, exception, customer);
                    if (isRecurringPayment)
                    {
                        throw new NopException("For recurring payments you need to save the card details");
                    }
                }
            }
Пример #2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Order" /> class.
 /// </summary>
 /// <param name="Id">The order&#39;s unique ID..</param>
 /// <param name="LocationId">The ID of the merchant location this order is associated with. (required).</param>
 /// <param name="ReferenceId">A client specified identifier to associate an entity in another system with this order..</param>
 /// <param name="Source">The origination details of the order..</param>
 /// <param name="CustomerId">The [Customer](#type-customer) ID of the customer associated with the order..</param>
 /// <param name="LineItems">The line items included in the order..</param>
 /// <param name="Taxes">The list of all taxes associated with the order.  Taxes can be scoped to either &#x60;ORDER&#x60; or &#x60;LINE_ITEM&#x60;. For taxes with &#x60;LINE_ITEM&#x60; scope, an &#x60;OrderLineItemAppliedTax&#x60; must be added to each line item that the tax applies to. For taxes with &#x60;ORDER&#x60; scope, the server will generate an &#x60;OrderLineItemAppliedTax&#x60; for every line item.  On reads, each tax in the list will include the total amount of that tax applied to the order.  __IMPORTANT__: If &#x60;LINE_ITEM&#x60; scope is set on any taxes in this field, usage of the deprecated &#x60;line_items.taxes&#x60; field will result in an error. Please use &#x60;line_items.applied_taxes&#x60; instead..</param>
 /// <param name="Discounts">The list of all discounts associated with the order.  Discounts can be scoped to either &#x60;ORDER&#x60; or &#x60;LINE_ITEM&#x60;. For discounts scoped to &#x60;LINE_ITEM&#x60;, an &#x60;OrderLineItemAppliedDiscount&#x60; must be added to each line item that the discount applies to. For discounts with &#x60;ORDER&#x60; scope, the server will generate an &#x60;OrderLineItemAppliedDiscount&#x60; for every line item.  __IMPORTANT__: If &#x60;LINE_ITEM&#x60; scope is set on any discounts in this field, usage of the deprecated &#x60;line_items.discounts&#x60; field will result in an error. Please use &#x60;line_items.applied_discounts&#x60; instead..</param>
 /// <param name="ServiceCharges">A list of service charges applied to the order..</param>
 /// <param name="Fulfillments">Details on order fulfillment.  Orders can only be created with at most one fulfillment. However, orders returned by the API may contain multiple fulfillments..</param>
 /// <param name="Returns">Collection of items from sale Orders being returned in this one. Normally part of an Itemized Return or Exchange.  There will be exactly one &#x60;Return&#x60; object per sale Order being referenced..</param>
 /// <param name="ReturnAmounts">Rollup of returned money amounts..</param>
 /// <param name="NetAmounts">Net money amounts (sale money - return money)..</param>
 /// <param name="RoundingAdjustment">A positive or negative rounding adjustment to the total of the order, commonly used to apply Cash Rounding when the minimum unit of account is smaller than the lowest physical denomination of currency..</param>
 /// <param name="Tenders">The Tenders which were used to pay for the Order..</param>
 /// <param name="Refunds">The Refunds that are part of this Order..</param>
 /// <param name="CreatedAt">Timestamp for when the order was created. In RFC 3339 format, e.g., \&quot;2016-09-04T23:59:33.123Z\&quot;..</param>
 /// <param name="UpdatedAt">Timestamp for when the order was last updated. In RFC 3339 format, e.g., \&quot;2016-09-04T23:59:33.123Z\&quot;..</param>
 /// <param name="ClosedAt">Timestamp for when the order was closed. In RFC 3339 format, e.g., \&quot;2016-09-04T23:59:33.123Z\&quot;..</param>
 /// <param name="State">The current state of the order. &#x60;OPEN&#x60;,&#x60;COMPLETED&#x60;,&#x60;CANCELED&#x60; See [OrderState](#type-orderstate) for possible values.</param>
 /// <param name="Version">Version number which is incremented each time an update is committed to the order. Orders that were not created through the API will not include a version and thus cannot be updated.  [Read more about working with versions](/orders-api/manage-orders#update-orders)..</param>
 /// <param name="TotalMoney">The total amount of money to collect for the order..</param>
 /// <param name="TotalTaxMoney">The total tax amount of money to collect for the order..</param>
 /// <param name="TotalDiscountMoney">The total discount amount of money to collect for the order..</param>
 /// <param name="TotalServiceChargeMoney">The total amount of money collected in service charges for the order.  Note: &#x60;total_service_charge_money&#x60; is the sum of &#x60;applied_money&#x60; fields for each individual service charge. Therefore, &#x60;total_service_charge_money&#x60; will only include inclusive tax amounts, not additive tax amounts..</param>
 public Order(string Id = default(string), string LocationId = default(string), string ReferenceId = default(string), OrderSource Source = default(OrderSource), string CustomerId = default(string), List <OrderLineItem> LineItems = default(List <OrderLineItem>), List <OrderLineItemTax> Taxes = default(List <OrderLineItemTax>), List <OrderLineItemDiscount> Discounts = default(List <OrderLineItemDiscount>), List <OrderServiceCharge> ServiceCharges = default(List <OrderServiceCharge>), List <OrderFulfillment> Fulfillments = default(List <OrderFulfillment>), List <OrderReturn> Returns = default(List <OrderReturn>), OrderMoneyAmounts ReturnAmounts = default(OrderMoneyAmounts), OrderMoneyAmounts NetAmounts = default(OrderMoneyAmounts), OrderRoundingAdjustment RoundingAdjustment = default(OrderRoundingAdjustment), List <Tender> Tenders = default(List <Tender>), List <Refund> Refunds = default(List <Refund>), string CreatedAt = default(string), string UpdatedAt = default(string), string ClosedAt = default(string), string State = default(string), int?Version = default(int?), Money TotalMoney = default(Money), Money TotalTaxMoney = default(Money), Money TotalDiscountMoney = default(Money), Money TotalServiceChargeMoney = default(Money))
 {
     // to ensure "LocationId" is required (not null)
     if (LocationId == null)
     {
         throw new InvalidDataException("LocationId is a required property for Order and cannot be null");
     }
     else
     {
         this.LocationId = LocationId;
     }
     this.Id                      = Id;
     this.ReferenceId             = ReferenceId;
     this.Source                  = Source;
     this.CustomerId              = CustomerId;
     this.LineItems               = LineItems;
     this.Taxes                   = Taxes;
     this.Discounts               = Discounts;
     this.ServiceCharges          = ServiceCharges;
     this.Fulfillments            = Fulfillments;
     this.Returns                 = Returns;
     this.ReturnAmounts           = ReturnAmounts;
     this.NetAmounts              = NetAmounts;
     this.RoundingAdjustment      = RoundingAdjustment;
     this.Tenders                 = Tenders;
     this.Refunds                 = Refunds;
     this.CreatedAt               = CreatedAt;
     this.UpdatedAt               = UpdatedAt;
     this.ClosedAt                = ClosedAt;
     this.State                   = State;
     this.Version                 = Version;
     this.TotalMoney              = TotalMoney;
     this.TotalTaxMoney           = TotalTaxMoney;
     this.TotalDiscountMoney      = TotalDiscountMoney;
     this.TotalServiceChargeMoney = TotalServiceChargeMoney;
 }