/// <summary> /// Process recurring payment /// </summary> /// <param name="processPaymentRequest">Payment info required for an order processing</param> /// <returns>Process payment result</returns> public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) { var result = new ProcessPaymentResult(); var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); if (customer == null) { throw new Exception("Customer cannot be loaded"); } try { var apiContext = PaypalHelper.GetApiContext(_paypalDirectPaymentSettings); var currency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); //check that webhook exists if (string.IsNullOrEmpty(_paypalDirectPaymentSettings.WebhookId)) { result.AddError("Recurring payments are not available until you create a webhook"); return(result); } Webhook.Get(apiContext, _paypalDirectPaymentSettings.WebhookId); //create the plan var url = _webHelper.GetStoreLocation(_storeContext.CurrentStore.SslEnabled); var billingPlan = new Plan { name = processPaymentRequest.OrderGuid.ToString(), description = string.Format("nopCommerce billing plan for the {0} order", processPaymentRequest.OrderGuid), type = "fixed", merchant_preferences = new MerchantPreferences { return_url = url, cancel_url = url, auto_bill_amount = "YES", //setting setup fee as the first payment (workaround for the processing first payment immediately) setup_fee = new PayPal.Api.Currency { currency = currency != null ? currency.CurrencyCode : null, value = processPaymentRequest.OrderTotal.ToString("N", new CultureInfo("en-US")) } }, payment_definitions = new List <PaymentDefinition> { new PaymentDefinition { name = "nopCommerce payment for the billing plan", type = "REGULAR", frequency_interval = processPaymentRequest.RecurringCycleLength.ToString(), frequency = processPaymentRequest.RecurringCyclePeriod.ToString().TrimEnd('s'), cycles = (processPaymentRequest.RecurringTotalCycles - 1).ToString(), amount = new PayPal.Api.Currency { currency = currency != null ? currency.CurrencyCode : null, value = processPaymentRequest.OrderTotal.ToString("N", new CultureInfo("en-US")) } } } }.Create(apiContext); //activate the plan var patchRequest = new PatchRequest() { new Patch() { op = "replace", path = "/", value = new Plan { state = "ACTIVE" } } }; billingPlan.Update(apiContext, patchRequest); //create subscription var subscription = new Agreement { name = string.Format("nopCommerce subscription for the {0} order", processPaymentRequest.OrderGuid), //we set order guid in the description, then use it in the webhook handler description = processPaymentRequest.OrderGuid.ToString(), //setting start date as the next date of recurring payments as the setup fee was the first payment start_date = GetStartDate(processPaymentRequest.RecurringCyclePeriod, processPaymentRequest.RecurringCycleLength), #region payer payer = new Payer() { payment_method = "credit_card", #region credit card info funding_instruments = new List <FundingInstrument> { new FundingInstrument { credit_card = new CreditCard { type = processPaymentRequest.CreditCardType.ToLowerInvariant(), number = processPaymentRequest.CreditCardNumber, cvv2 = processPaymentRequest.CreditCardCvv2, expire_month = processPaymentRequest.CreditCardExpireMonth, expire_year = processPaymentRequest.CreditCardExpireYear } } }, #endregion #region payer info payer_info = new PayerInfo { #region billing address billing_address = customer.BillingAddress == null ? null : new Address { country_code = customer.BillingAddress.Country != null ? customer.BillingAddress.Country.TwoLetterIsoCode : null, state = customer.BillingAddress.StateProvince != null ? customer.BillingAddress.StateProvince.Abbreviation : null, city = customer.BillingAddress.City, line1 = customer.BillingAddress.Address1, line2 = customer.BillingAddress.Address2, phone = customer.BillingAddress.PhoneNumber, postal_code = customer.BillingAddress.ZipPostalCode }, #endregion email = customer.BillingAddress.Email, first_name = customer.BillingAddress.FirstName, last_name = customer.BillingAddress.LastName } #endregion }, #endregion #region shipping address shipping_address = customer.ShippingAddress == null ? null : new ShippingAddress { country_code = customer.ShippingAddress.Country != null ? customer.ShippingAddress.Country.TwoLetterIsoCode : null, state = customer.ShippingAddress.StateProvince != null ? customer.ShippingAddress.StateProvince.Abbreviation : null, city = customer.ShippingAddress.City, line1 = customer.ShippingAddress.Address1, line2 = customer.ShippingAddress.Address2, phone = customer.ShippingAddress.PhoneNumber, postal_code = customer.ShippingAddress.ZipPostalCode }, #endregion plan = new Plan { id = billingPlan.id } }.Create(apiContext); //if first payment failed, try again if (string.IsNullOrEmpty(subscription.agreement_details.last_payment_date)) { subscription.BillBalance(apiContext, new AgreementStateDescriptor { amount = subscription.agreement_details.outstanding_balance }); } result.SubscriptionTransactionId = subscription.id; } catch (PayPal.PayPalException exc) { if (exc is PayPal.ConnectionException) { var error = JsonFormatter.ConvertFromJson <Error>((exc as PayPal.ConnectionException).Response); if (error != null) { result.AddError(string.Format("PayPal error: {0} ({1})", error.message, error.name)); if (error.details != null) { error.details.ForEach(x => result.AddError(string.Format("{0} {1}", x.field, x.issue))); } } } //if there are not the specific errors add exception message if (result.Success) { result.AddError(exc.InnerException != null ? exc.InnerException.Message : exc.Message); } } return(result); }