Beispiel #1
0
 /// <summary>
 /// Returns the customer information on Braintree for the given userID,
 /// or null if not exists.
 /// </summary>
 /// <param name="userID"></param>
 /// <param name="gateway">Optional, to reuse an opened gateway, else a new one is transparently created</param>
 /// <returns></returns>
 public static Braintree.Customer GetBraintreeCustomer(int userID, BraintreeGateway gateway = null)
 {
     gateway = LcPayment.NewBraintreeGateway(gateway);
     try {
         return(gateway.Customer.Find(GetCustomerId(userID)));
     } catch (Braintree.Exceptions.NotFoundException ex) {
     }
     return(null);
 }
        /// <summary>
        /// Saves/updates the payment method for the member, at the remote gateway,
        /// updates in place, and returns, the ID/token
        /// </summary>
        /// <returns>The saved payment method ID/token</returns>
        /// <param name="paymentData"></param>
        private static string CollectPaymentMethod(LcPayment.InputPaymentMethod paymentData, int memberUserID)
        {
            // On emulation, discard other steps, just generate
            // a fake ID
            if (LcPayment.TESTING_EMULATEBRAINTREE)
            {
                paymentData.paymentMethodID = LcPayment.CreateFakePaymentMethodId();
                return(paymentData.paymentMethodID);
            }

            // Standard way
            var gateway = LcPayment.NewBraintreeGateway();

            // The input paymentID must be one generated by Braintree, reset any (malicious?) attempt
            // to provide a special temp ID generated by this method
            if (paymentData.IsTemporaryID())
            {
                paymentData.paymentMethodID = null;
            }

            // Find or create Customer on Braintree (for membership subscriptions, the member
            // is a customer of Loconomics).
            var client = LcPayment.GetOrCreateBraintreeCustomer(LcPayment.Membership.GetFeePaymentUserId(memberUserID));

            // Quick way for saved payment method that does not needs to be updated
            if (paymentData.IsSavedID())
            {
                // Just double check payment exists to avoid mistake/malicious attempts:
                if (!paymentData.ExistsOnVault())
                {
                    // Since we have not input data to save, we can only throw an error
                    // invalidSavedPaymentMethod
                    throw new ConstraintException("[[[Chosen payment method has expired]]]");
                }
            }
            else
            {
                // Creates or updates a payment method with the given data

                // Must we set an ID as temporary to prevent it appears as a saved payment method?
                //paymentData.paymentMethodID = LcPayment.TempSavedCardPrefix + ASP.LcHelpers.Channel + "_paymentPlan";

                // Save on Braintree secure Vault
                // It updates the paymentMethodID if a new one was generated
                var saveCardError = paymentData.SaveInVault(client.Id);
                if (!String.IsNullOrEmpty(saveCardError))
                {
                    // paymentDataError
                    throw new ConstraintException(saveCardError);
                }
            }

            return(paymentData.paymentMethodID);
        }
Beispiel #3
0
    public static MerchantAccount GetProviderPaymentAccount(int userId, BraintreeGateway gateway = null)
    {
        gateway = NewBraintreeGateway(gateway);

        var             accountID = LcPayment.GetProviderPaymentAccountId(userId);
        MerchantAccount btAccount = null;

        // Find any existant one:
        try
        {
            btAccount = gateway.MerchantAccount.Find(accountID);
        }
        catch (Braintree.Exceptions.NotFoundException ex) { }

        return(btAccount);
    }
Beispiel #4
0
    /// <summary>
    /// Create or update the payment account for the provider at the payment gateway (Braintree) given
    /// that user information.
    /// On Braintree Marketplace, this is called 'Create a Sub Merchant'
    /// </summary>
    /// <param name="user"></param>
    /// <param name="address"></param>
    /// <param name="bank"></param>
    /// <param name="gateway"></param>
    /// <returns>It returns the result of the Braintree transaction (check for IsSuccess to know the result),
    /// or null when there Braintree doesn't authorize the operation (AuthorizationException catched),
    /// it means the details are not complete or malformed.</returns>
    public static dynamic CreateProviderPaymentAccount(dynamic user, LcData.Address address, dynamic bank, DateTime BirthDate, string Ssn, BraintreeGateway gateway = null)
    {
        gateway = NewBraintreeGateway(gateway);

        // We need to detect what FundingDestination notify depending on the provided
        // information
        // Analizing source bank information: asterisks means 'not to set -- preseve previous value', other value is send being
        // null or empty to clear/remove previous value
        // Next variables will have null for 'not to set' or any other to be udpated.
        string             routingNumber = null;
        string             accountNumber = null;
        FundingDestination fundingDest   = FundingDestination.EMAIL;

        if (bank != null)
        {
            // Null and asterisks values are not set
            if (bank.RoutingNumber != null && !bank.RoutingNumber.Contains("*"))
            {
                routingNumber = bank.RoutingNumber;
            }

            if (bank.AccountNumber != null && !bank.AccountNumber.Contains("*"))
            {
                accountNumber = bank.AccountNumber;
            }

            // We check against the bank object because has the original values.
            // Here, we allow an asterisks value as valid, because is a previous one
            // that will be preserved, or any new value to be set just different
            // from empty or null
            if (!String.IsNullOrEmpty(bank.AccountNumber) && !String.IsNullOrEmpty(bank.RoutingNumber))
            {
                fundingDest = FundingDestination.BANK;
            }
            else if (!String.IsNullOrWhiteSpace(user.MobilePhone))
            {
                fundingDest = FundingDestination.MOBILE_PHONE;
            }
        }

        var updateBankInfo = bank != null;

        var btAccount = GetProviderPaymentAccount((int)user.UserID);

        MerchantAccountRequest request = new MerchantAccountRequest
        {
            Individual = new IndividualRequest
            {
                FirstName = user.FirstName,
                LastName  = user.LastName,
                Email     = user.Email,
                Phone     = user.MobilePhone,
                Address   = new AddressRequest
                {
                    StreetAddress = address.AddressLine1,
                    // NOTE: We set the ExtendedAddress, but was communicated by Braintree on 2014-03-12
                    // that field is not being stored (support messages copies at #454).
                    // On the interface, we rely on our db for the copied version of that address part as fallback.
                    ExtendedAddress = address.AddressLine2,
                    PostalCode      = address.PostalCode,
                    Locality        = address.City,
                    Region          = address.StateProvinceCode,
                    //CountryCodeAlpha2 = address.CountryCodeAlpha2
                },
                DateOfBirth = BirthDate.ToString("yyyy-MM-dd")
            },
            TosAccepted             = true,
            MasterMerchantAccountId = BraintreeMerchantAccountId,
            Id = LcPayment.GetProviderPaymentAccountId((int)user.UserID)
        };

        if (btAccount == null || String.IsNullOrWhiteSpace(Ssn) || !Ssn.Contains("*"))
        {
            // Braintree require pass an empty string to remove the value of SSN in case of
            // user remove it from the form field:
            request.Individual.Ssn = String.IsNullOrWhiteSpace(Ssn) ? "" : Ssn;
        }

        // Set payment/funding information only on creation or explicitely
        // asked for update of its data
        if (btAccount == null || updateBankInfo)
        {
            request.Funding = new FundingRequest {
                Destination = fundingDest,
                Email       = user.Email,
                MobilePhone = user.MobilePhone
            };

            // On null, we don't set the values, empty to remove or value to set

            if (routingNumber != null)
            {
                request.Funding.RoutingNumber = routingNumber;
            }

            if (accountNumber != null)
            {
                request.Funding.AccountNumber = accountNumber;
            }
        }

        try{
            Result <MerchantAccount> ret = null;
            if (btAccount == null)
            {
                ret = gateway.MerchantAccount.Create(request);
            }
            else
            {
                ret = gateway.MerchantAccount.Update(request.Id, request);
            }

            // All Ok, register on database
            if (ret.IsSuccess())
            {
                LcData.SetProviderPaymentAccount(
                    user.UserID,
                    request.Id,
                    btAccount == null ? "pending" : null,
                    null,
                    null,
                    null
                    );
            }

            return(ret);
        } catch (Braintree.Exceptions.AuthorizationException ex) {
            throw ex;
            //return null;
        }
    }
        public static UserPaymentPlan CreateSubscription(
            int userID,
            SubscriptionPlan plan,
            LcPayment.InputPaymentMethod paymentMethod)
        {
            // Prepare payment method (in the remote gateway), get its ID
            var paymentMethodToken = CollectPaymentMethod(paymentMethod, userID);

            // Prepare initial object
            var userPlan = new UserPaymentPlan()
            {
                userID              = userID,
                paymentPlan         = plan,
                subscriptionEndDate = null
            };

            // Create subscription at gateway and set details
            // Wrapped in a try-catch to implement a transaction-like operation:
            // if something fail after succesfully create the Braintree subscription, like not being
            // able to save details on database, we need to 'rollback' the subscription, asking for removal
            // to Braintree
            string generatedSubscriptionId = null;
            var    paymentPlan             = new LcPayment.Membership();

            try
            {
                if (LcPayment.TESTING_EMULATEBRAINTREE)
                {
                    userPlan.subscriptionID             = LcPayment.CreateFakeSubscriptionId();
                    userPlan.paymentPlanLastChangedDate = DateTimeOffset.Now;
                    userPlan.nextPaymentDueDate         = DateTimeOffset.Now.Add(new TimeSpan(365, 0, 0, 0));
                    userPlan.nextPaymentAmount          = 99;
                    userPlan.firstBillingDate           = DateTimeOffset.Now;
                    userPlan.planStatus  = "ACTIVE";
                    userPlan.daysPastDue = 0;
                }
                else
                {
                    // Start creating the subscription at the payment gateway
                    var trialEndDate = GetUserTrialEndDate(userID);

                    // Create the subscription at the payment gateway
                    // It returns the subscription object with a correct ID on success, otherwise an exception is thrown
                    var subscription = paymentPlan.CreateSubscription(plan, paymentMethodToken, trialEndDate);
                    generatedSubscriptionId             = subscription.Id;
                    userPlan.subscriptionID             = subscription.Id;
                    userPlan.paymentPlanLastChangedDate = subscription.UpdatedAt.Value;
                    userPlan.nextPaymentDueDate         = subscription.NextBillingDate;
                    userPlan.nextPaymentAmount          = subscription.NextBillAmount;
                    userPlan.firstBillingDate           = subscription.FirstBillingDate.Value;
                    userPlan.planStatus  = subscription.Status.ToString();
                    userPlan.daysPastDue = subscription.DaysPastDue ?? 0;
                }

                // Fill payment method info
                var info = LcPayment.PaymentMethodInfo.Get(paymentMethodToken);
                userPlan.paymentExpiryDate  = info.ExpirationDate;
                userPlan.paymentMethodToken = paymentMethodToken;
                userPlan.paymentMethod      = info.Description;

                // Persist subscription on database
                Set(userPlan);
            }
            catch (Exception ex)
            {
                // Rollback
                if (generatedSubscriptionId != null)
                {
                    // Rollback subscription at Payment Gateway
                    paymentPlan.CancelSubscription(generatedSubscriptionId);
                }

                // The exception needs to be communicated anyway, so re-throw
                throw new Exception("[[[Failed subscription]]]", ex);
            }

            return(userPlan);
        }
        public static PaymentAccount Get(int userID)
        {
            PaymentAccount acc       = null;
            var            btAccount = LcPayment.GetProviderPaymentAccount(userID);

            if (btAccount != null &&
                btAccount.IndividualDetails != null)
            {
                acc = new PaymentAccount
                {
                    userID        = userID,
                    firstName     = btAccount.IndividualDetails.FirstName,
                    lastName      = btAccount.IndividualDetails.LastName,
                    phone         = btAccount.IndividualDetails.Phone,
                    email         = btAccount.IndividualDetails.Email,
                    streetAddress = btAccount.IndividualDetails.Address.StreetAddress,
                    //extendedAddress = btAccount.IndividualDetails.Address.ExtendedAddress,
                    city              = btAccount.IndividualDetails.Address.Locality,
                    postalCode        = btAccount.IndividualDetails.Address.PostalCode,
                    stateProvinceCode = btAccount.IndividualDetails.Address.Region,
                    countryCode       = btAccount.IndividualDetails.Address.CountryCodeAlpha2,
                    birthDate         = btAccount.IndividualDetails.DateOfBirth == null ? null :
                                        btAccount.IndividualDetails.DateOfBirth.IsDateTime() ?
                                        (DateTime?)btAccount.IndividualDetails.DateOfBirth.AsDateTime() :
                                        null,
                    ssn    = String.IsNullOrEmpty(btAccount.IndividualDetails.SsnLastFour) ? "" : btAccount.IndividualDetails.SsnLastFour.PadLeft(10, '*'),
                    status = (btAccount.Status ?? Braintree.MerchantAccountStatus.PENDING).ToString().ToLower()
                };
                // IMPORTANT: We need to strictly check for the null value of IndividualDetails and FundingDetails
                // since errors can arise, see #554
                if (btAccount.FundingDetails != null)
                {
                    acc.routingNumber = btAccount.FundingDetails.RoutingNumber;
                    acc.accountNumber = String.IsNullOrEmpty(btAccount.FundingDetails.AccountNumberLast4) ? "" : btAccount.FundingDetails.AccountNumberLast4.PadLeft(10, '*');
                    // Is Venmo account if there is no bank informatino
                    acc.isVenmo = String.IsNullOrEmpty(acc.accountNumber) && String.IsNullOrEmpty(acc.routingNumber);
                }
            }
            else
            {
                // Automatically fetch personal data from our DB (this will work as a preset)
                var data = LcRest.UserProfile.Get(userID);
                var add  = LcRest.Address.GetHomeAddress(userID);
                acc = new PaymentAccount {
                    userID            = userID,
                    firstName         = data.firstName,
                    lastName          = data.lastName,
                    phone             = data.phone,
                    email             = data.email,
                    streetAddress     = add.addressLine1,
                    postalCode        = add.postalCode,
                    city              = add.city,
                    stateProvinceCode = add.stateProvinceCode
                };
            }
            // Get data from our database as LAST step: both when there is data from Braintree and when not (this will let status to work
            // on localdev environments too, for testing)
            var dbAccount = LcData.GetProviderPaymentAccount(userID);

            if (dbAccount != null)
            {
                // Status from Braintree is not working, or has a big delay setting up the first time so user don't see the status,
                // using our saved copy:
                acc.status = (string)dbAccount.Status;
                //if (btAccount.Status == Braintree.MerchantAccountStatus.SUSPENDED)
                if (dbAccount.status == "suspended")
                {
                    var gw           = LcPayment.NewBraintreeGateway();
                    var notification = gw.WebhookNotification.Parse((string)dbAccount.bt_signature, (string)dbAccount.bt_payload);
                    var errors       = new List <string>();
                    errors.Add(notification.Message);
                    notification.Errors.All().Select(x => x.Code + ": " + x.Message);
                    acc.errors = errors;
                }
            }
            return(acc);
        }
        public static void Set(PaymentAccount data)
        {
            // Gathering state and postal IDs and verifying they match
            var add = new LcRest.Address {
                postalCode  = data.postalCode,
                countryCode = data.countryCode
            };

            if (!LcRest.Address.AutosetByCountryPostalCode(add))
            {
                throw new ValidationException("[[[Postal Code is not valid.]]]", "postalCode");
            }
            else
            {
                data.city = add.city;
                data.stateProvinceCode = add.stateProvinceCode;
            }

            var emulateBraintree = ASP.LcHelpers.Channel == "localdev";

            if (emulateBraintree)
            {
                LcData.SetProviderPaymentAccount(
                    data.userID,
                    "FAIK REQUEST ID: " + Guid.NewGuid(),
                    "pending",
                    null,
                    null,
                    null
                    );
            }
            else
            {
                var email  = LcRest.UserProfile.GetEmail(data.userID);
                var result = LcPayment.CreateProviderPaymentAccount(
                    new LcData.UserInfo
                {
                    UserID      = data.userID,
                    FirstName   = data.firstName,
                    LastName    = data.lastName,
                    Email       = email,
                    MobilePhone = data.phone
                }, new LcData.Address
                {
                    AddressLine1      = data.streetAddress,
                    PostalCode        = data.postalCode,
                    City              = data.city,
                    StateProvinceCode = data.stateProvinceCode,
                    CountryID         = add.countryID
                }, new LcPayment.BankInfo
                {
                    RoutingNumber = data.routingNumber,
                    AccountNumber = data.accountNumber
                },
                    data.birthDate.Value,
                    data.ssn
                    );

                if (result == null)
                {
                    throw new ValidationException("[[[It looks like you already have an account set up with Braintree. Please contact us, and we can help.]]]");
                }
                else if (!result.IsSuccess())
                {
                    throw new ValidationException(result.Message);
                    //foreach (var err in result.Errors.All()) { }
                }
            }
        }