다음을 통해 공유


PayPal로 체크 아웃 및 지불

작성자 : Erik Reitan

Wingtip Toys 샘플 프로젝트 다운로드(C#) 또는 전자책 다운로드(PDF)

이 자습서 시리즈에서는 웹용 ASP.NET 4.5 및 Microsoft Visual Studio Express 2013을 사용하여 ASP.NET Web Forms 애플리케이션을 빌드하는 기본 사항을 설명합니다. C# 소스 코드가 포함된 Visual Studio 2013 프로젝트는 이 자습서 시리즈와 함께 사용할 수 있습니다.

이 자습서에서는 PayPal을 사용하여 사용자 권한 부여, 등록 및 결제를 포함하도록 Wingtip Toys 샘플 애플리케이션을 수정하는 방법을 설명합니다. 로그인한 사용자만 제품을 구매할 수 있는 권한이 부여됩니다. ASP.NET 4.5 Web Forms 프로젝트 템플릿의 기본 제공 사용자 등록 기능에는 필요한 많은 기능이 이미 포함되어 있습니다. PayPal Express 체크 아웃 기능을 추가합니다. 이 자습서에서는 PayPal 개발자 테스트 환경을 사용하므로 실제 자금은 이체되지 않습니다. 자습서가 끝나면 쇼핑 카트에 추가할 제품을 선택하고 체크 아웃 단추를 클릭하고 데이터를 PayPal 테스트 웹 사이트로 전송하여 애플리케이션을 테스트합니다. PayPal 테스트 웹 사이트에서 배송 및 결제 정보를 확인한 다음, 로컬 Wingtip Toys 샘플 애플리케이션으로 돌아가 구매를 확인하고 완료합니다.

확장성 및 보안을 다루는 온라인 쇼핑을 전문으로 하는 몇 가지 숙련된 타사 결제 프로세서가 있습니다. ASP.NET 개발자는 쇼핑 및 구매 솔루션을 구현하기 전에 타사 결제 솔루션을 활용하는 이점을 고려해야 합니다.

참고

Wingtip Toys 샘플 애플리케이션은 ASP.NET 웹 개발자가 사용할 수 있는 특정 ASP.NET 개념 및 기능을 표시하도록 설계되었습니다. 이 샘플 애플리케이션은 확장성 및 보안과 관련하여 가능한 모든 상황에 최적화되지 않았습니다.

학습할 내용:

  • 폴더의 특정 페이지에 대한 액세스를 제한하는 방법입니다.
  • 익명 쇼핑 카트에서 알려진 쇼핑 카트를 만드는 방법.
  • 프로젝트에 대해 SSL을 사용하도록 설정하는 방법입니다.
  • 프로젝트에 OAuth 공급자를 추가하는 방법입니다.
  • PayPal을 사용하여 PayPal 테스트 환경을 사용하여 제품을 구매하는 방법입니다.
  • DetailsView 컨트롤에 PayPal의 세부 정보를 표시하는 방법입니다.
  • PayPal에서 얻은 세부 정보로 Wingtip Toys 애플리케이션의 데이터베이스를 업데이트하는 방법입니다.

주문 추적 추가

이 자습서에서는 사용자가 만든 순서의 데이터를 추적하는 두 개의 새 클래스를 만듭니다. 클래스는 배송 정보, 구매 총액 및 결제 확인과 관련된 데이터를 추적합니다.

Order 및 OrderDetail 모델 클래스 추가

이 자습서 시리즈의 앞부분에서는 Models 폴더에 , Product및 클래스를 만들어 Category범주, 제품 및 CartItem 쇼핑 카트 항목에 대한 스키마를 정의했습니다. 이제 제품 주문에 대한 스키마와 주문 세부 정보를 정의하는 두 개의 새 클래스를 추가합니다.

  1. Models 폴더에 Order.cs라는 새 클래스를 추가합니다.
    새 클래스 파일이 편집기에서 표시됩니다.

  2. 기본 코드를 다음 코드로 바꿉니다.

    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Collections.Generic;
    using System.ComponentModel;
    
    namespace WingtipToys.Models
    {
      public class Order
      {
        public int OrderId { get; set; }
    
        public DateTime OrderDate { get; set; }
    
        public string Username { get; set; }
    
        [Required(ErrorMessage = "First Name is required")]
        [DisplayName("First Name")]
        [StringLength(160)]
        public string FirstName { get; set; }
    
        [Required(ErrorMessage = "Last Name is required")]
        [DisplayName("Last Name")]
        [StringLength(160)]
        public string LastName { get; set; }
    
        [Required(ErrorMessage = "Address is required")]
        [StringLength(70)]
        public string Address { get; set; }
    
        [Required(ErrorMessage = "City is required")]
        [StringLength(40)]
        public string City { get; set; }
    
        [Required(ErrorMessage = "State is required")]
        [StringLength(40)]
        public string State { get; set; }
    
        [Required(ErrorMessage = "Postal Code is required")]
        [DisplayName("Postal Code")]
        [StringLength(10)]
        public string PostalCode { get; set; }
    
        [Required(ErrorMessage = "Country is required")]
        [StringLength(40)]
        public string Country { get; set; }
    
        [StringLength(24)]
        public string Phone { get; set; }
    
        [Required(ErrorMessage = "Email Address is required")]
        [DisplayName("Email Address")]
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
            ErrorMessage = "Email is is not valid.")]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
    
        [ScaffoldColumn(false)]
        public decimal Total { get; set; }
    
        [ScaffoldColumn(false)]
        public string PaymentTransactionId { get; set; }
    
        [ScaffoldColumn(false)]
        public bool HasBeenShipped { get; set; }
    
        public List<OrderDetail> OrderDetails { get; set; }
      }
    }
    
  3. Models 폴더에 OrderDetail.cs 클래스를 추가합니다.

  4. 기본 코드를 다음 코드로 바꿉니다.

    using System.ComponentModel.DataAnnotations;
    
    namespace WingtipToys.Models
    {
        public class OrderDetail
        {
            public int OrderDetailId { get; set; }
    
            public int OrderId { get; set; }
    
            public string Username { get; set; }
    
            public int ProductId { get; set; }
    
            public int Quantity { get; set; }
    
            public double? UnitPrice { get; set; }
    
        }
    }
    

OrderDetail 클래스에는 Order 구매 및 배송에 사용되는 주문 정보를 정의하는 스키마가 포함되어 있습니다.

또한 엔터티 클래스를 관리하고 데이터베이스에 대한 데이터 액세스를 제공하는 데이터베이스 컨텍스트 클래스를 업데이트해야 합니다. 이렇게 하려면 새로 만든 Order 및 OrderDetail model 클래스를 클래스에 ProductContext 추가합니다.

  1. 솔루션 탐색기ProductContext.cs 파일을 찾아 엽니다.

  2. 아래와 같이 강조 표시된 코드를 ProductContext.cs 파일에 추가합니다.

    using System.Data.Entity;
    
    namespace WingtipToys.Models
    {
      public class ProductContext : DbContext
      {
        public ProductContext()
          : base("WingtipToys")
        {
        }
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<CartItem> ShoppingCartItems { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderDetail> OrderDetails { get; set; }
      }
    }
    

이 자습서 시리즈의 앞에서 설명한 것처럼 ProductContext.cs 파일의 코드는 Entity Framework의 모든 핵심 기능에 액세스할 수 있도록 네임스페이스를 추가합니다 System.Data.Entity . 이 기능에는 강력한 형식의 개체를 사용하여 데이터를 쿼리, 삽입, 업데이트 및 삭제하는 기능이 포함됩니다. 클래스의 ProductContext 위의 코드는 새로 추가 Order 된 및 OrderDetail 클래스에 Entity Framework 액세스를 추가합니다.

체크 아웃 액세스 추가

Wingtip Toys 샘플 애플리케이션을 사용하면 익명 사용자가 쇼핑 카트에 제품을 검토하고 추가할 수 있습니다. 그러나 익명 사용자가 쇼핑 카트에 추가한 제품을 구매하도록 선택하면 사이트에 로그온해야 합니다. 로그온한 후에는 체크 아웃 및 구매 프로세스를 처리하는 웹 애플리케이션의 제한된 페이지에 액세스할 수 있습니다. 이러한 제한된 페이지는 애플리케이션의 체크 아웃 폴더에 포함됩니다.

체크 아웃 폴더 및 페이지 추가

이제 체크 아웃 폴더와 체크 아웃 프로세스 중에 고객이 볼 수 있는 페이지를 만듭니다. 이 자습서의 뒷부분에서 이러한 페이지를 업데이트합니다.

  1. 솔루션 탐색기 프로젝트 이름(Wingtip Toys)을 마우스 오른쪽 단추로 클릭하고 새 폴더 추가를 선택합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 새 폴더

  2. 새 폴더 이름을 Checkout으로 지정합니다.

  3. 체크 아웃 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가->새 항목을 선택합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 새 항목

  4. 새 항목 추가 대화 상자가 표시됩니다.

  5. 왼쪽에서 Visual C# ->Web 템플릿 그룹을 선택합니다. 그런 다음 가운데 창에서 마스터 페이지가 있는 웹 양식을선택하고 이름을 CheckoutStart.aspx로 지정합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 새 항목 추가 대화 상자

  6. 이전과 마찬가지로 Site.Master 파일을 master 페이지로 선택합니다.

  7. 위의 동일한 단계를 사용하여 Checkout 폴더에 다음 추가 페이지를 추가합니다.

    • CheckoutReview.aspx
    • CheckoutComplete.aspx
    • CheckoutCancel.aspx
    • CheckoutError.aspx

Web.config 파일 추가

Web.config 파일을 체크 아웃 폴더에 추가하면 폴더에 포함된 모든 페이지에 대한 액세스를 제한할 수 있습니다.

  1. 체크 아웃 폴더를 마우스 오른쪽 단추로 클릭하고 추가 ->새 항목을 선택합니다.
    새 항목 추가 대화 상자가 표시됩니다.

  2. 왼쪽에서 Visual C# ->Web 템플릿 그룹을 선택합니다. 그런 다음 가운데 창에서 웹 구성 파일을 선택하고 Web.config기본 이름을 그대로 적용한 다음 추가를 선택합니다.

  3. Web.config 파일의 기존 XML 콘텐츠를 다음으로 바꿉니다.

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <authorization>
          <deny users="?"/>
        </authorization>
      </system.web>
    </configuration>
    
  4. Web.config 파일을 저장합니다.

Web.config 파일은 웹 애플리케이션의 알 수 없는 모든 사용자가 체크 아웃 폴더에 포함된 페이지에 대한 액세스를 거부해야 한다고 지정합니다. 그러나 사용자가 계정을 등록하고 로그온한 경우 알려진 사용자가 되며 체크 아웃 폴더의 페이지에 액세스할 수 있습니다.

ASP.NET 구성은 계층 구조를 따릅니다. 여기서 각 Web.config 파일은 해당 폴더에 있는 폴더와 그 아래의 모든 자식 디렉터리에 구성 설정을 적용합니다.

프로젝트에 SSL 사용

SSL(Secure Sockets Layer)은 웹 서버 및 웹 클라이언트가 암호화를 사용하여 더욱 안전하게 통신할 수 있도록 정의된 프로토콜입니다. SSL을 사용하지 않으면 네트워크에 실제로 액세스하여 패킷을 스니핑하는 누군가에게 클라이언트와 서버 간에 전송된 데이터가 노출됩니다. 또한 몇 가지 일반적인 인증 체계는 일반 HTTP를 통해서는 보호되지 않습니다. 특히, 기본 인증 및 폼 인증은 암호화되지 않은 자격 증명을 보냅니다. 보안을 위해서 인증 체계는 SSL을 사용해야 합니다.

  1. 솔루션 탐색기WingtipToys 프로젝트를 클릭한 다음 F4 키를 눌러 속성 창을 표시합니다.
  2. SSL 사용true로 변경합니다.
  3. 나중에 사용할 수 있도록 SSL URL 을 복사합니다.
    SSL URL은 https://localhost:44300/ 이전에 SSL 웹 사이트를 만들지 않은 경우(아래와 같이) 됩니다.
    프로젝트 속성
  4. 솔루션 탐색기WingtipToys 프로젝트를 마우스 오른쪽 단추로 클릭하고 속성을 클릭합니다.
  5. 왼쪽 탭에서 을 클릭합니다.
  6. 이전에 저장한 SSL URL을 사용하도록 프로젝트 URL을 변경합니다.
    프로젝트 웹 속성
  7. CTRL+S키를 눌러 페이지를 저장합니다.
  8. Ctrl+F5를 눌러 애플리케이션을 실행합니다. Visual Studio에서 SSL 경고를 방지할 수 있도록 하는 옵션을 표시합니다.
  9. 를 클릭하여 IIS Express SSL 인증서를 신뢰하고 계속합니다.
    SSL 인증서 세부 정보 IIS Express
    보안 경고가 표시됩니다.
  10. 를 클릭하여 인증서를 localhost에 설치합니다.
    보안 경고 대화 상자
    브라우저 창이 표시됩니다.

이제 SSL을 사용하여 웹 애플리케이션을 로컬로 쉽게 테스트할 수 있습니다.

OAuth 2.0 공급자 추가

ASP.NET Web Forms는 멤버 자격 및 인증을 위해 개선된 옵션을 제공합니다. 이러한 개선 사항에는 OAuth가 포함됩니다. OAuth는 웹, 모바일 및 데스크톱 애플리케이션에서 간단한 표준 메서드로 보안 권한 부여를 허용하는 개방형 프로토콜입니다. ASP.NET Web Forms 템플릿은 OAuth를 사용하여 Facebook, Twitter, Google 및 Microsoft를 인증 공급자로 노출합니다. 이 자습서에서는 인증 공급자로 Google만 사용하지만 다른 공급자를 사용하도록 코드를 쉽게 수정할 수 있습니다. 다른 공급자를 구현하는 단계도 이 자습서에 표시되는 단계와 매우 비슷합니다.

자습서에서는 인증 외에도 역할을 사용하여 권한 부여를 구현합니다. canEdit 역할에 추가한 사용자만 데이터를 변경할 수 있습니다(연락처 만들기, 편집 또는 삭제).

참고

Windows Live 애플리케이션은 작동하는 웹 사이트에 대한 라이브 URL만 허용하므로 로그인 테스트에 로컬 웹 사이트 URL을 사용할 수 없습니다.

다음 단계를 통해 Google 인증 공급자를 추가할 수 있습니다.

  1. App_Start\Startup.Auth.cs 파일을 엽니다.

  2. 메서드가 다음과 같이 나타나도록 app.UseGoogleAuthentication() 메서드에서 주석 문자를 제거합니다.

    app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
    {
        ClientId = "",
        ClientSecret = ""
    });
    
  3. Google Developers Console로 이동합니다. Google 개발자 메일 계정(gmail.com)으로 로그인해야 합니다. Google 계정이 없으면 계정 만들기 링크를 선택합니다.
    다음으로, Google 개발자 콘솔이 표시됩니다.
    Google 개발자 콘솔

  4. 프로젝트 만들기 단추를 클릭하고 프로젝트 이름 및 ID를 입력합니다(기본값을 사용할 수 있음). 그런 다음 규약 확인란만들기 단추를 클릭합니다.

    Google - 새 프로젝트

    몇 초 내에 새 프로젝트가 만들어지고 브라우저에 새 프로젝트 페이지가 표시됩니다.

  5. 왼쪽 탭에서 인증을 & API를 클릭한 다음 자격 증명을 클릭합니다.

  6. OAuth에서 새 클라이언트 ID 만들기를 클릭합니다.
    Create Client ID 대화 상자가 표시됩니다.
    Google - 클라이언트 ID 만들기

  7. 클라이언트 ID 만들기 대화 상자에서 애플리케이션 유형에 대한 기본 웹 애플리케이션을 유지합니다.

  8. 권한 있는 JavaScript 원본을 이 자습서의 앞부분에서 사용한 SSL URL(https://localhost:44300/다른 SSL 프로젝트를 만들지 않은 경우)으로 설정합니다.
    이 URL이 애플리케이션의 원점입니다. 이 샘플의 경우 localhost 테스트 URL만 입력합니다. 그러나 localhost 및 프로덕션을 고려하기 위해 여러 URL을 입력할 수 있습니다.

  9. Authorized Redirect URI 를 다음으로 설정합니다.

    https://localhost:44300/signin-google
    

    이 값은 ASP.NET OAuth 사용자가 Google OAuth 서버와 통신하는 데 사용하는 URI입니다. 위에서 사용한 SSL URL( https://localhost:44300/ 다른 SSL 프로젝트를 만들지 않은 경우)을 기억하세요.

  10. 클라이언트 ID 만들기 단추를 클릭합니다.

  11. Google 개발자 콘솔의 왼쪽 메뉴에서 동의 화면 메뉴 항목을 클릭한 다음 전자 메일 주소 및 제품 이름을 설정합니다. 양식을 완료했으면 저장을 클릭합니다.

  12. API 메뉴 항목을 클릭하고 아래로 스크롤한 다음 Google+ API 옆에 있는 끄기 단추를 클릭합니다.
    이 옵션을 수락하면 Google+ API가 활성화됩니다.

  13. 또한 Microsoft.Owin NuGet 패키지를 버전 3.0.0으로 업데이트해야 합니다.
    도구 메뉴에서 NuGet 패키지 관리자를 선택한 다음 솔루션용 NuGet 패키지 관리를 선택합니다.
    NuGet 패키지 관리 창에서 Microsoft.Owin 패키지를 찾아서 버전 3.0.0으로 업데이트합니다.

  14. Visual Studio에서 클라이언트 ID클라이언트 암호를 복사하여 메서드에 붙여넣어 Startup.Auth.cs 페이지의 메서드를 업데이트 UseGoogleAuthentication 합니다. 아래 표시된 클라이언트 ID클라이언트 암호 값은 샘플이며 작동하지 않습니다.

    using System;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.DataProtection;
    using Microsoft.Owin.Security.Google;
    using Owin;
    using WingtipToys.Models;
    
    namespace WingtipToys
    {
        public partial class Startup {
    
            // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301883
            public void ConfigureAuth(IAppBuilder app)
            {
                // Configure the db context, user manager and signin manager to use a single instance per request
                app.CreatePerOwinContext(ApplicationDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
                app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
    
                // Enable the application to use a cookie to store information for the signed in user
                // and to use a cookie to temporarily store information about a user logging in with a third party login provider
                // Configure the sign in cookie
                app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/Account/Login"),
                    Provider = new CookieAuthenticationProvider
                    {
                        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                    }
                });
                // Use a cookie to temporarily store information about a user logging in with a third party login provider
                app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
                // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
                app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
    
                // Enables the application to remember the second login verification factor such as phone or email.
                // 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.
                // This is similar to the RememberMe option when you log in.
                app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    
                // Uncomment the following lines to enable logging in with third party login providers
                //app.UseMicrosoftAccountAuthentication(
                //    clientId: "",
                //    clientSecret: "");
    
                //app.UseTwitterAuthentication(
                //   consumerKey: "",
                //   consumerSecret: "");
    
                //app.UseFacebookAuthentication(
                //   appId: "",
                //   appSecret: "");
    
                app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
                {
                  ClientId = "000000000000.apps.googleusercontent.com",
                  ClientSecret = "00000000000"
                });
            }
        }
    }
    
  15. Ctrl+F5를 눌러 애플리케이션을 빌드하고 실행합니다. 로그인 링크를 클릭합니다.

  16. 다른 서비스를 사용하여 로그인에서Google을 클릭합니다.
    로그인

  17. 자격 증명을 입력해야 하는 경우 자격 증명을 입력할 google 사이트로 리디렉션됩니다.
    Google - 로그인

  18. 자격 증명을 입력하면 방금 만든 웹 애플리케이션에 권한을 부여하라는 메시지가 표시됩니다.
    프로젝트 기본 서비스 계정

  19. Accept를 클릭합니다. 이제 Google 계정을 등록할 수 있는 WingtipToys 애플리케이션의 등록 페이지로 다시 리디렉션됩니다.
    Google 계정에 등록

  20. Gmail 계정에 사용되는 로컬 전자 메일 등록 이름을 변경할 수 있지만 일반적으로 기본 전자 메일 별칭(즉, 인증에 사용하는 별칭)을 그대로 사용합니다. 위와 같이 로그인 을 클릭합니다.

로그인 기능 수정

이 자습서 시리즈에서 앞서 설명한 것처럼 대부분의 사용자 등록 기능은 기본적으로 ASP.NET Web Forms 템플릿에 포함되어 있습니다. 이제 기본 Login.aspxRegister.aspx 페이지를 수정하여 메서드를 호출합니다 MigrateCart . 메서드는 MigrateCart 새로 로그인한 사용자를 익명 쇼핑 카트와 연결합니다. Wingtip Toys 샘플 애플리케이션은 사용자와 쇼핑 카트를 연결하여 방문 사이에 사용자의 쇼핑 카트를 유지할 수 있습니다.

  1. 솔루션 탐색기계정 폴더를 찾아 엽니다.

  2. 다음과 같이 표시되도록 Login.aspx.cs 라는 코드 숨김 페이지를 수정하여 노란색으로 강조 표시된 코드를 포함합니다.

    using System;
    using System.Web;
    using System.Web.UI;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Owin;
    using WingtipToys.Models;
    
    namespace WingtipToys.Account
    {
        public partial class Login : Page
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                RegisterHyperLink.NavigateUrl = "Register";
                // Enable this once you have account confirmation enabled for password reset functionality
                //ForgotPasswordHyperLink.NavigateUrl = "Forgot";
                OpenAuthLogin.ReturnUrl = Request.QueryString["ReturnUrl"];
                var returnUrl = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
                if (!String.IsNullOrEmpty(returnUrl))
                {
                    RegisterHyperLink.NavigateUrl += "?ReturnUrl=" + returnUrl;
                }
            }
    
            protected void LogIn(object sender, EventArgs e)
            {
                if (IsValid)
                {
                    // Validate the user password
                    var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
                    var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>();
    
                    // This doen't count login failures towards account lockout
                    // To enable password failures to trigger lockout, change to shouldLockout: true
                    var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false);
    
                    switch (result)
                    {
                        case SignInStatus.Success:
                            WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions();
                            String cartId = usersShoppingCart.GetCartId();
                            usersShoppingCart.MigrateCart(cartId, Email.Text);
    
                            IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
                            break;
                        case SignInStatus.LockedOut:
                            Response.Redirect("/Account/Lockout");
                            break;
                        case SignInStatus.RequiresVerification:
                            Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", 
                                                            Request.QueryString["ReturnUrl"],
                                                            RememberMe.Checked),
                                              true);
                            break;
                        case SignInStatus.Failure:
                        default:
                            FailureText.Text = "Invalid login attempt";
                            ErrorMessage.Visible = true;
                            break;
                    }
                }
            }
        }
    }
    
  3. Login.aspx.cs 파일을 저장합니다.

지금은 메서드에 대한 MigrateCart 정의가 없다는 경고를 무시할 수 있습니다. 이 자습서의 뒷부분에서 조금 더 추가할 예정입니다.

Login.aspx.cs 코드 숨김 파일은 LogIn 메서드를 지원합니다. Login.aspx 페이지를 검사하면 이 페이지에는 클릭 시 코드 숨김에서 처리기를 트리거하는 "로그인" 단추가 포함됩니다 LogIn .

LoginLogin.aspx.cs의 메서드가 호출되면 라는 usersShoppingCart 쇼핑 카트의 새 instance 만들어집니다. 쇼핑 카트의 ID(GUID)가 검색되어 변수로 cartId 설정됩니다. 그런 다음, 메서드가 MigrateCart 호출되어 로그인한 사용자의 이름과 를 모두 cartId 이 메서드에 전달합니다. 장바구니를 마이그레이션하면 익명 쇼핑 카트를 식별하는 데 사용되는 GUID가 사용자 이름으로 대체됩니다.

사용자가 로그인할 때 쇼핑 카트를 마이그레이션하도록 Login.aspx.cs 코드 숨김 파일을 수정하는 것 외에도 Register.aspx.cs 코드 숨김 파일을 수정하여 사용자가 새 계정을 만들고 로그인할 때 쇼핑 카트를 마이그레이션해야 합니다.

  1. 계정 폴더에서 Register.aspx.cs라는 코드 숨김 파일을 엽니다.

  2. 다음과 같이 표시되도록 코드를 노란색으로 포함하여 코드 숨김 파일을 수정합니다.

    using System;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Owin;
    using WingtipToys.Models;
    
    namespace WingtipToys.Account
    {
        public partial class Register : Page
        {
            protected void CreateUser_Click(object sender, EventArgs e)
            {
                var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
                var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text };
                IdentityResult result = manager.Create(user, Password.Text);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
                    //string code = manager.GenerateEmailConfirmationToken(user.Id);
                    //string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
                    //manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>.");
    
                    IdentityHelper.SignIn(manager, user, isPersistent: false);
    
                    using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions())
                    {
                      String cartId = usersShoppingCart.GetCartId();
                      usersShoppingCart.MigrateCart(cartId, user.Id);
                    }
    
                    IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
                }
                else 
                {
                    ErrorMessage.Text = result.Errors.FirstOrDefault();
                }
            }
        }
    }
    
  3. Register.aspx.cs 파일을 저장합니다. 다시 한 번 메서드에 대한 경고를 무시합니다 MigrateCart .

이벤트 처리기에서 사용한 코드는 메서드에서 CreateUser_ClickLogIn 사용한 코드와 매우 유사합니다. 사용자가 사이트에 등록하거나 로그인하면 메서드를 MigrateCart 호출합니다.

쇼핑 카트 마이그레이션

이제 로그인 및 등록 프로세스가 업데이트되었으므로 메서드를 사용하여 MigrateCart 쇼핑 카트를 마이그레이션하는 코드를 추가할 수 있습니다.

  1. 솔루션 탐색기논리 폴더를 찾아 ShoppingCartActions.cs 클래스 파일을 엽니다.

  2. ShoppingCartActions.cs 파일의 코드가 다음과 같이 표시되도록 노란색으로 강조 표시된 코드를 ShoppingCartActions.cs 파일의 기존 코드에 추가합니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    
    namespace WingtipToys.Logic
    {
      public class ShoppingCartActions : IDisposable
      {
        public string ShoppingCartId { get; set; }
    
        private ProductContext _db = new ProductContext();
    
        public const string CartSessionKey = "CartId";
    
        public void AddToCart(int id)
        {
          // Retrieve the product from the database.           
          ShoppingCartId = GetCartId();
    
          var cartItem = _db.ShoppingCartItems.SingleOrDefault(
              c => c.CartId == ShoppingCartId
              && c.ProductId == id);
          if (cartItem == null)
          {
            // Create a new cart item if no cart item exists.                 
            cartItem = new CartItem
            {
              ItemId = Guid.NewGuid().ToString(),
              ProductId = id,
              CartId = ShoppingCartId,
              Product = _db.Products.SingleOrDefault(
               p => p.ProductID == id),
              Quantity = 1,
              DateCreated = DateTime.Now
            };
    
            _db.ShoppingCartItems.Add(cartItem);
          }
          else
          {
            // If the item does exist in the cart,                  
            // then add one to the quantity.                 
            cartItem.Quantity++;
          }
          _db.SaveChanges();
        }
    
        public void Dispose()
        {
          if (_db != null)
          {
            _db.Dispose();
            _db = null;
          }
        }
    
        public string GetCartId()
        {
          if (HttpContext.Current.Session[CartSessionKey] == null)
          {
            if (!string.IsNullOrWhiteSpace(HttpContext.Current.User.Identity.Name))
            {
              HttpContext.Current.Session[CartSessionKey] = HttpContext.Current.User.Identity.Name;
            }
            else
            {
              // Generate a new random GUID using System.Guid class.     
              Guid tempCartId = Guid.NewGuid();
              HttpContext.Current.Session[CartSessionKey] = tempCartId.ToString();
            }
          }
          return HttpContext.Current.Session[CartSessionKey].ToString();
        }
    
        public List<CartItem> GetCartItems()
        {
          ShoppingCartId = GetCartId();
    
          return _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId).ToList();
        }
    
        public decimal GetTotal()
        {
          ShoppingCartId = GetCartId();
          // Multiply product price by quantity of that product to get        
          // the current price for each of those products in the cart.  
          // Sum all product price totals to get the cart total.   
          decimal? total = decimal.Zero;
          total = (decimal?)(from cartItems in _db.ShoppingCartItems
                             where cartItems.CartId == ShoppingCartId
                             select (int?)cartItems.Quantity *
                             cartItems.Product.UnitPrice).Sum();
          return total ?? decimal.Zero;
        }
    
        public ShoppingCartActions GetCart(HttpContext context)
        {
          using (var cart = new ShoppingCartActions())
          {
            cart.ShoppingCartId = cart.GetCartId();
            return cart;
          }
        }
    
        public void UpdateShoppingCartDatabase(String cartId, ShoppingCartUpdates[] CartItemUpdates)
        {
          using (var db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              int CartItemCount = CartItemUpdates.Count();
              List<CartItem> myCart = GetCartItems();
              foreach (var cartItem in myCart)
              {
                // Iterate through all rows within shopping cart list
                for (int i = 0; i < CartItemCount; i++)
                {
                  if (cartItem.Product.ProductID == CartItemUpdates[i].ProductId)
                  {
                    if (CartItemUpdates[i].PurchaseQuantity < 1 || CartItemUpdates[i].RemoveItem == true)
                    {
                      RemoveItem(cartId, cartItem.ProductId);
                    }
                    else
                    {
                      UpdateItem(cartId, cartItem.ProductId, CartItemUpdates[i].PurchaseQuantity);
                    }
                  }
                }
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Update Cart Database - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void RemoveItem(string removeCartID, int removeProductID)
        {
          using (var _db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == removeCartID && c.Product.ProductID == removeProductID select c).FirstOrDefault();
              if (myItem != null)
              {
                // Remove Item.
                _db.ShoppingCartItems.Remove(myItem);
                _db.SaveChanges();
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Remove Cart Item - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void UpdateItem(string updateCartID, int updateProductID, int quantity)
        {
          using (var _db = new WingtipToys.Models.ProductContext())
          {
            try
            {
              var myItem = (from c in _db.ShoppingCartItems where c.CartId == updateCartID && c.Product.ProductID == updateProductID select c).FirstOrDefault();
              if (myItem != null)
              {
                myItem.Quantity = quantity;
                _db.SaveChanges();
              }
            }
            catch (Exception exp)
            {
              throw new Exception("ERROR: Unable to Update Cart Item - " + exp.Message.ToString(), exp);
            }
          }
        }
    
        public void EmptyCart()
        {
          ShoppingCartId = GetCartId();
          var cartItems = _db.ShoppingCartItems.Where(
              c => c.CartId == ShoppingCartId);
          foreach (var cartItem in cartItems)
          {
            _db.ShoppingCartItems.Remove(cartItem);
          }
          // Save changes.             
          _db.SaveChanges();
        }
    
        public int GetCount()
        {
          ShoppingCartId = GetCartId();
    
          // Get the count of each item in the cart and sum them up          
          int? count = (from cartItems in _db.ShoppingCartItems
                        where cartItems.CartId == ShoppingCartId
                        select (int?)cartItems.Quantity).Sum();
          // Return 0 if all entries are null         
          return count ?? 0;
        }
    
        public struct ShoppingCartUpdates
        {
          public int ProductId;
          public int PurchaseQuantity;
          public bool RemoveItem;
        }
    
        public void MigrateCart(string cartId, string userName)
        {
          var shoppingCart = _db.ShoppingCartItems.Where(c => c.CartId == cartId);
          foreach (CartItem item in shoppingCart)
          {
            item.CartId = userName;
          }
          HttpContext.Current.Session[CartSessionKey] = userName;
          _db.SaveChanges();
        }
      }
    }
    

메서드는 MigrateCart 기존 cartId를 사용하여 사용자의 쇼핑 카트를 찾습니다. 다음으로 코드는 모든 쇼핑 카트 항목을 반복하고 속성(스키마에 지정된 대로)을 CartItem 로그인한 사용자 이름으로 바꿉 CartId 니다.

데이터베이스 연결 업데이트

미리 빌드된 Wingtip Toys 샘플 애플리케이션을 사용하여 이 자습서를 따르는 경우 기본 멤버 자격 데이터베이스를 다시 만들어야 합니다. 기본 연결 문자열 수정하면 다음에 애플리케이션이 실행될 때 멤버 자격 데이터베이스가 만들어집니다.

  1. 프로젝트의 루트에서 Web.config 파일을 엽니다.

  2. 다음과 같이 표시되도록 기본 연결 문자열 업데이트합니다.

    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=aspnet-WingtipToys;Integrated Security=True" providerName="System.Data.SqlClient" />
    

PayPal 통합

PayPal은 온라인 판매자의 결제를 수락하는 웹 기반 청구 플랫폼입니다. 다음 자습서에서는 PayPal의 Express Checkout 기능을 애플리케이션에 통합하는 방법을 설명합니다. Express Checkout을 사용하면 고객이 PayPal을 사용하여 쇼핑 카트에 추가한 항목에 대한 요금을 지불할 수 있습니다.

PayPal 테스트 계정 만들기

PayPal 테스트 환경을 사용하려면 개발자 테스트 계정을 만들고 확인해야 합니다. 개발자 테스트 계정을 사용하여 구매자 테스트 계정 및 판매자 테스트 계정을 만듭니다. 또한 개발자 테스트 계정 자격 증명을 사용하면 Wingtip Toys 샘플 애플리케이션이 PayPal 테스트 환경에 액세스할 수 있습니다.

  1. 브라우저에서 PayPal 개발자 테스트 사이트로 이동합니다.
    https://developer.paypal.com

  2. PayPal 개발자 계정이 없는 경우 등록을 클릭하고 등록 단계를 수행하여 새 계정을 만듭니다. 기존 PayPal 개발자 계정이 있는 경우 로그인을 클릭하여 로그인합니다. 이 자습서의 뒷부분에서 Wingtip Toys 샘플 애플리케이션을 테스트하려면 PayPal 개발자 계정이 필요합니다.

  3. PayPal 개발자 계정에 방금 등록한 경우 PayPal을 사용하여 PayPal 개발자 계정을 확인해야 할 수 있습니다. PayPal이 전자 메일 계정으로 보낸 단계에 따라 계정을 확인할 수 있습니다. PayPal 개발자 계정을 확인한 후에는 PayPal 개발자 테스트 사이트에 다시 로그인합니다.

  4. PayPal 개발자 계정으로 PayPal 개발자 사이트에 로그인한 후 아직 없는 경우 PayPal 구매자 테스트 계정을 만들어야 합니다. 구매자 테스트 계정을 만들려면 PayPal 사이트에서 애플리케이션 탭을 클릭한 다음 샌드박스 계정을 클릭합니다.
    샌드박스 테스트 계정 페이지가 표시됩니다.

    참고

    PayPal 개발자 사이트는 이미 가맹점 테스트 계정을 제공합니다.

    애플리케이션 탭이 강조 표시된 샌드박스 테스트 계정 페이지를 보여 주는 스크린샷

  5. 샌드박스 테스트 계정 페이지에서 계정 만들기를 클릭합니다.

  6. 테스트 계정 만들기 페이지에서 선택한 구매자 테스트 계정 이메일 및 암호를 선택합니다.

    참고

    이 자습서의 끝에서 Wingtip Toys 샘플 애플리케이션을 테스트하려면 구매자 이메일 주소와 암호가 필요합니다.

    생성 중인 계정의 필드를 표시하는 테스트 계정 만들기 페이지의 스크린샷.

  7. 계정 만들기 단추를 클릭하여 구매자 테스트 계정을 만듭니 다.
    샌드박스 테스트 계정 페이지가 표시됩니다.

    PayPal을 사용하여 체크 아웃 및 결제 - PayPal 계정

  8. 샌드박스 테스트 계정 페이지에서 촉진자 전자 메일 계정을 클릭합니다.
    프로필알림 옵션이 나타납니다.

  9. 프로필 옵션을 선택한 다음, API 자격 증명을 클릭하여 가맹점 테스트 계정에 대한 API 자격 증명을 확인합니다.

  10. TEST API 자격 증명을 메모장에 복사합니다.

Wingtip Toys 샘플 애플리케이션에서 PayPal 테스트 환경으로 API를 호출하려면 표시된 클래식 TEST API 자격 증명(사용자 이름, 암호 및 서명)이 필요합니다. 다음 단계에서 자격 증명을 추가합니다.

PayPal 클래스 및 API 자격 증명 추가

대부분의 PayPal 코드를 단일 클래스에 배치합니다. 이 클래스에는 PayPal과 통신하는 데 사용되는 메서드가 포함되어 있습니다. 또한 이 클래스에 PayPal 자격 증명을 추가합니다.

  1. Visual Studio 내의 Wingtip Toys 샘플 애플리케이션에서 논리 폴더를 마우스 오른쪽 단추로 클릭한 다음 , 추가 ->새 항목을 선택합니다.
    새 항목 추가 대화 상자가 표시됩니다.

  2. 왼쪽의 설치된 창에서 Visual C#에서 코드를 선택합니다.

  3. 가운데 창에서 클래스를 선택합니다. 이 새 클래스의 이름을 PayPalFunctions.cs로 지정합니다.

  4. 추가를 클릭합니다.
    새 클래스 파일이 편집기에서 표시됩니다.

  5. 기본 코드를 다음 코드로 바꿉니다.

    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using WingtipToys;
    using WingtipToys.Models;
    using System.Collections.Generic;
    using System.Linq;
    
    public class NVPAPICaller
    {
      //Flag that determines the PayPal environment (live or sandbox)
      private const bool bSandbox = true;
      private const string CVV2 = "CVV2";
    
      // Live strings.
      private string pEndPointURL = "https://api-3t.paypal.com/nvp";
      private string host = "www.paypal.com";
    
      // Sandbox strings.
      private string pEndPointURL_SB = "https://api-3t.sandbox.paypal.com/nvp";
      private string host_SB = "www.sandbox.paypal.com";
    
      private const string SIGNATURE = "SIGNATURE";
      private const string PWD = "PWD";
      private const string ACCT = "ACCT";
    
      //Replace <Your API Username> with your API Username
      //Replace <Your API Password> with your API Password
      //Replace <Your Signature> with your Signature
      public string APIUsername = "<Your API Username>";
      private string APIPassword = "<Your API Password>";
      private string APISignature = "<Your Signature>";
      private string Subject = "";
      private string BNCode = "PP-ECWizard";
    
      //HttpWebRequest Timeout specified in milliseconds 
      private const int Timeout = 15000;
      private static readonly string[] SECURED_NVPS = new string[] { ACCT, CVV2, SIGNATURE, PWD };
    
      public void SetCredentials(string Userid, string Pwd, string Signature)
      {
        APIUsername = Userid;
        APIPassword = Pwd;
        APISignature = Signature;
      }
    
      public bool ShortcutExpressCheckout(string amt, ref string token, ref string retMsg)
      {
        if (bSandbox)
        {
          pEndPointURL = pEndPointURL_SB;
          host = host_SB;
        }
    
        string returnURL = "https://localhost:44300/Checkout/CheckoutReview.aspx";
        string cancelURL = "https://localhost:44300/Checkout/CheckoutCancel.aspx";
    
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "SetExpressCheckout";
        encoder["RETURNURL"] = returnURL;
        encoder["CANCELURL"] = cancelURL;
        encoder["BRANDNAME"] = "Wingtip Toys Sample Application";
        encoder["PAYMENTREQUEST_0_AMT"] = amt;
        encoder["PAYMENTREQUEST_0_ITEMAMT"] = amt;
        encoder["PAYMENTREQUEST_0_PAYMENTACTION"] = "Sale";
        encoder["PAYMENTREQUEST_0_CURRENCYCODE"] = "USD";
    
        // Get the Shopping Cart Products
        using (WingtipToys.Logic.ShoppingCartActions myCartOrders = new WingtipToys.Logic.ShoppingCartActions())
        {
          List<CartItem> myOrderList = myCartOrders.GetCartItems();
    
          for (int i = 0; i < myOrderList.Count; i++)
          {
            encoder["L_PAYMENTREQUEST_0_NAME" + i] = myOrderList[i].Product.ProductName.ToString();
            encoder["L_PAYMENTREQUEST_0_AMT" + i] = myOrderList[i].Product.UnitPrice.ToString();
            encoder["L_PAYMENTREQUEST_0_QTY" + i] = myOrderList[i].Quantity.ToString();
          }
        }
    
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
    
        NVPCodec decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
    
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
        {
          token = decoder["TOKEN"];
          string ECURL = "https://" + host + "/cgi-bin/webscr?cmd=_express-checkout" + "&token=" + token;
          retMsg = ECURL;
          return true;
        }
        else
        {
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
          return false;
        }
      }
    
      public bool GetCheckoutDetails(string token, ref string PayerID, ref NVPCodec decoder, ref string retMsg)
      {
        if (bSandbox)
        {
          pEndPointURL = pEndPointURL_SB;
        }
    
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "GetExpressCheckoutDetails";
        encoder["TOKEN"] = token;
    
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
    
        decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
    
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
        {
          PayerID = decoder["PAYERID"];
          return true;
        }
        else
        {
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
    
          return false;
        }
      }
    
      public bool DoCheckoutPayment(string finalPaymentAmount, string token, string PayerID, ref NVPCodec decoder, ref string retMsg)
      {
        if (bSandbox)
        {
          pEndPointURL = pEndPointURL_SB;
        }
    
        NVPCodec encoder = new NVPCodec();
        encoder["METHOD"] = "DoExpressCheckoutPayment";
        encoder["TOKEN"] = token;
        encoder["PAYERID"] = PayerID;
        encoder["PAYMENTREQUEST_0_AMT"] = finalPaymentAmount;
        encoder["PAYMENTREQUEST_0_CURRENCYCODE"] = "USD";
        encoder["PAYMENTREQUEST_0_PAYMENTACTION"] = "Sale";
    
        string pStrrequestforNvp = encoder.Encode();
        string pStresponsenvp = HttpCall(pStrrequestforNvp);
    
        decoder = new NVPCodec();
        decoder.Decode(pStresponsenvp);
    
        string strAck = decoder["ACK"].ToLower();
        if (strAck != null && (strAck == "success" || strAck == "successwithwarning"))
        {
          return true;
        }
        else
        {
          retMsg = "ErrorCode=" + decoder["L_ERRORCODE0"] + "&" +
              "Desc=" + decoder["L_SHORTMESSAGE0"] + "&" +
              "Desc2=" + decoder["L_LONGMESSAGE0"];
    
          return false;
        }
      }
    
      public string HttpCall(string NvpRequest)
      {
        string url = pEndPointURL;
    
        string strPost = NvpRequest + "&" + buildCredentialsNVPString();
        strPost = strPost + "&BUTTONSOURCE=" + HttpUtility.UrlEncode(BNCode);
    
        HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(url);
        objRequest.Timeout = Timeout;
        objRequest.Method = "POST";
        objRequest.ContentLength = strPost.Length;
    
        try
        {
          using (StreamWriter myWriter = new StreamWriter(objRequest.GetRequestStream()))
          {
            myWriter.Write(strPost);
          }
        }
        catch (Exception)
        {
          // No logging for this tutorial.
        }
    
        //Retrieve the Response returned from the NVP API call to PayPal.
        HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
        string result;
        using (StreamReader sr = new StreamReader(objResponse.GetResponseStream()))
        {
          result = sr.ReadToEnd();
        }
    
        return result;
      }
    
      private string buildCredentialsNVPString()
      {
        NVPCodec codec = new NVPCodec();
    
        if (!IsEmpty(APIUsername))
          codec["USER"] = APIUsername;
    
        if (!IsEmpty(APIPassword))
          codec[PWD] = APIPassword;
    
        if (!IsEmpty(APISignature))
          codec[SIGNATURE] = APISignature;
    
        if (!IsEmpty(Subject))
          codec["SUBJECT"] = Subject;
    
        codec["VERSION"] = "88.0";
    
        return codec.Encode();
      }
    
      public static bool IsEmpty(string s)
      {
        return s == null || s.Trim() == string.Empty;
      }
    }
    
    public sealed class NVPCodec : NameValueCollection
    {
      private const string AMPERSAND = "&";
      private const string EQUALS = "=";
      private static readonly char[] AMPERSAND_CHAR_ARRAY = AMPERSAND.ToCharArray();
      private static readonly char[] EQUALS_CHAR_ARRAY = EQUALS.ToCharArray();
    
      public string Encode()
      {
        StringBuilder sb = new StringBuilder();
        bool firstPair = true;
        foreach (string kv in AllKeys)
        {
          string name = HttpUtility.UrlEncode(kv);
          string value = HttpUtility.UrlEncode(this[kv]);
          if (!firstPair)
          {
            sb.Append(AMPERSAND);
          }
          sb.Append(name).Append(EQUALS).Append(value);
          firstPair = false;
        }
        return sb.ToString();
      }
    
      public void Decode(string nvpstring)
      {
        Clear();
        foreach (string nvp in nvpstring.Split(AMPERSAND_CHAR_ARRAY))
        {
          string[] tokens = nvp.Split(EQUALS_CHAR_ARRAY);
          if (tokens.Length >= 2)
          {
            string name = HttpUtility.UrlDecode(tokens[0]);
            string value = HttpUtility.UrlDecode(tokens[1]);
            Add(name, value);
          }
        }
      }
    
      public void Add(string name, string value, int index)
      {
        this.Add(GetArrayName(index, name), value);
      }
    
      public void Remove(string arrayName, int index)
      {
        this.Remove(GetArrayName(index, arrayName));
      }
    
      public string this[string name, int index]
      {
        get
        {
          return this[GetArrayName(index, name)];
        }
        set
        {
          this[GetArrayName(index, name)] = value;
        }
      }
    
      private static string GetArrayName(int index, string name)
      {
        if (index < 0)
        {
          throw new ArgumentOutOfRangeException("index", "index cannot be negative : " + index);
        }
        return name + index;
      }
    }
    
  6. PayPal 테스트 환경에 대한 함수 호출을 수행할 수 있도록 이 자습서의 앞부분에서 표시한 가맹점 API 자격 증명(사용자 이름, 암호 및 서명)을 추가합니다.

    public string APIUsername = "<Your API Username>";
    private string APIPassword = "<Your API Password>";
    private string APISignature = "<Your Signature>";
    

참고

이 샘플 애플리케이션에서는 C# 파일(.cs)에 자격 증명을 추가하기만 하면 됩니다. 그러나 구현된 솔루션에서는 구성 파일에서 자격 증명을 암호화하는 것이 좋습니다.

NVPAPICaller 클래스에는 대부분의 PayPal 기능이 포함되어 있습니다. 클래스의 코드는 PayPal 테스트 환경에서 테스트 구매를 하는 데 필요한 메서드를 제공합니다. 구매하는 데 사용되는 세 가지 PayPal 함수는 다음과 같습니다.

  • SetExpressCheckout 함수
  • GetExpressCheckoutDetails 함수
  • DoExpressCheckoutPayment 함수

메서드는 ShortcutExpressCheckout 쇼핑 카트에서 테스트 구매 정보 및 제품 세부 정보를 수집하고 PayPal 함수를 SetExpressCheckout 호출합니다. 메서드는 GetCheckoutDetails 구매 세부 정보를 확인하고 테스트 구매 전에 PayPal 함수를 호출 GetExpressCheckoutDetails 합니다. 메서드는 DoCheckoutPayment PayPal 함수를 호출하여 테스트 환경에서 테스트 구매를 DoExpressCheckoutPayment 완료합니다. 나머지 코드는 문자열 인코딩, 문자열 디코딩, 배열 처리 및 자격 증명 확인과 같은 PayPal 메서드 및 프로세스를 지원합니다.

참고

PayPal을 사용하면 PayPal의 API 사양에 따라 선택적 구매 세부 정보를 포함할 수 있습니다. Wingtip Toys 샘플 애플리케이션에서 코드를 확장하면 지역화 세부 정보, 제품 설명, 세금, 고객 서비스 번호 및 기타 많은 선택적 필드를 포함할 수 있습니다.

ShortcutExpressCheckout 메서드에 지정된 반환 및 취소 URL은 포트 번호를 사용합니다.

string returnURL = "https://localhost:44300/Checkout/CheckoutReview.aspx";
       string cancelURL = "https://localhost:44300/Checkout/CheckoutCancel.aspx";

Visual Web Developer가 SSL을 사용하여 웹 프로젝트를 실행하는 경우 일반적으로 포트 44300이 웹 서버에 사용됩니다. 위와 같이 포트 번호는 44300입니다. 애플리케이션을 실행할 때 다른 포트 번호를 볼 수 있습니다. 이 자습서의 끝에서 Wingtip Toys 샘플 애플리케이션을 성공적으로 실행할 수 있도록 코드에서 포트 번호를 올바르게 설정해야 합니다. 이 자습서의 다음 섹션에서는 로컬 호스트 포트 번호를 검색하고 PayPal 클래스를 업데이트하는 방법을 설명합니다.

PayPal 클래스에서 LocalHost 포트 번호 업데이트

Wingtip Toys 샘플 애플리케이션은 PayPal 테스트 사이트로 이동하여 Wingtip Toys 샘플 애플리케이션의 로컬 instance 돌아와 제품을 구매합니다. PayPal이 올바른 URL로 돌아가도록 하려면 위에서 언급한 PayPal 코드에서 로컬로 실행 중인 샘플 애플리케이션의 포트 번호를 지정해야 합니다.

  1. 솔루션 탐색기 프로젝트 이름(WingtipToys)을 마우스 오른쪽 단추로 클릭하고 속성을 선택합니다.

  2. 왼쪽 열에서 탭을 선택합니다.

  3. 프로젝트 URL 상자에서 포트 번호를 검색합니다.

  4. 필요한 경우 PayPalFunctions.cs 파일의 PayPal 클래스(NVPAPICaller)에서 및 cancelURL 를 업데이트 returnURL 하여 웹 애플리케이션의 포트 번호를 사용합니다.

    string returnURL = "https://localhost:<Your Port Number>/Checkout/CheckoutReview.aspx";
    string cancelURL = "https://localhost:<Your Port Number>/Checkout/CheckoutCancel.aspx";
    

이제 추가한 코드가 로컬 웹 애플리케이션의 예상 포트와 일치합니다. PayPal은 로컬 컴퓨터에서 올바른 URL로 돌아갈 수 있습니다.

PayPal 체크 아웃 단추 추가

이제 기본 PayPal 함수가 샘플 애플리케이션에 추가되었으므로 이러한 함수를 호출하는 데 필요한 태그 및 코드 추가를 시작할 수 있습니다. 먼저 쇼핑 카트 페이지에 표시되는 체크 아웃 단추를 추가해야 합니다.

  1. ShoppingCart.aspx 파일을 엽니다.

  2. 파일 아래쪽으로 스크롤하여 주석을 찾습니다 <!--Checkout Placeholder --> .

  3. 다음과 같이 태그를 바꾸도록 주석 ImageButton 을 컨트롤로 바꿉니다.

    <asp:ImageButton ID="CheckoutImageBtn" runat="server" 
                          ImageUrl="https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif" 
                          Width="145" AlternateText="Check out with PayPal" 
                          OnClick="CheckoutBtn_Click" 
                          BackColor="Transparent" BorderWidth="0" />
    
  4. ShoppingCart.aspx.cs 파일에서 파일 끝 근처의 이벤트 처리기 다음에 UpdateBtn_Click 이벤트 처리기를 추가 CheckOutBtn_Click 합니다.

    protected void CheckoutBtn_Click(object sender, ImageClickEventArgs e)
    {
        using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
        {
            Session["payment_amt"] = usersShoppingCart.GetTotal();
        }
        Response.Redirect("Checkout/CheckoutStart.aspx");
    }
    
  5. 또한 ShoppingCart.aspx.cs 파일에서 에 대한 참조를 CheckoutBtn추가하여 새 이미지 단추가 다음과 같이 참조되도록 합니다.

    protected void Page_Load(object sender, EventArgs e)
    {
        using (ShoppingCartActions usersShoppingCart = new ShoppingCartActions())
        {
            decimal cartTotal = 0;
            cartTotal = usersShoppingCart.GetTotal();
            if (cartTotal > 0)
            {
                // Display Total.
                lblTotal.Text = String.Format("{0:c}", cartTotal);
            }
            else
            {
                LabelTotalText.Text = "";
                lblTotal.Text = "";
                ShoppingCartTitle.InnerText = "Shopping Cart is Empty";
                UpdateBtn.Visible = false;
                CheckoutImageBtn.Visible = false;
            }
        }
    }
    
  6. 변경 내용을 ShoppingCart.aspx 파일과 ShoppingCart.aspx.cs 파일에 모두 저장합니다.

  7. 메뉴에서 Debug-Build>WingtipToys를 선택합니다.
    프로젝트는 새로 추가된 ImageButton 컨트롤을 사용하여 다시 빌드됩니다.

PayPal에 구매 세부 정보 보내기

사용자가 쇼핑 카트 페이지(ShoppingCart.aspx)에서 체크 아웃 단추를 클릭하면 구매 프로세스가 시작됩니다. 다음 코드는 제품을 구매하는 데 필요한 첫 번째 PayPal 함수를 호출합니다.

  1. Checkout 폴더에서 CheckoutStart.aspx.cs라는 코드 숨김 파일을 엽니다.
    코드 숨김 파일을 열어야 합니다.

  2. 기존 코드를 다음으로 바꿉니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    namespace WingtipToys.Checkout
    {
        public partial class CheckoutStart : System.Web.UI.Page
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                NVPAPICaller payPalCaller = new NVPAPICaller();
                string retMsg = "";
                string token = "";
    
                if (Session["payment_amt"] != null)
                {
                    string amt = Session["payment_amt"].ToString();
    
                    bool ret = payPalCaller.ShortcutExpressCheckout(amt, ref token, ref retMsg);
                    if (ret)
                    {
                        Session["token"] = token;
                        Response.Redirect(retMsg);
                    }
                    else
                    {
                        Response.Redirect("CheckoutError.aspx?" + retMsg);
                    }
                }
                else
                {
                    Response.Redirect("CheckoutError.aspx?ErrorCode=AmtMissing");
                }
            }
        }
    }
    

애플리케이션 사용자가 쇼핑 카트 페이지에서 체크 아웃 단추를 클릭하면 브라우저가 CheckoutStart.aspx 페이지로 이동합니다. CheckoutStart.aspx 페이지가 로드되면 메서드가 ShortcutExpressCheckout 호출됩니다. 이 시점에서 사용자는 PayPal 테스트 웹 사이트로 전송됩니다. PayPal 사이트에서 사용자는 PayPal 자격 증명을 입력하고, 구매 세부 정보를 검토하고, PayPal 계약을 수락하고, 메서드가 완료된 ShortcutExpressCheckout Wingtip Toys 샘플 애플리케이션으로 돌아갑니다. 메서드가 ShortcutExpressCheckout 완료되면 메서드에 지정된 ShortcutExpressCheckoutCheckoutReview.aspx 페이지로 사용자를 리디렉션합니다. 이를 통해 사용자는 Wingtip Toys 샘플 애플리케이션 내에서 주문 세부 정보를 검토할 수 있습니다.

주문 세부 정보 검토

PayPal에서 돌아온 후 Wingtip Toys 샘플 애플리케이션의 CheckoutReview.aspx 페이지에 주문 세부 정보가 표시됩니다. 이 페이지에서는 제품을 구매하기 전에 주문 세부 정보를 검토할 수 있습니다. CheckoutReview.aspx 페이지는 다음과 같이 만들어야 합니다.

  1. Checkout 폴더에서 CheckoutReview.aspx라는 페이지를 엽니다.

  2. 기존 태그를 다음으로 바꿉다.

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutReview.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutReview" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Order Review</h1>
        <p></p>
        <h3 style="padding-left: 33px">Products:</h3>
        <asp:GridView ID="OrderItemList" runat="server" AutoGenerateColumns="False" GridLines="Both" CellPadding="10" Width="500" BorderColor="#efeeef" BorderWidth="33">              
            <Columns>
                <asp:BoundField DataField="ProductId" HeaderText=" Product ID" />        
                <asp:BoundField DataField="Product.ProductName" HeaderText=" Product Name" />        
                <asp:BoundField DataField="Product.UnitPrice" HeaderText="Price (each)" DataFormatString="{0:c}"/>     
                <asp:BoundField DataField="Quantity" HeaderText="Quantity" />        
            </Columns>    
        </asp:GridView>
        <asp:DetailsView ID="ShipInfo" runat="server" AutoGenerateRows="false" GridLines="None" CellPadding="10" BorderStyle="None" CommandRowStyle-BorderStyle="None">
            <Fields>
            <asp:TemplateField>
                <ItemTemplate>
                    <h3>Shipping Address:</h3>
                    <br />
                    <asp:Label ID="FirstName" runat="server" Text='<%#: Eval("FirstName") %>'></asp:Label>  
                    <asp:Label ID="LastName" runat="server" Text='<%#: Eval("LastName") %>'></asp:Label>
                    <br />
                    <asp:Label ID="Address" runat="server" Text='<%#: Eval("Address") %>'></asp:Label>
                    <br />
                    <asp:Label ID="City" runat="server" Text='<%#: Eval("City") %>'></asp:Label>
                    <asp:Label ID="State" runat="server" Text='<%#: Eval("State") %>'></asp:Label>
                    <asp:Label ID="PostalCode" runat="server" Text='<%#: Eval("PostalCode") %>'></asp:Label>
                    <p></p>
                    <h3>Order Total:</h3>
                    <br />
                    <asp:Label ID="Total" runat="server" Text='<%#: Eval("Total", "{0:C}") %>'></asp:Label>
                </ItemTemplate>
                <ItemStyle HorizontalAlign="Left" />
            </asp:TemplateField>
              </Fields>
        </asp:DetailsView>
        <p></p>
        <hr />
        <asp:Button ID="CheckoutConfirm" runat="server" Text="Complete Order" OnClick="CheckoutConfirm_Click" />
    </asp:Content>
    
  3. CheckoutReview.aspx.cs라는 코드 숨김 페이지를 열고 기존 코드를 다음으로 바꿉니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    
    namespace WingtipToys.Checkout
    {
      public partial class CheckoutReview : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
          if (!IsPostBack)
          {
            NVPAPICaller payPalCaller = new NVPAPICaller();
    
            string retMsg = "";
            string token = "";
            string PayerID = "";
            NVPCodec decoder = new NVPCodec();
            token = Session["token"].ToString();
    
            bool ret = payPalCaller.GetCheckoutDetails(token, ref PayerID, ref decoder, ref retMsg);
            if (ret)
            {
              Session["payerId"] = PayerID;
    
              var myOrder = new Order();
              myOrder.OrderDate = Convert.ToDateTime(decoder["TIMESTAMP"].ToString());
              myOrder.Username = User.Identity.Name;
              myOrder.FirstName = decoder["FIRSTNAME"].ToString();
              myOrder.LastName = decoder["LASTNAME"].ToString();
              myOrder.Address = decoder["SHIPTOSTREET"].ToString();
              myOrder.City = decoder["SHIPTOCITY"].ToString();
              myOrder.State = decoder["SHIPTOSTATE"].ToString();
              myOrder.PostalCode = decoder["SHIPTOZIP"].ToString();
              myOrder.Country = decoder["SHIPTOCOUNTRYCODE"].ToString();
              myOrder.Email = decoder["EMAIL"].ToString();
              myOrder.Total = Convert.ToDecimal(decoder["AMT"].ToString());
    
              // Verify total payment amount as set on CheckoutStart.aspx.
              try
              {
                decimal paymentAmountOnCheckout = Convert.ToDecimal(Session["payment_amt"].ToString());
                decimal paymentAmoutFromPayPal = Convert.ToDecimal(decoder["AMT"].ToString());
                if (paymentAmountOnCheckout != paymentAmoutFromPayPal)
                {
                  Response.Redirect("CheckoutError.aspx?" + "Desc=Amount%20total%20mismatch.");
                }
              }
              catch (Exception)
              {
                Response.Redirect("CheckoutError.aspx?" + "Desc=Amount%20total%20mismatch.");
              }
    
              // Get DB context.
              ProductContext _db = new ProductContext();
    
              // Add order to DB.
              _db.Orders.Add(myOrder);
              _db.SaveChanges();
    
              // Get the shopping cart items and process them.
              using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart = new WingtipToys.Logic.ShoppingCartActions())
              {
                List<CartItem> myOrderList = usersShoppingCart.GetCartItems();
    
                // Add OrderDetail information to the DB for each product purchased.
                for (int i = 0; i < myOrderList.Count; i++)
                {
                  // Create a new OrderDetail object.
                  var myOrderDetail = new OrderDetail();
                  myOrderDetail.OrderId = myOrder.OrderId;
                  myOrderDetail.Username = User.Identity.Name;
                  myOrderDetail.ProductId = myOrderList[i].ProductId;
                  myOrderDetail.Quantity = myOrderList[i].Quantity;
                  myOrderDetail.UnitPrice = myOrderList[i].Product.UnitPrice;
    
                  // Add OrderDetail to DB.
                  _db.OrderDetails.Add(myOrderDetail);
                  _db.SaveChanges();
                }
    
                // Set OrderId.
                Session["currentOrderId"] = myOrder.OrderId;
    
                // Display Order information.
                List<Order> orderList = new List<Order>();
                orderList.Add(myOrder);
                ShipInfo.DataSource = orderList;
                ShipInfo.DataBind();
    
                // Display OrderDetails.
                OrderItemList.DataSource = myOrderList;
                OrderItemList.DataBind();
              }
            }
            else
            {
              Response.Redirect("CheckoutError.aspx?" + retMsg);
            }
          }
        }
    
        protected void CheckoutConfirm_Click(object sender, EventArgs e)
        {
          Session["userCheckoutCompleted"] = "true";
          Response.Redirect("~/Checkout/CheckoutComplete.aspx");
        }
      }
    }
    

DetailsView 컨트롤은 PayPal에서 반환된 주문 세부 정보를 표시하는 데 사용됩니다. 또한 위의 코드는 주문 세부 정보를 Wingtip Toys 데이터베이스에 개체로 OrderDetail 저장합니다. 사용자가 주문 완료 단추를 클릭하면 CheckoutComplete.aspx 페이지로 리디렉션됩니다.

참고

CheckoutReview.aspx 페이지의 태그에서 태그가 <ItemStyle> 페이지 아래쪽 근처의 DetailsView 컨트롤 내에서 항목의 스타일을 변경하는 데 사용됩니다. 디자인 뷰에서 페이지를 보고(Visual Studio의 왼쪽 아래 모서리에서 디자인을 선택한 다음, DetailsView 컨트롤을 선택하고, 스마트 태그(컨트롤의 오른쪽 위에 있는 화살표 아이콘)를 선택하면 DetailsView 작업을 볼 수 있습니다.

PayPal을 사용하여 체크 아웃 및 결제 - 필드 편집

필드 편집을 선택하면 필드 대화 상자가 나타납니다. 이 대화 상자에서는 DetailsView 컨트롤의 ItemStyle과 같은 시각적 속성을 쉽게 제어할 수 있습니다.

PayPal을 사용하여 체크 아웃 및 결제 - 필드 대화 상자

구매 완료

CheckoutComplete.aspx 페이지는 PayPal에서 구매합니다. 위에서 설명한 대로 애플리케이션이 CheckoutComplete.aspx 페이지로 이동하기 전에 사용자가 주문 완료 단추를 클릭해야 합니다.

  1. Checkout 폴더에서 CheckoutComplete.aspx라는 페이지를 엽니다.

  2. 기존 태그를 다음으로 바꿉다.

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutComplete.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutComplete" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Complete</h1>
        <p></p>
        <h3>Payment Transaction ID:</h3> <asp:Label ID="TransactionId" runat="server"></asp:Label>
        <p></p>
        <h3>Thank You!</h3>
        <p></p>
        <hr />
        <asp:Button ID="Continue" runat="server" Text="Continue Shopping" OnClick="Continue_Click" />
    </asp:Content>
    
  3. CheckoutComplete.aspx.cs라는 코드 숨김 페이지를 열고 기존 코드를 다음으로 바꿉니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    
    namespace WingtipToys.Checkout
    {
      public partial class CheckoutComplete : System.Web.UI.Page
      {
        protected void Page_Load(object sender, EventArgs e)
        {
          if (!IsPostBack)
          {
            // Verify user has completed the checkout process.
            if ((string)Session["userCheckoutCompleted"] != "true")
            {
              Session["userCheckoutCompleted"] = string.Empty;
              Response.Redirect("CheckoutError.aspx?" + "Desc=Unvalidated%20Checkout.");
            }
    
            NVPAPICaller payPalCaller = new NVPAPICaller();
    
            string retMsg = "";
            string token = "";
            string finalPaymentAmount = "";
            string PayerID = "";
            NVPCodec decoder = new NVPCodec();
    
            token = Session["token"].ToString();
            PayerID = Session["payerId"].ToString();
            finalPaymentAmount = Session["payment_amt"].ToString();
    
            bool ret = payPalCaller.DoCheckoutPayment(finalPaymentAmount, token, PayerID, ref decoder, ref retMsg);
            if (ret)
            {
              // Retrieve PayPal confirmation value.
              string PaymentConfirmation = decoder["PAYMENTINFO_0_TRANSACTIONID"].ToString();
              TransactionId.Text = PaymentConfirmation;
    
              ProductContext _db = new ProductContext();
              // Get the current order id.
              int currentOrderId = -1;
              if (Session["currentOrderId"] != string.Empty)
              {
                currentOrderId = Convert.ToInt32(Session["currentOrderID"]);
              }
              Order myCurrentOrder;
              if (currentOrderId >= 0)
              {
                // Get the order based on order id.
                myCurrentOrder = _db.Orders.Single(o => o.OrderId == currentOrderId);
                // Update the order to reflect payment has been completed.
                myCurrentOrder.PaymentTransactionId = PaymentConfirmation;
                // Save to DB.
                _db.SaveChanges();
              }
    
              // Clear shopping cart.
              using (WingtipToys.Logic.ShoppingCartActions usersShoppingCart =
                  new WingtipToys.Logic.ShoppingCartActions())
              {
                usersShoppingCart.EmptyCart();
              }
    
              // Clear order id.
              Session["currentOrderId"] = string.Empty;
            }
            else
            {
              Response.Redirect("CheckoutError.aspx?" + retMsg);
            }
          }
        }
    
        protected void Continue_Click(object sender, EventArgs e)
        {
          Response.Redirect("~/Default.aspx");
        }
      }
    }
    

CheckoutComplete.aspx 페이지가 로드되면 메서드가 DoCheckoutPayment 호출됩니다. 앞에서 설명한 대로 메서드는 DoCheckoutPayment PayPal 테스트 환경에서 구매를 완료합니다. PayPal이 주문 구매를 완료하면 CheckoutComplete.aspx 페이지에 구매자에 대한 결제 거래가 ID 표시됩니다.

구매 취소 처리

사용자가 구매를 취소하기로 결정한 경우 주문이 취소된 것을 볼 수 있는 CheckoutCancel.aspx 페이지로 이동됩니다.

  1. Checkout 폴더에서 CheckoutCancel.aspx라는 페이지를 엽니다.

  2. 기존 태그를 다음으로 바꿉다.

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutCancel.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutCancel" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Cancelled</h1>
        <p></p>
        <h3>Your purchase has been cancelled.</h3>
    </asp:Content>
    

구매 오류 처리

구매 프로세스 중 오류는 CheckoutError.aspx 페이지에서 처리됩니다. 오류가 발생하면 CheckoutStart.aspx 페이지, CheckoutReview.aspx 페이지 및 CheckoutComplete.aspx 페이지의 코드 숨김이 각각 CheckoutError.aspx 페이지로 리디렉션됩니다.

  1. Checkout 폴더에서 CheckoutError.aspx라는 페이지를 엽니다.

  2. 기존 태그를 다음으로 바꿉다.

    <%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CheckoutError.aspx.cs" Inherits="WingtipToys.Checkout.CheckoutError" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <h1>Checkout Error</h1>
        <p></p>
    <table id="ErrorTable">
        <tr>
            <td class="field"></td>
            <td><%=Request.QueryString.Get("ErrorCode")%></td>
        </tr>
        <tr>
            <td class="field"></td>
            <td><%=Request.QueryString.Get("Desc")%></td>
        </tr>
        <tr>
            <td class="field"></td>
            <td><%=Request.QueryString.Get("Desc2")%></td>
        </tr>
    </table>
        <p></p>
    </asp:Content>
    

CheckoutError.aspx 페이지는 체크 아웃 프로세스 중에 오류가 발생할 때 오류 세부 정보와 함께 표시됩니다.

애플리케이션 실행

애플리케이션을 실행하여 제품을 구매하는 방법을 확인합니다. PayPal 테스트 환경에서 실행됩니다. 실제 돈은 교환되지 않습니다.

  1. 모든 파일이 Visual Studio에 저장되어 있는지 확인합니다.

  2. 웹 브라우저를 열고 로 이동합니다 https://developer.paypal.com.

  3. 이 자습서의 앞부분에서 만든 PayPal 개발자 계정으로 로그인합니다.
    PayPal의 개발자 샌드박스의 경우 에 https://developer.paypal.com 로그인하여 빠른 체크 아웃을 테스트해야 합니다. 이는 PayPal의 라이브 환경이 아닌 PayPal의 샌드박스 테스트에만 적용됩니다.

  4. Visual Studio에서 F5 키를 눌러 Wingtip Toys 샘플 애플리케이션을 실행합니다.
    데이터베이스가 다시 빌드되면 브라우저가 열리고 Default.aspx 페이지가 표시됩니다.

  5. "자동차"와 같은 제품 범주를 선택한 다음 각 제품 옆에 있는 카트에 추가를 클릭하여 쇼핑 카트에 세 가지 제품을 추가합니다.
    쇼핑 카트에 선택한 제품이 표시됩니다.

  6. 페이팔 단추를 클릭하여 체크 아웃합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 카트

    체크 아웃하려면 Wingtip Toys 샘플 애플리케이션에 대한 사용자 계정이 있어야 합니다.

  7. 페이지 오른쪽에서 Google 링크를 클릭하여 기존 gmail.com 전자 메일 계정으로 로그인합니다.
    gmail.com 계정이 없는 경우 www.gmail.com 테스트 목적으로 계정을 만들 수 있습니다. "등록"을 클릭하여 표준 로컬 계정을 사용할 수도 있습니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 로그인

  8. gmail 계정 및 암호로 로그인합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - Gmail 로그인

  9. 로그인 단추를 클릭하여 Wingtip Toys 샘플 애플리케이션 사용자 이름으로 gmail 계정을 등록합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 계정 등록

  10. PayPal 테스트 사이트에서 이 자습서의 앞부분에서 만든 구매자 이메일 주소와 암호를 추가한 다음 로그인 단추를 클릭합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - PayPal 로그인

  11. PayPal 정책에 동의하고 동의 및 계속 단추를 클릭합니다.
    이 페이지는 이 PayPal 계정을 처음 사용할 때만 표시됩니다. 다시 말하지만 이것은 테스트 계정이며 실제 돈은 교환되지 않습니다.

    PayPal을 사용하여 체크 아웃 및 결제 - PayPal 정책

  12. PayPal 테스트 환경 검토 페이지에서 주문 정보를 검토하고 계속을 클릭합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 정보 검토

  13. CheckoutReview.aspx 페이지에서 주문 금액을 확인하고 생성된 배송 주소를 확인합니다. 그런 다음 주문 완료 단추를 클릭합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 주문 검토

  14. 결제 트랜잭션 ID와 함께 CheckoutComplete.aspx 페이지가 표시됩니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 체크 아웃 완료

데이터베이스 검토

애플리케이션을 실행한 후 Wingtip Toys 샘플 애플리케이션 데이터베이스에서 업데이트된 데이터를 검토하면 애플리케이션이 제품 구매를 성공적으로 기록했음을 알 수 있습니다.

이 자습서 시리즈의 앞부분에서와 같이 데이터베이스 Explorer 창(Visual Studio의 서버 Explorer 창)을 사용하여 Wingtiptoys.mdf 데이터베이스 파일에 포함된 데이터를 검사할 수 있습니다.

  1. 여전히 열려 있는 경우 브라우저 창을 닫습니다.

  2. Visual Studio에서 솔루션 탐색기 맨 위에 있는 모든 파일 표시 아이콘을 선택하여 App_Data 폴더를 확장할 수 있습니다.

  3. App_Data 폴더를 확장합니다.
    폴더에 대한 모든 파일 표시 아이콘을 선택해야 할 수도 있습니다.

  4. Wingtiptoys.mdf 데이터베이스 파일을 마우스 오른쪽 단추로 클릭하고 열기를 선택합니다.
    서버 Explorer 표시됩니다.

  5. 테이블 폴더를 확장합니다.

  6. Orders테이블을 마우스 오른쪽 단추로 클릭하고 테이블 데이터 표시를 선택합니다.
    Orders 테이블이 표시됩니다.

  7. PaymentTransactionID 열을 검토하여 성공적인 트랜잭션을 확인합니다.

    PayPal을 사용하여 체크 아웃 및 결제 - 데이터베이스 검토

  8. 주문 테이블 창을 닫습니다.

  9. 서버 Explorer OrderDetails 테이블을 마우스 오른쪽 단추로 클릭하고 테이블 데이터 표시를 선택합니다.

  10. OrderIdOrderDetails 테이블에서 및 Username 값을 검토합니다. 이러한 값은 Orders 테이블에 포함된 및 값과 Username 일치 OrderId 합니다.

  11. OrderDetails 테이블 창을 닫습니다.

  12. Wingtip Toys 데이터베이스 파일(Wingtiptoys.mdf)을 마우스 오른쪽 단추로 클릭하고 연결 닫기를 선택합니다.

  13. 솔루션 탐색기 창이 표시되지 않으면 서버 Explorer 창 아래쪽의 솔루션 탐색기 클릭하여 솔루션 탐색기 다시 표시합니다.

요약

이 자습서에서는 제품 구매를 추적하기 위해 주문 및 주문 세부 스키마를 추가했습니다. 또한 PayPal 기능을 Wingtip Toys 샘플 애플리케이션에 통합했습니다.

추가 리소스

ASP.NET 구성 개요
멤버 자격, OAuth 및 SQL Database 사용하여 보안 ASP.NET Web Forms 앱을 배포하여 Azure App Service
Microsoft Azure - 평가판

고지 사항

이 자습서에는 샘플 코드가 포함되어 있습니다. 이러한 샘플 코드는 어떠한 종류의 보증도 없이 "있는 그대로" 제공됩니다. 따라서 Microsoft는 샘플 코드의 정확도, 무결성 또는 품질을 보장하지 않습니다. 사용자 고유의 위험에 샘플 코드를 사용하는 데 동의합니다. 어떠한 경우에도 Microsoft는 샘플 코드, 콘텐츠, 샘플 코드의 오류 또는 누락 또는 샘플 코드 사용으로 인해 발생한 모든 종류의 손실 또는 손상을 포함하되 이에 국한되지 않는 샘플 코드에 대해 어떠한 방식으로든 귀하에게 책임을 지지 않습니다. 귀하는 이에 따라 귀하가 게시, 전송, 사용 또는 의존하는 자료를 포함하여 모든 종류의 손실, 상해 또는 손해에 대한 모든 손실, 손실, 상해 또는 손해에 대해 Microsoft의 무해한 배상, 저장 및 보유에 동의하며 이에 국한되지 않습니다.