[Flutter] Toss Payments 연결(업데이트 1)

S_Soo100·2022년 11월 11일
4

flutter

목록 보기
1/19
post-thumbnail

0. 참고자료

플러터 라이브러리 : toss_payment | Flutter Package

토스 개발자센터 : 시작하기 | 토스페이먼츠 개발자센터

1. 사용법

*pub-dev의 사용법을 따라서 차근차근 해보자!
라이브러리 자체가 토스페이먼츠가 지원해준건 아니지만, Flutter Webview를 활용해서 결제 연동이 가능하게 만들어주기 때문에 매우 유용하다.

1.1. OS별 환경세팅


1.1.1. 안드로이드 세팅

  • min SDK 20이상 설정 android/app/build.gradle
    defaultConfig {
            ...
            **minSdkVersion 20 //20이상이면 더 높아도 괜찮음**
    				...
        }
  • AndroidManifest 의 scheme와 권한 설정
    AndroidManifest.xml에서 수정한다. 👉🏻 안드로이드 권한은 AndroidManifest.xml에서 uses-permission 태그를 사용해서 부여한다.
    ```xml
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
      <queries>
          <!-- 토스 -->
          <package android:name="viva.republica.toss" />
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="supertoss" />
          </intent>
          <!-- 삼성카드 -->
          <package android:name="kr.co.samsungcard.mpocket" />
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="mpocket.online.ansimclick" />
          </intent>
          <!-- 현대카드 -->
          <package android:name="com.hyundaicard.appcard" />
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="hdcardappcardansimclick" />
          </intent>
          <!-- 현대카드공인인증서 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="smhyundaiansimclick" />
          </intent>
          <!-- 우리카드앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="wooripay" />
          </intent>
          <!-- 신한카드앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="shinhan-sr-ansimclick" />
          </intent>
          <!-- 신한카드공인인증서 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="smshinhanansimclick" />
          </intent>
          <!-- 국민카드앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="kb-acp" />
          </intent>
          <!-- 롯데카드모바일결제 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="lottesmartpay" />
          </intent>
          <!-- 롯데카드앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="lotteappcard" />
          </intent>
          <!-- 하나카드앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="cloudpay" />
          </intent>
          <!-- 농협카드-앱카드 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="nhappvardansimclick" />
          </intent>
          <!-- 농협카드공인인증서 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="nonghyupcardansimclick" />
          </intent>
          <!-- 씨티카드공인인증서 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="citicardappkr" />
          </intent>
          <!-- ISP모바일 -->
          <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="ispmobile" />
          </intent>
      </queries>
    ```
    
- 라이브러리에서 제공하는 추가팁
    1. 안드로이드 웹에 접근이 안 될때
        
        매니페스트에 아래 코드를 추가
        
        ```xml
        <application
          ...
          android:usesCleartextTraffic="true">
        ...
        </application>
        ```
        
    2. 안드로이드 12 이상에서 설치/실행이 안될 때
        
        ```xml
        <activity
          android:name=".MainActivity"
          ...
          android:exported="true">
        ...
        </activity>
        ```
        

1.1.2. Ios 세팅

  • info.plist에 외부 앱 실행을 위한 스키마 추가 아래와 같이 소스코드로 켜서 수정하면 굉장히 편하다.

*우클릭 후 Open as Source Code를 하면 코드 에디터 처럼 쓸 수 있다.

- 추가해야하는 코드
    
    ```
    <key>LSApplicationQueriesSchemes</key>
      <array>
          <string>supertoss</string> <!-- 토스페이 -->
          <string>mpocket.online.ansimclick</string> <!-- 삼성카드앱카드 -->
          <string>hdcardappcardansimclick</string> <!-- 현대카드앱카드 -->
          <string>smhyundaiansimclick</string> <!-- 현대카드공인인증서 -->
          <string>wooripay</string> <!-- 우리카드앱카드 -->
          <string>shinhan-sr-ansimclick</string> <!-- 신한카드앱카드 -->
          <string>smshinhanansimclick</string> <!-- 신한카드공인인증서 -->
          <string>kb-acp</string> <!-- 국민카드앱카드 -->
          <string>lottesmartpay</string> <!-- 롯데카드모바일결제 -->
          <string>lotteappcard</string> <!-- 롯데카드앱카드 -->
          <string>cloudpay</string> <!-- 하나카드앱카드 -->
          <string>nhappvardansimclick</string> <!-- 농협카드-앱카드 -->
          <string>nonghyupcardansimclick</string> <!-- 농협카드공인인증서 -->
          <string>citispay</string> <!-- 씨티카드앱카드 -->
          <string>citicardappkr</string> <!-- 씨티카드공인인증서 -->
          <string>ispmobile</string> <!-- ISP모바일 -->
      </array>
    ```
    
  • url_launcher 가 없어서 에러가 났다. 터미널에 설치명령어 flutter pub add url_launcher 를 입력하고 info.plist에 아래 코드를 추가로 넣어둔다.
    • 추가할 코드
      <dict>
        ...
        <key>LSApplicationQueriesSchemes</key> //이미 위에서 넣음!
        <array>  //그 밑 어레이 안애 아래 2줄만 추가하면 된다.
          <string>https</string>
          <string>http</string>
        </array>
      </dict>

1.1.3. 플러터 라이브러리 추가

//그냥 아래 코드대로 터미널 입력하거나
flutter pub add toss_payment

//pubspec.yaml에 아래 버젼을 추가후 pub get
dependencies:
  toss_payment: ^0.2.6

1.1.4. 샘플 화면 구현

토스 페이먼츠에 필요한 위젯과 mock-server를 직접 만들거나, 🔗 토스 페이먼츠 깃허브로 들어가서 파일들을 따온다.

  • 라이브러리에서 제공하는 PaymentWebView 위젯은 우측 화면에 나오는, 앱바 높이 정도를 남기고 올라온 bottom modal sheet위의 웹뷰 페이지 이다.

2. 샘플 사용


2.1 결제 요청


2.1.1. 진행 방식(카드결제 기준)


출처 : https://docs.tosspayments.com/guides/windows/card

  • 앱(클라이언트)에서 해줘야 하는 일은 결제창을 호출해주고(웹뷰 호출, 결제정보 전달)
    결제가 완료되면 해당 결과를 표시해주면 된다.
  • 우리는 Firebase를 사용 할 것이기 때문에, Firebase Functions로 html을 전달받아 보낼 것이다.
    현재는 MockServer와 html_service.dart에서 html을 받아오고 있다.

2.1.2. 테스트 결제 진행

  • 기본적으로 문자열로 {결제 형태}를 보내야 하며,
    {주문번호}, {주문 명}, {성공 URL}, {실패 URL}, {주문자 명}은 필수로 전달해줘야 한다.
    - 다른 서류들은 토스 개발자 공식문서를 참고하자
  • 우선 결제 정보를 담는다. 샘플에는 PaymentRequest라는 모델 클래스를 사용해서 정보를 담고 jsonParse를 했다.
    PaymentRequest request = PaymentRequest( //모델 클래스 PaymentRequest
    	payBy: "카드"
      amount: {price},
      orderId: {id}, //"8pk23f" 같이 6자리 문자열로 랜덤 생성
      orderName: {name},  //"생수 외 2건" 같은 식으로
      customerName: {userName},
    );
  • 요청을 보낼 URL 전달 샘플에서는 extension 을 사용해서 url을 덧붙이고 있고, localhost 8080번을 사용했다.
    extension PaymentRequestExtension on PaymentRequest {
      Uri get url {
        // TODO 토스페이를 위해 만든 Web 주소를 넣어주세요. 아래는 예시입니다.
        return Uri.http("localhost:8080", "payment", json);
      }
  • 현재는 mock server가 서버 역할을 해주고 있는데, mock server가 쓰는
    http://localhost:8080/payment?{query} 에서는 우리가 보낸 결제요청사항을
    자체 clientKey와 {성공 URL}, {실패 URL},과 엮어서 토스 api를 호출해준다.
  • Firebase의 CloudFunction을 활용해도 간단히 구현할 수 있다.
    우선 payment, success, fail 3가지 유형의 펑션이 필요하다.
    payment에서는 TossPaymentApi를 호출하는 html을 날려주고
    나머지는 api호출 성공시 활용하며 성공 혹은 실패되었다는 메세지를 담아서 응답으로 날려주면 된다.

  • 잘 호출했다면 토스 API를 통해 카드결제창이 웹뷰에 출력된다.

    그리고 이 웹뷰창에서 결제를 완료하면, 위에서 미리 설정해둔 success 혹은 fail url이 전달되고,
    서버는 우리에게 그 url을 response로 준다.
    성공시 url 구성은 아래와 같다.
    https://{ORIGIN}/success?paymentKey={PAYMENT_KEY}&orderId={ORDER_ID}&amount={AMOUNT}
    - `paymentKey`: 결제 건에 대한 고유한 키 값
    - `orderId`: 쇼핑몰에서 주문 건을 구분하기 위해 발급한 고유 ID,
                 결제창을 열 때 [requestPayment]에 담아 보낸 값
    - `amount`: 실제로 결제된 금액
    
    이 3개의 쿼리 파라미터를 가지고 결제 완료 validation을 진행해야 한다.

2.1.3. 결제 금액 validation

  • 결제창을 열 때 requestPayment 메서드에 담아 보냈던 amount 값과
    리다이렉트 URL에 있는 실제 결제 금액인 amount 값이 같은지 확인하고,
    값이 같은 경우에만 결제 승인 API를 호출해야 한다.
    var temp = url.split('success?')[1];
      Map<String, String> params = {};
      temp.split('&').forEach((e) {
        var data = e.split('=');
        params[data[0]] = data[1];
      });
    
    if(request.amont == params['amount']){
    	//다음 결제 승인 API 호출
    }

2.1.4. 결제 승인 api 호출

  • 현재 라이브러리는 결제 요청을 보내고 응답을 받는 화면까지만 구현되어 있다.
    *2022년 11월 10일 기준

  • 그러므로 우리는 현재 라이브러리와 깃허브 코드로 구현되어 있는 PaymentWebView 위젯의 onPageFinished 파라미터에 결제 금액이 맞게 들어온 후의 결제 승인 api호출을 구현해야 한다.

  • 결제는 onPageStarted, onPageFinished에 여러 주소가 계속 꽂히기 때문에, if문으로 우리 successUrl을 onPageFinished가 받았는지 체크해주고, true면 이제 결제 승인 api호출을 하도록 만들어두자.

  • 리다이렉트 URL로 받은 [paymentKey][orderId][amount]를 요청 본문으로 함께 보내야 한다.

Dio dio = new Dio(); //dio(http통신 라이브러리의 인스턴스 준비)

if (request.amount.toString() == params['amount'].toString()) {
  dio.options.headers['content-Type'] = 'application/json';
  dio.options.headers['authorization'] =
      'Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg';
      //이부분은 내 토스페이먼츠 시크릿키를 콜론( : ) 을 뒤에 더해서 base64로 인코딩한 것이다.
      //'Basic ${시크릿키+: 를 인코딩한 값}'

	try{
	  var response = await dio.post(
	    'https://api.tosspayments.com/v1/payments/confirm',
	    data: params,
	  );
		//성공후 어떻게 할건지 구현 필요
	  Navigator.of(context).pop(true);
	}catch(e){
		//실패부분 구현 필요
	  Navigator.of(context).pop(false);
	}
}

2.1.5. 결과 확인

  • respons가 http코드 200으로 돌아오면 결제 승인이 성공한 것이다. 해당 결과를 앱에 출력해주거나, 서버에 저장하는 등의 기능을 구현해주면 된다.
    아래는 응답에 붙어있는 json예시인데, 이걸 참고해서 결과 확인 후에 유저에게 보여줄 정보들을 가공하면 된다.
{ mId: tvivarepublica,
	transactionKey: [transactionKey], 
	lastTransactionKey: {lastTransactionKey}, 
	paymentKey: {paymentKey}, 
	orderId: BwcHAgIC, 
	orderName: 토스 티셔츠, 
	taxExemptionAmount: 0, 
	status: DONE, 
	requestedAt: 2022-11-11T14:28:29+09:00, 
	approvedAt: 2022-11-11T14:28:51+09:00, 
	useEscrow: false, 
	cultureExpense: false, 
	card: {
		company: 신한, 
		issuerCode: {issuerCode}, 
		acquirerCode: {acquireCode}, 
		number: {number}, 
		installmentPlanMonths: 0, 
		isInterestFree: false, 
		interestPayer: null, 
		approveNo: 00000000, 
		useCardPoint: false, 
		cardType: 신용, 
		ownerType: 개인, 
		acquireStatus: READY, 
		receiptUrl: https://dashboard.tosspayments.com/sales-slip?
								transactionId=aGIScxIQzHzn9hcbBAlB8O568wgUN%2FNHfTGNLX9XXEE5Q9d1aPc5j2S%2BtDaF0J%2B6&
								ref=PX
		, 
		provider: null, 
		amount: 100  //totalAmount와 비교 필요
	}, 
	virtualAccount: null, 
	transfer: null, 
	mobilePhone: null, 
	giftCertificate: null, 
	cashReceipt: null, 
	discount: null, 
	cancels: null, 
	secret: {secret}, 
	type: NORMAL, 
	easyPay: 네이버페이, 
	easyPayAmount: 0, 
	easyPayDiscountAmount: 0, 
	country: KR, 
	failure: null, 
	isPartialCancelable: true, 
	receipt: {
		url: https://dashboard.tosspayments.com/sales-slip?transactionId=XL9Xc9DM2EziXHrhUDjITWhnCAXJ8TfN%2BTKwTJo8RHH7QUR8ITR5OhfFZI%2FUeFRs&ref=PX
		}, 
	checkout: {
		url: https://api.tosspayments.com/v1/payments/p5EnNZRJGvaBX7zk2yd8yP2GAzyBXrx9POLqKQjmAw4b0e1Y/checkout
		}, 
	currency: KRW, 
	totalAmount: 100, 
	balanceAmount: 100, 
	suppliedAmount: 91, 
	vat: 9, 
	taxFreeAmount: 0, 
	method: 카드, 
	version: 1.4 }
profile
플러터, 리액트

0개의 댓글