();
16 |
17 | public ActionResult Index()
18 | {
19 | var model = new IndexVm()
20 | {
21 | Beers = _dbContext.Beers.ToList(),
22 | Plans = Plan.Plans
23 | };
24 |
25 | return View(model);
26 | }
27 |
28 | public ActionResult About()
29 | {
30 | ViewBag.Message = "Your application description page.";
31 |
32 | return View();
33 | }
34 |
35 | public ActionResult Contact()
36 | {
37 | ViewBag.Message = "Your contact page.";
38 |
39 | return View();
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/BeerPal/compilerconfig.json.defaults:
--------------------------------------------------------------------------------
1 | {
2 | "compilers": {
3 | "less": {
4 | "autoPrefix": "",
5 | "cssComb": "none",
6 | "ieCompat": true,
7 | "strictMath": false,
8 | "strictUnits": false,
9 | "relativeUrls": true,
10 | "rootPath": "",
11 | "sourceMapRoot": "",
12 | "sourceMapBasePath": "",
13 | "sourceMap": false
14 | },
15 | "sass": {
16 | "includePath": "",
17 | "indentType": "space",
18 | "indentWidth": 2,
19 | "outputStyle": "nested",
20 | "Precision": 5,
21 | "relativeUrls": true,
22 | "sourceMapRoot": "",
23 | "sourceMap": false
24 | },
25 | "stylus": {
26 | "sourceMap": false
27 | },
28 | "babel": {
29 | "sourceMap": false
30 | },
31 | "coffeescript": {
32 | "bare": false,
33 | "runtimeMode": "node",
34 | "sourceMap": false
35 | }
36 | },
37 | "minifiers": {
38 | "css": {
39 | "enabled": true,
40 | "termSemicolons": true,
41 | "gzip": false
42 | },
43 | "javascript": {
44 | "enabled": true,
45 | "termSemicolons": true,
46 | "gzip": false
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Account/_ExternalLoginsListPartial.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ExternalLoginListViewModel
2 | @using Microsoft.Owin.Security
3 |
4 | Use another service to log in.
5 |
6 | @{
7 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
8 | if (loginProviders.Count() == 0) {
9 |
10 |
11 | There are no external authentication services configured. See this article
12 | for details on setting up this ASP.NET application to support logging in via external services.
13 |
14 |
15 | }
16 | else {
17 | using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) {
18 | @Html.AntiForgeryToken()
19 |
20 |
21 | @foreach (AuthenticationDescription p in loginProviders) {
22 | @p.AuthenticationType
23 | }
24 |
25 |
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/SalesController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using System.Web.Mvc;
6 | using PayPal.Api;
7 |
8 | namespace BeerPal.Controllers
9 | {
10 | public class SalesController : Controller
11 | {
12 | public ActionResult Index()
13 | {
14 | var apiContext = GetApiContext();
15 |
16 | var sales = Payment.List(apiContext);
17 |
18 | return View(sales);
19 | }
20 |
21 | public ActionResult Refund(string saleId)
22 | {
23 | var apiContext = GetApiContext();
24 |
25 | var sale = new Sale()
26 | {
27 | id = saleId
28 | };
29 |
30 | // A refund with no details refunds the entire amount.
31 | var refund = sale.Refund(apiContext, new Refund());
32 |
33 | return RedirectToAction("Index");
34 | }
35 |
36 | private APIContext GetApiContext()
37 | {
38 | // Authenticate with PayPal
39 | var config = ConfigManager.Instance.GetProperties();
40 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
41 | var apiContext = new APIContext(accessToken);
42 | return apiContext;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/BeerPal/Web.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/BeerPal/App_Start/BundleConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Web;
2 | using System.Web.Optimization;
3 |
4 | namespace BeerPal
5 | {
6 | public class BundleConfig
7 | {
8 | // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
9 | public static void RegisterBundles(BundleCollection bundles)
10 | {
11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
12 | "~/Scripts/jquery-{version}.js"));
13 |
14 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
15 | "~/Scripts/jquery.validate*"));
16 |
17 | // Use the development version of Modernizr to develop with and learn from. Then, when you're
18 | // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
19 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
20 | "~/Scripts/modernizr-*"));
21 |
22 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
23 | "~/Scripts/bootstrap.js",
24 | "~/Scripts/respond.js"));
25 |
26 | bundles.Add(new StyleBundle("~/Content/css").Include(
27 | "~/Content/bootstrap.css",
28 | "~/Content/site.css"));
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BeerPal/Views/Subscribers/Details.cshtml:
--------------------------------------------------------------------------------
1 | @using Newtonsoft.Json
2 | @model PayPal.Api.Agreement
3 |
4 | @{
5 | ViewBag.Title = "Subscription Details";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Subscription Details
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
@JsonConvert.SerializeObject(Model, Formatting.Indented)
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/BeerPal/Views/Account/VerifyCode.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.VerifyCodeViewModel
2 | @{
3 | ViewBag.Title = "Verify";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) {
9 | @Html.AntiForgeryToken()
10 | @Html.Hidden("provider", @Model.Provider)
11 | @Html.Hidden("rememberMe", @Model.RememberMe)
12 | Enter verification code
13 |
14 | @Html.ValidationSummary("", new { @class = "text-danger" })
15 |
21 |
29 |
34 | }
35 |
36 | @section Scripts {
37 | @Scripts.Render("~/bundles/jqueryval")
38 | }
39 |
--------------------------------------------------------------------------------
/BeerPal/Web.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/BeerPal/Public/css/wizard.min.css:
--------------------------------------------------------------------------------
1 | .nav-pills.nav-wizard>li{position:relative;overflow:visible;border-right:15px solid transparent;border-left:15px solid transparent;}.nav-pills.nav-wizard>li+li{margin-left:0;}.nav-pills.nav-wizard>li:first-child{border-left:0;}.nav-pills.nav-wizard>li:first-child a{border-radius:5px 0 0 5px;}.nav-pills.nav-wizard>li:last-child{border-right:0;}.nav-pills.nav-wizard>li:last-child a{border-radius:0 5px 5px 0;}.nav-pills.nav-wizard>li a{border-radius:0;background-color:#eee;}.nav-pills.nav-wizard>li:not(:last-child) a:after{position:absolute;content:"";top:0;right:-20px;width:0;height:0;border-style:solid;border-width:20px 0 20px 20px;border-color:transparent transparent transparent #eee;z-index:150;}.nav-pills.nav-wizard>li:not(:first-child) a:before{position:absolute;content:"";top:0;left:-20px;width:0;height:0;border-style:solid;border-width:20px 0 20px 20px;border-color:#eee #eee #eee transparent;z-index:150;}.nav-pills.nav-wizard>li:hover:not(:last-child) a:after{border-color:transparent transparent transparent #aaa;}.nav-pills.nav-wizard>li:hover:not(:first-child) a:before{border-color:#aaa #aaa #aaa transparent;}.nav-pills.nav-wizard>li:hover a{background-color:#aaa;color:#fff;}.nav-pills.nav-wizard>li.active:not(:last-child) a:after{border-color:transparent transparent transparent #428bca;}.nav-pills.nav-wizard>li.active:not(:first-child) a:before{border-color:#428bca #428bca #428bca transparent;}.nav-pills.nav-wizard>li.active a{background-color:#428bca;}
--------------------------------------------------------------------------------
/BeerPal/Views/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.SetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Create Password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | You do not have a local username/password for this site. Add a local
9 | account so you can log in without an external login.
10 |
11 |
12 | @using (Html.BeginForm("SetPassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
13 | {
14 | @Html.AntiForgeryToken()
15 |
16 | Create Local Login
17 |
18 | @Html.ValidationSummary("", new { @class = "text-danger" })
19 |
25 |
31 |
36 | }
37 | @section Scripts {
38 | @Scripts.Render("~/bundles/jqueryval")
39 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Account/ExternalLoginConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ExternalLoginConfirmationViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 | @ViewBag.Title.
6 | Associate your @ViewBag.LoginProvider account.
7 |
8 | @using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 |
12 | Association Form
13 |
14 | @Html.ValidationSummary(true, "", new { @class = "text-danger" })
15 |
16 | You've successfully authenticated with @ViewBag.LoginProvider .
17 | Please enter a user name for this site below and click the Register button to finish
18 | logging in.
19 |
20 |
27 |
32 | }
33 |
34 | @section Scripts {
35 | @Scripts.Render("~/bundles/jqueryval")
36 | }
37 |
--------------------------------------------------------------------------------
/BeerPal/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("BeerPal")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("BeerPal")]
13 | [assembly: AssemblyCopyright("Copyright © 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0b72fd66-c1b8-4500-bf77-dc4a1b3edffa")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Revision and Build Numbers
33 | // by using the '*' as shown below:
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 |
--------------------------------------------------------------------------------
/BeerPal/Views/Account/Register.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.RegisterViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Create a new account.
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
26 |
32 |
37 | }
38 |
39 | @section Scripts {
40 | @Scripts.Render("~/bundles/jqueryval")
41 | }
42 |
--------------------------------------------------------------------------------
/BeerPal/Extras/SimulatableWebhook.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using Newtonsoft.Json;
6 | using PayPal.Api;
7 |
8 | namespace BeerPal.Extras
9 | {
10 | public class SimulatableWebhook : Webhook
11 | {
12 | public WebhookEvent SimulateEvent(string eventType, string webhookId)
13 | {
14 | var apiContext = GetApiContext();
15 |
16 | if (apiContext.HTTPHeaders == null)
17 | apiContext.HTTPHeaders = new Dictionary();
18 | apiContext.HTTPHeaders["Content-Type"] = "application/json";
19 | apiContext.SdkVersion = new SDKVersion();
20 |
21 | string resource = "v1/notifications/simulate-event";
22 |
23 | var data = new
24 | {
25 | webhook_id = webhookId,
26 | event_type = eventType
27 | };
28 |
29 | var webhookEvent = PayPalResource.ConfigureAndExecute(apiContext, PayPalResource.HttpMethod.POST, resource, JsonConvert.SerializeObject(data), "", true);
30 |
31 | return webhookEvent;
32 | }
33 |
34 | private APIContext GetApiContext()
35 | {
36 | // Authenticate with PayPal
37 | var config = ConfigManager.Instance.GetProperties();
38 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
39 | var apiContext = new APIContext(accessToken);
40 | return apiContext;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Manage/ChangePassword.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ChangePasswordViewModel
2 | @{
3 | ViewBag.Title = "Change Password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Change Password Form
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 |
20 |
26 |
32 |
37 | }
38 | @section Scripts {
39 | @Scripts.Render("~/bundles/jqueryval")
40 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Account/ResetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ResetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Reset password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
9 | {
10 | @Html.AntiForgeryToken()
11 | Reset your password.
12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" })
14 | @Html.HiddenFor(model => model.Code)
15 |
21 |
27 |
33 |
38 | }
39 |
40 | @section Scripts {
41 | @Scripts.Render("~/bundles/jqueryval")
42 | }
43 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/WebExperienceProfilesController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using System.Web.Mvc;
6 | using PayPal.Api;
7 |
8 | namespace BeerPal.Controllers
9 | {
10 | public class WebExperienceProfilesController : Controller
11 | {
12 | public ActionResult Index()
13 | {
14 | var apiContext = GetApiContext();
15 |
16 | var list = WebProfile.GetList(apiContext);
17 |
18 | if (!list.Any())
19 | {
20 | SeedWebProfiles(apiContext);
21 | list = WebProfile.GetList(apiContext);
22 | }
23 |
24 | return View(list);
25 | }
26 |
27 | ///
28 | /// Create the default web experience profiles for this example website
29 | ///
30 | private void SeedWebProfiles(APIContext apiContext)
31 | {
32 | var digitalGoods = new WebProfile()
33 | {
34 | name = "DigitalGoods",
35 | input_fields = new InputFields()
36 | {
37 | no_shipping = 1
38 | }
39 | };
40 | WebProfile.Create(apiContext, digitalGoods);
41 | }
42 |
43 | private APIContext GetApiContext()
44 | {
45 | // Authenticate with PayPal
46 | var config = ConfigManager.Instance.GetProperties();
47 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
48 | var apiContext = new APIContext(accessToken);
49 | return apiContext;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/BeerPal/Views/WebExperienceProfiles/Index.cshtml:
--------------------------------------------------------------------------------
1 | @using Newtonsoft.Json
2 | @model PayPal.Api.WebProfileList
3 |
4 | @{
5 | ViewBag.Title = "Web Experience Profiles";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Web Experience Profiles
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ID
27 | Name
28 | Flow Config
29 | Input Fields
30 | Presentation
31 |
32 |
33 |
34 | @foreach (var profile in Model)
35 | {
36 |
37 | @profile.id
38 | @profile.name
39 | @JsonConvert.SerializeObject(profile.flow_config)
40 | @JsonConvert.SerializeObject(profile.input_fields)
41 | @JsonConvert.SerializeObject(profile.presentation)
42 |
43 | }
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/BeerPal/Views/Subscribers/Index.cshtml:
--------------------------------------------------------------------------------
1 | @using BeerPal.Models.Subscription
2 | @model List
3 |
4 | @{
5 | ViewBag.Title = "Subscribers";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Subscribers
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ID
26 | Created Date
27 | Plan
28 |
29 |
30 |
31 |
32 | @foreach (var item in Model)
33 | {
34 |
35 | @item.PayPalAgreementId
36 | @item.StartDate
37 | @(Plan.Plans.FirstOrDefault(x => x.PayPalPlanId == item.PayPalPlanId)?.Name ?? "[Deleted Plan]")
38 |
39 | View Details
40 |
41 |
42 | }
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/BeerPal/Models/IdentityModels.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Data.Entity;
4 | using System.Security.Claims;
5 | using System.Threading.Tasks;
6 | using BeerPal.Entities;
7 | using Microsoft.AspNet.Identity;
8 | using Microsoft.AspNet.Identity.EntityFramework;
9 |
10 | namespace BeerPal.Models
11 | {
12 | // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
13 | public class ApplicationUser : IdentityUser
14 | {
15 | public ApplicationUser()
16 | {
17 | }
18 |
19 | public async Task GenerateUserIdentityAsync(UserManager manager)
20 | {
21 | // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
22 | var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
23 | // Add custom user claims here
24 | return userIdentity;
25 | }
26 | }
27 |
28 | public class ApplicationDbContext : IdentityDbContext
29 | {
30 | public DbSet Tickets { get; set; }
31 | public DbSet Orders { get; set; }
32 | public DbSet OrderItems { get; set; }
33 | public DbSet Beers { get; set; }
34 | public DbSet Subscriptions { get; set; }
35 | public DbSet WebhookEvents { get; set; }
36 |
37 | public ApplicationDbContext()
38 | : base("DefaultConnection", throwIfV1Schema: false)
39 | {
40 | }
41 |
42 | public static ApplicationDbContext Create()
43 | {
44 | return new ApplicationDbContext();
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/BeerPal/Views/BillingPlans/Index.cshtml:
--------------------------------------------------------------------------------
1 | @using Newtonsoft.Json
2 | @model PayPal.Api.PlanList
3 |
4 | @{
5 | ViewBag.Title = "Billing Plans";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Billing Plans
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ID
27 | Name
28 | Type
29 | State
30 | Payment Definitions
31 | Merchant Preferences
32 |
33 |
34 |
35 | @foreach (var plan in Model.plans)
36 | {
37 |
38 | @plan.id
39 | @plan.name
40 | @plan.type
41 | @plan.state
42 | @JsonConvert.SerializeObject(plan.payment_definitions)
43 | @JsonConvert.SerializeObject(plan.merchant_preferences)
44 |
45 | }
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/BeerPal/Views/ECommerce/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ECommerce.IndexVm
2 |
3 | @{
4 | ViewBag.Title = "Beers";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
// Beers for Sale
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Beer
25 | Price
26 |
27 |
28 |
29 |
30 | @foreach (var item in Model.Beers)
31 | {
32 |
33 | @item.Name
34 | $@((item.Price / 100M).ToString("0.00"))
35 |
36 | @using (Html.BeginForm("Add", "ECommerce", FormMethod.Post))
37 | {
38 | @Html.Hidden("BeerId", item.Id)
39 | Add to cart
40 | }
41 |
42 |
43 | }
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/BeerPal/Views/Subscription/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.Subscription.IndexVm
2 |
3 | @{
4 | ViewBag.Title = "Subscription Plans";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
// Beer Subscriptions
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Plan
25 | Description
26 | Price
27 |
28 |
29 |
30 |
31 | @foreach (var item in Model.Plans)
32 | {
33 |
34 | @item.Name
35 | @(item.NumberOfBeers) beer(s) delivered to your door. @(item.Description1). @item.Description2
36 | $@((item.Price / 100M).ToString("0.00"))
37 |
38 | Subscribe
39 |
40 |
41 | }
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/BeerPal/Public/css/wizard.css:
--------------------------------------------------------------------------------
1 | .nav-pills.nav-wizard > li {
2 | position: relative;
3 | overflow: visible;
4 | border-right: 15px solid transparent;
5 | border-left: 15px solid transparent;
6 | }
7 | .nav-pills.nav-wizard > li + li {
8 | margin-left: 0;
9 | }
10 | .nav-pills.nav-wizard > li:first-child {
11 | border-left: 0;
12 | }
13 | .nav-pills.nav-wizard > li:first-child a {
14 | border-radius: 5px 0 0 5px;
15 | }
16 | .nav-pills.nav-wizard > li:last-child {
17 | border-right: 0;
18 | }
19 | .nav-pills.nav-wizard > li:last-child a {
20 | border-radius: 0 5px 5px 0;
21 | }
22 | .nav-pills.nav-wizard > li a {
23 | border-radius: 0;
24 | background-color: #eee;
25 | }
26 | .nav-pills.nav-wizard > li:not(:last-child) a:after {
27 | position: absolute;
28 | content: "";
29 | top: 0px;
30 | right: -20px;
31 | width: 0px;
32 | height: 0px;
33 | border-style: solid;
34 | border-width: 20px 0 20px 20px;
35 | border-color: transparent transparent transparent #eee;
36 | z-index: 150;
37 | }
38 | .nav-pills.nav-wizard > li:not(:first-child) a:before {
39 | position: absolute;
40 | content: "";
41 | top: 0px;
42 | left: -20px;
43 | width: 0px;
44 | height: 0px;
45 | border-style: solid;
46 | border-width: 20px 0 20px 20px;
47 | border-color: #eee #eee #eee transparent;
48 | z-index: 150;
49 | }
50 | .nav-pills.nav-wizard > li:hover:not(:last-child) a:after {
51 | border-color: transparent transparent transparent #aaa;
52 | }
53 | .nav-pills.nav-wizard > li:hover:not(:first-child) a:before {
54 | border-color: #aaa #aaa #aaa transparent;
55 | }
56 | .nav-pills.nav-wizard > li:hover a {
57 | background-color: #aaa;
58 | color: #fff;
59 | }
60 | .nav-pills.nav-wizard > li.active:not(:last-child) a:after {
61 | border-color: transparent transparent transparent #428bca;
62 | }
63 | .nav-pills.nav-wizard > li.active:not(:first-child) a:before {
64 | border-color: #428bca #428bca #428bca transparent;
65 | }
66 | .nav-pills.nav-wizard > li.active a {
67 | background-color: #428bca;
68 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/BeerPal/Views/Webhooks/Index.cshtml:
--------------------------------------------------------------------------------
1 | @using Newtonsoft.Json
2 | @model PayPal.Api.WebhookList
3 |
4 | @{
5 | ViewBag.Title = "Web Experience Profiles";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Webhooks
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ID
27 | URL
28 | Event Types
29 |
30 |
31 |
32 |
33 | @foreach (var webhook in Model.webhooks)
34 | {
35 |
36 | @webhook.id
37 | @webhook.url
38 | @JsonConvert.SerializeObject(webhook.event_types)
39 |
40 | @using (Html.BeginForm("Delete", "Webhooks", FormMethod.Post))
41 | {
42 | @Html.Hidden("webhookId", webhook.id)
43 | Delete
44 | }
45 |
46 |
47 | }
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/BeerPal/Migrations/201608251008534_AddedOrders.cs:
--------------------------------------------------------------------------------
1 | namespace BeerPal.Migrations
2 | {
3 | using System;
4 | using System.Data.Entity.Migrations;
5 |
6 | public partial class AddedOrders : DbMigration
7 | {
8 | public override void Up()
9 | {
10 | CreateTable(
11 | "dbo.OrderItems",
12 | c => new
13 | {
14 | Id = c.Int(nullable: false, identity: true),
15 | OrderId = c.Int(nullable: false),
16 | Name = c.String(),
17 | Price = c.Int(nullable: false),
18 | Quantity = c.Int(nullable: false),
19 | })
20 | .PrimaryKey(t => t.Id)
21 | .ForeignKey("dbo.Orders", t => t.OrderId, cascadeDelete: true)
22 | .Index(t => t.OrderId);
23 |
24 | CreateTable(
25 | "dbo.Orders",
26 | c => new
27 | {
28 | Id = c.Int(nullable: false, identity: true),
29 | ApplicationUserId = c.Int(nullable: false),
30 | OrderDate = c.DateTime(nullable: false),
31 | Total = c.Int(nullable: false),
32 | PayPalReference = c.String(),
33 | ApplicationUser_Id = c.String(maxLength: 128),
34 | })
35 | .PrimaryKey(t => t.Id)
36 | .ForeignKey("dbo.AspNetUsers", t => t.ApplicationUser_Id)
37 | .Index(t => t.ApplicationUser_Id);
38 |
39 | }
40 |
41 | public override void Down()
42 | {
43 | DropForeignKey("dbo.OrderItems", "OrderId", "dbo.Orders");
44 | DropForeignKey("dbo.Orders", "ApplicationUser_Id", "dbo.AspNetUsers");
45 | DropIndex("dbo.Orders", new[] { "ApplicationUser_Id" });
46 | DropIndex("dbo.OrderItems", new[] { "OrderId" });
47 | DropTable("dbo.Orders");
48 | DropTable("dbo.OrderItems");
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/BeerPal/Models/Subscription/Plan.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 |
6 | namespace BeerPal.Models.Subscription
7 | {
8 | public class Plan
9 | {
10 | public string PayPalPlanId { get; set; }
11 | public string Name { get; set; }
12 | public int Price { get; set; }
13 | public int NumberOfBeers { get; set; }
14 | public string Description1 { get; set; }
15 | public string Description2 { get; set; }
16 |
17 | public static List Plans => new List()
18 | {
19 | new Plan()
20 | {
21 | Name = "Just Browsing Plan",
22 | Price = 500,
23 | PayPalPlanId = "P-27H34871BG666983A2CPYWUI", // Created in the BillingPlansController.
24 | NumberOfBeers = 1,
25 | Description1 = "Nothing else",
26 | Description2 = "Not even a thank you"
27 | },
28 | new Plan()
29 | {
30 | Name = "Let's Do This Plan",
31 | Price = 2495,
32 | PayPalPlanId = "P-6JH27250LV780153N2CPY5MA", // Created in the BillingPlansController.
33 | NumberOfBeers = 6,
34 | Description1 = "Welcome to the club",
35 | Description2 = "Thank you!"
36 | },
37 | new Plan()
38 | {
39 | Name = "Beard Included Plan",
40 | Price = 5995,
41 | PayPalPlanId = "P-94257379NN474020F2CPZDWY", // Created in the BillingPlansController.
42 | NumberOfBeers = 24,
43 | Description1 = "This plan used to be a secret",
44 | Description2 = "Not anymore I guess"
45 | },
46 | new Plan()
47 | {
48 | Name = "Hook It To My Veins Plan",
49 | Price = 10000,
50 | PayPalPlanId = "P-4H181978HP557725N2CPZKOI", // Created in the BillingPlansController.
51 | NumberOfBeers = 48,
52 | Description1 = "My personal plan",
53 | Description2 = "Should I just move in?"
54 | }
55 | };
56 | }
57 |
58 |
59 | }
--------------------------------------------------------------------------------
/BeerPal/Public/js/html5shiv.js:
--------------------------------------------------------------------------------
1 | /*
2 | HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x";
6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML=" ";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d HttpContext.GetOwinContext().Get();
15 |
16 | public ActionResult Index()
17 | {
18 | var subscriptions = _dbContext.Subscriptions.OrderByDescending(x => x.StartDate).Take(50).ToList();
19 |
20 | return View(subscriptions);
21 | }
22 |
23 | public ActionResult Details(string id)
24 | {
25 | var apiContext = GetApiContext();
26 |
27 | var agreement = Agreement.Get(apiContext, id);
28 |
29 | return View(agreement);
30 | }
31 |
32 | public ActionResult Suspend(string id)
33 | {
34 | var apiContext = GetApiContext();
35 |
36 | Agreement.Suspend(apiContext, id, new AgreementStateDescriptor()
37 | {
38 | note = "Suspended"
39 | });
40 |
41 | return RedirectToAction("Details", new {id = id});
42 | }
43 |
44 | public ActionResult Reactivate(string id)
45 | {
46 | var apiContext = GetApiContext();
47 |
48 | Agreement.ReActivate(apiContext, id, new AgreementStateDescriptor()
49 | {
50 | note = "Reactivated"
51 | });
52 |
53 | return RedirectToAction("Details", new {id = id});
54 | }
55 |
56 | public ActionResult Cancel(string id)
57 | {
58 | var apiContext = GetApiContext();
59 |
60 | Agreement.Cancel(apiContext, id, new AgreementStateDescriptor()
61 | {
62 | note = "Cancelled"
63 | });
64 |
65 | return RedirectToAction("Details", new {id = id});
66 | }
67 |
68 |
69 |
70 | private APIContext GetApiContext()
71 | {
72 | // Authenticate with PayPal
73 | var config = ConfigManager.Instance.GetProperties();
74 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
75 | var apiContext = new APIContext(accessToken);
76 | return apiContext;
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/BeerPal/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/BeerPal/Public/css/wizard.less:
--------------------------------------------------------------------------------
1 | .nav-pills {
2 | &.nav-wizard {
3 | > li {
4 | position: relative;
5 | overflow: visible;
6 | border-right: 15px solid transparent;
7 | border-left: 15px solid transparent;
8 |
9 | + li {
10 | margin-left: 0;
11 | }
12 |
13 | &:first-child {
14 | border-left: 0;
15 |
16 | a {
17 | border-radius:5px 0 0 5px;
18 | }
19 | }
20 |
21 | &:last-child {
22 | border-right: 0;
23 |
24 | a {
25 | border-radius:0 5px 5px 0;
26 | }
27 | }
28 |
29 | a {
30 | border-radius: 0;
31 | background-color: #eee;
32 | }
33 |
34 | &:not(:last-child) a:after {
35 | position: absolute;
36 | content: "";
37 | top: 0px;
38 | right: -20px;
39 | width: 0px;
40 | height: 0px;
41 | border-style: solid;
42 | border-width: 20px 0 20px 20px;
43 | border-color: transparent transparent transparent #eee;
44 | z-index: 150;
45 | }
46 |
47 | &:not(:first-child) a:before {
48 | position: absolute;
49 | content: "";
50 | top: 0px;
51 | left: -20px;
52 | width: 0px;
53 | height: 0px;
54 | border-style: solid;
55 | border-width: 20px 0 20px 20px;
56 | border-color: #eee #eee #eee transparent;
57 | z-index: 150;
58 | }
59 |
60 | &:hover {
61 | &:not(:last-child) a:after {
62 | border-color: transparent transparent transparent #aaa;
63 | }
64 |
65 | &:not(:first-child) a:before {
66 | border-color: #aaa #aaa #aaa transparent;
67 | }
68 |
69 | a {
70 | background-color: #aaa;
71 | color: #fff;
72 | }
73 | }
74 |
75 | &.active {
76 | &:not(:last-child) a:after {
77 | border-color: transparent transparent transparent #428bca;
78 | }
79 |
80 | &:not(:first-child) a:before {
81 | border-color: #428bca #428bca #428bca transparent;
82 | }
83 |
84 | a {
85 | background-color: #428bca;
86 | }
87 | }
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Single/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.Single.IndexVm
2 |
3 | @{
4 | ViewBag.Title = "Brewery Tour";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
// Brewery Tour
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | @using (Html.BeginForm("Index", "Single", FormMethod.Post, new {@class = "form-horizontal"}))
23 | {
24 |
Ticket for @Model.TourDate.ToString("dddd, dd MMMM yyyy")
25 |
26 |
35 |
36 |
45 |
46 |
55 |
56 |
61 | }
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/BeerPal/Views/Account/Login.cshtml:
--------------------------------------------------------------------------------
1 | @using BeerPal.Models
2 | @model LoginViewModel
3 | @{
4 | ViewBag.Title = "Log in";
5 | }
6 |
7 | @ViewBag.Title.
8 |
60 |
61 | @section Scripts {
62 | @Scripts.Render("~/bundles/jqueryval")
63 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Sales/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model PayPal.Api.PaymentHistory
2 |
3 | @{
4 | ViewBag.Title = "Sales";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
// Sales
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ID
25 | Created Date
26 | Payment Status
27 | Total
28 | Sale Status
29 |
30 |
31 |
32 |
33 | @foreach (var payment in Model.payments)
34 | {
35 |
36 | @payment.id
37 | @payment.create_time
38 | @payment.state
39 | $@(decimal.Parse(payment.transactions.First().amount.total).ToString("0.00"))
40 | @(payment.transactions.First().related_resources.First(x => x.sale != null).sale.state)
41 |
42 | @if (payment.transactions.First().related_resources.First(x => x.sale != null).sale.state == "refunded")
43 | {
44 | Refunded
45 | }
46 | else
47 | {
48 | using (Html.BeginForm("Refund", "Sales", FormMethod.Post))
49 | {
50 | @Html.Hidden("saleId", payment.transactions.First().related_resources.First(x => x.sale != null).sale.id)
51 | Refund
52 | }
53 | }
54 |
55 |
56 | }
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/WebhookEventsController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Web;
7 | using System.Web.Mvc;
8 | using BeerPal.Extras;
9 | using BeerPal.Models;
10 | using Microsoft.AspNet.Identity.Owin;
11 | using Newtonsoft.Json;
12 | using PayPal.Api;
13 |
14 | namespace BeerPal.Controllers
15 | {
16 | public class WebhookEventsController : Controller
17 | {
18 | private ApplicationDbContext _dbContext => HttpContext.GetOwinContext().Get();
19 |
20 | public ActionResult Index()
21 | {
22 | var webhookEvents = _dbContext.WebhookEvents.OrderByDescending(x => x.DateReceived).Take(50).ToList();
23 |
24 | return View(webhookEvents);
25 | }
26 |
27 | [HttpPost]
28 | public ActionResult SimulateEvent(string eventType)
29 | {
30 | var simlulatableWebhook = new SimulatableWebhook();
31 | var webhookEvent = simlulatableWebhook.SimulateEvent(eventType, "0L586542FF7643339");
32 |
33 | return RedirectToAction("Index");
34 | }
35 |
36 | [HttpPost]
37 | public ActionResult Receive()
38 | {
39 | var apiContext = GetApiContext();
40 |
41 | var postBody = new StreamReader(Request.InputStream).ReadToEnd();
42 | var webhookEvent = JsonConvert.DeserializeObject(postBody);
43 |
44 | // Ensure this event was genuinely sent by PayPal
45 | var isValid = WebhookEvent.ValidateReceivedEvent(apiContext, Request.Headers, postBody, "0L586542FF7643339"); // Last parameter is your Webhook ID
46 |
47 | if (!isValid)
48 | {
49 | return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
50 | }
51 |
52 | var localEvent = new Entities.WebhookEvent()
53 | {
54 | EventType = webhookEvent.event_type,
55 | PayPalWebHookEventId = webhookEvent.id,
56 | ResourceType = webhookEvent.resource_type,
57 | Summary = webhookEvent.summary,
58 | ResourceJson = JsonConvert.SerializeObject(webhookEvent.resource),
59 | DateCreated = DateTime.Parse(webhookEvent.create_time),
60 | DateReceived = DateTime.UtcNow
61 | };
62 | _dbContext.WebhookEvents.Add(localEvent);
63 | _dbContext.SaveChanges();
64 |
65 | return new HttpStatusCodeResult(HttpStatusCode.OK);
66 | }
67 |
68 | private APIContext GetApiContext()
69 | {
70 | // Authenticate with PayPal
71 | var config = ConfigManager.Instance.GetProperties();
72 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
73 | var apiContext = new APIContext(accessToken);
74 | return apiContext;
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/BeerPal/Models/ManageViewModels.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.DataAnnotations;
3 | using Microsoft.AspNet.Identity;
4 | using Microsoft.Owin.Security;
5 |
6 | namespace BeerPal.Models
7 | {
8 | public class IndexViewModel
9 | {
10 | public bool HasPassword { get; set; }
11 | public IList Logins { get; set; }
12 | public string PhoneNumber { get; set; }
13 | public bool TwoFactor { get; set; }
14 | public bool BrowserRemembered { get; set; }
15 | }
16 |
17 | public class ManageLoginsViewModel
18 | {
19 | public IList CurrentLogins { get; set; }
20 | public IList OtherLogins { get; set; }
21 | }
22 |
23 | public class FactorViewModel
24 | {
25 | public string Purpose { get; set; }
26 | }
27 |
28 | public class SetPasswordViewModel
29 | {
30 | [Required]
31 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
32 | [DataType(DataType.Password)]
33 | [Display(Name = "New password")]
34 | public string NewPassword { get; set; }
35 |
36 | [DataType(DataType.Password)]
37 | [Display(Name = "Confirm new password")]
38 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
39 | public string ConfirmPassword { get; set; }
40 | }
41 |
42 | public class ChangePasswordViewModel
43 | {
44 | [Required]
45 | [DataType(DataType.Password)]
46 | [Display(Name = "Current password")]
47 | public string OldPassword { get; set; }
48 |
49 | [Required]
50 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
51 | [DataType(DataType.Password)]
52 | [Display(Name = "New password")]
53 | public string NewPassword { get; set; }
54 |
55 | [DataType(DataType.Password)]
56 | [Display(Name = "Confirm new password")]
57 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
58 | public string ConfirmPassword { get; set; }
59 | }
60 |
61 | public class AddPhoneNumberViewModel
62 | {
63 | [Required]
64 | [Phone]
65 | [Display(Name = "Phone Number")]
66 | public string Number { get; set; }
67 | }
68 |
69 | public class VerifyPhoneNumberViewModel
70 | {
71 | [Required]
72 | [Display(Name = "Code")]
73 | public string Code { get; set; }
74 |
75 | [Required]
76 | [Phone]
77 | [Display(Name = "Phone Number")]
78 | public string PhoneNumber { get; set; }
79 | }
80 |
81 | public class ConfigureTwoFactorViewModel
82 | {
83 | public string SelectedProvider { get; set; }
84 | public ICollection Providers { get; set; }
85 | }
86 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Manage/ManageLogins.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.ManageLoginsViewModel
2 | @using Microsoft.Owin.Security
3 | @{
4 | ViewBag.Title = "Manage your external logins";
5 | }
6 |
7 | @ViewBag.Title.
8 |
9 | @ViewBag.StatusMessage
10 | @{
11 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
12 | if (loginProviders.Count() == 0) {
13 |
14 |
15 | There are no external authentication services configured. See this article
16 | for details on setting up this ASP.NET application to support logging in via external services.
17 |
18 |
19 | }
20 | else
21 | {
22 | if (Model.CurrentLogins.Count > 0)
23 | {
24 | Registered Logins
25 |
53 | }
54 | if (Model.OtherLogins.Count > 0)
55 | {
56 | using (Html.BeginForm("LinkLogin", "Manage"))
57 | {
58 | @Html.AntiForgeryToken()
59 |
60 |
61 | @foreach (AuthenticationDescription p in Model.OtherLogins)
62 | {
63 | @p.AuthenticationType
64 | }
65 |
66 |
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/WebhooksController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Web;
7 | using System.Web.Mvc;
8 | using Microsoft.Owin.Security.Twitter.Messages;
9 | using Newtonsoft.Json;
10 | using PayPal.Api;
11 |
12 | namespace BeerPal.Controllers
13 | {
14 | public class WebhooksController : Controller
15 | {
16 | // GET: Webhooks
17 | public ActionResult Index()
18 | {
19 | var apiContext = GetApiContext();
20 |
21 | var list = Webhook.GetAll(apiContext);
22 |
23 | if (!list.webhooks.Any())
24 | {
25 | SeedWebhooks(apiContext);
26 | list = Webhook.GetAll(apiContext);
27 | }
28 |
29 | return View(list);
30 | }
31 |
32 | [HttpPost]
33 | public ActionResult Delete(string webhookId)
34 | {
35 | var apiContext = GetApiContext();
36 |
37 | var webhook = new Webhook()
38 | {
39 | id = webhookId
40 | };
41 |
42 | webhook.Delete(apiContext);
43 |
44 | return RedirectToAction("Index");
45 | }
46 |
47 | private void SeedWebhooks(APIContext apiContext)
48 | {
49 | var callbackUrl = Url.Action("Receive", "WebhookEvents", null, Request.Url.Scheme);
50 |
51 | if (Request.Url.Host == "localhost")
52 | {
53 | // Replace with your Ngrok tunnel url
54 | callbackUrl = "https://f156f219.ngrok.io/WebhookEvents/Receive";
55 | }
56 |
57 | var everythingWebhook = new Webhook()
58 | {
59 | url = callbackUrl,
60 | event_types = new List
61 | {
62 | new WebhookEventType
63 | {
64 | name = "PAYMENT.SALE.REFUNDED"
65 | },
66 | new WebhookEventType
67 | {
68 | name = "PAYMENT.SALE.REVERSED"
69 | },
70 | new WebhookEventType
71 | {
72 | name = "CUSTOMER.DISPUTE.CREATED"
73 | },
74 | new WebhookEventType
75 | {
76 | name = "BILLING.SUBSCRIPTION.CANCELLED"
77 | },
78 | new WebhookEventType
79 | {
80 | name = "BILLING.SUBSCRIPTION.SUSPENDED"
81 | },
82 | new WebhookEventType
83 | {
84 | name = "BILLING.SUBSCRIPTION.RE-ACTIVATED"
85 | },
86 | }
87 | };
88 | Webhook.Create(apiContext, everythingWebhook);
89 | }
90 |
91 | private APIContext GetApiContext()
92 | {
93 | // Authenticate with PayPal
94 | var config = ConfigManager.Instance.GetProperties();
95 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
96 | var apiContext = new APIContext(accessToken);
97 | return apiContext;
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/BeerPal/Views/WebhookEvents/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 |
3 | @{
4 | ViewBag.Title = "Sales";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
// Webhook Events
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Simulate Events
22 |
23 | @using (Html.BeginForm("SimulateEvent", "WebhookEvents", FormMethod.Post))
24 | {
25 | @Html.Hidden("eventType", "PAYMENT.SALE.REFUNDED")
26 | Payment Refunded
27 | }
28 |
29 |
30 | @using (Html.BeginForm("SimulateEvent", "WebhookEvents", FormMethod.Post))
31 | {
32 | @Html.Hidden("eventType", "BILLING.SUBSCRIPTION.CANCELLED")
33 | Subscription Cancelled
34 | }
35 |
36 |
37 | @using (Html.BeginForm("SimulateEvent", "WebhookEvents", FormMethod.Post))
38 | {
39 | @Html.Hidden("eventType", "CUSTOMER.DISPUTE.CREATED")
40 | Customer Dispute Created
41 | }
42 |
43 |
Notifications may take a few minutes to appear.
44 |
45 |
46 |
47 |
48 |
Received Events
49 |
50 |
51 |
52 | ID
53 | Created Date
54 | Event Type
55 | Summary
56 |
57 |
58 |
59 |
60 | @foreach (var webhookEvent in Model)
61 | {
62 |
63 | @webhookEvent.PayPalWebHookEventId
64 | @webhookEvent.DateCreated (Received: @webhookEvent.DateReceived)
65 | @webhookEvent.EventType
66 | @webhookEvent.Summary
67 |
68 | @using (Html.BeginForm("Resend", "WebhookEvents", FormMethod.Post))
69 | {
70 | @Html.Hidden("webhookEventId", webhookEvent.PayPalWebHookEventId)
71 | Resend
72 | }
73 |
74 |
75 | }
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/BeerPal/Views/Subscription/Purchase.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.Subscription.PurchaseVm
2 |
3 | @{
4 | ViewBag.Title = "Beer Subscription";
5 | }
6 |
7 |
8 |
9 |
10 |
11 |
12 |
// Beer Subscription
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | @using (Html.BeginForm("Purchase", "Subscription", FormMethod.Post, new {@class = "form-horizontal"}))
24 | {
25 | @Html.HiddenFor(x => x.Plan.PayPalPlanId)
26 |
27 |
@Model.Plan.Name
28 |
29 |
38 |
39 |
48 |
49 |
58 |
59 |
64 | }
65 |
66 |
67 |
68 | @Model.Plan.Name
69 |
70 | $ @((Model.Plan.Price / 100M).ToString("0.00"))
71 | Monthly
72 |
73 |
74 | @Model.Plan.NumberOfBeers Beer@(Model.Plan.NumberOfBeers != 1 ? "s" : "")
75 | @Model.Plan.Description1
76 | @Model.Plan.Description2
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/BeerPal/Views/Manage/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.IndexViewModel
2 | @{
3 | ViewBag.Title = "Manage";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @ViewBag.StatusMessage
9 |
10 |
Change your account settings
11 |
12 |
13 | Password:
14 |
15 | [
16 | @if (Model.HasPassword)
17 | {
18 | @Html.ActionLink("Change your password", "ChangePassword")
19 | }
20 | else
21 | {
22 | @Html.ActionLink("Create", "SetPassword")
23 | }
24 | ]
25 |
26 | External Logins:
27 |
28 | @Model.Logins.Count [
29 | @Html.ActionLink("Manage", "ManageLogins") ]
30 |
31 | @*
32 | Phone Numbers can used as a second factor of verification in a two-factor authentication system.
33 |
34 | See this article
35 | for details on setting up this ASP.NET application to support two-factor authentication using SMS.
36 |
37 | Uncomment the following block after you have set up two-factor authentication
38 | *@
39 | @*
40 | Phone Number:
41 |
42 | @(Model.PhoneNumber ?? "None")
43 | @if (Model.PhoneNumber != null)
44 | {
45 |
46 | [ @Html.ActionLink("Change", "AddPhoneNumber") ]
47 | using (Html.BeginForm("RemovePhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
48 | {
49 | @Html.AntiForgeryToken()
50 | [ ]
51 | }
52 | }
53 | else
54 | {
55 | [ @Html.ActionLink("Add", "AddPhoneNumber")
56 | }
57 |
58 | *@
59 | Two-Factor Authentication:
60 |
61 |
62 | There are no two-factor authentication providers configured. See this article
63 | for details on setting up this ASP.NET application to support two-factor authentication.
64 |
65 | @*@if (Model.TwoFactor)
66 | {
67 | using (Html.BeginForm("DisableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
68 | {
69 | @Html.AntiForgeryToken()
70 | Enabled
71 |
72 |
73 | }
74 | }
75 | else
76 | {
77 | using (Html.BeginForm("EnableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
78 | {
79 | @Html.AntiForgeryToken()
80 | Disabled
81 |
82 |
83 | }
84 | }*@
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/BeerPal/Models/AccountViewModels.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace BeerPal.Models
5 | {
6 | public class ExternalLoginConfirmationViewModel
7 | {
8 | [Required]
9 | [Display(Name = "Email")]
10 | public string Email { get; set; }
11 | }
12 |
13 | public class ExternalLoginListViewModel
14 | {
15 | public string ReturnUrl { get; set; }
16 | }
17 |
18 | public class SendCodeViewModel
19 | {
20 | public string SelectedProvider { get; set; }
21 | public ICollection Providers { get; set; }
22 | public string ReturnUrl { get; set; }
23 | public bool RememberMe { get; set; }
24 | }
25 |
26 | public class VerifyCodeViewModel
27 | {
28 | [Required]
29 | public string Provider { get; set; }
30 |
31 | [Required]
32 | [Display(Name = "Code")]
33 | public string Code { get; set; }
34 | public string ReturnUrl { get; set; }
35 |
36 | [Display(Name = "Remember this browser?")]
37 | public bool RememberBrowser { get; set; }
38 |
39 | public bool RememberMe { get; set; }
40 | }
41 |
42 | public class ForgotViewModel
43 | {
44 | [Required]
45 | [Display(Name = "Email")]
46 | public string Email { get; set; }
47 | }
48 |
49 | public class LoginViewModel
50 | {
51 | [Required]
52 | [Display(Name = "Email")]
53 | [EmailAddress]
54 | public string Email { get; set; }
55 |
56 | [Required]
57 | [DataType(DataType.Password)]
58 | [Display(Name = "Password")]
59 | public string Password { get; set; }
60 |
61 | [Display(Name = "Remember me?")]
62 | public bool RememberMe { get; set; }
63 | }
64 |
65 | public class RegisterViewModel
66 | {
67 | [Required]
68 | [EmailAddress]
69 | [Display(Name = "Email")]
70 | public string Email { get; set; }
71 |
72 | [Required]
73 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
74 | [DataType(DataType.Password)]
75 | [Display(Name = "Password")]
76 | public string Password { get; set; }
77 |
78 | [DataType(DataType.Password)]
79 | [Display(Name = "Confirm password")]
80 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
81 | public string ConfirmPassword { get; set; }
82 | }
83 |
84 | public class ResetPasswordViewModel
85 | {
86 | [Required]
87 | [EmailAddress]
88 | [Display(Name = "Email")]
89 | public string Email { get; set; }
90 |
91 | [Required]
92 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
93 | [DataType(DataType.Password)]
94 | [Display(Name = "Password")]
95 | public string Password { get; set; }
96 |
97 | [DataType(DataType.Password)]
98 | [Display(Name = "Confirm password")]
99 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
100 | public string ConfirmPassword { get; set; }
101 |
102 | public string Code { get; set; }
103 | }
104 |
105 | public class ForgotPasswordViewModel
106 | {
107 | [Required]
108 | [EmailAddress]
109 | [Display(Name = "Email")]
110 | public string Email { get; set; }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/BeerPal/App_Start/Startup.Auth.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity;
3 | using BeerPal.Migrations;
4 | using Microsoft.AspNet.Identity;
5 | using Microsoft.AspNet.Identity.Owin;
6 | using Microsoft.Owin;
7 | using Microsoft.Owin.Security.Cookies;
8 | using Microsoft.Owin.Security.Google;
9 | using Owin;
10 | using BeerPal.Models;
11 |
12 | namespace BeerPal
13 | {
14 | public partial class Startup
15 | {
16 | // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
17 | public void ConfigureAuth(IAppBuilder app)
18 | {
19 | // Configure the db context, user manager and signin manager to use a single instance per request
20 | app.CreatePerOwinContext(ApplicationDbContext.Create);
21 | app.CreatePerOwinContext(ApplicationUserManager.Create);
22 | app.CreatePerOwinContext(ApplicationSignInManager.Create);
23 | Database.SetInitializer(new MigrateDatabaseToLatestVersion());
24 |
25 | // Enable the application to use a cookie to store information for the signed in user
26 | // and to use a cookie to temporarily store information about a user logging in with a third party login provider
27 | // Configure the sign in cookie
28 | app.UseCookieAuthentication(new CookieAuthenticationOptions
29 | {
30 | AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
31 | LoginPath = new PathString("/Account/Login"),
32 | Provider = new CookieAuthenticationProvider
33 | {
34 | // Enables the application to validate the security stamp when the user logs in.
35 | // This is a security feature which is used when you change a password or add an external login to your account.
36 | OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(
37 | validateInterval: TimeSpan.FromMinutes(30),
38 | regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
39 | }
40 | });
41 | app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
42 |
43 | // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
44 | app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
45 |
46 | // Enables the application to remember the second login verification factor such as phone or email.
47 | // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
48 | // This is similar to the RememberMe option when you log in.
49 | app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
50 |
51 | // Uncomment the following lines to enable logging in with third party login providers
52 | //app.UseMicrosoftAccountAuthentication(
53 | // clientId: "",
54 | // clientSecret: "");
55 |
56 | //app.UseTwitterAuthentication(
57 | // consumerKey: "",
58 | // consumerSecret: "");
59 |
60 | //app.UseFacebookAuthentication(
61 | // appId: "",
62 | // appSecret: "");
63 |
64 | //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
65 | //{
66 | // ClientId = "",
67 | // ClientSecret = ""
68 | //});
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/BeerPal/Views/ECommerce/Cart.cshtml:
--------------------------------------------------------------------------------
1 | @using Newtonsoft.Json
2 | @model BeerPal.Models.ECommerce.Cart
3 |
4 | @{
5 | ViewBag.Title = "Shopping Cart";
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
// Shopping Cart
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Beer
26 | Price
27 | Quantity
28 | Sub Total
29 |
30 |
31 |
32 |
33 | @foreach (var item in Model.CartItems)
34 | {
35 |
36 | @item.Name
37 | $@((item.Price / 100M).ToString("0.00"))
38 | @item.Quantity
39 | $@(((item.Price * item.Quantity) / 100M).ToString("0.00"))
40 |
41 | @using (Html.BeginForm("Delete", "ECommerce", FormMethod.Post))
42 | {
43 | @Html.Hidden("BeerId", item.BeerId)
44 | Delete
45 | }
46 |
47 |
48 | }
49 | @if (!Model.CartItems.Any())
50 | {
51 |
52 | No items in shopping cart
53 |
54 | }
55 |
56 |
57 |
58 | Total
59 | $@((Model.CartItems.Sum(x => x.Price * x.Quantity) / 100M).ToString("0.00"))
60 |
61 |
62 |
63 |
64 |
70 |
71 | Popup Checkout Experience
72 |
73 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/BeerPal/Public/js/respond.min.js:
--------------------------------------------------------------------------------
1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
3 | window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
4 |
5 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
6 | (function(a){"use strict";function x(){u(!0)}var b={};a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,b.mediaQueriesSupported;var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var a=m.shift();v(a.href,function(b){p(b,a.href,a.media),h[a.href]=!0,setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(a){var b="clientWidth",h=d[b],k="CSS1Compat"===c.compatMode&&h||c.body[b]||h,m={},n=l[l.length-1],o=(new Date).getTime();if(a&&q&&i>o-q)return clearTimeout(r),r=setTimeout(u,i),void 0;q=o;for(var p in e)if(e.hasOwnProperty(p)){var v=e[p],w=v.minw,x=v.maxw,y=null===w,z=null===x,A="em";w&&(w=parseFloat(w)*(w.indexOf(A)>-1?t||s():1)),x&&(x=parseFloat(x)*(x.indexOf(A)>-1?t||s():1)),v.hasquery&&(y&&z||!(y||k>=w)||!(z||x>=k))||(m[v.media]||(m[v.media]=[]),m[v.media].push(f[v.rules]))}for(var B in g)g.hasOwnProperty(B)&&g[B]&&g[B].parentNode===j&&j.removeChild(g[B]);for(var C in m)if(m.hasOwnProperty(C)){var D=c.createElement("style"),E=m[C].join("\n");D.type="text/css",D.media=C,j.insertBefore(D,n.nextSibling),D.styleSheet?D.styleSheet.cssText=E:D.appendChild(c.createTextNode(E)),g.push(D)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)})(this);
7 |
--------------------------------------------------------------------------------
/BeerPal/Scripts/respond.min.js:
--------------------------------------------------------------------------------
1 | /* NUGET: BEGIN LICENSE TEXT
2 | *
3 | * Microsoft grants you the right to use these script files for the sole
4 | * purpose of either: (i) interacting through your browser with the Microsoft
5 | * website or online service, subject to the applicable licensing or use
6 | * terms; or (ii) using the files as included with a Microsoft product subject
7 | * to that product's license terms. Microsoft reserves all other rights to the
8 | * files not expressly granted by Microsoft, whether by implication, estoppel
9 | * or otherwise. Insofar as a script file is dual licensed under GPL,
10 | * Microsoft neither took the code under GPL nor distributes it thereunder but
11 | * under the terms set out in this paragraph. All notices and licenses
12 | * below are for informational purposes only.
13 | *
14 | * NUGET: END LICENSE TEXT */
15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document);
18 |
19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this);
--------------------------------------------------------------------------------
/BeerPal/App_Start/IdentityConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Entity;
4 | using System.Linq;
5 | using System.Security.Claims;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 | using Microsoft.AspNet.Identity;
9 | using Microsoft.AspNet.Identity.EntityFramework;
10 | using Microsoft.AspNet.Identity.Owin;
11 | using Microsoft.Owin;
12 | using Microsoft.Owin.Security;
13 | using BeerPal.Models;
14 |
15 | namespace BeerPal
16 | {
17 | public class EmailService : IIdentityMessageService
18 | {
19 | public Task SendAsync(IdentityMessage message)
20 | {
21 | // Plug in your email service here to send an email.
22 | return Task.FromResult(0);
23 | }
24 | }
25 |
26 | public class SmsService : IIdentityMessageService
27 | {
28 | public Task SendAsync(IdentityMessage message)
29 | {
30 | // Plug in your SMS service here to send a text message.
31 | return Task.FromResult(0);
32 | }
33 | }
34 |
35 | // Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
36 | public class ApplicationUserManager : UserManager
37 | {
38 | public ApplicationUserManager(IUserStore store)
39 | : base(store)
40 | {
41 | }
42 |
43 | public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context)
44 | {
45 | var manager = new ApplicationUserManager(new UserStore(context.Get()));
46 | // Configure validation logic for usernames
47 | manager.UserValidator = new UserValidator(manager)
48 | {
49 | AllowOnlyAlphanumericUserNames = false,
50 | RequireUniqueEmail = true
51 | };
52 |
53 | // Configure validation logic for passwords
54 | manager.PasswordValidator = new PasswordValidator
55 | {
56 | RequiredLength = 6,
57 | RequireNonLetterOrDigit = true,
58 | RequireDigit = true,
59 | RequireLowercase = true,
60 | RequireUppercase = true,
61 | };
62 |
63 | // Configure user lockout defaults
64 | manager.UserLockoutEnabledByDefault = true;
65 | manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
66 | manager.MaxFailedAccessAttemptsBeforeLockout = 5;
67 |
68 | // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
69 | // You can write your own provider and plug it in here.
70 | manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider
71 | {
72 | MessageFormat = "Your security code is {0}"
73 | });
74 | manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider
75 | {
76 | Subject = "Security Code",
77 | BodyFormat = "Your security code is {0}"
78 | });
79 | manager.EmailService = new EmailService();
80 | manager.SmsService = new SmsService();
81 | var dataProtectionProvider = options.DataProtectionProvider;
82 | if (dataProtectionProvider != null)
83 | {
84 | manager.UserTokenProvider =
85 | new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity"));
86 | }
87 | return manager;
88 | }
89 | }
90 |
91 | // Configure the application sign-in manager which is used in this application.
92 | public class ApplicationSignInManager : SignInManager
93 | {
94 | public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
95 | : base(userManager, authenticationManager)
96 | {
97 | }
98 |
99 | public override Task CreateUserIdentityAsync(ApplicationUser user)
100 | {
101 | return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
102 | }
103 |
104 | public static ApplicationSignInManager Create(IdentityFactoryOptions options, IOwinContext context)
105 | {
106 | return new ApplicationSignInManager(context.GetUserManager(), context.Authentication);
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/SubscriptionController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using System.Web.Mvc;
6 | using BeerPal.Entities;
7 | using BeerPal.Models;
8 | using BeerPal.Models.Subscription;
9 | using Microsoft.AspNet.Identity.Owin;
10 | using PayPal.Api;
11 | using Plan = BeerPal.Models.Subscription.Plan;
12 |
13 | namespace BeerPal.Controllers
14 | {
15 | public class SubscriptionController : Controller
16 | {
17 | private ApplicationDbContext _dbContext => HttpContext.GetOwinContext().Get();
18 |
19 | public ActionResult Index()
20 | {
21 | var model = new IndexVm()
22 | {
23 | Plans = Plan.Plans
24 | };
25 |
26 | return View(model);
27 | }
28 |
29 | public ActionResult Purchase(string id)
30 | {
31 | var model = new PurchaseVm()
32 | {
33 | Plan = Plan.Plans.FirstOrDefault(x => x.PayPalPlanId == id)
34 | };
35 |
36 | return View(model);
37 | }
38 |
39 | [HttpPost]
40 | public ActionResult Purchase(PurchaseVm model)
41 | {
42 | var plan = Plan.Plans.FirstOrDefault(x => x.PayPalPlanId == model.Plan.PayPalPlanId);
43 |
44 | if (ModelState.IsValid)
45 | {
46 | // Since we take an Initial Payment (instant payment), the start date of the recurring payments will be next month.
47 | var startDate = DateTime.UtcNow.AddMonths(1);
48 |
49 | var apiContext = GetApiContext();
50 |
51 | var subscription = new Subscription()
52 | {
53 | FirstName = model.FirstName,
54 | LastName = model.LastName,
55 | Email = model.Email,
56 | StartDate = startDate,
57 | PayPalPlanId = plan.PayPalPlanId
58 | };
59 | _dbContext.Subscriptions.Add(subscription);
60 | _dbContext.SaveChanges();
61 |
62 | var agreement = new Agreement()
63 | {
64 | name = plan.Name,
65 | description = $"{plan.NumberOfBeers} beer(s) delivered for ${(plan.Price/100M).ToString("0.00")} each month.",
66 | start_date = startDate.ToString("yyyy-MM-ddTHH:mm:ssZ"),
67 | plan = new PayPal.Api.Plan()
68 | {
69 | id = plan.PayPalPlanId
70 | },
71 | payer = new Payer()
72 | {
73 | payment_method = "paypal"
74 | }
75 | };
76 |
77 | // Send the agreement to PayPal
78 | var createdAgreement = agreement.Create(apiContext);
79 |
80 | // Save the token so we can match the returned request to our subscription.
81 | subscription.PayPalAgreementToken = createdAgreement.token;
82 | _dbContext.SaveChanges();
83 |
84 | // Find the Approval URL to send our user to
85 | var approvalUrl =
86 | createdAgreement.links.FirstOrDefault(
87 | x => x.rel.Equals("approval_url", StringComparison.OrdinalIgnoreCase));
88 |
89 | // Send the user to PayPal to approve the payment
90 | return Redirect(approvalUrl.href);
91 | }
92 |
93 | model.Plan = plan;
94 | return View(model);
95 | }
96 |
97 | public ActionResult Return(string token)
98 | {
99 | var subscription = _dbContext.Subscriptions.FirstOrDefault(x => x.PayPalAgreementToken == token);
100 |
101 | var apiContext = GetApiContext();
102 |
103 | var agreement = new Agreement()
104 | {
105 | token = token
106 | };
107 |
108 | var executedAgreement = agreement.Execute(apiContext);
109 |
110 | // Save the PayPal agreement in our subscription so we can look it up later.
111 | subscription.PayPalAgreementId = executedAgreement.id;
112 | _dbContext.SaveChanges();
113 |
114 | return RedirectToAction("Thankyou");
115 | }
116 |
117 | public ActionResult Cancel()
118 | {
119 | return View();
120 | }
121 |
122 | public ActionResult ThankYou()
123 | {
124 | return View();
125 | }
126 |
127 | private APIContext GetApiContext()
128 | {
129 | // Authenticate with PayPal
130 | var config = ConfigManager.Instance.GetProperties();
131 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
132 | var apiContext = new APIContext(accessToken);
133 | return apiContext;
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/BeerPal/Migrations/201608250815248_InitialMigration.cs:
--------------------------------------------------------------------------------
1 | namespace BeerPal.Migrations
2 | {
3 | using System;
4 | using System.Data.Entity.Migrations;
5 |
6 | public partial class InitialMigration : DbMigration
7 | {
8 | public override void Up()
9 | {
10 | CreateTable(
11 | "dbo.AspNetRoles",
12 | c => new
13 | {
14 | Id = c.String(nullable: false, maxLength: 128),
15 | Name = c.String(nullable: false, maxLength: 256),
16 | })
17 | .PrimaryKey(t => t.Id)
18 | .Index(t => t.Name, unique: true, name: "RoleNameIndex");
19 |
20 | CreateTable(
21 | "dbo.AspNetUserRoles",
22 | c => new
23 | {
24 | UserId = c.String(nullable: false, maxLength: 128),
25 | RoleId = c.String(nullable: false, maxLength: 128),
26 | })
27 | .PrimaryKey(t => new { t.UserId, t.RoleId })
28 | .ForeignKey("dbo.AspNetRoles", t => t.RoleId, cascadeDelete: true)
29 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
30 | .Index(t => t.UserId)
31 | .Index(t => t.RoleId);
32 |
33 | CreateTable(
34 | "dbo.Tickets",
35 | c => new
36 | {
37 | Id = c.Int(nullable: false, identity: true),
38 | FirstName = c.String(),
39 | LastName = c.String(),
40 | Email = c.String(),
41 | TourDate = c.DateTime(nullable: false),
42 | PayPalReference = c.String(),
43 | })
44 | .PrimaryKey(t => t.Id);
45 |
46 | CreateTable(
47 | "dbo.AspNetUsers",
48 | c => new
49 | {
50 | Id = c.String(nullable: false, maxLength: 128),
51 | Email = c.String(maxLength: 256),
52 | EmailConfirmed = c.Boolean(nullable: false),
53 | PasswordHash = c.String(),
54 | SecurityStamp = c.String(),
55 | PhoneNumber = c.String(),
56 | PhoneNumberConfirmed = c.Boolean(nullable: false),
57 | TwoFactorEnabled = c.Boolean(nullable: false),
58 | LockoutEndDateUtc = c.DateTime(),
59 | LockoutEnabled = c.Boolean(nullable: false),
60 | AccessFailedCount = c.Int(nullable: false),
61 | UserName = c.String(nullable: false, maxLength: 256),
62 | })
63 | .PrimaryKey(t => t.Id)
64 | .Index(t => t.UserName, unique: true, name: "UserNameIndex");
65 |
66 | CreateTable(
67 | "dbo.AspNetUserClaims",
68 | c => new
69 | {
70 | Id = c.Int(nullable: false, identity: true),
71 | UserId = c.String(nullable: false, maxLength: 128),
72 | ClaimType = c.String(),
73 | ClaimValue = c.String(),
74 | })
75 | .PrimaryKey(t => t.Id)
76 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
77 | .Index(t => t.UserId);
78 |
79 | CreateTable(
80 | "dbo.AspNetUserLogins",
81 | c => new
82 | {
83 | LoginProvider = c.String(nullable: false, maxLength: 128),
84 | ProviderKey = c.String(nullable: false, maxLength: 128),
85 | UserId = c.String(nullable: false, maxLength: 128),
86 | })
87 | .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId })
88 | .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
89 | .Index(t => t.UserId);
90 |
91 | }
92 |
93 | public override void Down()
94 | {
95 | DropForeignKey("dbo.AspNetUserRoles", "UserId", "dbo.AspNetUsers");
96 | DropForeignKey("dbo.AspNetUserLogins", "UserId", "dbo.AspNetUsers");
97 | DropForeignKey("dbo.AspNetUserClaims", "UserId", "dbo.AspNetUsers");
98 | DropForeignKey("dbo.AspNetUserRoles", "RoleId", "dbo.AspNetRoles");
99 | DropIndex("dbo.AspNetUserLogins", new[] { "UserId" });
100 | DropIndex("dbo.AspNetUserClaims", new[] { "UserId" });
101 | DropIndex("dbo.AspNetUsers", "UserNameIndex");
102 | DropIndex("dbo.AspNetUserRoles", new[] { "RoleId" });
103 | DropIndex("dbo.AspNetUserRoles", new[] { "UserId" });
104 | DropIndex("dbo.AspNetRoles", "RoleNameIndex");
105 | DropTable("dbo.AspNetUserLogins");
106 | DropTable("dbo.AspNetUserClaims");
107 | DropTable("dbo.AspNetUsers");
108 | DropTable("dbo.Tickets");
109 | DropTable("dbo.AspNetUserRoles");
110 | DropTable("dbo.AspNetRoles");
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/BeerPal/Project_Readme.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Your ASP.NET application
6 |
95 |
96 |
97 |
98 |
102 |
103 |
104 |
105 |
This application consists of:
106 |
107 | Sample pages showing basic nav between Home, About, and Contact
108 | Theming using Bootstrap
109 | Authentication , if selected, shows how to register and sign in
110 | ASP.NET features managed using NuGet
111 |
112 |
113 |
114 |
115 |
Customize app
116 |
130 |
131 |
132 |
140 |
141 |
142 |
Get help
143 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/BeerPal/Web.config:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/BeerPal/Scripts/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | /* NUGET: BEGIN LICENSE TEXT
2 | *
3 | * Microsoft grants you the right to use these script files for the sole
4 | * purpose of either: (i) interacting through your browser with the Microsoft
5 | * website or online service, subject to the applicable licensing or use
6 | * terms; or (ii) using the files as included with a Microsoft product subject
7 | * to that product's license terms. Microsoft reserves all other rights to the
8 | * files not expressly granted by Microsoft, whether by implication, estoppel
9 | * or otherwise. Insofar as a script file is dual licensed under GPL,
10 | * Microsoft neither took the code under GPL nor distributes it thereunder but
11 | * under the terms set out in this paragraph. All notices and licenses
12 | * below are for informational purposes only.
13 | *
14 | * NUGET: END LICENSE TEXT */
15 | /*
16 | ** Unobtrusive validation support library for jQuery and jQuery Validate
17 | ** Copyright (C) Microsoft Corporation. All rights reserved.
18 | */
19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a(" ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery);
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | # =========================
255 | # Operating System Files
256 | # =========================
257 |
258 | # OSX
259 | # =========================
260 |
261 | .DS_Store
262 | .AppleDouble
263 | .LSOverride
264 |
265 | # Thumbnails
266 | ._*
267 |
268 | # Files that might appear in the root of a volume
269 | .DocumentRevisions-V100
270 | .fseventsd
271 | .Spotlight-V100
272 | .TemporaryItems
273 | .Trashes
274 | .VolumeIcon.icns
275 |
276 | # Directories potentially created on remote AFP share
277 | .AppleDB
278 | .AppleDesktop
279 | Network Trash Folder
280 | Temporary Items
281 | .apdisk
282 |
283 | # Windows
284 | # =========================
285 |
286 | # Windows image file caches
287 | Thumbs.db
288 | ehthumbs.db
289 |
290 | # Folder config file
291 | Desktop.ini
292 |
293 | # Recycle Bin used on file shares
294 | $RECYCLE.BIN/
295 |
296 | # Windows Installer files
297 | *.cab
298 | *.msi
299 | *.msm
300 | *.msp
301 |
302 | # Windows shortcuts
303 | *.lnk
304 |
--------------------------------------------------------------------------------
/BeerPal/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | BeerPal Brewery
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
39 |
40 |
41 |
42 |
48 |
49 |
50 |
89 |
90 |
91 |
92 |
93 |
94 | @RenderBody()
95 |
96 |
108 |
109 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/BeerPal/Controllers/SingleController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using System.Web.Mvc;
6 | using BeerPal.Entities;
7 | using BeerPal.Models;
8 | using BeerPal.Models.Single;
9 | using Microsoft.AspNet.Identity.Owin;
10 | using PayPal.Api;
11 |
12 | namespace BeerPal.Controllers
13 | {
14 | public class SingleController : Controller
15 | {
16 | private ApplicationDbContext _dbContext => HttpContext.GetOwinContext().Get();
17 |
18 | public ActionResult Index()
19 | {
20 | var model = GetNextTourInfo();
21 |
22 | return View(model);
23 | }
24 |
25 | [HttpPost]
26 | public ActionResult Index(IndexVm model)
27 | {
28 | if (ModelState.IsValid)
29 | {
30 | // Fetch the tour info from the server and NOT from the POST data.
31 | // Otherwise users could manipulate the data
32 | var tourInfo = GetNextTourInfo();
33 |
34 | // Create a Ticket object to store info about the purchaser
35 | var ticket = new Ticket()
36 | {
37 | FirstName = model.FirstName,
38 | LastName = model.LastName,
39 | Email = model.Email,
40 | TourDate = tourInfo.TourDate
41 | };
42 | _dbContext.Tickets.Add(ticket);
43 | _dbContext.SaveChanges();
44 |
45 | // Get PayPal API Context using configuration from web.config
46 | var apiContext = GetApiContext();
47 |
48 | // Create a new payment object
49 | var payment = new Payment
50 | {
51 | experience_profile_id = "XP-HMV5-V9ES-8QXN-2F33", // Created in the WebExperienceProfilesController. This one is for DigitalGoods.
52 | intent = "sale",
53 | payer = new Payer
54 | {
55 | payment_method = "paypal"
56 | },
57 | transactions = new List
58 | {
59 | new Transaction
60 | {
61 | description = $"Brewery Tour (Single Payment) for {tourInfo.TourDate:dddd, dd MMMM yyyy}",
62 | amount = new Amount
63 | {
64 | currency = "USD",
65 | total = (tourInfo.Price/100M).ToString(), // PayPal expects string amounts, eg. "20.00"
66 | },
67 | item_list = new ItemList()
68 | {
69 | items = new List- ()
70 | {
71 | new Item()
72 | {
73 | description = $"Brewery Tour (Single Payment) for {tourInfo.TourDate:dddd, dd MMMM yyyy}",
74 | currency = "USD",
75 | quantity = "1",
76 | price = (tourInfo.Price/100M).ToString(), // PayPal expects string amounts, eg. "20.00"
77 | }
78 | }
79 | }
80 | }
81 | },
82 | redirect_urls = new RedirectUrls
83 | {
84 | return_url = Url.Action("Return", "Single", null, Request.Url.Scheme),
85 | cancel_url = Url.Action("Cancel", "Single", null, Request.Url.Scheme)
86 | }
87 | };
88 |
89 | // Send the payment to PayPal
90 | var createdPayment = payment.Create(apiContext);
91 |
92 | // Save a reference to the paypal payment
93 | ticket.PayPalReference = createdPayment.id;
94 | _dbContext.SaveChanges();
95 |
96 | // Find the Approval URL to send our user to
97 | var approvalUrl =
98 | createdPayment.links.FirstOrDefault(
99 | x => x.rel.Equals("approval_url", StringComparison.OrdinalIgnoreCase));
100 |
101 | // Send the user to PayPal to approve the payment
102 | return Redirect(approvalUrl.href);
103 | }
104 |
105 | return View(model);
106 | }
107 |
108 | public ActionResult Return(string payerId, string paymentId)
109 | {
110 | // Fetch the existing ticket
111 | var ticket = _dbContext.Tickets.FirstOrDefault(x => x.PayPalReference == paymentId);
112 |
113 | // Get PayPal API Context using configuration from web.config
114 | var apiContext = GetApiContext();
115 |
116 | // Set the payer for the payment
117 | var paymentExecution = new PaymentExecution()
118 | {
119 | payer_id = payerId
120 | };
121 |
122 | // Identify the payment to execute
123 | var payment = new Payment()
124 | {
125 | id = paymentId
126 | };
127 |
128 | // Execute the Payment
129 | var executedPayment = payment.Execute(apiContext, paymentExecution);
130 |
131 | return RedirectToAction("Thankyou");
132 | }
133 |
134 | public ActionResult Cancel()
135 | {
136 | return View();
137 | }
138 |
139 | public ActionResult ThankYou()
140 | {
141 | return View();
142 | }
143 |
144 | private IndexVm GetNextTourInfo()
145 | {
146 | return new IndexVm()
147 | {
148 | // Always set tour for tomorrow
149 | TourDate = DateTime.Today.AddDays(1),
150 | // Represent price in cents to avoid rounding errors
151 | Price = 2000
152 | };
153 | }
154 |
155 | private APIContext GetApiContext()
156 | {
157 | // Authenticate with PayPal
158 | var config = ConfigManager.Instance.GetProperties();
159 | var accessToken = new OAuthTokenCredential(config).GetAccessToken();
160 | var apiContext = new APIContext(accessToken);
161 | return apiContext;
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/BeerPal/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model BeerPal.Models.Home.IndexVm
2 | @{
3 | ViewBag.Title = "Home Page";
4 | }
5 |
6 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Integration Options
39 |
I demonstrate three ways to integrate with PayPal.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Single Payment
50 |
Take a one off payment.
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
E-Commerce
59 |
Shopping cart style checkout process, one off payment, but shows products selected.
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
Subscription
68 |
Sign up customers to a recurring billing plan.
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
Brewery Tour (Single Payment)
83 |
Come along for a great tour of our brewery.
84 |
Next Event
85 |
86 | @(DateTime.Today.AddDays(1).ToString("dddd, dd MMMM yyyy"))
87 |
88 |
Buy Ticket Now ($20)
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
Online Beer Shopping (E-Commerce)
100 |
The best of the beers most wanted in @DateTime.Today.Year
101 |
102 |
103 |
104 |
105 | @foreach (var beer in Model.Beers)
106 | {
107 |
108 | @using (Html.BeginForm("Add", "ECommerce", FormMethod.Post))
109 | {
110 | @Html.Hidden("BeerId", beer.Id)
111 |
112 |
113 |
114 |
@beer.Name
115 | $@((beer.Price/100M).ToString("0.00"))
116 |
117 |
118 |
119 |
120 |
121 | }
122 |
123 | }
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
Beer Delivery Subscription (Subscription Billing)
134 |
Beer delivered to your door, can it get any better?
135 |
136 |
137 |
138 |
139 | @foreach (var plan in Model.Plans)
140 | {
141 |
142 |
143 | @plan.Name
144 | $ @((plan.Price / 100M).ToString("0.00"))
145 | Monthly
146 |
147 | @plan.NumberOfBeers Beer@(plan.NumberOfBeers != 1 ? "s" : "")
148 | @plan.Description1
149 | @plan.Description2
150 |
151 |
152 |
153 | }
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
21 | @foreach (AuthenticationDescription p in loginProviders) { 22 | 23 | } 24 |
25 |