/// <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); }
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); }
/// <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()) { } } } }