PayPal Webhook events not being triggered in Sandbox mode - paypal

I am using the PayPal Rest API, more specific the PayPal .Net SDK for my project and I like it a lot.
My problem is that I am not able to receive any webhook notifications from PayPal until now.
Note: The webhook simulator however does work, my handler receives the json post properly from PayPal.
These are the steps I take:
After successfully creating a new invoice (using sandbox credentials from my paypal app), I send it to the test buyer account.
I open the url from the notification to view the invoice and I pay it using the buyer test account.
Both facilitator and buyer get notified that the invoice has been paid successfully.
Nothing happens at my server. No webhook event requests are being received at my listener endpoint.
I have an active webhook event listener in my app's sandbox configuration, tracking all possible events.
What could be the reason that no events are being fired by PayPal?
I do not want to switch to IPN yet, and periodically iterating over a list of invoices from a search result is not an efficient alternative either.
Update:
Since the sandbox webhook event server is up and running again, I got it to work using the following code (c# controller action). Pay attention that the json body structure fired from the webhook simulator is different from the one sent after paying an invoice. Therefore I extended the WebHookEvt object from the PayPal SDK accordingly.
/// <summary>
/// PayPal Webhook handler
/// </summary>
/// <param name="evt"></param>
/// <returns></returns>
[HttpPost]
public ActionResult EventHandler(WebhookEvent evt) { // [System.Web.Http.FromBody]
string json = null;
Capture capture = null;
InvoiceObject invoiceObject = null;
InvoiceExt invoice = null;
WebHookEventCapture captureEvent = null;
WebHookEventInvoice invoiceEvent = null;
if (evt == null) {
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
} else {
this.logger.Debug(string.Format("***** Webhook event type [{0}] received from PayPal *****", evt.event_type));
logger.Debug(string.Format("Event id: {0}", evt.id));
logger.Debug(string.Format("Event create time: {0}", evt.create_time));
logger.Debug(string.Format("Event type: {0}", evt.event_type));
logger.Debug(string.Format("Event resource: {0}", evt.resource));
logger.Debug(string.Format("Event resource type: {0}", evt.resource_type));
logger.Debug(string.Format("Event summary: {0}", evt.summary));
using (Stream req = Request.InputStream) {
req.Seek(0, System.IO.SeekOrigin.Begin);
using (StreamReader sr = new StreamReader(req)) {
json = sr.ReadToEnd();
}
}
logger.Debug(string.Format("WebHook request json form PayPal: ***{0}***", json));
// Validate webhook event
bool isValid = WebhookEvent.ValidateReceivedEvent(new PayPalPaymentProvider(IPN_SANDBOX_MODE).Context, Request.Headers, json, WEBHOOK_ID_INVOICE_PAID);
logger.Debug(string.Format("Validating webhook event... Is valid = {0}", isValid ? "TRUE" : "FALSE"));
try {
if ("capture".Equals(evt.resource_type)) {
captureEvent = JsonConvert.DeserializeObject<WebHookEventCapture>(json);
capture = captureEvent.resource;
} else if ("invoices".Equals(evt.resource_type)) {
invoiceEvent = JsonConvert.DeserializeObject<WebHookEventInvoice>(json);
invoiceObject = invoiceEvent.resource;
invoice = invoiceObject.invoice;
}
//if (capture != null) {
// logger.Debug(string.Format("Capture amount: {0}", capture.amount));
// logger.Debug(string.Format("Capture create time: {0}", capture.create_time));
// logger.Debug(string.Format("Capture id: {0}", capture.id));
// logger.Debug(string.Format("Capture is final: {0}", capture.is_final_capture.HasValue ? capture.is_final_capture : false));
// logger.Debug(string.Format("Capture parent payment: {0}", capture.parent_payment));
// logger.Debug(string.Format("Capture state: {0}", capture.state));
// logger.Debug(string.Format("Capture transaction fee: {0}", capture.transaction_fee));
// logger.Debug(string.Format("Capture update time: {0}", capture.update_time));
//}
if (isValid && invoice != null) {
logger.Debug(string.Format("Invoice [{0}]", invoice));
logger.Debug(string.Format("Invoice id: [{0}]", invoice.id));
logger.Debug(string.Format("Invoice merchant memo: [{0}]", invoice.merchant_memo));
logger.Debug(string.Format("Invoice date: [{0}]", invoice.invoice_date));
logger.Debug(string.Format("Invoice status: [{0}]", invoice.status));
logger.Debug(string.Format("Invoice total amount: [{0}]", invoice.total_amount != null ? invoice.total_amount.currency + invoice.total_amount.value : "??"));
if (invoice.billingInfo != null) { // billing_info
foreach (var billingInfo in invoice.billingInfo) {
logger.Debug(string.Format("Invoice billing info address: {0}", billingInfo.address ?? new InvoiceAddress()));
logger.Debug(string.Format("Invoice billing info business name: {0}", billingInfo.business_name ?? "??"));
logger.Debug(string.Format("Invoice billing info email: {0}", billingInfo.email ?? "??"));
logger.Debug(string.Format("Invoice billing info first name: {0}", billingInfo.first_name ?? "??"));
logger.Debug(string.Format("Invoice billing info last name: {0}", billingInfo.last_name ?? "??"));
logger.Debug(string.Format("Invoice billing info language: {0}", billingInfo.language ?? "??"));
logger.Debug(string.Format("Invoice billing info notification channel: {0}", billingInfo.notification_channel ?? "??"));
logger.Debug(string.Format("Invoice billing info phone: {0}", billingInfo.phone ?? new Phone()));
}
}
// Update clientproductpayment
SubscriptionRepository db = new SubscriptionRepository();
var subscription = db.Context.ClientProductPayments.Where(
q => invoice.id.Equals(q.TransactionId)).FirstOrDefault();
if (subscription != null) {
logger.Debug(string.Format("Subscription (ClientProductPayment) with transaction_id = [{0}] found", invoice.id));
// Update subscription
db.RegisterPayment(subscription, invoice.id);
logger.Debug(string.Format(
"Subscription (ClientProductPayment) with id = [{0}] updated successfully with transaction id = [{1}]",
subscription.Id, invoice.id));
} else {
logger.Warn(string.Format("Subscription (ClientProductPayment) with transaction_id = [{0}] not found", invoice.id));
}
}
}
catch (Exception e) {
logger.Error(e.Message, e);
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
logger.Debug("Sending Http status code 200 response...");
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
Extending WebHookEvt
using Newtonsoft.Json;
using PayPal.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PaymentManager.Provider {
public class WebHookEventCapture : WebhookEvent {
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "resource")]
public new Capture resource { get; set; }
}
public class WebHookEventInvoice : WebhookEvent {
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "resource")]
public new InvoiceObject resource { get; set; }
}
public class InvoiceObject {
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "invoice")]
public InvoiceExt invoice { get; set; }
}
public class InvoiceExt : Invoice {
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "billingInfo")]
public List<BillingInfo> billingInfo { get; set; }
}
}

We are sorry about that. There was a temporary issue at our end which has been fixed. You should be able to receive Invoicing webhook notifications now. Please try them and let us know how it goes.
Thanks

Related

Sandbox Paypal payment Checkout using stored card Vault

I am using Vault to stored credit card information to Paypal sandbox account which is done.
Now i want to integrate the payment process using stored credit card id. please guide how achieve this. Below are code block.
public class HomeController : Controller
{
public ActionResult Index()
{
//
PaypalCCModel ccmodel = new PaypalCCModel();
ccmodel.Number = "5555555555554444"; //"4111111111111111"; //"4012888888881881";// "4417119669820331"; // Visa
ccmodel.ExpireMonth = "11";
ccmodel.ExpireYear = "2021";
//ccmodel.cvv2 = "222";
ccmodel.Type = "MasterCard";
PaymentViaStoredCard(StoreCreditCardInPaypal(ccmodel));
return View();
}
// It return Vault Credit Card Id
private string StoreCreditCardInPaypal(PaypalCCModel ccmodel)
{
Address billingAddress = new Address();
billingAddress.city = "Johnstown";
billingAddress.country_code = "US";
billingAddress.line1 = "52 N Main ST";
billingAddress.postal_code = "43210";
billingAddress.state = "OH";
//Creating the CreditCard Object and assigning values
CreditCard credtCard = new CreditCard();
credtCard.expire_month = int.Parse(ccmodel.ExpireMonth);
credtCard.expire_year = int.Parse(ccmodel.ExpireYear);
credtCard.number = ccmodel.Number;
credtCard.type = ccmodel.Type.ToLower();
//credtCard.cvv2 = ccmodel.cvv2;
credtCard.first_name = "pankaj";
credtCard.last_name = "Kumar";
credtCard.billing_address = billingAddress;
try
{
//Getting the API Context to authenticate the call to Paypal Server
PayPal.Api.APIContext apiContext = Configuration.GetAPIContext();
Dictionary<string, string> headers = new Dictionary<string, string>();
//headers.Add("Content-Type", "application/json");
//headers.Add("Authorization", "Bearer "+ apiContext.AccessToken.ToString());
apiContext.HTTPHeaders = headers;
// Storing the Credit Card Info in the PayPal Vault Server
CreditCard createdCreditCard = credtCard.Create(apiContext);
//Saving the User's Credit Card ID returned by the PayPal
//You can use this ID for future payments via User's Credit Card
//SaveCardID(User.Identity.Name, createdCreditCard.id);
return createdCreditCard.id;
}
catch (PayPal.PayPalException ex)
{
//Logger.LogError("Error: " + ex.Message);
return "-1";
}
catch (Exception ex)
{
//Logger.LogError("Error: " + ex.Message);
}
return "-1";
}
public void PaymentViaStoredCard(string CardID)
{
// Preparing Item
Item item = new Item();
item.name = "Item Name";
item.currency = "USD";
item.price = "1";
item.quantity = "1";
item.sku = "sku-1123324";
List<Item> itms = new List<Item>();
itms.Add(item);
ItemList itemList = new ItemList();
itemList.items = itms;
//Here is the Credit Card detail which we need to use from our database
CreditCardToken credCardToken = new CreditCardToken();
//Here, we are assigning the User's Credit Card ID which we saved in Database
credCardToken.credit_card_id = CardID;
//Specify Payment Details
Details details = new Details();
details.shipping = "1";
details.subtotal = "1";
details.tax = "1";
//Specify the Total Amount
Amount amnt = new Amount();
amnt.currency = "USD";
// Total must be equal to the sum of shipping, tax and subtotal.
amnt.total = "3";
amnt.details = details;
//Create Transaction Object
Transaction tran = new Transaction();
tran.amount = amnt;
tran.description = "This is the recurring payment transaction";
tran.item_list = itemList;
//Adding the transaction above to the list
List<Transaction> transactions = new List<Transaction>();
transactions.Add(tran);
//Specifying the funding Instrument here.
//Notice that we are not using any credit card details here
//But we are using the Credit Card Object which has the Card ID
FundingInstrument fundInstrument = new FundingInstrument();
fundInstrument.credit_card_token = credCardToken;
//Adding the Funding Instrument to the list
List<FundingInstrument> fundingInstrumentList = new List<FundingInstrument>();
fundingInstrumentList.Add(fundInstrument);
//Specify the fundind Instrument List and Payment Method as Credit Card
//Because we are making the payment using the store Credit Card
Payer payr = new Payer();
payr.funding_instruments = fundingInstrumentList;
payr.payment_method = "credit_card";
//Finally Making payment Object and filling it with values
Payment pymnt = new Payment();
pymnt.intent = "sale";
pymnt.payer = payr;
pymnt.transactions = transactions;
try
{
//Getting the API Context to authenticate the Call to Paypal Server
PayPal.Api.APIContext apiContext = Configuration.GetAPIContext();
// Making the payment using a valid APIContext
Payment createdPayment = pymnt.Create(apiContext);
}
catch (PayPal.PayPalException ex)
{
//Logger.LogError("Error: " + ex.Message);
}
}
}
The PaymentViaStoredCard method is stored credit card information on PayPal sandbox vault and return vault Card id which is passed to PaymentViaStoredCard method to pay amount PayPal. But I am getting error in PaymentViaStoredCard method like internal server error.
The full error description is
{"name":"INTERNAL_SERVICE_ERROR","message":"An internal service error occurred.","information_link":"developer.paypal.com/docs/api/payments/…"}

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

Sending a message to a azure service bus queue in .Net Core Core using the REST API

I want to send a message to my Azure Service Bus Queue in .Net Core but the WindowsAzure.ServiceBus Package is not compatible with .Net Core.
Can anyone show me how to send a message to the queue using the REST API?
While the current client is not .NET Core compatible, the new client, that is a work in progress, is 100% compatible. The pre-release package will be available on April 3rd and the status can be tracked here. You could pull down the course code and compile it already today with the caveat that API will be changing as the team is trying to flesh out the design details.
Can anyone show me how to send a message to the queue using the REST API?
As 4c74356b41 mentioned in his comment, we could send a message to Azure Service Bus queue via this REST API:
POST http{s}://{serviceNamespace}.servicebus.windows.net/{queuePath|topicPath}/messages
here is a example
In above request, I provide a Shared Access Signature (token), to generate a Shared Access Signature (token), please refer to this article.
Thanks to Fred's answer, I've expanded to include how to post the authentication header with signature.
public class AzureServiceBusSettings
{
public string BaseUrl { get; set; }
public string SharedAccessKey { get; set; }
public string SharedAccessKeyName { get; set; }
}
public interface IServiceBus
{
/// <summary>
/// Publish domain events to domain topic.
/// </summary>
Task PublishAsync<T>(T #event)
/// <summary>
/// Send commands to command queue.
/// </summary>
Task SendAsync<T>(T command)
}
public class ServiceBus : IServiceBus
{
private readonly AzureServiceBusSettings _settings;
public ServiceBus(IOptions<AzureServiceBusSettings> azureServiceBusSettings)
{
_settings = azureServiceBusSettings.Value;
}
/// <summary>
/// Publish domain events to domain topic.
/// </summary>
public async Task PublishAsync<T>(T #event)
{
await SendInternalAsync(#event, "domain");
}
/// <summary>
/// Send commands to command queue.
/// </summary>
public async Task SendAsync<T>(T command)
{
await SendInternalAsync(command, "commands");
}
private async Task SendInternalAsync<T>(T command, string queueName)
{
var json = JsonConvert.SerializeObject(command);
var content = new StringContent(json, Encoding.UTF8, "application/json");
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(_settings.BaseUrl);
try
{
var url = $"/{queueName}/messages";
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("SharedAccessSignature", GetSasToken(queueName));
var response = await httpClient.PostAsync(url, content);
// Success returns 201 Created.
if (!response.IsSuccessStatusCode)
{
// Handle this.
}
}
catch (Exception ex)
{
// Handle this.
// throw;
}
}
}
private string GetSasToken(string queueName)
{
var url = $"{_settings.BaseUrl}/{queueName}";
// Expiry minutes should be a setting.
var expiry = (int)DateTime.UtcNow.AddMinutes(20).Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
var signature = GetSignature(url, _settings.SharedAccessKey);
var token = $"sr={WebUtility.UrlEncode(url)}&sig={WebUtility.UrlEncode(signature)}&se={expiry}&skn={_settings.SharedAccessKeyName}";
return token;
}
private static string GetSignature(string url, string key)
{
var expiry = (int)DateTime.UtcNow.AddMinutes(20).Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
var value = WebUtility.UrlEncode(url) + "\n" + expiry;
var encoding = new UTF8Encoding();
var keyByte = encoding.GetBytes(key);
var valueBytes = encoding.GetBytes(value);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
var hashmessage = hmacsha256.ComputeHash(valueBytes);
var result = Convert.ToBase64String(hashmessage);
return result;
}
}
}
And a simple xunit test to post:
public class ServiceBusTests
{
public class FooCommand : ICommand
{
public Guid CommandId { get; set; }
}
private Mock<IOptions<AzureServiceBusSettings>> _mockAzureServiceBusOptions;
private ServiceBus _sut;
public ServiceBusTests()
{
var settings = new AzureServiceBusSettings
{
BaseUrl = "https://my-domain.servicebus.windows.net",
SharedAccessKey = "my-key-goes-here",
SharedAccessKeyName = "RootManageSharedAccessKey"
};
_mockAzureServiceBusOptions = new Mock<IOptions<AzureServiceBusSettings>>();
_mockAzureServiceBusOptions.SetupGet(o => o.Value).Returns(settings);
_sut = new ServiceBus(
_mockAzureServiceBusOptions.Object);
}
[Fact]
public async Task should_send_message()
{
// Arrange.
var command = new FooCommand {CommandId = Guid.NewGuid()};
// Act.
await _sut.SendAsync(command);
// Assert.
// TODO: Get the command from the queue and assert something.
}
}

Post to group wall using Facebook SDK .net 2015

It seems that nowdays there's no way to allow to post onto facebook user groups via a new, unreviewed app.
I've created an App with live features availible to gen public, with approvements for email, public_profile, user_friends.
I'm trying to post on the wall of a group I've created and I'm an admin of.
Now, when firing up my code, I get the nasty "OAuthException - #200) (#200) Insufficient permission to post to target..." exception.
Posting my wall work perfectly...
public class HomeController : Controller
{
private const long GroupId = 15473890820xxxx;
//
// GET: /Home/
public ActionResult Index(string code)
{
ViewBag.Message = "Welcome to Facebook App Demo!";
//user denied permissions on Facebook.
if (Request["error_reason"] == "user_denied")
{
//this is not implemented. For reference only.
return RedirectToAction("LogOn", "Account");
}
if (string.IsNullOrEmpty(code))
{
ViewBag.Error = "There was an error while loggin into Facebook. Please try again later.";
return RedirectToAction("LogOn", "Account");
}
var fb = new FacebookClient();
dynamic result = fb.Post("oauth/access_token", new
{
client_id = "93199615018xxxx",
client_secret = "2089ae4447877e7388500b844235xxxx",
redirect_uri = "http://localhost:13301/",
code = code
});
var appToken = result.access_token as string;
// update the facebook client with the access token so
// we can make requests on behalf of the user
fb.AccessToken = appToken;
//get extended App/User Token
dynamic result2 = fb.Get("oauth/access_token", new
{
client_id = "93199615018xxxx"
client_secret = "2089ae4447877e7388500b844235xxxx",
grant_type="fb_exchange_token",
fb_exchange_token = appToken,
// code = code
});
var extendedAppToken = result2.access_token as string;
//generate Extended User Token (valid for 60 days)
Session["Facebooktoken"] = extendedAppToken;
dynamic me = fb.Get("me/accounts");
var response = fb.Get("/me/accounts?access_token=" + extendedAppToken) as JsonObject;
return View(me);
}
[HttpPost]
public ActionResult Index(FormCollection collection) //rename to PostWall
{
ViewBag.Message = "Welcome to Facebook App Demo!";
// Post to the wall..
try
{
if (Session["Facebooktoken"] != null)
{
string message = collection["txtPost"].ToString();
FacebookClient client = new FacebookClient(Session["Facebooktoken"].ToString());
dynamic result = client.Post(GroupId+"/feed", new
{
message = message
});
ViewBag.SaveMessage = "Successfuly Post on your wall : " + result.id;
}
else
{
return RedirectToAction("LogOn", "Account");
}
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
return View();
}
public ActionResult About()
{
return View();
}
}
public class AccountController : Controller
{
//
// GET: /Account/
public ActionResult LogOn()
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
var facebookAppId = "93199615018xxxx";
var facebookAppSecret = "2089ae4447877e7388500b844235xxxx";
var facebookRedirectUrl = "http://localhost:13301/";
//
var facebookScope = "email,publish_pages,publish_actions,manage_pages,user_managed_groups,user_hometown,user_website";
if (facebookAppId != null && facebookAppSecret != null && facebookRedirectUrl != null && facebookScope != null)
{
var url = string.Format(#"https://www.facebook.com/dialog/oauth/?client_id={0}&redirect_uri={1}&scope={2}", facebookAppId, facebookRedirectUrl, facebookScope);
Response.Redirect(url, true);
}
return this.RedirectToAction("Index", "Home");
}
}
Any clue on how to set up an testing environment for above functionality without the extra-round of establishing a fully fledged App that has to be reviewed by Facebook admins? I've looked on testing versions for Apps and Users, but no groups either.
Many thanks in advance
Darren, thanks for your considerations. It seems like due to my privacy restrictions I couldnt get any group info of my personal account. Either programmatically, nor manually in Facebooks Graph API Explorer.
It worked finally on an business (fan)'Page' profile by getting an 'long-lived' Page Access Token in facebooks Access Token Debuger.
Yes, user_managed_groups and publish_actions had been enabled.

How to get the distinct record from a Table and delete it

I am working on a plugin on Delete button, when ever I delete any selected record all the distinct(Selected record) should be deleted as well.
In my case I have an Attendee which is invited in a meeting, The Attendee also have some discussion point records and Action Items records in it, As per the requirement when I will delete my Meeting Attendee it should delete this Attendee where it has any discussion point record and Action items record.Need help on this regard.
Below is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;
namespace SFDSendEmail.SFDDeleteAttendee.Class
{
public class SFDDeleteAttendee : IPlugin
{
Guid Internaluser;
Guid Externaluser;
private IOrganizationService _sdk = null;
public void Execute(IServiceProvider serviceProvider)
{
try
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
_sdk = (IOrganizationService)factory.CreateOrganizationService(context.UserId);
if (context.IsExecutingOffline || context.IsOfflinePlayback)
return;
// The InputParameters collection contains all the data passed
// in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is EntityReference)
{
// EntityReference RequiredAttendee = context.InputParameters["new_requiredattendee"] as EntityReference;
// Entity entity = _sdk.Retrieve("new_requiredattendee", ((EntityReference)RequiredAttendee["new_requiredattendeeid"]).Id, new ColumnSet(true));
// Entity eUser = _sdk.Retrieve(RequiredAttendee.LogicalName, RequiredAttendee.Id, new ColumnSet(true));
EntityReference RequiredAttendee = (EntityReference)context.InputParameters["Target"];
Entity eUser = _sdk.Retrieve(RequiredAttendee.LogicalName, RequiredAttendee.Id, new ColumnSet(true));
if (context.MessageName == "Delete")
{
if (eUser.LogicalName != "new_requiredattendee")
{
return;
}
else
{
//If User Selects the Interner User for Deletion
if (eUser.Attributes.Contains("new_internaluser"))
{
//Save Internal User ID
Internaluser = ((EntityReference)eUser["new_internaluser"]).Id;
//Function to fetch the Meeting Attendee with the Above ID
Guid VerifyAttendee = VerifyMeetingAttendee(eUser,_sdk,Internaluser);
//If its the Meeting Attendee
if (VerifyAttendee != null)
{
//Get the Attendee with its Discussion Point
Guid AttendeewithDp = VerifyDPMeetingAttendee(eUser, _sdk, Internaluser);
if (AttendeewithDp != null)
{
//Get the Attendee with Action item
Guid AttendeewithAI = VerifyAIMeetingAttendee(eUser, _sdk, Internaluser);
if (AttendeewithAI != null)
{
//DO your code here....................................
_sdk.Delete("new_requiredattendee", AttendeewithAI);
}
}
}
}
//If User Selects the Interner User for Deletion
else if (eUser.Attributes.Contains("new_externaluser"))
{
//Save Internal User ID
Externaluser = ((EntityReference)eUser.Attributes["new_externaluser"]).Id;
//Function to fetch the Meeting Attendee with the Above ID
Guid verifyExternalAttendee = VerifyExternalMeetingAttendee(eUser,_sdk,Externaluser);
if (verifyExternalAttendee != null)
{
//Get the Attendee with its Discussion Point
Guid EXAttendeewithDp = VerifyEXDPMeetingAttendee(eUser, _sdk, Externaluser);
if (EXAttendeewithDp != null)
{
//Get the Attendee with Action item
Guid EXAttendeewithAI = VerifyEXAIMeetingAttendee(eUser, _sdk, Externaluser);
if (EXAttendeewithAI != null)
{
//DO your code here....................................
_sdk.Delete("new_requiredattendee", EXAttendeewithAI);
}
}
}
}
}
}
}
}
catch (Exception ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
//Get Internal Meeting Attendee
public Guid VerifyMeetingAttendee(Entity RequiredAttendee,IOrganizationService _orgService,Guid user)
{
QueryExpression GetMeetingAttendees = new QueryExpression();
GetMeetingAttendees.EntityName = "new_requiredattendee";
GetMeetingAttendees.ColumnSet = new ColumnSet("new_internaluser");
GetMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Equal,user);
GetMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.Null);
GetMeetingAttendees.Criteria.AddCondition("new_discussionpoint", ConditionOperator.Null);
GetMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetMeetingAttendees);
return (Guid)GMA[0]["new_internaluser"];
}
//Get Internal Meeting Attendee with Discussion Point
public Guid VerifyDPMeetingAttendee(Entity RequiredAttendee, IOrganizationService _orgService, Guid user)
{
QueryExpression GetDPMeetingAttendees = new QueryExpression();
GetDPMeetingAttendees.EntityName = "new_requiredattendee";
GetDPMeetingAttendees.ColumnSet = new ColumnSet("new_internaluser");
GetDPMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetDPMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Equal,user);
GetDPMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.Null);
GetDPMeetingAttendees.Criteria.AddCondition("new_discussionpoint", ConditionOperator.NotNull);
GetDPMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetDPMeetingAttendees);
return (Guid)GMA[0]["new_internaluser"];
}
//Get Internal Meeting Attendee with Action Items
public Guid VerifyAIMeetingAttendee(Entity RequiredAttendee, IOrganizationService _orgService, Guid user)
{
QueryExpression GetDPMeetingAttendees = new QueryExpression();
GetDPMeetingAttendees.EntityName = "new_requiredattendee";
GetDPMeetingAttendees.ColumnSet = new ColumnSet("new_internaluser");
GetDPMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetDPMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Equal,user);
GetDPMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.NotNull);
GetDPMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetDPMeetingAttendees);
return (Guid)GMA[0]["new_internaluser"];
}
//Get External Meeting Attendee
public Guid VerifyExternalMeetingAttendee(Entity RequiredAttendee, IOrganizationService _orgService, Guid user)
{
QueryExpression GetMeetingAttendees = new QueryExpression();
GetMeetingAttendees.EntityName = "new_requiredattendee";
GetMeetingAttendees.ColumnSet = new ColumnSet("new_externaluser");
GetMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Equal, user);
GetMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.Null);
GetMeetingAttendees.Criteria.AddCondition("new_discussionpoint", ConditionOperator.Null);
GetMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetMeetingAttendees);
return (Guid)GMA[0]["new_externaluser"];
}
//Get External Meeting Attendee with Discussion Point
public Guid VerifyEXDPMeetingAttendee(Entity RequiredAttendee, IOrganizationService _orgService, Guid user)
{
QueryExpression GetMeetingAttendees = new QueryExpression();
GetMeetingAttendees.EntityName = "new_requiredattendee";
GetMeetingAttendees.ColumnSet = new ColumnSet("new_externaluser");
GetMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Equal, user);
GetMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.Null);
GetMeetingAttendees.Criteria.AddCondition("new_discussionpoint", ConditionOperator.NotNull);
GetMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetMeetingAttendees);
return (Guid)GMA[0]["new_externaluser"];
}
//Get External Meeting Attendee with Action Item
public Guid VerifyEXAIMeetingAttendee(Entity RequiredAttendee, IOrganizationService _orgService, Guid user)
{
QueryExpression GetMeetingAttendees = new QueryExpression();
GetMeetingAttendees.EntityName = "new_requiredattendee";
GetMeetingAttendees.ColumnSet = new ColumnSet("new_externaluser");
GetMeetingAttendees.Criteria.AddCondition("new_mom", ConditionOperator.Equal, RequiredAttendee["new_mom"]);
GetMeetingAttendees.Criteria.AddCondition("new_externaluser", ConditionOperator.Equal, user);
GetMeetingAttendees.Criteria.AddCondition("new_actionitem", ConditionOperator.NotNull);
GetMeetingAttendees.Criteria.AddCondition("new_internaluser", ConditionOperator.Null);
EntityCollection GMA = _orgService.RetrieveMultiple(GetMeetingAttendees);
return (Guid)GMA[0]["new_externaluser"];
}
}
}
I'm having a little trouble understanding the issue here, but here's my thoughts.
1) If it is rolling back the delete, then there is either an error with the delete or with cascading deletes underneath this one. Providing the exception message would go a long way to helping us troubleshoot your problem.
2) You can make your code much more concise and readable using LINQ queries instead of the CRM QueryExpression. Another area for improvement is the exact same code multiple times with 1 minor tweak - aka the branches for attributes.contains("new_internaluser") and attributes.contains("new_external") user could be much improved - see below).
//Add this method to the class
internal void deleteUser(string userString){
user = ((EntityReference)eUser[userString]).Id;
//Function to fetch the Meeting Attendee with the Above ID
Guid VerifyAttendee = VerifyMeetingAttendee(eUser,_sdk,user);
//If its the Meeting Attendee
if (VerifyAttendee != null)
{
//Get the Attendee with its Discussion Point
Guid AttendeewithDp = VerifyDPMeetingAttendee(eUser, _sdk, user);
if (AttendeewithDp != null)
{
//Get the Attendee with Action item
Guid AttendeewithAI = VerifyAIMeetingAttendee(eUser, _sdk, user);
if (AttendeewithAI != null)
{
//DO your code here....................................
_sdk.Delete("new_requiredattendee", AttendeewithAI);
}
}
}
}
//And call it with these few simple lines within your main execute method:
if (eUser.Attributes.Contains("new_internaluser"))
{
deleteUser("new_internaluser");
}
//If User Selects the Interner User for Deletion
else if (eUser.Attributes.Contains("new_externaluser"))
{
deleteUser("new_externaluser");
}