카테고리 없음

결제 기능 구현 2 : 사전검증 전 단계 - jsp, 오라클, mybatis

blogOwner 2024. 4. 1. 21:53

사전 검증 전 단계를 해보자.

위 페이지에서, 3개 있는 저게 상품이다. 

결제하기 버튼은 a태그로 만들었다. 

<a href="/show-order-sheet?productId=21">결제하기</a>

이런식으로 되어 있는데, 원래는 상품을 판매하는 사람이 판매할 상품을 등록하면, 그 상품의 PRODUCTS 테이블 id 컬럼값이 지금 이 a태그의 href 속성 중 productId 값으로 바인딩되도록 해야겠지만, 현재 그것까지 고려할 수는 없었다. 그래서, 그냥 terminal 에서 insert 쿼리를 PRODUCTS 테이블에 삽입했다. 

INSERT INTO PRODUCTS (id, product_name, stock, amount) VALUES (21, '3개월 무료', 1, 1); 

 

어쨋든, 위 a 태그를 클릭하면, 해당 상품의 PRODUCTS 테이블의 id 컬럼값인 21이 컨트롤러로 전송되도록 하였다. 

이 get요청을 받는 컨트롤러는 아래와 같다. 

private final ShowOrderSheetService showOrderSheetService;

@GetMapping("/show-order-sheet")
public String showOrderSheet(@RequestParam String productId, HttpServletRequest request, Model model) {
    String returnString = showOrderSheetService.showOrderSheet(productId, request, model);
    if (returnString.equals("outOfStock")) {
        return "/payment/paymentHome";
    } else{
        return "/payment/orderSheet";
    }
}

ShowOrderSheetService 클래스의 showOrderSheet() 이라는 메서드를 호출하였다. 

@Service
@Slf4j
@RequiredArgsConstructor
//@Transactional <- select 문 만 존재하는 서비스계층이기 때문에 쓰지 않는다.
public class ShowOrderSheetService {
    private final ProductsMapper productsMapper;
    private final MemberMapper memberMapper;

    public String showOrderSheet(String productId, HttpServletRequest request, Model model){
        //재고확인
        Integer stock = productsMapper.findStock(productId);
        if (stock == 0) {
            model.addAttribute("outOfStockMessage", "죄송합니다. 상품이 품절되었습니다.");
            return "outOfStock";
        }

        //회원 정보 model에 담아주기
        Integer id = FindLoginedMemberIdUtil.findLoginedMember(request);
        MemberAllDataFindDTO memberTotalData = memberMapper.findMemberTotalData(String.valueOf(id));
        model.addAttribute("memberTotalData", memberTotalData);

        //사려는 상품의 <id + 이름 + 가격> 을 model에 담아주기
        String productName = productsMapper.findProductNameByProductId(productId);
        Integer amount = productsMapper.findAmount(productId);

        model.addAttribute("productId", productId);
        model.addAttribute("productName", productName);
        model.addAttribute("amount", amount);

        return "success";
    }
}

사용자가 결제하기 버튼을 딱 클릭하면, 가장 먼저 체크해줘야 할 것이, 재고확인이라고 생각했다. 

 

ProductsMapper 의 findStock 메서드를 호출하면 실행되는 쿼리는 아래와 같다. 

    <select id="findStock" resultType="Integer">
        SELECT stock FROM products
        WHERE id = #{productId}
    </select>

조회된 stock 이 0이라면, outOfStockMessage 라는 키에 "죄송합니다. 상품이 품절되었습니다." 라는 메세지를 담은 다음, outOfStock 이라는 문자열을 리턴한다. 

그러면, 리턴되서, showOrderSheet 이라는 메서드 중 아래 부분에 의해 paymentHome.jsp 파일로 제어권이 넘겨질 것이다. 

    if (returnString.equals("outOfStock")) {
        return "/payment/paymentHome";
    }

paymentHome.jsp 는 처음에 봤던, 

이 페이지이다. 이 페이지에서는 

    <!-- 재고가 없을 경우에만 띄워질 alert 창 -->
        <% if (request.getAttribute("outOfStockMessage") != null) { %>
        <script>
            // alert 창으로 메시지 띄우기
            alert("<%= request.getAttribute("outOfStockMessage") %>");
        </script>
        <% } %>
    <!-- ---------------------------- -->

이런식으로, outOfStockMessage 라는 키에 담긴 게 null 이 아니라면, alert 창을 띄워주도록 하였다. 

다시 ShowOrderSheetService 클래스의 showOrderSheet() 이라는 메서드 로 돌아가자. 

만약 재고가 0이 아닐 경우에는, 어떻게 할 것인가?

아래와 같이 주문서를 띄워줄 것이다. 

그러기 위해서는, 사용자에 대한 정보,

사용자가 구매하려고 클릭한 상품의 PRODUCTS 테이블 "id컬럼값" + "product_name컬럼값" + "amount(가격) 컬럼값" 을 model 에 담아줘야 한다. 

그럼 이제, ShowOrderSheetService 클래스의 showOrderSheet() 이라는 메서드 중 아래 부분에 의해 orderSheet.jsp 파일로 제어권이 넘어갈 것이다. orderSheet.jsp 파일이 렌더링 된 모습이 위 이미지이다.

else{
    return "/payment/orderSheet";
}

 

orderSheet.jsp 파일의 전체 코드는 아래와 같다. 

<form method="post" id="order-form">
<div id="full-container">
    <div id="order-sheet-container">
            <div id="left-container">


                <div id="page-title">주문서</div>
                <div id="buyer-info-title">구매자 정보</div>
                <div id="buyer-info-container">
                    <div id="buyer-info-first">
                        <span>이름  </span>
                        <input type="text" name="userName" value="${memberTotalData.userName}" readonly="true">
                    </div>
                    <div id="buyer-info-second">
                        <span>이메일 </span>
                        <span>${memberTotalData.email} @ ${memberTotalData.domain}</span>
                    </div>
                    <div id="buyer-info-third">
                        <span>휴대폰 번호  </span>
                        <input type="text" name="phoneNumStart" value="${memberTotalData.phoneNumStart}" class="can-write">
                        <input type="text" name="phoneNumMiddle" value="${memberTotalData.phoneNumMiddle}" class="can-write">
                        <input type="text" name="phoneNumEnd" value="${memberTotalData.phoneNumEnd}" class="can-write">
                    </div>

                    <div id="buyer-info-fourth">
                        <input class="address-btn" type="button" onclick="sample4_execDaumPostcode()" value="우편번호 찾기"><br>
                    </div>

                    <div id="buyer-info-fifth">
                        <span>우편번호</span>
                        <input type="text" name="zipCode" id="sample4_postcode" class="input-tag" placeholder="우편번호" value="${memberTotalData.zipCode}" readonly="true"/>
                    </div>

                    <div id="buyer-info-sixth">
                        <span>도로명 주소</span>
                        <input type="text" name="streetAddress" id="sample4_roadAddress" class="input-tag" placeholder="도로명주소" style="margin-top:1.5px;" value="${memberTotalData.streetAddress}" readonly="true" />
                    </div>
                    <div id="buyer-info-seventh">
                        <span>지번주소</span>
                        <input type="text" name="address" id="sample4_jibunAddress" class="input-tag" placeholder="지번주소" value="${memberTotalData.address}"style="margin-top:1.5px;" readonly="true"/>
                    </div>
                        <span id="guide" style="color:#999;display:none"></span>
                    <div id="buyer-info-eightth">
                        <span>상세주소</span>
                        <input type="text" name="detailAddress" id="sample4_detailAddress" class="can-write" value="${memberTotalData.detailAddress}" placeholder="상세주소" style="margin-top:1.5px;" class="can-write" />

                    </div>
                    <div id="buyer-info-nineth">
                        <span>참고항목</span>
                        <input type="text" name="referenceItem" id="sample4_extraAddress" class="input-tag" placeholder="참고항목" value="${memberTotalData.referenceItem}"  readonly="true" />
                    </div>

                    <div id="product-info-title">상품 정보</div>
                        <div id="product-info-first">
                            <span>상품 이름 </span>
                            <input type="text" name="productName" value="${productName}" readonly="true">
                            <input type="hidden" name="productId" value="${productId}">
                        </div>
                    <div id="pay-info-title">결제 정보</div>
                    <div id="pay-info-container">
                            <span>총상품가격 : </span>
                            <input type="text" name="totalAmount" value="${amount}" readonly="true">
                    </div>

                </div>



            </div>
            <div id="middle-container"></div>
            <div id="right-container">
                    <button type="submit" id="pay-btn">결제하기</button>
            </div>
    </div>
</div>
</form>

특별한 건 없다. 서비스계층에서 model 에 담아줬던 구매자에 대한 정보나 상품의 이름 결제가격 등에 대한 걸 표현하고 있다. 

주문서에서 전화번호와 배송지는 변경할 수 있도록 해두었다. 

이제 결제하기 버튼을 딱 누르면, form 이 전송될 거 아니야? 

이제 이 순간부터 orderSheet.jsp 파일 중 head 태그 안에 있는 아래 코드가 실행될 것이다. 

<head>
      <link rel="stylesheet" href="../../css/payment/orderSheet.css">
      <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
      <script src="https://cdn.iamport.kr/v1/iamport.js"></script>
      <script>
      document.addEventListener("DOMContentLoaded", function() {

          var IMP = window.IMP;
          IMP.init("imp46526078");

          // 폼 선택
          const form = document.getElementById('order-form');

          form.addEventListener('submit', function(event) { //submit(제출) 되면 이벤트 발생
              event.preventDefault(); // 폼의 기본 제출 동작을 방지

              // FormData 객체 생성
              const formData = new FormData(form);

              // fetch API를 사용하여 폼 데이터를 서버로 비동기적으로 전송
              fetch('/order-process', {
                  method: 'POST',
                  body: formData, // 폼 데이터. , 끝에 붙이는 거 문제 안된다고 함. 오히려 개발자들이 선호한다고 함.
              })
              .then(response => {
                  if (!response.ok) {
                      throw new Error('Network response was not ok');
                  }
                  return response.json(); // 서버로부터 반환된 JSON 응답을 파싱. 이러면 아래 data 여기에 알아서 파싱된 json 이 담김. data.json키 를 쓰면, value 를 얻을 수 있지.
              })
              .then(data => {
                  if(data.result = 'success'){
                        //사전검증 시작
                            fetch('/pre-validation?productId=${productId}&merchantUid='+ data.merchantUid)
                            .then(response => {
                                if (!response.ok) {
                                  throw new Error('Network response was not ok');
                                }
                                return response.json();
                            })
                            .then(data => {
                                //사전검증이 끝나면 할 것들 : 포트원 api 통신
                                //일단, 재고부족인 경우 처리
                                if(data.outOfStock == true){
                                    alert('재고가 부족하여 주문이 취소되었습니다.'); // alert창 띄우고,
                                    window.location.href="/payment-home"; // paymentHome.jsp 로 인도하는 컨트롤러로 get요청.
                                }
                                // 재고가 있는 경우,
                                var merchantUid = data.merchantUid;
                                var productName = data.productName;
                                var amount = data.amount;
                                var email = data.memberTotalData.email;
                                var domain = data.memberTotalData.domain;
                                var emailaddress = email + '@' + domain;
                                var userName = data.memberTotalData.userName;
                                var phoneNumStart = data.memberTotalData.phoneNumStart;
                                var phoneNumMiddle = data.memberTotalData.phoneNumMiddle;
                                var phoneNumEnd = data.memberTotalData.phoneNumEnd;
                                var phone = phoneNumStart + phoneNumMiddle + phoneNumEnd;
                                var streetAddress = data.memberTotalData.streetAddress;
                                var zipCode = data.memberTotalData.zipCode;




                                //여기서, portone 시작
                                    IMP.request_pay(

                                              ///////////////////////// 첫번째 ////////////////////////////////
                                              {
                                                pg: "html5_inicis",
                                                pay_method: "card",
                                                merchant_uid: merchantUid,
                                                name: productName,
                                                amount: amount,
                                                buyer_email: emailaddress,
                                                buyer_name: userName,
                                                buyer_tel: phone,
                                                buyer_addr: streetAddress,
                                                buyer_postcode: zipCode,
                                              },

                                              ////////////////////////두번째 //////////////////////////////////
                                              function (rsp) {
                                                    if (rsp.success) {
                                                              // axios로 HTTP 요청
                                                              axios({
                                                                url: "/payment-response", // 실제 서버의 엔드포인트 주소로 수정
                                                                method: "post",
                                                                headers: { "Content-Type": "application/json" },
                                                                data: {
                                                                  imp_uid: rsp.imp_uid,
                                                                  merchant_uid: rsp.merchant_uid
                                                                }
                                                              })

                                                              .then((response) => {
                                                                // 서버 결제 API 성공 시 로직
                                                                if(response = 'success'){ // 성공한 경우
                                                                    alert('결제가 성공하였습니다.');
                                                                    window.location.href="/payment-home";


                                                                } else {
                                                                    alert(response); // 그 외의 경우
                                                                    window.location.href="/payment-home";
                                                                }
                                                              })
                                                              .catch((error) => {
                                                                console.error(error);
                                                              });
                                                    } else {
                                                            alert('결제에 실패하였습니다. 에러 내용 : ' + rsp.error_msg);
                                                            //결제에 실패한 경우, 재고 +1 해줘야 하고, orders테이블에서 행 삭제
                                                            window.location.href="/payment-fail?merchantUid=" + merchantUid + "&impUid=" + rsp.imp_uid;
                                                    }
                                              }


                                    );

                                    //여기서, portone 끝



                            })
                            .catch(error => {
                                console.error('There has been a problem with your fetch operation:', error)
                                alert('결제 중 오류가 발생했습니다.');
                            });


                        //사전검증 끝


                  }

              })
              .catch(error => {
                  console.error('There has been a problem with your fetch operation:',
                                error);
              });

      		});

      });
      </script>


</head>

이번 포스팅은 사전 검증 전 까지의 단계를 담을 거라고 했기 때문에, 위 코드 중 아래부분만 다룰 것이다. 

document.addEventListener("DOMContentLoaded", function() {

  var IMP = window.IMP;
  IMP.init("impXXXXXXXXXX");

  // 폼 선택
  const form = document.getElementById('order-form');

  form.addEventListener('submit', function(event) { //submit(제출) 되면 이벤트 발생
      event.preventDefault(); // 폼의 기본 제출 동작을 방지

      // FormData 객체 생성
      const formData = new FormData(form);

      // fetch API를 사용하여 폼 데이터를 서버로 비동기적으로 전송
      fetch('/order-process', {
          method: 'POST',
          body: formData, // 폼 데이터. , 끝에 붙이는 거 문제 안된다고 함. 오히려 개발자들이 선호한다고 함.
      })
      .then(response => {
          if (!response.ok) {
              throw new Error('Network response was not ok');
          }
          return response.json(); // 서버로부터 반환된 JSON 응답을 파싱. 이러면 아래 data 여기에 알아서 파싱된 json 이 담김. data.json키 를 쓰면, value 를 얻을 수 있지.
      })
      .then(data => {
          if(data.result = 'success'){

우선, 

  var IMP = window.IMP;
  IMP.init("impXXXXXXXXXX");

 

이 부분은 아직 필요 없으니까, 놔둔다.

 

그리고 그 밑에 보면, 폼이 제출되면, 폼의 기본 제출 동작을 방지한 다음에, /order-process 라는 url 로 폼데이터를 POST 방식으로 넘기고 있다. 이 폼데이터를 받는 컨트롤러는 아래와 같은데, OrderProcessService 클래스의 orderProcessService 메서드를 호출하고, 리턴된 Map자료구조를 그대로 @ResponseBody 로 jsp파일에 돌려주고 있다. 

private final OrderProcessService orderProcessService;

@PostMapping("/order-process")
@ResponseBody
public Map<String, String> orderProcess(@ModelAttribute OrderFormDTO orderFormDTO, HttpServletRequest request) {
    Map<String, String> returnMap = orderProcessService.orderProcess(orderFormDTO, request);
    return returnMap;
}

 

참고로, OrderFormDTO 는 아래 접은글에 담아두었다.

더보기
@Data
public class OrderFormDTO {
    private String userName;
    private String email;
    private String domain;
    private String phoneNumStart;
    private String phoneNumMiddle;
    private String phoneNumEnd;
    private String zipCode;
    private String streetAddress;
    private String address;
    private String detailAddress;
    private String referenceItem;
    private String productName;
    private String productId;
    private String totalAmount;


}

 

OrderProcessService 클래스의 orderProcessService 메서드 는 아래와 같다. 

여기서, 해준건 뭐냐면, 폼데이터를 가지고, ORDERS 테이블에 행을 삽입하는 것이다. 

즉, 주문 행을 삽입하는 코드이다. 결제하기를 누르는 순간, 주문 테이블에 행을 삽입했다. 

왜 지금인가? 왜 지금 주문테이블에 행을 삽입했는가? 

결제를 하다가 구매자가 그냥 결제창을 닫아버릴 수 있잖아. 또는 결제에 문제가 생길 수도 있잖아. 

그럴 경우, 어떤 주문이 어떤 문제로 인해 실패했는지에 대해 담아두려면, 주문 테이블에 행이 존재하고 있어야 하기 때문이다. 

@Slf4j
@Service
@RequiredArgsConstructor
//@Transactional 은 쓰지 않는다. 왜? '하나의' insert 문만 있는 서비스계층이기 때문이다.
public class OrderProcessService {
    private final OrdersMapper ordersMapper;
    public Map<String, String> orderProcess(OrderFormDTO orderFormDTO, HttpServletRequest request){
        //orders 테이블에 행을 삽입하기 위한 재료 모음.
        // 1. memberId 가져오기
        Integer memberId = FindLoginedMemberIdUtil.findLoginedMember(request);

        // 2. productId
        String productId = orderFormDTO.getProductId();

        // 3. merchantUid(주문번호, 이 주문에 부여된 고유한 키로서, 다른 주문과의 구별을 하게 해주는 식별키) 생성
        long nano = System.currentTimeMillis();
        String merchantUid = "pid-" + nano;

        // 4. amount : 해당 주문을 하는 사용자가 지불해야할 총 가격
        Integer amount = Integer.parseInt(orderFormDTO.getTotalAmount());

        // 5. phoneNum : member 테이블에서 해당 사용자의 핸드폰번호를 가져오지 않았다. 왜? 해당 주문을 한 사용자가 어떤 주문에서는 연락받을 핸드폰번호를 바꾸고 싶어하는 경우도 있을 수 있기 때문에.
        String phoneNumStart = orderFormDTO.getPhoneNumStart();
        String phoneNumMiddle = orderFormDTO.getPhoneNumMiddle();
        String phoneNumEnd = orderFormDTO.getPhoneNumEnd();

        // 6. address : member 테이블에서 해당 사용자의 주소를 가져오지 않았다. 왜? 해당 주문을 한 사용자가 어떤 주문은 다른 곳에서 상품을 받고 싶어할 수 있기 때문이다.
        String zipCode = orderFormDTO.getZipCode();
        String streetAddress = orderFormDTO.getStreetAddress();
        String address = orderFormDTO.getAddress();
        String detailAddress = orderFormDTO.getDetailAddress();
        String referenceItem = orderFormDTO.getReferenceItem();

        //orders 테이블에 행을 삽입해야해.
        // Pending : 주문이 접수되었지만, 아직 재고확인 & 결제확인 이 되지 않은 상태. 라고 통용되는 단어라고 함.
        // 참고로, 현재, 재고확인은 showOrderSheet 라는 메서드에서 하고 있지만, preparePayment 메서드 에서 사전검증에 들어가기 전에 한번 더 할 것이다.
        ordersMapper.insertOrder(memberId,
                productId,
                merchantUid,
                amount,
                "Pending",
                "내용없음",
                phoneNumStart,
                phoneNumMiddle,
                phoneNumEnd,
                zipCode,
                streetAddress,
                address,
                detailAddress,
                referenceItem);

        Map<String, String> returnMap = new ConcurrentHashMap<>();
        returnMap.put("result", "success");
        // orderSheet.jsp 에 생성된 merchantUid 를 넘겨주는 이유는, orderSheet.jsp 에서 다시 preparePayment메서드에 넘겨주기 위함이다.
        returnMap.put("merchantUid", merchantUid);

        return returnMap;
    }
}

일단, 

        // 3. merchantUid(주문번호, 이 주문에 부여된 고유한 키로서, 다른 주문과의 구별을 하게 해주는 식별키) 생성
        long nano = System.currentTimeMillis();
        String merchantUid = "pid-" + nano;

이 부분을 보자. 

merchantUid 라는 게 뭐냐면, 내 애플리케이션단에서 랜덤하게 만들어준 해당 주문건에 대한 고유한 값이다. 

간단히 말해, 내 애플리케이션에서 해당 주문을 식별할 수 있는 값을 만들었다는 것이다. orderSheet.jsp 파일에서 써야 되기 때문에, Map<String,String> 자료구조에 담아놨다. 

 

그리고 주문 테이블에 행을 삽입하는 

        ordersMapper.insertOrder(memberId,
                productId,
                merchantUid,
                amount,
                "Pending",
                "내용없음",
                phoneNumStart,
                phoneNumMiddle,
                phoneNumEnd,
                zipCode,
                streetAddress,
                address,
                detailAddress,
                referenceItem);

이 코드 중 주의깊게 봐야할 부분은, status 컬럼에는 Pending 이라는 문자열을 넣고 있는데, 이는 "주문이 생성되었지만 아직 그 주문이 결제되거나 하는등의 처리가 되지 않은 상태" 로서 "주문이 생성된 초기 상태" 를 표현하는 단어라고 한다. 

 

여차저차해서, orderSheet.jsp 에 만들어진 Map<String,String> 자료구조를 넘겨준다. 

그럼

document.addEventListener("DOMContentLoaded", function() {

  var IMP = window.IMP;
  IMP.init("impXXXXXXXXXX");

  // 폼 선택
  const form = document.getElementById('order-form');

  form.addEventListener('submit', function(event) { //submit(제출) 되면 이벤트 발생
      event.preventDefault(); // 폼의 기본 제출 동작을 방지

      // FormData 객체 생성
      const formData = new FormData(form);

      // fetch API를 사용하여 폼 데이터를 서버로 비동기적으로 전송
      fetch('/order-process', {
          method: 'POST',
          body: formData, // 폼 데이터. , 끝에 붙이는 거 문제 안된다고 함. 오히려 개발자들이 선호한다고 함.
      })
      .then(response => {
          if (!response.ok) {
              throw new Error('Network response was not ok');
          }
          return response.json(); // 서버로부터 반환된 JSON 응답을 파싱. 이러면 아래 data 여기에 알아서 파싱된 json 이 담김. data.json키 를 쓰면, value 를 얻을 수 있지.
      })
      .then(data => {
          if(data.result = 'success'){

위 코드 중,       

.then(response => {
          if (!response.ok) {
              throw new Error('Network response was not ok');
          }
          return response.json(); // 서버로부터 반환된 JSON 응답을 파싱. 이러면 아래 data 여기에 알아서 파싱된 json 이 담김.               data.json키 를 쓰면, value 를 얻을 수 있지.
      })
      .then(data => {
          if(data.result = 'success'){

이 부분으로 오겠지. result 에 success 를 담아줬으므로, 이제 다음 부분인 아래 부분이 시작될 것이다. 

즉, 이제 사전검증의 시작이다. 

              .then(response => {
                  if (!response.ok) {
                      throw new Error('Network response was not ok');
                  }
                  return response.json(); // 서버로부터 반환된 JSON 응답을 파싱. 이러면 아래 data 여기에 알아서 파싱된 json 이 담김. data.json키 를 쓰면, value 를 얻을 수 있지.
              })
              .then(data => {
                  if(data.result = 'success'){
                        //사전검증 시작
                            fetch('/pre-validation?productId=${productId}&merchantUid='+ data.merchantUid)
                            .then(response => {
                                if (!response.ok) {
                                  throw new Error('Network response was not ok');
                                }
                                return response.json();
                            })
                            .then(data => {
                                //사전검증이 끝나면 할 것들 : 포트원 api 통신
                                //일단, 재고부족인 경우 처리
                                if(data.outOfStock == true){
                                    alert('재고가 부족하여 주문이 취소되었습니다.'); // alert창 띄우고,
                                    window.location.href="/payment-home"; // paymentHome.jsp 로 인도하는 컨트롤러로 get요청.
                                }
                                // 재고가 있는 경우,
                                var merchantUid = data.merchantUid;
                                var productName = data.productName;
                                var amount = data.amount;
                                var email = data.memberTotalData.email;
                                var domain = data.memberTotalData.domain;
                                var emailaddress = email + '@' + domain;
                                var userName = data.memberTotalData.userName;
                                var phoneNumStart = data.memberTotalData.phoneNumStart;
                                var phoneNumMiddle = data.memberTotalData.phoneNumMiddle;
                                var phoneNumEnd = data.memberTotalData.phoneNumEnd;
                                var phone = phoneNumStart + phoneNumMiddle + phoneNumEnd;
                                var streetAddress = data.memberTotalData.streetAddress;
                                var zipCode = data.memberTotalData.zipCode;