Skip to content

Commit

Permalink
Merge pull request #31 from PartnerCenterSamples/dev
Browse files Browse the repository at this point in the history
Support for Pre-Approved transactions in Web Storefront.
  • Loading branch information
tameemansari authored Feb 2, 2017
2 parents c376ff3 + 00f1246 commit 68e963e
Show file tree
Hide file tree
Showing 40 changed files with 1,517 additions and 196 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Customers can <ol>
</ol>
</p>

This project has adopted the [Microsoft Open Source Code of Conduct] (https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ] (https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com] (mailto:opencode@microsoft.com) with any additional questions or comments.


<h2>Deployment</h2>
The portal can be deployed from within Partner Center at: <a href="https://partnercenter.microsoft.com/en-us/pcv/webstore/preparedeployment">https://partnercenter.microsoft.com/en-us/pcv/webstore/preparedeployment</a>.
There is also a deployment project included in the solution through which, deployment can be started with the specified inputs.
Expand Down Expand Up @@ -77,4 +80,3 @@ Optionally, specify a REDIS cache connection string to improve performance.<br/>
</div>
</li>
</ol>

23 changes: 4 additions & 19 deletions Source/PartnerCenter.CustomerPortal/App_Start/Startup.Auth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,11 @@ public void ConfigureAuth(IAppBuilder app)
{
string partnerCenterCustomerId = string.Empty;

try
{
// Check to see if this login came from the tenant of a customer of the partner
var customerDetails = ApplicationDomain.Instance.PartnerCenterClient.Customers.ById(userTenantId).Get();
// Check to see if this login came from the tenant of a customer of the partner
var customerDetails = await ApplicationDomain.Instance.PartnerCenterClient.Customers.ById(userTenantId).GetAsync();

// indeed a customer
partnerCenterCustomerId = customerDetails.Id;
}
catch (PartnerException readCustomerProblem)
{
if (readCustomerProblem.ErrorCategory == PartnerErrorCategory.NotFound)
{
// this is not an exiting customer tenant, try to locate the user in the customers repository
partnerCenterCustomerId = await ApplicationDomain.Instance.CustomersRepository.RetrieveAsync(userTenantId);
}
else
{
throw;
}
}
// indeed a customer
partnerCenterCustomerId = customerDetails.Id;

if (!string.IsNullOrWhiteSpace(partnerCenterCustomerId))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ private ApplicationDomain()
/// </summary>
public IAggregatePartner PartnerCenterClient { get; private set; }

/// <summary>
/// Gets a Partner Center customer repository.
/// </summary>
public PartnerCenterCustomersRepository CustomersRepository { get; private set; }

/// <summary>
/// Gets the partner offers repository.
/// </summary>
Expand Down Expand Up @@ -76,6 +71,11 @@ private ApplicationDomain()
/// </summary>
public PaymentConfigurationRepository PaymentConfigurationRepository { get; private set; }

/// <summary>
/// Gets the portal PreApprovedCustomers configuration repository.
/// </summary>
public PreApprovedCustomersRepository PreApprovedCustomersRepository { get; private set; }

/// <summary>
/// Gets the customer subscriptions repository.
/// </summary>
Expand All @@ -86,6 +86,11 @@ private ApplicationDomain()
/// </summary>
public CustomerPurchasesRepository CustomerPurchasesRepository { get; private set; }

/// <summary>
/// Gets the customer orders repository.
/// </summary>
public OrdersRepository CustomerOrdersRepository { get; private set; }

/// <summary>
/// Gets the portal telemetry service.
/// </summary>
Expand All @@ -104,14 +109,15 @@ public static async Task InitializeAsync()
Instance.AzureStorageService = new AzureStorageService(ApplicationConfiguration.AzureStorageConnectionString, ApplicationConfiguration.AzureStorageConnectionEndpointSuffix);
Instance.CachingService = new CachingService(Instance, ApplicationConfiguration.CacheConnectionString);
Instance.PartnerCenterClient = await AcquirePartnerCenterAccessAsync();
Instance.PortalLocalization = new PortalLocalization(Instance);
Instance.CustomersRepository = new PartnerCenterCustomersRepository(Instance);
Instance.PortalLocalization = new PortalLocalization(Instance);
Instance.OffersRepository = new PartnerOffersRepository(Instance);
Instance.MicrosoftOfferLogoIndexer = new MicrosoftOfferLogoIndexer(Instance);
Instance.PortalBranding = new PortalBranding(Instance);
Instance.PaymentConfigurationRepository = new PaymentConfigurationRepository(Instance);
Instance.PreApprovedCustomersRepository = new PreApprovedCustomersRepository(Instance);
Instance.CustomerSubscriptionsRepository = new CustomerSubscriptionsRepository(Instance);
Instance.CustomerPurchasesRepository = new CustomerPurchasesRepository(ApplicationDomain.Instance);
Instance.CustomerOrdersRepository = new OrdersRepository(ApplicationDomain.Instance);
Instance.TelemetryService = new TelemetryService(Instance);

await Instance.PortalLocalization.InitializeAsync();
Expand All @@ -126,7 +132,7 @@ public static async Task InitializeAsync()
private static async Task<IAggregatePartner> AcquirePartnerCenterAccessAsync()
{
PartnerService.Instance.ApiRootUrl = ConfigurationManager.AppSettings["partnerCenter.apiEndPoint"];
PartnerService.Instance.ApplicationName = "Web Store Front V1.1";
PartnerService.Instance.ApplicationName = "Web Store Front V1.3";

var credentials = await PartnerCredentials.Instance.GenerateByApplicationCredentialsAsync(
ConfigurationManager.AppSettings["partnercenter.applicationId"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public class AzureStorageService
/// </summary>
private const string CustomerPurchasesTableName = "CustomerPurchases";

/// <summary>
/// The name of the customer orders Azure table.
/// </summary>
private const string CustomerOrdersTableName = "PreApprovedCustomerOrders";

/// <summary>
/// The Azure cloud storage account.
/// </summary>
Expand Down Expand Up @@ -75,6 +80,11 @@ public class AzureStorageService
/// </summary>
private CloudTable customerPurchasesTable;

/// <summary>
/// The Azure customer orders table.
/// </summary>
private CloudTable customerOrdersTable;

/// <summary>
/// Initializes a new instance of the <see cref="AzureStorageService"/> class.
/// </summary>
Expand All @@ -85,10 +95,22 @@ public AzureStorageService(string azureStorageConnectionString, string azureStor
azureStorageConnectionString.AssertNotEmpty(nameof(azureStorageConnectionString));
azureStorageConnectionEndpointSuffix.AssertNotEmpty(nameof(azureStorageConnectionEndpointSuffix));

CloudStorageAccount cloudStorageAccount;
CloudStorageAccount cloudStorageAccount;
if (CloudStorageAccount.TryParse(azureStorageConnectionString, out cloudStorageAccount))
{
this.storageAccount = new CloudStorageAccount(cloudStorageAccount.Credentials, endpointSuffix: azureStorageConnectionEndpointSuffix, useHttps: true);
{
if (azureStorageConnectionString.Equals("UseDevelopmentStorage=true", StringComparison.InvariantCultureIgnoreCase))
{
this.storageAccount = new CloudStorageAccount(
cloudStorageAccount.Credentials,
cloudStorageAccount.BlobStorageUri,
cloudStorageAccount.QueueStorageUri,
cloudStorageAccount.TableStorageUri,
cloudStorageAccount.FileStorageUri);
}
else
{
this.storageAccount = new CloudStorageAccount(cloudStorageAccount.Credentials, endpointSuffix: azureStorageConnectionEndpointSuffix, useHttps: true);
}
}
else
{
Expand Down Expand Up @@ -214,5 +236,22 @@ public async Task<CloudTable> GetCustomerPurchasesTableAsync()
await this.customerPurchasesTable.CreateIfNotExistsAsync();
return this.customerPurchasesTable;
}

/// <summary>
/// Gets the customer orders table.
/// </summary>
/// <returns>The customer purchases table.</returns>
public async Task<CloudTable> GetCustomerOrdersTableAsync()
{
if (this.customerOrdersTable == null)
{
CloudTableClient tableClient = this.storageAccount.CreateCloudTableClient();
this.customerOrdersTable = tableClient.GetTableReference(AzureStorageService.CustomerOrdersTableName);
}

// someone can delete the table externally
await this.customerOrdersTable.CreateIfNotExistsAsync();
return this.customerOrdersTable;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
// -----------------------------------------------------------------------

namespace Microsoft.Store.PartnerCenter.CustomerPortal.BusinessLogic.Commerce
{
{
using System.Threading.Tasks;
using Models;

/// <summary>
/// The payment gateway contract. Implement this interface to provide payment capabilities.
Expand All @@ -32,5 +33,23 @@ public interface IPaymentGateway
/// <param name="authorizationCode">The authorization code for the payment to void.</param>
/// <returns>a Task</returns>
Task VoidAsync(string authorizationCode);

/// <summary>
/// Generates the Payment gateway Url where actual payment collection is done.
/// </summary>
/// <param name="returnUrl">Application return Url.</param>
/// <param name="order">Order information.</param>
/// <returns>The payment gateway url.</returns>
Task<string> GeneratePaymentUriAsync(string returnUrl, OrderViewModel order);

/// <summary>
/// Retrieves the order details maintained for the payment gateway.
/// </summary>
/// <param name="payerId">The Payer Id.</param>
/// <param name="paymentId">The Payment Id.</param>
/// <param name="orderId">The Order Id.</param>
/// <param name="customerId">The Customer Id.</param>
/// <returns>The order associated with this payment transaction.</returns>
Task<OrderViewModel> GetOrderDetailsFromPaymentAsync(string payerId, string paymentId, string orderId, string customerId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public async Task<OrderViewModel> NormalizeRenewSubscriptionOrderAsync()
OrderViewModel orderResult = new OrderViewModel()
{
CustomerId = order.CustomerId,
OrderId = order.OrderId,
OperationType = order.OperationType
};

Expand Down Expand Up @@ -114,6 +115,7 @@ public async Task<OrderViewModel> NormalizePurchaseSubscriptionOrderAsync()
OrderViewModel orderResult = new OrderViewModel()
{
CustomerId = order.CustomerId,
OrderId = order.OrderId,
OperationType = order.OperationType
};

Expand Down Expand Up @@ -175,6 +177,7 @@ public async Task<OrderViewModel> NormalizePurchaseAdditionalSeatsOrderAsync()
OrderViewModel orderResult = new OrderViewModel()
{
CustomerId = order.CustomerId,
OrderId = order.OrderId,
OperationType = order.OperationType
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// -----------------------------------------------------------------------
// <copyright file="OrdersRepository.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Store.PartnerCenter.CustomerPortal.BusinessLogic.Commerce
{
using System;
using System.Linq;
using System.Threading.Tasks;
using Exceptions;
using Models;
using Newtonsoft.Json;
using WindowsAzure.Storage.Table;

/// <summary>
/// Encapsulates persistence for orders during customer purchases.
/// </summary>
public class OrdersRepository : DomainObject
{
/// <summary>
/// Initializes a new instance of the <see cref="OrdersRepository"/> class.
/// </summary>
/// <param name="applicationDomain">An instance of the application domain.</param>
public OrdersRepository(ApplicationDomain applicationDomain) : base(applicationDomain)
{
}

/// <summary>
/// Adds a new order into persistence.
/// </summary>
/// <param name="newOrder">The new customer order to add.</param>
/// <returns>The resulting customer order that got added.</returns>
public async Task<OrderViewModel> AddAsync(OrderViewModel newOrder)
{
newOrder.AssertNotNull(nameof(newOrder));

var customerOrdersTable = await this.ApplicationDomain.AzureStorageService.GetCustomerOrdersTableAsync();
CustomerOrderTableEntity orderEntity = new CustomerOrderTableEntity(newOrder);

var insertionResult = await customerOrdersTable.ExecuteAsync(TableOperation.Insert(orderEntity));
insertionResult.HttpStatusCode.AssertHttpResponseSuccess(ErrorCode.PersistenceFailure, "Failed to add customer order", insertionResult.Result);

return newOrder;
}

/// <summary>
/// Removes an order from persistence.
/// </summary>
/// <param name="orderId">Id of the order to remove.</param>
/// <param name="customerId">Id of the customer whose order to remove.</param>
/// <returns>A task.</returns>
public async Task DeleteAsync(string orderId, string customerId)
{
orderId.AssertNotEmpty(nameof(orderId));
customerId.AssertNotEmpty(nameof(customerId));

var customerOrdersTable = await this.ApplicationDomain.AzureStorageService.GetCustomerOrdersTableAsync();

var deletionResult = await customerOrdersTable.ExecuteAsync(
TableOperation.Delete(new CustomerOrderTableEntity() { PartitionKey = customerId, RowKey = orderId, ETag = "*" }));

deletionResult.HttpStatusCode.AssertHttpResponseSuccess(ErrorCode.PersistenceFailure, "Failed to delete customer order", deletionResult.Result);
}

/// <summary>
/// Retrieves specific order made by a customer from persistence.
/// </summary>
/// <param name="orderId">The order ID.</param>
/// <param name="customerId">The customer ID.</param>
/// <returns>The customer's order.</returns>
public async Task<OrderViewModel> RetrieveAsync(string orderId, string customerId)
{
orderId.AssertNotEmpty(nameof(orderId));
customerId.AssertNotEmpty(nameof(customerId));

var customerOrdersTable = await this.ApplicationDomain.AzureStorageService.GetCustomerOrdersTableAsync();

string tableQueryFilter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, customerId),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, orderId));

var getCustomerOrdersQuery = new TableQuery<CustomerOrderTableEntity>().Where(tableQueryFilter);

TableQuerySegment<CustomerOrderTableEntity> resultSegment = null;
OrderViewModel customerOrder = null;
do
{
resultSegment = await customerOrdersTable.ExecuteQuerySegmentedAsync<CustomerOrderTableEntity>(getCustomerOrdersQuery, resultSegment?.ContinuationToken);

foreach (var orderResult in resultSegment.AsEnumerable())
{
if (orderResult.RowKey == orderId)
{
customerOrder = JsonConvert.DeserializeObject<OrderViewModel>(orderResult.OrderBlob);
}
}
}
while (resultSegment.ContinuationToken != null);

return customerOrder;
}

/// <summary>
/// An azure table entity that describes a customer order.
/// </summary>
private class CustomerOrderTableEntity : TableEntity
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomerOrderTableEntity"/> class.
/// </summary>
public CustomerOrderTableEntity()
{
this.RowKey = Guid.NewGuid().ToString();
}

/// <summary>
/// Initializes a new instance of the <see cref="CustomerOrderTableEntity"/> class.
/// </summary>
/// <param name="order">The order details.</param>
public CustomerOrderTableEntity(OrderViewModel order)
{
this.PartitionKey = order.CustomerId;
this.RowKey = order.OrderId;
this.OrderBlob = JsonConvert.SerializeObject(order, Formatting.None);
}

/// <summary>
/// Gets or sets the blob which contains the order details.
/// </summary>
public string OrderBlob { get; set; }
}
}
}
Loading

0 comments on commit 68e963e

Please sign in to comment.