├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SUPPORT.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ └── kotlin │ └── io │ └── portone │ └── portone_flutter │ └── IamportFlutterPlugin.kt ├── assets └── images │ ├── allow-arbitrary.gif │ ├── app-scheme-registry.gif │ └── iamport-logo.png ├── build.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── io │ │ │ │ │ └── portone │ │ │ │ │ ├── example │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── portone_flutter_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle.kts ├── assets │ └── images │ │ └── iamport-logo.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── Runner-Bridging-Header.h │ │ └── main.m ├── lib │ ├── main.dart │ ├── model │ │ ├── carrier.dart │ │ ├── method.dart │ │ ├── pg.dart │ │ └── quota.dart │ └── screens │ │ ├── certification.dart │ │ ├── certification_result.dart │ │ ├── certification_test.dart │ │ ├── home.dart │ │ ├── payment.dart │ │ ├── payment_result.dart │ │ └── payment_test.dart ├── manuals │ ├── CALLBACK.md │ └── TRANS.md ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── IamportFlutterPlugin.h │ ├── IamportFlutterPlugin.m │ └── SwiftIamportFlutterPlugin.swift └── portone_flutter.podspec ├── lib ├── Iamport_certification.dart ├── iamport_payment.dart ├── model │ ├── certification_data.dart │ ├── certification_data.g.dart │ ├── iamport_url.dart │ ├── iamport_validation.dart │ ├── payment_data.dart │ ├── payment_data.g.dart │ ├── pg │ │ ├── bypass.dart │ │ ├── bypass.g.dart │ │ ├── danal │ │ │ ├── danal.dart │ │ │ └── danal.g.dart │ │ ├── daou │ │ │ ├── daou.dart │ │ │ └── daou.g.dart │ │ ├── kcp │ │ │ ├── kcp_products.dart │ │ │ └── kcp_products.g.dart │ │ ├── naver │ │ │ ├── naver_co_products.dart │ │ │ ├── naver_co_products.g.dart │ │ │ ├── naver_interface.dart │ │ │ ├── naver_interface.g.dart │ │ │ ├── naver_pay_products.dart │ │ │ ├── naver_pay_products.g.dart │ │ │ ├── naver_products.dart │ │ │ └── naver_products.g.dart │ │ ├── settle │ │ │ ├── settle.dart │ │ │ └── settle.g.dart │ │ └── tosspayments │ │ │ ├── tosspayments.dart │ │ │ └── tosspayments.g.dart │ └── url_data.dart └── widget │ ├── iamport_error.dart │ └── iamport_webview.dart ├── portone_flutter.iml └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | .atom/ 4 | .idea 5 | .vscode 6 | .packages 7 | .pub/ 8 | build/ 9 | ios/.generated/ 10 | packages 11 | pubspec.lock 12 | doc/ 13 | 14 | example/ios/Podfile.lock 15 | **/Flutter/App.framework/ 16 | **/Flutter/Flutter.framework/ 17 | **/Flutter/Generated.xcconfig/ 18 | **/Flutter/flutter_assets/ 19 | example/ios/Podfile.lock 20 | example/.flutter-plugins-dependencies 21 | example/ios/Flutter/flutter_export_environment.sh 22 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.12.1](https://github.com/portone-io/portone_flutter/tree/main) 2 | - tierCode 결제창 호출 기능을 추가했습니다. 3 | 4 | ## [0.12.0](https://github.com/portone-io/portone_flutter/tree/v0.12.0) 5 | - Flutter 3.28 컴파일 오류의 원인인 iamport_webview_flutter 에서 v1 android embedding 제거 6 | 7 | ## [0.11.0](https://github.com/portone-io/portone_flutter/tree/v0.11.0) 8 | - portone_flutter 패키지로 이전 9 | 10 | ## [0.10.21](https://github.com/portone/portone_flutter/tree/v0.10.21) 11 | - iamport_flutter discontinued 안내 추가 12 | 13 | ## [0.10.20](https://github.com/portone/portone_flutter/tree/v0.10.20) 14 | - Flutter 3.27.0 부터 발생하는 오류를 해결했습니다. 15 | 16 | ## [0.10.19](https://github.com/portone-io/portone_flutter/tree/v0.10.19) 17 | - 안드로이드 gradle plugin 버전을 8.4.2로 업데이트했습니다. 18 | - uni_links를 app_links로 변경했습니다. 19 | 20 | ## [0.10.18](https://github.com/portone-io/portone_flutter/tree/v0.10.18) 21 | - customer_id 파라미터를 추가했습니다. 22 | 23 | ## [0.10.17](https://github.com/portone-io/portone_flutter/tree/v0.10.17) 24 | - 누락된 네이버페이 전용 파라미터들을 추가했습니다. 25 | 26 | ## [0.10.16](https://github.com/portone-io/portone_flutter/tree/v0.10.16) 27 | - 현금영수증 노출 관련 파라미터들을 추가했습니다. 28 | 29 | ## [0.10.15](https://github.com/portone-io/portone_flutter/tree/v0.10.15) 30 | - 앱 실행 방식 개선 후 오류를 해결하였습니다. 31 | 32 | ## [0.10.14](https://github.com/portone-io/portone_flutter/tree/v0.10.14) 33 | - 앱 실행 방식을 개선했습니다. 34 | 35 | ## [0.10.13](https://github.com/portone-io/portone_flutter/tree/v0.10.13) 36 | - 헥토파이낸셜 전용 파라미터를 추가했습니다. 37 | 38 | ## [0.10.12](https://github.com/portone-io/portone_flutter/tree/v0.10.12) 39 | - 본인인증시 임의의 user agent를 넣을 수 있는 기능을 추가했습니다. 40 | 41 | ## [0.10.11](https://github.com/portone-io/portone_flutter/tree/v0.10.11) 42 | - 안드로이드에서 이니시스 간편결제 아이콘이 깨지는 문제를 해결했습니다. 43 | - flutter sdk 버전 조건을 수정했습니다. 44 | 45 | ## [0.10.10](https://github.com/portone-io/portone_flutter/tree/v0.10.10) 46 | - 본인인증 pg 파라미터를 추가했습니다. 47 | 48 | ## [0.10.9](https://github.com/portone-io/portone_flutter/tree/v0.10.9) 49 | - 토스페이먼츠 전용 파라미터를 추가했습니다. 50 | - v3, KB 앱스킴들을 추가했습니다. 51 | 52 | ## [0.10.8](https://github.com/portone-io/portone_flutter/tree/v0.10.8) 53 | - 키움페이(다우, 페이조아) 전용 파라미터를 추가했습니다. 54 | 55 | ## [0.10.7](https://github.com/portone-io/portone_flutter/tree/v0.10.7) 56 | - 네이버페이 비밀번호 패드에서 클릭이 잘 되지 않는 문제를 해결했습니다. 57 | 58 | ## [0.10.6](https://github.com/portone-io/portone_flutter/tree/v0.10.6) 59 | - Javascript SDK 버전을 변경했습니다. 60 | - 토스페이먼츠를 추가했습니다. 61 | 62 | ## [0.10.5](https://github.com/portone-io/portone_flutter/tree/v0.10.5) 63 | - 코틀린 버전을 1.7.20으로 업데이트했습니다. 64 | 65 | ## [0.10.4](https://github.com/portone-io/portone_flutter/tree/v0.10.4) 66 | - 할부개월수 파라미터가 제대로 들어가도록 수정했습니다. 67 | 68 | ## [0.10.3](https://github.com/portone-io/portone_flutter/tree/v0.10.3) 69 | - kcpProducts 파라미터를 추가했습니다. 70 | 71 | ## [0.10.2](https://github.com/portone-io/portone_flutter/tree/v0.10.2) 72 | - 웹뷰 컴포넌트들에 gestureRecognizers 파라미터를 추가했습니다. 73 | 74 | ## [0.10.1+4](https://github.com/portone-io/portone_flutter/tree/v0.10.1+4) 75 | - json_serializable 및 json_annotation의 버전 제약조건을 완화했습니다. 76 | 77 | ## [0.10.1+3](https://github.com/portone-io/portone_flutter/tree/v0.10.1+3) 78 | - gradle kotlin 플러그인 버전을 업데이트했습니다. 79 | - amount 값을 int 대신 num으로 받을 수 있도록 변경했습니다. 80 | 81 | ## [0.10.1+2](https://github.com/portone-io/portone_flutter/tree/v0.10.1+2) 82 | - confirm_url 지원을 추가했습니다. 83 | 84 | ## [0.10.1+1](https://github.com/portone-io/portone_flutter/tree/v0.10.1+1) 85 | - README 예제 및 링크를 수정했습니다. 86 | - 일부 기기에서 의도치 않게 확대가 되는 기능을 차단했습니다. 87 | 88 | ## [0.10.1](https://github.com/portone-io/portone_flutter/tree/v0.10.1) 89 | - 안드로이드 12에서 웹뷰에서 키보드가 나타니지 않는 문제를 해결했습니다. 90 | - 임의의 m_redirect_url을 받고 처리할 수 있도록 수정했습니다. 91 | - 본인인증시 팝업 파라미터를 추가했습니다.(명시적 false 필요할 경우 사용) 92 | 93 | ## [0.10.0](https://github.com/portone-io/portone_flutter/tree/v0.10.0) 94 | - 예제 UI를 개선했습니다. 95 | - 로딩 컴포넌트가 보이지 않는 문제를 해결했습니다. 96 | - 로딩 컴포넌트 및 AppBar 커스터마이징 기능을 개선했습니다. 97 | - json 라이브러리를 json_serializable로 교체했습니다. 98 | - 우리페이 확대에 따른 우리WON뱅킹 지원을 추가했습니다. 99 | - [iOS] 연속적인 자바스크립트 실행으로 인해 불필요한 로그가 생성되지 않도록 수정했습니다. 100 | 101 | ## [0.10.0-dev.6](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.6) 102 | - [안드로이드] 다날 일반결제에서 페이북을 통해 결제할 때 앱을 실행시 발생하는 오류를 해결했습니다. 103 | 104 | ## [0.10.0-dev.5](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.5) 105 | - [안드로이드] 리뉴얼된 다날 본인인증 UI에서 PASS 앱 실행을 위한 m_redirect_url을 추가했습니다. 106 | 107 | ## [0.10.0-dev.4](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.4) 108 | - JS SDK를 v1.2.0로 업데이트했습니다. 109 | - 스마트로를 추가했습니다. 110 | 111 | ## [0.10.0-dev.3](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.3) 112 | - 토스 지원을 추가했습니다. 113 | - 네이버페이 결제형에서 발생하는 json에 불필요한 값이 들어가는 오류를 수정했습니다. 114 | 115 | ## [0.10.0-dev.2](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.2) 116 | - [안드로이드] NH카드 V3 url 파싱 에러를 해결했습니다. 117 | - [ios] 리브 앱 지원을 추가했습니다. 118 | - 우리페이 앱 지원 종료에 따른 우리WON카드 지원을 추가했습니다. 119 | - L.PAY 및 L.POINT 앱 통합에 따른 L.POINT 지원을 추가했습니다. 120 | - 네이버페이 앱 로그인 기능을 추가했습니다. 121 | - webview_flutter 사용에 따른 웹뷰 충돌을 해결했습니다. 122 | 123 | ## [0.10.0-dev.1](https://github.com/portone-io/portone_flutter/tree/v0.10.0-dev.1) 124 | - [안드로이드] 외부 앱 로딩 방식을 변경했습니다. 125 | - flutter v2로의 마이그레이션 및 dart 버전 업그레이드에 따른 null safety에 대한 대응을 완료했습니다. 126 | - 네이버페이 및 체크아웃 결제용 파라미터를 추가했습니다. 127 | - 일부 pg 결제화면에서 javascript 팝업이 제대로 나타나지 않는 현상을 해결했습니다. 128 | - 결제 데이터를 json으로 변환하는 방식을 변경했습니다. 129 | - 페이나우 지원을 추가했습니다. 130 | 131 | ## [0.9.15](https://github.com/portone-io/portone_flutter/tree/v0.9.15) 132 | - [안드로이드] 코틀린 환경에서 registerWith 함수가 트리거되지 않는 상황을 대비해 ActivityAware 인터페이스를 implement 하도록 로직을 추가하였습니다. 133 | 134 | ## [0.9.14](https://github.com/portone-io/portone_flutter/tree/v0.9.14) 135 | - [안드로이드] API 30 지원을 위해 url_launcher의 canLaunch 코드를 제거하였습니다. 136 | 137 | ## [0.9.13](https://github.com/portone-io/portone_flutter/tree/v0.9.13) 138 | - [다날 - 휴대폰 소액결제] 주문명이 중복되는 것을 방지하기 위해 company 파라메터를 추가하였습니다. 139 | 140 | ## [0.9.12](https://github.com/portone-io/portone_flutter/tree/v0.9.12) 141 | - [안드로이드] KG이니시스 - 실시간계좌이체시 국민리브, NH앱캐시, NG상상뱅크, BNK경남은행 앱의 링크가 누락된 부분을 추가하였습니다. 142 | - uni_links 패키지의 버전을 0.4.0으로 올렸습니다. 143 | 144 | ## [0.9.11](https://github.com/portone-io/portone_flutter/tree/v0.9.11) 145 | - 스마일페이, 차이페이, 페이플, 알리페이 예제 코드를 추가하였습니다. 146 | - 다날 - 본인인증 방식을 리디렉션 방식으로 변경하였습니다. 147 | 148 | ## [0.9.10](https://github.com/portone-io/portone_flutter/tree/v0.9.10) 149 | - iamport javascript sdk 버전을 v1.1.8로 올렸습니다. 150 | - 안드로이드6에서 결제/본인인증 창 미렌더링 이슈를 해결하였습니다. 151 | 152 | ## [0.9.9](https://github.com/portone-io/portone_flutter/tree/v.0.9.9) 153 | - 농협 올원페이 앱 스킴 값 오타를 수정하였습니다. 154 | - build시 필요하지 않은 asset을 제거하기 위해 pubspec.yaml 파일을 수정하였습니다. 155 | 156 | ## [0.9.8](https://github.com/portone-io/portone_flutter/tree/v0.9.8) 157 | - 할부개월수 설정 파라미터 오타를 고쳤습니다. 158 | 159 | ## [0.9.6](https://github.com/portone-io/portone_flutter/tree/v0.9.6) 160 | - niceMobileV2를 true로 기본으로 적용하고 이와 관련된 실시간 계좌이체 대비 코드를 적용하였습니다. 161 | 162 | ## [0.9.5](https://github.com/portone-io/portone_flutter/tree/v0.9.5) 163 | - 이니시스, 나이스 그리고 다날 일반결제시 제공기간 표기를 위한 파라미터(period)를 추가하였습니다. 164 | 165 | ## [0.9.4](https://github.com/portone-io/portone_flutter/tree/v0.9.4) 166 | - 안드로이드에서 NH농협카드 일반결제시, intent uri를 파싱할때 발생하는 exception을 처리하였습니다. 167 | 168 | ## [0.9.3](https://github.com/portone-io/portone_flutter/tree/v0.9.3) 169 | - CupertinoNavigationBar 지원을 위한 로직을 추가하였습니다. 170 | 171 | ## [0.9.2](https://github.com/portone-io/portone_flutter/tree/v0.9.2) 172 | - 3rd-party 앱 URL scheme값에 하나멤버스를 추가하였습니다. 173 | 174 | ## [0.9.1](https://github.com/portone-io/portone_flutter/tree/v0.9.1) 175 | - Health suggestions을 적용하였습니다. 176 | 177 | ## [0.9.0](https://github.com/portone-io/portone_flutter/tree/v0.9.0) 178 | - IOS 및 안드로이드 플러터 프로젝트에서 아임포트 일반/정기결제 및 휴대폰 본인인증 연동 기능을 제공합니다. 179 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 © Siot Inc. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software 5 | and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, 8 | modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # portone_flutter 2 | [![pub package](https://img.shields.io/pub/v/portone_flutter.svg)](https://pub.dev/packages/portone_flutter) 3 | 4 | --- 5 | 포트원 V1 플러터 모듈입니다. 6 | 7 | ## 목차 8 | - [버전정보](CHANGELOG.md) 9 | - [지원정보](SUPPORT.md) 10 | - 설치하기 11 | - 설정하기 12 | - 공통 사항 13 | - IOS 설정하기 14 | - Android 설정하기 15 | - [실시간 계좌이체 설정하기](example/manuals/TRANS.md) 16 | - [예제](example/README.md) 17 | - [콜백 함수 설정하기](example/manuals/CALLBACK.md) 18 | 19 | ## 버전정보 20 | 최신버전은 [v0.12.0](https://github.com/portone-io/portone_flutter/tree/main) 입니다. 버전 히스토리는 [버전정보](CHANGELOG.md)를 참고하세요. 21 | 22 | ## 지원정보 23 | 포트원 V1 플러터 모듈은 일반/정기결제 및 휴대폰 본인인증 기능을 지원합니다. 결제 모듈이 지원하는 PG사 및 결제수단에 대한 자세한 내용은 [지원정보](SUPPORT.md)를 참고해주세요. 24 | 25 | ## 설치하기 26 | `pubspec.yaml` 파일에 `portone_flutter` 모듈을 추가해 귀하의 프로젝트에 포트원 V1 플러터 모듈을 설치할 수 있습니다. 27 | 28 | ``` 29 | dependencies: 30 | portone_flutter: ^0.12.0 31 | ``` 32 | 33 | ## 설정하기 34 | 35 | ### IOS 설정하기 36 | IOS에서 포트원 V1 결제연동 모듈을 사용하기 위해서는 `info.plist` 파일에 아래 3가지 항목을 설정해주셔야 합니다. `[프로젝트 이름]/ios/Runner.xcworkspace` 파일을 열어 왼쪽 프로젝트 패널 > Runner > info.plist 파일을 클릭합니다. 37 | 38 | #### 1. App Scheme 등록 39 | 외부 결제 앱(예) 페이코, 신한 판 페이)에서 결제 후 돌아올 때 사용할 URL identifier를 설정해야합니다. 40 | 41 | ![](assets/images/app-scheme-registry.gif) 42 | 43 | 1. `URL types` 속성을 추가합니다. 44 | 2. item `0`를 확장하여 `URL schemes` 속성을 추가합니다. 45 | 3. item `0`에 App URL Scheme 값(EX. example)을 작성합니다. 46 | 47 | ```html 48 | ... 49 | CFBundleURLTypes 50 | 51 | 52 | CFBundleTypeRole 53 | Editor 54 | CFBundleURLSchemes 55 | 56 | 57 | example 58 | 59 | 60 | 61 | ... 62 | ``` 63 | 64 | #### 2. 외부 앱 리스트 등록 65 | 3rd party앱(예) 카드사 앱, 간편결제 앱 등)을 실행할 수 있도록 외부 앱 리스트를 등록해야합니다. 66 | 67 | 1. [LSApplicationQueriesSchemes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW14) 속성을 추가합니다. 68 | 2. 외부 앱 URL Scheme 값을 하나씩 추가합니다. 69 | 70 | ```html 71 | ... 72 | LSApplicationQueriesSchemes 73 | 74 | kftc-bankpay 75 | ispmobile 76 | itms-apps 77 | hdcardappcardansimclick 78 | smhyundaiansimclick 79 | shinhan-sr-ansimclick 80 | smshinhanansimclick 81 | kb-acp 82 | mpocket.online.ansimclick 83 | ansimclickscard 84 | ansimclickipcollect 85 | vguardstart 86 | samsungpay 87 | scardcertiapp 88 | lottesmartpay 89 | lotteappcard 90 | cloudpay 91 | nhappcardansimclick 92 | nonghyupcardansimclick 93 | citispay 94 | citicardappkr 95 | citimobileapp 96 | kakaotalk 97 | payco 98 | lpayapp 99 | hanamopmoasign 100 | wooripay 101 | nhallonepayansimclick 102 | hanawalletmembers 103 | chaipayment 104 | kb-auth 105 | hyundaicardappcardid 106 | com.wooricard.wcard 107 | lmslpay 108 | lguthepay-xpay 109 | liivbank 110 | supertoss 111 | newsmartpib 112 | naversearchthirdlogin 113 | 114 | ... 115 | ``` 116 | 117 | #### 3. App Transport Security 설정 118 | 119 | ![](assets/images/allow-arbitrary.gif) 120 | 121 | 1. `App Transport Security Settings` 속성에 [Allow Arbitrary Loads in Web Content](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsallowsarbitraryloadsinwebcontent) 속성을 추가합니다. 122 | 2. 그 값을 `true`로 설정합니다. 123 | 124 | ```html 125 | ... 126 | NSAppTransportSecurity 127 | 128 | 129 | NSAllowsArbitraryLoadsInWebContent 130 | 131 | NSAllowsArbitraryLoads 132 | 133 | 134 | ... 135 | ``` 136 | 137 | #### 4. PASS 앱 Universal Link 설정 (선택 사항) 138 | Push 알림 없이 PASS 앱(SKT PASS, KT 인증, U+인증)을 실행해 본인인증을 진행하기 위해서는 `mRedirectUrl` 파라미터 설정이 필요합니다. 139 | 140 | ##### 4.1 mRedirectUrl 설정 141 | 본인인증 요청 시 `CertificationData` 객체의 `mRedirectUrl` 파라미터에 `UrlData.redirectUrl` 값을 설정해 주세요. 다날의 경우, `mRedirectUrl` 설정 시 `carrier` 파라미터로 전달한 통신사 선택을 사용자가 변경할 수 없으므로 주의해주세요. 142 | 143 | ```dart 144 | import 'package:portone_flutter/model/certification_data.dart'; 145 | import 'package:portone_flutter/model/url_data.dart'; // UrlData import 추가 146 | 147 | // ... 148 | 149 | CertificationData data = CertificationData( 150 | mRedirectUrl: UrlData.redirectUrl 151 | // ... 기타 본인인증 데이터 152 | ); 153 | 154 | // ... 155 | ``` 156 | 157 | ### Android 설정하기 158 | 안드로이드 API 레벨 30에서 특정 카드사로 결제 시도시 `net::ERR_CLEARTEXT_NOT_PERMITTED` 오류가 발생한다는 버그가 보고되었습니다. 이를 해결하기 위해서는 [AndroidManifest.xml](https://github.com/portone-io/portone_flutter/blob/develop/example/android/app/src/main/AndroidManifest.xml#L13) 파일에 아래와 같이 [usesclearTextTraffic](https://developer.android.com/guide/topics/manifest/application-element#usesCleartextTraffic) 속성을 `true`로 설정해주셔야 합니다. 159 | 160 | ```xml 161 | 162 | ... 163 | 167 | 168 | 169 | ``` 170 | 171 | ### 실시간 계좌이체 설정하기 172 | 웹 표준 이니시스와 나이스 정보통신은 뱅크페이 앱을 통해 실시간 계좌이체를 진행합니다. 뱅크페이에서 결제 인증 후 본래의 앱으로 복귀 해 다음단계로 진행을 하려면 별도 설정이 요구됩니다. 자세한 내용은 [실시간 계좌이체 설정하기](example/manuals/TRANS.md)를 참고해주세요. 173 | 174 | 175 | ## 예제 176 | 포트원 V1 플러터 모듈로 아래와 같이 일반/정기결제 및 휴대폰 본인인증 기능을 구현할 수 있습니다. 보다 자세한 내용은 [예제](example/README.md)를 참고하세요. 177 | 178 | #### 일반/정기결제 예제 179 | ```dart 180 | import 'package:flutter/material.dart'; 181 | 182 | /* 포트원 V1 결제 모듈을 불러옵니다. */ 183 | import 'package:portone_flutter/iamport_payment.dart'; 184 | /* 포트원 V1 결제 데이터 모델을 불러옵니다. */ 185 | import 'package:portone_flutter/model/payment_data.dart'; 186 | 187 | class Payment extends StatelessWidget { 188 | @override 189 | Widget build(BuildContext context) { 190 | return IamportPayment( 191 | appBar: new AppBar( 192 | title: new Text('포트원 V1 결제'), 193 | ), 194 | /* 웹뷰 로딩 컴포넌트 */ 195 | initialChild: Container( 196 | child: Center( 197 | child: Column( 198 | mainAxisAlignment: MainAxisAlignment.center, 199 | children: [ 200 | Image.asset('assets/images/iamport-logo.png'), 201 | Padding(padding: EdgeInsets.symmetric(vertical: 15)), 202 | Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20)), 203 | ], 204 | ), 205 | ), 206 | ), 207 | /* [필수입력] 가맹점 식별코드 */ 208 | userCode: 'iamport', 209 | /* [필수입력] 결제 데이터 */ 210 | data: PaymentData( 211 | pg: 'html5_inicis', // PG사 212 | payMethod: 'card', // 결제수단 213 | name: '포트원 V1 결제데이터 분석', // 주문명 214 | merchantUid: 'mid_${DateTime.now().millisecondsSinceEpoch}', // 주문번호 215 | amount: 39000, // 결제금액 216 | buyerName: '홍길동', // 구매자 이름 217 | buyerTel: '01012345678', // 구매자 연락처 218 | buyerEmail: 'example@naver.com', // 구매자 이메일 219 | buyerAddr: '서울시 강남구 신사동 661-16', // 구매자 주소 220 | buyerPostcode: '06018', // 구매자 우편번호 221 | appScheme: 'example', // 앱 URL scheme 222 | cardQuota : [2,3] //결제창 UI 내 할부개월수 제한 223 | ), 224 | /* [필수입력] 콜백 함수 */ 225 | callback: (Map result) { 226 | Navigator.pushReplacementNamed( 227 | context, 228 | '/result', 229 | arguments: result, 230 | ); 231 | }, 232 | ); 233 | } 234 | } 235 | ``` 236 | 237 | 238 | #### 본인인증 예제 239 | 240 | 이니시스 통합인증의 경우 다날 휴대폰 본인인증과 달리 `mRedirectUrl` 파라미터를 필수로 설정해주셔야 합니다. 241 | 242 | ##### 다날 휴대폰 본인인증 243 | ```dart 244 | import 'package:flutter/material.dart'; 245 | 246 | /* 포트원 V1 휴대폰 본인인증 모듈을 불러옵니다. */ 247 | import 'package:portone_flutter/iamport_certification.dart'; 248 | /* 포트원 V1 휴대폰 본인인증 데이터 모델을 불러옵니다. */ 249 | import 'package:portone_flutter/model/certification_data.dart'; 250 | 251 | class Certification extends StatelessWidget { 252 | @override 253 | Widget build(BuildContext context) { 254 | return IamportCertification( 255 | appBar: new AppBar( 256 | title: new Text('포트원 V1 본인인증'), 257 | ), 258 | /* 웹뷰 로딩 컴포넌트 */ 259 | initialChild: Container( 260 | child: Center( 261 | child: Column( 262 | mainAxisAlignment: MainAxisAlignment.center, 263 | children: [ 264 | Image.asset('assets/images/iamport-logo.png'), 265 | Padding(padding: EdgeInsets.symmetric(vertical: 15)), 266 | Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20)), 267 | ], 268 | ), 269 | ), 270 | ), 271 | /* [필수입력] 가맹점 식별코드 */ 272 | userCode: 'iamport', 273 | /* [필수입력] 본인인증 데이터 */ 274 | data: CertificationData( 275 | pg: 'danal', // PG사 276 | merchantUid: 'mid_${DateTime.now().millisecondsSinceEpoch}', // 주문번호 277 | company: '포트원 V1', // 회사명 또는 URL 278 | carrier: 'SKT', // 통신사 279 | name: '홍길동', // 이름 280 | phone: '01012341234', // 전화번호 281 | ), 282 | /* [필수입력] 콜백 함수 */ 283 | callback: (Map result) { 284 | Navigator.pushReplacementNamed( 285 | context, 286 | '/result', 287 | arguments: result, 288 | ); 289 | }, 290 | ); 291 | } 292 | } 293 | ``` 294 | ##### 이니시스 통합인증 295 | ```dart 296 | import 'package:flutter/material.dart'; 297 | 298 | /* 포트원 V1 휴대폰 본인인증 모듈을 불러옵니다. */ 299 | import 'package:portone_flutter/iamport_certification.dart'; 300 | /* 포트원 V1 휴대폰 본인인증 데이터 모델을 불러옵니다. */ 301 | import 'package:portone_flutter/model/certification_data.dart'; 302 | 303 | class Certification extends StatelessWidget { 304 | @override 305 | Widget build(BuildContext context) { 306 | return IamportCertification( 307 | appBar: new AppBar( 308 | title: new Text('포트원 V1 본인인증'), 309 | ), 310 | /* 웹뷰 로딩 컴포넌트 */ 311 | initialChild: Container( 312 | child: Center( 313 | child: Column( 314 | mainAxisAlignment: MainAxisAlignment.center, 315 | children: [ 316 | Image.asset('assets/images/iamport-logo.png'), 317 | Padding(padding: EdgeInsets.symmetric(vertical: 15)), 318 | Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20)), 319 | ], 320 | ), 321 | ), 322 | ), 323 | /* [필수입력] 가맹점 식별코드 */ 324 | userCode: 'iamport', 325 | /* [필수입력] 본인인증 데이터 */ 326 | data: CertificationData( 327 | pg: 'inicis_unified', // PG사 328 | merchantUid: 'mid_${DateTime.now().millisecondsSinceEpoch}', // 주문번호 329 | mRedirectUrl: 'https://example.com', // 본인인증 후 이동할 URL 330 | ), 331 | /* [필수입력] 콜백 함수 */ 332 | callback: (Map result) { 333 | Navigator.pushReplacementNamed( 334 | context, 335 | '/result', 336 | arguments: result, 337 | ); 338 | }, 339 | ); 340 | } 341 | } 342 | ``` 343 | 344 | ## 콜백 함수 설정하기 345 | 콜백 함수는 필수입력 필드로, 결제/본인인증 완료 후 라우트 이동을 위해 아래와 같이 로직을 작성할 수 있습니다. 콜백 함수에 대한 자세한 설명은 [콜백 설정하기](example/manuals/CALLBACK.md)를 참고하세요. 346 | 347 | ```dart 348 | ... 349 | callback: (Map result) { 350 | Navigator.pushReplacementNamed( 351 | context, 352 | '/result', 353 | arguments: result, 354 | ); 355 | }, 356 | ... 357 | ``` 358 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # 지원정보 2 | 3 | 포트원 V1 플러터 모듈이 지원하는 PG사 및 결제수단 안내입니다. 4 | 5 | | pg | 이름 | 결제수단 | 6 | |--------------|---------------------|-----------------------------------------------------------------------------------------------| 7 | | html5_inicis | 웹 표준 이니시스 | 신용카드, 가상계좌, 실시간 계좌이체(IOS 별도 설정 필요), 휴대폰 소액결제, 삼성페이, KPAY, 문화상품권, 스마트문상, 해피머니 | 8 | | kcp | NHN KCP | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제, 삼성페이 | 9 | | kcp_billing | NHN KCP 정기결제 | 신용카드 | 10 | | uplus | 토스페이먼츠 - (구)LG 유플러스 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제, 문화상품권, 스마트문상, 도서상푼권 | 11 | | jtnet | JTNET | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제 | 12 | | nice | 나이스 정보통신 | 신용카드, 가상계좌, 실시간 계좌이체(IOS/안드로이드 별도 설정 필요), 휴대폰 소액결제 | 13 | | kakaopay | 신 - 카카오페이 | 신용카드 | 14 | | kakao | 구 - LG CNS 카카오페이 | 신용카드 | 15 | | danal | 다날 휴대폰 소액결제 | 휴대폰 소액결제 | 16 | | danal_tpay | 다날 일반결제 | 신용카드, 가상계좌, 실시간 계좌이체 | 17 | | kicc | 한국정보통신 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제 | 18 | | paypal | 페이팔 | 신용카드 | 19 | | mobilians | 모빌리언스 | 신용카드, 가상계좌(준비중), 실시간 계좌이체(준비중), 휴대폰 소액결제 | 20 | | payco | 페이코 | 신용카드 | 21 | | eximbay | 엑심베이 | 신용카드 [주의사항](https://github.com/iamport/iamport-react-native/issues/70#issuecomment-704601908) | 22 | | settle | 세틀뱅크 | 가상계좌 | 23 | | naverco | 네이버 체크아웃 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제 | 24 | | naverpay | 네이버페이 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제 | 25 | | smilepay | 스마일페이 | 신용카드 [주의사항](https://github.com/iamport/iamport-react-native/issues/71) | 26 | | chai | 차이페이 | 신용카드 | 27 | | payple | 페이플 | 실시간 계좌이체 | 28 | | alipay | 알리페이 | 신용카드 | 29 | | bluewalnut | 블루월넛 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제 | 30 | | tosspay | 토스 - 간편결제 | 신용카드 | 31 | | smartro | 스마트로 | 신용카드, 가상계좌, 실시간 계좌이체 | 32 | | tosspayments | 토스페이먼츠 | 신용카드 | 33 | | welcome | 웰컴페이먼츠 | 신용카드, 가상계좌, 실시간 계좌이체, 휴대폰 소액결제, 문화상품권 | 34 | | tosspay_v2 | 토스페이(V2) | 신용카드, 실시간 계좌이체 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'io.portone.portone_flutter' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.9.23' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.4.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdk 34 29 | 30 | namespace = 'io.portone.portone_flutter' 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | defaultConfig { 36 | minSdkVersion 23 37 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 38 | } 39 | lintOptions { 40 | disable 'InvalidPackage' 41 | } 42 | 43 | dependencies { 44 | implementation 'androidx.annotation:annotation:1.8.0' 45 | } 46 | 47 | compileOptions { 48 | sourceCompatibility JavaVersion.VERSION_17 49 | targetCompatibility JavaVersion.VERSION_17 50 | } 51 | 52 | kotlinOptions { 53 | jvmTarget = "17" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1024M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 12 17:20:40 KST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'portone_flutter' 2 | -------------------------------------------------------------------------------- /android/src/main/kotlin/io/portone/portone_flutter/IamportFlutterPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.portone.portone_flutter 2 | 3 | import androidx.annotation.NonNull 4 | 5 | import io.flutter.embedding.engine.plugins.FlutterPlugin 6 | import io.flutter.plugin.common.MethodCall 7 | import io.flutter.plugin.common.MethodChannel 8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 9 | import io.flutter.plugin.common.MethodChannel.Result 10 | 11 | /** IamportFlutterPlugin */ 12 | class IamportFlutterPlugin: FlutterPlugin, MethodCallHandler { 13 | /// The MethodChannel that will the communication between Flutter and native Android 14 | /// 15 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 16 | /// when the Flutter Engine is detached from the Activity 17 | private lateinit var channel : MethodChannel 18 | 19 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 20 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "portone_flutter") 21 | channel.setMethodCallHandler(this) 22 | } 23 | 24 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 25 | if (call.method == "getPlatformVersion") { 26 | result.success("Android ${android.os.Build.VERSION.RELEASE}") 27 | } else { 28 | result.notImplemented() 29 | } 30 | } 31 | 32 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 33 | channel.setMethodCallHandler(null) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/images/allow-arbitrary.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/assets/images/allow-arbitrary.gif -------------------------------------------------------------------------------- /assets/images/app-scheme-registry.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/assets/images/app-scheme-registry.gif -------------------------------------------------------------------------------- /assets/images/iamport-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/assets/images/iamport-logo.png -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | include_if_null: false -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .flutter-plugins-dependencies 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | /build/ 30 | .pubspec.lock 31 | lib/main.mapper.g.dart 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # portone_flutter_example 2 | 3 | 포트원 V1 플러터 모듈 예제 안내입니다. 4 | 5 | ## 일반/정기결제 예제 6 | ```dart 7 | import 'package:flutter/material.dart'; 8 | 9 | /* 포트원 V1 결제 모듈을 불러옵니다. */ 10 | import 'package:portone_flutter/iamport_payment.dart'; 11 | /* 포트원 V1 결제 데이터 모델을 불러옵니다. */ 12 | import 'package:portone_flutter/model/payment_data.dart'; 13 | 14 | class Payment extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return IamportPayment( 18 | appBar: new AppBar( 19 | title: new Text('포트원 V1 결제'), 20 | ), 21 | /* 웹뷰 로딩 컴포넌트 */ 22 | initialChild: Container( 23 | child: Center( 24 | child: Column( 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | children: [ 27 | Image.asset('assets/images/iamport-logo.png'), 28 | Container( 29 | padding: EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0), 30 | child: Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20.0)), 31 | ), 32 | ], 33 | ), 34 | ), 35 | ), 36 | /* [필수입력] 가맹점 식별코드 */ 37 | userCode: 'iamport', 38 | /* [필수입력] 결제 데이터 */ 39 | data: PaymentData({ 40 | pg: 'html5_inicis', // PG사 41 | payMethod: 'card', // 결제수단 42 | name: '포트원 V1 결제데이터 분석', // 주문명 43 | merchantUid: 'mid_${DateTime.now().millisecondsSinceEpoch}', // 주문번호 44 | amount: 39000, // 결제금액 45 | buyerName: '홍길동', // 구매자 이름 46 | buyerTel: '01012345678', // 구매자 연락처 47 | buyerEmail: 'example@naver.com', // 구매자 이메일 48 | buyerAddr: '서울시 강남구 신사동 661-16', // 구매자 주소 49 | buyerPostcode: '06018', // 구매자 우편번호 50 | appScheme: 'example', // 앱 URL scheme 51 | displayCardQuota: [2, 3] // 결제창 UI 내 할부개월수 제한 52 | }), 53 | /* [필수입력] 콜백 함수 */ 54 | callback: (Map result) { 55 | Navigator.pushReplacementNamed( 56 | context, 57 | '/payment-result', 58 | arguments: result, 59 | ); 60 | }, 61 | ); 62 | } 63 | } 64 | ``` 65 | 66 | ### IamportPayment 모델 67 | 68 | | Prop | Type | Description | Required | 69 | | ---------------- | ------------------- | -------------------- | ---------- | 70 | | appBar | PreferredSizeWidget | 앱 네비게이션 헤더 바 | false | 71 | | userCode | String | 가맹점 식별코드 | true | 72 | | initialChild | 플러터 컴포넌트 | 웹뷰 로드시 보여질 컴포넌트 | false | 73 | | data | `PaymentData` | 결제에 필요한 정보 | true | 74 | | callback | function | 결제 후 실행 될 함수 | true | 75 | 76 | ### PaymentData 모델 [자세히보기](https://docs.iamport.kr/sdk/javascript-sdk#request_pay) 77 | 78 | | Key | Type | Description | Required | 79 | | -------------- | ------------------------ | -------------- | ------------------------ | 80 | | pg | String | PG사 | false | 81 | | payMethod | String | 결제수단 | false | 82 | | display | `Map>` | 할부개월수 | false | 83 | | vbankDue | String | 가상계좌 입금기한 | false (가상계좌시 필수) | 84 | | bizNum | String | 사업자번호 | false (다날 - 가상계좌시 필수) | 85 | | digital | bool | 실물컨텐츠 여부 | false (휴대폰 소액결제시 필수) | 86 | | escrow | bool | 에스크로 여부 | false | 87 | | name | String | 주문명 | true | 88 | | amount | int | 결제금액 | true | 89 | | currency | String | 화폐 단위 | false | 90 | | customData | Map | 임의 지정 데이터 | false | 91 | | taxFree | int | 면세 공급 가액 | false | 92 | | vat | int | 부가세 | false | 93 | | language | String | 결제 창 언어설정 | false | 94 | | merchantUid | String | 주문번호 | true | 95 | | buyerName | String | 구매자 이름 | false | 96 | | buyerTel | String | 구매자 연락처 | false | 97 | | buyerEmail | String | 구매자 이메일 | false | 98 | | buyerAddr | String | 구매자 주소 | false | 99 | | buyerPostcode | String | 구매자 우편번호 | false | 100 | | noticeUrl | String | 웹훅 URL | false | 101 | | customerUid | String | 정기결제 카드정보 | false (정기결제시 필수) | 102 | | appScheme | String | 앱 스킴 | true | 103 | | popup | bool | 페이팔 팝업 여부 | false | 104 | | naverPopupMode | bool | 네이버페이 팝업 여부 | false | 105 | | `period` | Map | 다날 - 신용카드/계좌이체/가상계좌 전용 제공기간 | false | 106 | | `company` | String | 다날 - 휴대폰 소액결제 전용 주문명 앞 괄호 안 텍스트 | false | 107 | 108 | ### period 109 | 이니시스, 나이스 그리고 다날 신용카드/계좌이체/가상계좌 결제시 제공기간 표기를 위한 파라미터입니다. 제공기간 시작 날짜(`from`)와 끝 날짜(`to`)를 아래와 같이 `YYYYMMDD` 형태로 넘겨주세요. 110 | 111 | | key | Type | Description | 112 | | ---- | ---------------- | ------------- | 113 | | from | String(YYYYMMDD) | 제공기간 시작 날짜 | 114 | | to | String(YYYYMMDD) | 제공기간 종료날짜 | 115 | 116 | ```dart 117 | data: PaymentData.fromJson({ 118 | ... 119 | period: { // 제공기간 2020년 1월 1일 ~ 2020년 12월 31일 120 | from: '20200101', // 제공기간 시작 날짜 121 | to: '20201231', // 제공기간 종료 날짜 122 | }, 123 | }), 124 | ``` 125 | 126 | ### company 127 | 다날 휴대폰 소액결제시 주문명 앞 괄호 안 텍스트에 표기될 회사명을 위한 파라미터입니다. 결제창 내 주문명은 `(회사명) 주문명`과 같이 표기되며, 누락시 `(주문명) 주문명`과 같이 표기됩니다. 128 | 129 | ## 휴대폰 본인인증 예제 130 | ```dart 131 | import 'package:flutter/material.dart'; 132 | 133 | /* 포트원 V1 휴대폰 본인인증 모듈을 불러옵니다. */ 134 | import 'package:portone_flutter/iamport_certification.dart'; 135 | /* 포트원 V1 휴대폰 본인인증 데이터 모델을 불러옵니다. */ 136 | import 'package:portone_flutter/model/certification_data.dart'; 137 | 138 | class Certification extends StatelessWidget { 139 | @override 140 | Widget build(BuildContext context) { 141 | return IamportCertification( 142 | appBar: new AppBar( 143 | title: new Text('포트원 V1 본인인증'), 144 | ), 145 | /* 웹뷰 로딩 컴포넌트 */ 146 | initialChild: Container( 147 | child: Center( 148 | child: Column( 149 | mainAxisAlignment: MainAxisAlignment.center, 150 | children: [ 151 | Image.asset('assets/images/iamport-logo.png'), 152 | Container( 153 | padding: EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0), 154 | child: Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20.0)), 155 | ), 156 | ], 157 | ), 158 | ), 159 | ), 160 | /* [필수입력] 가맹점 식별코드 */ 161 | userCode: 'iamport', 162 | /* [필수입력] 본인인증 데이터 */ 163 | data: CertificationData({ 164 | merchantUid: 'mid_${DateTime.now().millisecondsSinceEpoch}', // 주문번호 165 | company: '포트원 V1', // 회사명 또는 URL 166 | carrier: 'SKT', // 통신사 167 | name: '홍길동', // 이름 168 | phone: '01012341234', // 전화번호 169 | }), 170 | /* [필수입력] 콜백 함수 */ 171 | callback: (Map result) { 172 | Navigator.pushReplacementNamed( 173 | context, 174 | '/certification-result', 175 | arguments: result, 176 | ); 177 | }, 178 | ); 179 | } 180 | } 181 | ``` 182 | 183 | ### IamportCertification 모델 184 | 185 | | Prop | Type | Description | Required | 186 | | ------------- | ------------------- | -------------------- | ---------- | 187 | | appBar | PreferredSizeWidget | 앱 네비게이션 헤더 바 | false | 188 | | userCode | string | 가맹점 식별코드 | true | 189 | | initialChild | 플러터 컴포넌트 | 웹뷰 로드시 보여질 컴포넌트 | false | 190 | | data | `CertificationData` | 본인인증에 필요한 정보 | true | 191 | | callback | function | 본인인증 후 실행 될 함수 | true | 192 | 193 | ### CertificationData 모델 [자세히보기](https://docs.iamport.kr/sdk/javascript-sdk#certification) 194 | 195 | | Key | Type | Description | Required | 196 | | ------------ | ------ | --------------- | -------- | 197 | | merchantUid | String | 주문번호 | true | 198 | | company | String | 회사명 또는 URL | false | 199 | | carrier | String | 통신사 | false | 200 | | | | - `SKT`: SKT | | 201 | | | | - `KTF`: KT | | 202 | | | | - `LGT`: LGU+ | | 203 | | | | - `MVNO`: 알뜰폰 | | 204 | | name | String | 본인인증 할 이름 | false | 205 | | phone | String | 본인인증 할 전화번호 | false | 206 | | minAge | int | 허용 최소 만 나이 | false | 207 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = 'io.portone.portone_flutter_example' 10 | compileSdk = flutter.compileSdkVersion 11 | 12 | sourceSets { 13 | main.java.srcDirs += 'src/main/kotlin' 14 | } 15 | 16 | defaultConfig { 17 | applicationId "io.portone.portone_flutter_example" 18 | minSdk = 23 19 | targetSdk = flutter.targetSdkVersion 20 | versionCode = flutter.versionCode 21 | versionName = flutter.versionName 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | release { 27 | // TODO: Add your own signing config for the release build. 28 | // Signing with the debug keys for now, so `flutter run --release` works. 29 | signingConfig = signingConfigs.getByName("debug") 30 | } 31 | } 32 | lint { 33 | disable 'InvalidPackage' 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_17 38 | targetCompatibility JavaVersion.VERSION_17 39 | } 40 | 41 | kotlinOptions { 42 | jvmTarget = JavaVersion.VERSION_17.toString() 43 | } 44 | } 45 | 46 | flutter { 47 | source '../..' 48 | } 49 | 50 | dependencies { 51 | testImplementation 'junit:junit:4.13.2' 52 | androidTestImplementation 'androidx.test:runner:1.5.2' 53 | } 54 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 16 | 24 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/io/portone/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.portone.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/io/portone/portone_flutter_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.portone.portone_flutter_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1024M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.nonTransitiveRClass=false 5 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.2" apply false 22 | id("org.jetbrains.kotlin.android") version "1.9.23" apply false 23 | } 24 | 25 | include(":app") -------------------------------------------------------------------------------- /example/assets/images/iamport-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/assets/images/iamport-logo.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | portone_flutter_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLSchemes 27 | 28 | flutterexample 29 | 30 | 31 | 32 | CFBundleVersion 33 | $(FLUTTER_BUILD_NUMBER) 34 | LSApplicationQueriesSchemes 35 | 36 | kftc-bankpay 37 | ispmobile 38 | itms-apps 39 | hdcardappcardansimclick 40 | smhyundaiansimclick 41 | shinhan-sr-ansimclick 42 | smshinhanansimclick 43 | kb-acp 44 | mpocket.online.ansimclick 45 | ansimclickscard 46 | ansimclickipcollect 47 | vguardstart 48 | samsungpay 49 | scardcertiapp 50 | lottesmartpay 51 | lotteappcard 52 | cloudpay 53 | nhappcardansimclick 54 | nonghyupcardansimclick 55 | citispay 56 | citicardappkr 57 | citimobileapp 58 | kakaotalk 59 | payco 60 | lpayapp 61 | hanamopmoasign 62 | wooripay 63 | nhallonepayansimclick 64 | hanawalletmembers 65 | liivbank 66 | supertoss 67 | newsmartpib 68 | v3mobileplusweb 69 | kbbank 70 | newliiv 71 | naversearchthirdlogin 72 | 73 | LSRequiresIPhoneOS 74 | 75 | NSAppTransportSecurity 76 | 77 | NSAllowsArbitraryLoads 78 | 79 | NSAllowsArbitraryLoadsInWebContent 80 | 81 | 82 | UILaunchStoryboardName 83 | LaunchScreen 84 | UIMainStoryboardFile 85 | Main 86 | UISupportedInterfaceOrientations 87 | 88 | UIInterfaceOrientationPortrait 89 | UIInterfaceOrientationLandscapeLeft 90 | UIInterfaceOrientationLandscapeRight 91 | 92 | UISupportedInterfaceOrientations~ipad 93 | 94 | UIInterfaceOrientationPortrait 95 | UIInterfaceOrientationPortraitUpsideDown 96 | UIInterfaceOrientationLandscapeLeft 97 | UIInterfaceOrientationLandscapeRight 98 | 99 | UIViewControllerBasedStatusBarAppearance 100 | 101 | CADisableMinimumFrameDurationOnPhone 102 | 103 | UIApplicationSupportsIndirectInputEvents 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:portone_flutter_example/screens/certification.dart'; 5 | import 'package:portone_flutter_example/screens/certification_result.dart'; 6 | import 'package:portone_flutter_example/screens/certification_test.dart'; 7 | import 'package:portone_flutter_example/screens/home.dart'; 8 | import 'package:portone_flutter_example/screens/payment.dart'; 9 | import 'package:portone_flutter_example/screens/payment_result.dart'; 10 | import 'package:portone_flutter_example/screens/payment_test.dart'; 11 | 12 | void main() { 13 | runApp(IamportApp()); 14 | } 15 | 16 | class IamportApp extends StatefulWidget { 17 | @override 18 | _IamportAppState createState() => _IamportAppState(); 19 | } 20 | 21 | class _IamportAppState extends State { 22 | static const Color primaryColor = Color(0xff344e81); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( 27 | systemNavigationBarColor: Colors.transparent, 28 | statusBarColor: Colors.transparent, 29 | )); 30 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 31 | 32 | return GetMaterialApp( 33 | initialRoute: '/', 34 | theme: ThemeData( 35 | primaryColor: primaryColor, 36 | ), 37 | getPages: [ 38 | GetPage(name: '/', page: () => Home()), 39 | GetPage(name: '/payment-test', page: () => PaymentTest()), 40 | GetPage(name: '/payment', page: () => Payment()), 41 | GetPage(name: '/payment-result', page: () => PaymentResult()), 42 | GetPage(name: '/certification-test', page: () => CertificationTest()), 43 | GetPage(name: '/certification', page: () => Certification()), 44 | GetPage( 45 | name: '/certification-result', page: () => CertificationResult()), 46 | ], 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/lib/model/carrier.dart: -------------------------------------------------------------------------------- 1 | class Carrier { 2 | static List CARRIERS = [ 3 | 'SKT', 4 | 'KTF', 5 | 'LGT', 6 | 'MVNO', 7 | ]; 8 | 9 | static String getLabel(String pg) { 10 | switch (pg) { 11 | case 'SKT': 12 | return 'SKT'; 13 | case 'KTF': 14 | return 'KT'; 15 | case 'LGT': 16 | return 'LGU+'; 17 | case 'MVNO': 18 | return '알뜰폰'; 19 | default: 20 | return '-'; 21 | } 22 | } 23 | 24 | static List getLists() { 25 | return CARRIERS; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/model/method.dart: -------------------------------------------------------------------------------- 1 | class Method { 2 | static List METHODS = [ 3 | 'card', 4 | 'trans', 5 | 'vbank', 6 | 'phone', 7 | ]; 8 | 9 | static List METHODS_FOR_INICIS = METHODS + 10 | [ 11 | 'samsung', 12 | 'kpay', 13 | 'cultureland', 14 | 'smartculture', 15 | 'happymoney', 16 | ]; 17 | 18 | static List METHODS_FOR_UPLUS = METHODS + 19 | [ 20 | 'cultureland', 21 | 'smartculture', 22 | 'booknlife', 23 | ]; 24 | 25 | static List METHODS_FOR_KCP = METHODS + ['samsung', 'naverpay']; 26 | 27 | static List METHODS_FOR_MOBILIANS = ['card', 'phone']; 28 | static List METHOD_FOR_CARD = ['card']; 29 | static List METHOD_FOR_PHONE = ['phone']; 30 | static List METHOD_FOR_VBANK = ['vbank']; 31 | static List METHOD_FOR_TRANS = ['trans']; 32 | static List METHOD_FOR_TOSSPAY_V2 = ['tosspay']; 33 | 34 | static String getLabel(String method) { 35 | switch (method) { 36 | case 'card': 37 | return '신용카드'; 38 | case 'vbank': 39 | return '가상계좌'; 40 | case 'trans': 41 | return '실시간 계좌이체'; 42 | case 'phone': 43 | return '휴대폰 소액결제'; 44 | case 'samsung': 45 | return '삼성페이'; 46 | case 'kpay': 47 | return 'KPAY'; 48 | case 'cultureland': 49 | return '문화상품권'; 50 | case 'smartculture': 51 | return '스마트문상'; 52 | case 'happymoney': 53 | return '해피머니'; 54 | case 'booknlife': 55 | return '도서상품권'; 56 | case 'naverpay': 57 | return '네이버페이'; 58 | case 'tosspay': 59 | return '토스페이'; 60 | default: 61 | return '-'; 62 | } 63 | } 64 | 65 | static String getValueByPg(String pg) { 66 | switch (pg) { 67 | case 'danal': 68 | return 'phone'; 69 | case 'settle': 70 | return 'vbank'; 71 | case 'chai': 72 | case 'payple': 73 | return 'trans'; 74 | case 'tosspay_v2': 75 | return 'tosspay'; 76 | default: 77 | return 'card'; 78 | } 79 | } 80 | 81 | static List getListsByPg(String pg) { 82 | switch (pg) { 83 | case 'html5_inicis': 84 | return METHODS_FOR_INICIS; 85 | case 'kcp': 86 | return METHODS_FOR_KCP; 87 | case 'kcp_billing': 88 | case 'kakaopay': 89 | case 'kakao': 90 | case 'paypal': 91 | case 'payco': 92 | case 'eximbay': 93 | case 'smilepay': 94 | case 'alipay': 95 | return METHOD_FOR_CARD; 96 | case 'uplus': 97 | return METHODS_FOR_UPLUS; 98 | case 'danal': 99 | return METHOD_FOR_PHONE; 100 | case 'mobilians': 101 | return METHODS_FOR_MOBILIANS; 102 | case 'settle': 103 | return METHOD_FOR_VBANK; 104 | case 'chai': 105 | case 'payple': 106 | return METHOD_FOR_TRANS; 107 | case 'tosspay_v2': 108 | return METHOD_FOR_TOSSPAY_V2; 109 | default: 110 | return METHODS; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /example/lib/model/pg.dart: -------------------------------------------------------------------------------- 1 | class Pg { 2 | static List PGS = [ 3 | 'html5_inicis', 4 | 'kcp', 5 | 'kcp_billing', 6 | 'uplus', 7 | 'jtnet', 8 | 'nice', 9 | 'kakaopay', 10 | 'danal', 11 | 'danal_tpay', 12 | 'kicc', 13 | 'paypal', 14 | 'mobilians', 15 | 'payco', 16 | 'eximbay', 17 | 'settle', 18 | 'naverpay', 19 | 'smilepay', 20 | 'chai', 21 | 'payple', 22 | 'alipay', 23 | 'bluewalnut', 24 | 'tosspay', 25 | 'smartro', 26 | 'tosspayments', 27 | 'ksnet', 28 | 'welcome', 29 | 'tosspay_v2' 30 | ]; 31 | 32 | static String getLabel(String pg) { 33 | switch (pg) { 34 | case 'html5_inicis': 35 | return '웹 표준 이니시스'; 36 | case 'kcp': 37 | return 'NHN KCP'; 38 | case 'kcp_billing': 39 | return 'NHN KCP 정기결제'; 40 | case 'uplus': 41 | return '(구)토스페이먼츠'; 42 | case 'jtnet': 43 | return 'JTNET'; 44 | case 'nice': 45 | return '나이스 정보통신'; 46 | case 'kakaopay': 47 | return '카카오페이'; 48 | case 'danal': 49 | return '다날 휴대폰 소액결제'; 50 | case 'danal_tpay': 51 | return '다날 일반결제'; 52 | case 'kicc': 53 | return '한국정보통신'; 54 | case 'paypal': 55 | return '페이팔'; 56 | case 'mobilians': 57 | return '모빌리언스'; 58 | case 'payco': 59 | return '페이코'; 60 | case 'eximbay': 61 | return '엑심베이'; 62 | case 'settle': 63 | return '세틀뱅크 가상계좌'; 64 | case 'naverpay': 65 | return '네이버페이'; 66 | case 'smilepay': 67 | return '스마일페이'; 68 | case 'chai': 69 | return '차이페이'; 70 | case 'payple': 71 | return '페이플'; 72 | case 'alipay': 73 | return '알리페이'; 74 | case 'bluewalnut': 75 | return '블루월넛'; 76 | case 'tosspay': 77 | return '토스 간편결제'; 78 | case 'smartro': 79 | return '스마트로'; 80 | case 'tosspayments': 81 | return '토스페이먼츠'; 82 | case 'ksnet': 83 | return 'KSNET'; 84 | case 'welcome': 85 | return '웰컴페이먼츠'; 86 | case 'tosspay_v2': 87 | return '토스페이(V2)'; 88 | default: 89 | return '-'; 90 | } 91 | } 92 | 93 | static List getLists() { 94 | return PGS; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /example/lib/model/quota.dart: -------------------------------------------------------------------------------- 1 | class Quota { 2 | static List QUOTAS = [ 3 | '0', 4 | '1', 5 | ]; 6 | 7 | static String getLabel(String quota) { 8 | switch (quota) { 9 | case '0': 10 | return 'PG사 기본 제공'; 11 | case '1': 12 | return '일시불'; 13 | case '2': 14 | return '2개월'; 15 | case '3': 16 | return '3개월'; 17 | case '4': 18 | return '4개월'; 19 | case '5': 20 | return '5개월'; 21 | case '6': 22 | return '6개월'; 23 | default: 24 | return '-'; 25 | } 26 | } 27 | 28 | static List getListsByPg(String pg) { 29 | switch (pg) { 30 | case 'html5_inicis': 31 | case 'kcp': 32 | return QUOTAS + ['2', '3', '4', '5', '6']; 33 | default: 34 | return QUOTAS; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/screens/certification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:portone_flutter/iamport_certification.dart'; 4 | import 'package:portone_flutter/model/certification_data.dart'; 5 | 6 | class Certification extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | String userCode = Get.arguments['userCode'] as String; 10 | CertificationData data = Get.arguments['data'] as CertificationData; 11 | 12 | return IamportCertification( 13 | appBar: AppBar( 14 | title: Text('포트원 V1 본인인증'), 15 | centerTitle: true, 16 | titleTextStyle: TextStyle( 17 | fontSize: 24, 18 | color: Colors.white, 19 | ), 20 | backgroundColor: Colors.blue, 21 | leading: IconButton( 22 | icon: Icon(Icons.arrow_back_ios), 23 | onPressed: () { 24 | Get.back(); 25 | }, 26 | ), 27 | ), 28 | initialChild: SafeArea( 29 | child: Center( 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Image.asset('assets/images/iamport-logo.png'), 34 | Padding(padding: EdgeInsets.symmetric(vertical: 15)), 35 | Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20.0)), 36 | ], 37 | ), 38 | ), 39 | ), 40 | userCode: userCode, 41 | data: data, 42 | callback: (Map result) { 43 | Get.offNamed('/certification-result', arguments: result); 44 | }, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/lib/screens/certification_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class CertificationResult extends StatelessWidget { 5 | static const Color successColor = Color(0xff52c41a); 6 | static const Color failureColor = Color(0xfff5222d); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | Map result = Get.arguments as Map; 11 | String message; 12 | IconData icon; 13 | Color color; 14 | bool isErrorMessageRendering; 15 | if (result['success'] == 'true') { 16 | message = '본인인증에 성공하였습니다'; 17 | icon = Icons.check_circle; 18 | color = successColor; 19 | isErrorMessageRendering = false; 20 | } else { 21 | message = '본인인증에 실패하였습니다'; 22 | icon = Icons.error; 23 | color = failureColor; 24 | isErrorMessageRendering = true; 25 | } 26 | 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text('본인인증 결과'), 30 | centerTitle: true, 31 | automaticallyImplyLeading: false, 32 | ), 33 | body: SafeArea( 34 | child: Column( 35 | mainAxisAlignment: MainAxisAlignment.center, 36 | children: [ 37 | Icon( 38 | icon, 39 | color: color, 40 | size: 200, 41 | ), 42 | Text( 43 | message, 44 | style: TextStyle( 45 | fontWeight: FontWeight.bold, 46 | fontSize: 20, 47 | ), 48 | ), 49 | Container( 50 | padding: EdgeInsets.fromLTRB(50, 30, 50, 50), 51 | child: Column( 52 | children: [ 53 | Container( 54 | padding: EdgeInsets.symmetric(vertical: 5), 55 | child: Row( 56 | mainAxisAlignment: MainAxisAlignment.start, 57 | children: [ 58 | Expanded( 59 | flex: 4, 60 | child: Text('포트원 고유 결제번호', 61 | style: TextStyle(color: Colors.grey))), 62 | Expanded( 63 | flex: 5, 64 | child: Text(result['imp_uid'] != null 65 | ? result['imp_uid']! 66 | : ""), 67 | ), 68 | ], 69 | ), 70 | ), 71 | if (isErrorMessageRendering) 72 | Container( 73 | padding: EdgeInsets.symmetric(vertical: 5), 74 | child: Row( 75 | mainAxisAlignment: MainAxisAlignment.start, 76 | children: [ 77 | Expanded( 78 | flex: 4, 79 | child: Text('에러 메시지', 80 | style: TextStyle(color: Colors.grey)), 81 | ), 82 | Expanded( 83 | flex: 5, 84 | child: Text(result['error_msg'] != null 85 | ? result['error_msg']! 86 | : ""), 87 | ), 88 | ], 89 | ), 90 | ), 91 | ], 92 | ), 93 | ), 94 | ElevatedButton.icon( 95 | icon: Icon(Icons.arrow_back), 96 | onPressed: () { 97 | Get.offAllNamed('/'); 98 | }, 99 | label: Text( 100 | '돌아가기', 101 | style: TextStyle(fontSize: 16, color: Colors.white), 102 | ), 103 | style: ElevatedButton.styleFrom( 104 | elevation: 0, 105 | shadowColor: Colors.transparent, 106 | ), 107 | ), 108 | ], 109 | ), 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /example/lib/screens/certification_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:portone_flutter/model/certification_data.dart'; 4 | import 'package:portone_flutter_example/model/carrier.dart'; 5 | import 'package:portone_flutter/model/url_data.dart'; 6 | 7 | class CertificationTest extends StatefulWidget { 8 | @override 9 | _CertificationTestState createState() => _CertificationTestState(); 10 | } 11 | 12 | class _CertificationTestState extends State { 13 | final _formKey = GlobalKey(); 14 | late String userCode; // 가맹점 식별코드 15 | String pg = 'danal'; // PG사 16 | late String merchantUid; // 주문번호 17 | String company = '포트원'; // 회사명 또는 URL 18 | String carrier = 'SKT'; // 통신사 19 | late String name; // 본인인증 할 이름 20 | late String phone; // 본인인증 할 전화번호 21 | late String minAge; // 최소 허용 만 나이 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar( 27 | title: Text('포트원 V1 본인인증 테스트'), 28 | centerTitle: true, 29 | titleTextStyle: TextStyle( 30 | fontSize: 24, 31 | color: Colors.white, 32 | ), 33 | backgroundColor: Colors.blue, 34 | leading: IconButton( 35 | icon: Icon(Icons.arrow_back_ios), 36 | onPressed: () { 37 | Get.back(); 38 | }, 39 | ), 40 | ), 41 | body: SafeArea( 42 | minimum: EdgeInsets.symmetric(horizontal: 15), 43 | child: Form( 44 | key: _formKey, 45 | child: ListView( 46 | children: [ 47 | TextFormField( 48 | decoration: InputDecoration( 49 | labelText: '가맹점 식별코드', 50 | ), 51 | validator: (value) => 52 | value!.isEmpty ? '가맹점 식별코드는 필수입력입니다' : null, 53 | initialValue: '', 54 | onSaved: (String? value) { 55 | userCode = value!; 56 | }, 57 | ), 58 | DropdownButtonFormField( 59 | decoration: InputDecoration( 60 | labelText: 'PG사', 61 | ), 62 | value: pg, 63 | onChanged: (String? value) { 64 | setState(() { 65 | pg = value!; 66 | }); 67 | }, 68 | items: ['danal', 'inicis_unified'] 69 | .map>((String value) { 70 | return DropdownMenuItem( 71 | value: value, 72 | child: Text(value == 'danal' 73 | ? '다날 휴대폰 본인인증' 74 | : (value == 'inicis_unified' ? '이니시스 통합인증' : '')), 75 | ); 76 | }).toList(), 77 | ), 78 | TextFormField( 79 | decoration: InputDecoration( 80 | labelText: '주문번호', 81 | ), 82 | validator: (value) => value!.isEmpty ? '주문번호는 필수입력입니다' : null, 83 | initialValue: 'mid_${DateTime.now().millisecondsSinceEpoch}', 84 | onSaved: (String? value) { 85 | merchantUid = value!; 86 | }, 87 | ), 88 | Visibility( 89 | child: TextFormField( 90 | initialValue: company, 91 | decoration: InputDecoration( 92 | labelText: '회사명', 93 | ), 94 | onSaved: (String? value) { 95 | company = value!; 96 | }, 97 | ), 98 | visible: pg == 'danal', 99 | ), 100 | Visibility( 101 | child: DropdownButtonFormField( 102 | decoration: InputDecoration( 103 | labelText: '통신사', 104 | ), 105 | value: carrier, 106 | onChanged: (String? value) { 107 | setState(() { 108 | carrier = value!; 109 | }); 110 | }, 111 | items: Carrier.getLists() 112 | .map>((String value) { 113 | return DropdownMenuItem( 114 | value: value, 115 | child: Text(Carrier.getLabel(value)), 116 | ); 117 | }).toList(), 118 | ), 119 | visible: pg == 'danal', 120 | ), 121 | Visibility( 122 | child: TextFormField( 123 | decoration: InputDecoration( 124 | labelText: '이름', 125 | hintText: '본인인증 할 이름', 126 | ), 127 | onSaved: (String? value) { 128 | name = value!; 129 | }, 130 | ), 131 | visible: pg == 'danal', 132 | ), 133 | Visibility( 134 | child: TextFormField( 135 | decoration: InputDecoration( 136 | labelText: '전화번호', 137 | hintText: '본인인증 할 전화번호', 138 | ), 139 | keyboardType: TextInputType.number, 140 | onSaved: (String? value) { 141 | phone = value!; 142 | }, 143 | ), 144 | visible: pg == 'danal', 145 | ), 146 | Visibility( 147 | child: TextFormField( 148 | decoration: InputDecoration( 149 | labelText: '최소연령', 150 | hintText: '허용 최소 만 나이', 151 | ), 152 | validator: (value) { 153 | if (value!.length > 0) { 154 | RegExp regex = RegExp(r'^[0-9]+$'); 155 | if (!regex.hasMatch(value)) return '최소 연령이 올바르지 않습니다.'; 156 | } 157 | return null; 158 | }, 159 | keyboardType: TextInputType.number, 160 | onSaved: (String? value) { 161 | minAge = value!; 162 | }, 163 | ), 164 | visible: pg == 'danal', 165 | ), 166 | Container( 167 | padding: EdgeInsets.symmetric(vertical: 10), 168 | child: ElevatedButton( 169 | onPressed: () { 170 | if (_formKey.currentState!.validate()) { 171 | _formKey.currentState!.save(); 172 | 173 | CertificationData data = CertificationData( 174 | pg: pg, 175 | merchantUid: merchantUid, 176 | ); 177 | 178 | if (pg == 'inicis_unified') { 179 | data.mRedirectUrl = UrlData.redirectUrl; 180 | } else { 181 | data.carrier = carrier; 182 | data.company = company; 183 | data.name = name; 184 | data.phone = phone; 185 | if (minAge.length > 0) { 186 | data.minAge = int.parse(minAge); 187 | } 188 | } 189 | 190 | Get.toNamed( 191 | '/certification', 192 | arguments: { 193 | 'userCode': userCode, 194 | 'data': data, 195 | }, 196 | ); 197 | } 198 | }, 199 | child: Text( 200 | '본인인증 하기', 201 | style: TextStyle( 202 | fontSize: 25, 203 | color: Colors.white, 204 | fontWeight: FontWeight.bold, 205 | ), 206 | ), 207 | style: ElevatedButton.styleFrom( 208 | padding: EdgeInsets.symmetric(vertical: 10), 209 | shape: RoundedRectangleBorder( 210 | borderRadius: BorderRadius.circular(25), 211 | ), 212 | elevation: 0, 213 | shadowColor: Colors.transparent, 214 | backgroundColor: Colors.blue, 215 | ), 216 | ), 217 | ), 218 | ], 219 | ), 220 | ), 221 | ), 222 | ); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /example/lib/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class Home extends StatefulWidget { 5 | @override 6 | _HomeState createState() => _HomeState(); 7 | } 8 | 9 | class _HomeState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Material( 13 | child: Container( 14 | decoration: BoxDecoration(color: Color(0xff344e81)), 15 | child: Center( 16 | child: Column( 17 | mainAxisAlignment: MainAxisAlignment.center, 18 | children: [ 19 | Row( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | children: [ 22 | Padding( 23 | padding: const EdgeInsets.only( 24 | left: 0, 25 | top: 20.0, 26 | right: 0, 27 | bottom: 20.0, 28 | ), 29 | child: Text( 30 | '포트원 V1 테스트', 31 | style: TextStyle( 32 | fontSize: 24.0, 33 | color: Colors.white, 34 | height: 2.0, 35 | ), 36 | ), 37 | ), 38 | ], 39 | ), 40 | Row( 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | children: [ 43 | Text( 44 | '포트원 V1 플러터 모듈 테스트 화면입니다.', 45 | style: TextStyle( 46 | fontSize: 14.0, 47 | color: Colors.white, 48 | height: 1.2, 49 | ), 50 | ), 51 | ], 52 | ), 53 | Row( 54 | mainAxisAlignment: MainAxisAlignment.center, 55 | children: [ 56 | Text( 57 | '아래 버튼을 눌러 결제 또는 본인인증 테스트를 진행해주세요.', 58 | style: TextStyle( 59 | fontSize: 14.0, 60 | color: Colors.white, 61 | height: 1.2, 62 | ), 63 | ), 64 | ], 65 | ), 66 | Padding( 67 | padding: const EdgeInsets.only( 68 | left: 0, 69 | top: 50.0, 70 | right: 0, 71 | bottom: 0, 72 | ), 73 | child: Row( 74 | mainAxisAlignment: MainAxisAlignment.center, 75 | children: [ 76 | Padding( 77 | padding: EdgeInsets.only( 78 | left: 20, 79 | ), 80 | ), 81 | Expanded( 82 | child: ElevatedButton.icon( 83 | style: ElevatedButton.styleFrom( 84 | padding: EdgeInsets.symmetric(vertical: 10), 85 | shape: RoundedRectangleBorder( 86 | borderRadius: BorderRadius.circular(20), 87 | ), 88 | elevation: 0, 89 | shadowColor: Colors.transparent, 90 | ), 91 | icon: Icon( 92 | Icons.payment, 93 | color: Colors.black, 94 | size: 25, 95 | ), 96 | label: Text( 97 | '결제 테스트', 98 | style: TextStyle( 99 | color: Colors.black, 100 | fontSize: 15, 101 | fontWeight: FontWeight.bold, 102 | ), 103 | ), 104 | onPressed: () { 105 | Get.toNamed('/payment-test'); 106 | }, 107 | ), 108 | ), 109 | Padding( 110 | padding: EdgeInsets.symmetric(horizontal: 10), 111 | ), 112 | Expanded( 113 | child: ElevatedButton.icon( 114 | style: ElevatedButton.styleFrom( 115 | padding: EdgeInsets.symmetric(vertical: 10), 116 | shape: RoundedRectangleBorder( 117 | borderRadius: BorderRadius.circular(20), 118 | ), 119 | elevation: 0, 120 | shadowColor: Colors.transparent, 121 | ), 122 | icon: Icon( 123 | Icons.people, 124 | color: Colors.black, 125 | size: 25, 126 | ), 127 | label: Text( 128 | '본인인증 테스트', 129 | style: TextStyle( 130 | color: Colors.black, 131 | fontSize: 15, 132 | fontWeight: FontWeight.bold, 133 | ), 134 | ), 135 | onPressed: () { 136 | Get.toNamed('/certification-test'); 137 | }, 138 | ), 139 | ), 140 | Padding(padding: EdgeInsets.only(right: 20)), 141 | ], 142 | ), 143 | ), 144 | ], 145 | ), 146 | ), 147 | ), 148 | ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /example/lib/screens/payment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:portone_flutter/iamport_payment.dart'; 4 | import 'package:portone_flutter/model/payment_data.dart'; 5 | 6 | class Payment extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | String userCode = Get.arguments['userCode'] as String; 10 | PaymentData data = Get.arguments['data'] as PaymentData; 11 | 12 | return IamportPayment( 13 | appBar: AppBar( 14 | title: Text('포트원 V1 결제'), 15 | centerTitle: true, 16 | titleTextStyle: TextStyle( 17 | fontSize: 24, 18 | color: Colors.white, 19 | ), 20 | backgroundColor: Colors.blue, 21 | leading: IconButton( 22 | icon: Icon(Icons.arrow_back_ios), 23 | onPressed: () { 24 | Get.back(); 25 | }, 26 | ), 27 | ), 28 | initialChild: SafeArea( 29 | child: Center( 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Image.asset('assets/images/iamport-logo.png'), 34 | Padding(padding: EdgeInsets.symmetric(vertical: 15)), 35 | Text('잠시만 기다려주세요...', style: TextStyle(fontSize: 20.0)), 36 | ], 37 | ), 38 | ), 39 | ), 40 | userCode: userCode, 41 | data: data, 42 | callback: (Map result) { 43 | Get.offNamed('/payment-result', arguments: result); 44 | }, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/lib/screens/payment_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | typedef PaymentResultPayload = Map; 5 | 6 | extension SuccessResult on PaymentResultPayload { 7 | bool get isSuccessed { 8 | bool? getBoolean(String? value) { 9 | switch (value) { 10 | case "true": 11 | return true; 12 | case "false": 13 | return false; 14 | } 15 | return null; 16 | } 17 | 18 | return getBoolean(this["imp_success"]) ?? 19 | getBoolean(this['success']) ?? 20 | this['error_code'] == null && this['code'] == null; 21 | } 22 | 23 | String get transactionId { 24 | return this['imp_uid'] ?? this['txId'] ?? '-'; 25 | } 26 | 27 | String get paymentId { 28 | return this['merchant_uid'] ?? this['paymentId'] ?? '-'; 29 | } 30 | 31 | String get errorCode { 32 | return this['error_code'] ?? this['code'] ?? '-'; 33 | } 34 | 35 | String get errorMessage { 36 | return this['error_msg'] ?? this['message'] ?? '-'; 37 | } 38 | } 39 | 40 | class PaymentResult extends StatelessWidget { 41 | static const Color successColor = Color(0xff52c41a); 42 | static const Color failureColor = Color(0xfff5222d); 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | PaymentResultPayload payload = Get.arguments as PaymentResultPayload; 47 | bool isSuccessed = payload.isSuccessed; 48 | String message; 49 | IconData icon; 50 | Color color; 51 | if (isSuccessed) { 52 | message = '결제에 성공하였습니다'; 53 | icon = Icons.check_circle; 54 | color = successColor; 55 | } else { 56 | message = '결제에 실패하였습니다'; 57 | icon = Icons.error; 58 | color = failureColor; 59 | } 60 | 61 | return Scaffold( 62 | appBar: AppBar( 63 | title: Text('포트원 V1 결제 결과'), 64 | centerTitle: true, 65 | automaticallyImplyLeading: false, 66 | ), 67 | body: SafeArea( 68 | child: Column( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | children: [ 71 | Icon( 72 | icon, 73 | color: color, 74 | size: 200, 75 | ), 76 | Text( 77 | message, 78 | style: TextStyle( 79 | fontWeight: FontWeight.bold, 80 | fontSize: 20, 81 | ), 82 | ), 83 | Container( 84 | padding: EdgeInsets.fromLTRB(50, 30, 50, 50), 85 | child: Column( 86 | children: [ 87 | Container( 88 | padding: EdgeInsets.symmetric(vertical: 5), 89 | child: Row( 90 | mainAxisAlignment: MainAxisAlignment.start, 91 | children: [ 92 | Expanded( 93 | flex: 4, 94 | child: Text('포트원 고유 결제번호', 95 | style: TextStyle(color: Colors.grey))), 96 | Expanded( 97 | flex: 5, 98 | child: Text(payload.transactionId), 99 | ), 100 | ], 101 | ), 102 | ), 103 | isSuccessed 104 | ? Container( 105 | padding: EdgeInsets.symmetric(vertical: 5), 106 | child: Row( 107 | mainAxisAlignment: MainAxisAlignment.start, 108 | children: [ 109 | Expanded( 110 | flex: 4, 111 | child: Text('주문 번호', 112 | style: TextStyle(color: Colors.grey))), 113 | Expanded( 114 | flex: 5, 115 | child: Text(payload.paymentId), 116 | ), 117 | ], 118 | ), 119 | ) 120 | : Container( 121 | padding: EdgeInsets.symmetric(vertical: 5), 122 | child: Column( 123 | children: [ 124 | Row( 125 | mainAxisAlignment: MainAxisAlignment.start, 126 | children: [ 127 | Expanded( 128 | flex: 4, 129 | child: Text('에러 코드', 130 | style: TextStyle(color: Colors.grey)), 131 | ), 132 | Expanded( 133 | flex: 5, 134 | child: Text(payload.errorCode), 135 | ) 136 | ]), 137 | Row( 138 | mainAxisAlignment: MainAxisAlignment.start, 139 | children: [ 140 | Expanded( 141 | flex: 4, 142 | child: Text('에러 메시지', 143 | style: TextStyle(color: Colors.grey)), 144 | ), 145 | Expanded( 146 | flex: 5, 147 | child: Text(payload.errorMessage), 148 | ), 149 | ]) 150 | ], 151 | ), 152 | ), 153 | ], 154 | ), 155 | ), 156 | ElevatedButton.icon( 157 | icon: Icon(Icons.arrow_back), 158 | onPressed: () { 159 | Get.offAllNamed('/'); 160 | }, 161 | label: Text( 162 | '돌아가기', 163 | style: TextStyle(fontSize: 16, color: Colors.white), 164 | ), 165 | style: ElevatedButton.styleFrom( 166 | elevation: 0, 167 | shadowColor: Colors.transparent, 168 | ), 169 | ), 170 | ], 171 | ), 172 | ), 173 | ); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /example/manuals/CALLBACK.md: -------------------------------------------------------------------------------- 1 | # 콜백 함수 설정하기 2 | 포트원 V1 플러터 모듈 콜백 함수 설정을 위한 안내입니다. 3 | 4 | 콜백 함수는 필수입력 필드로, 결제/본인인증 완료 후 라우트를 이동하도록 로직을 작성해야합니다. 아래와 같이 [push](https://api.flutter.dev/flutter/widgets/Navigator/push.html) 함수가 아닌 [pushReplacementNamed](https://api.flutter.dev/flutter/widgets/Navigator/pushReplacementNamed.html) 함수를 사용해야 합니다. 5 | `push` 함수를 사용할 경우, 결제/본인인증 완료 후 라우터가 변경되더라도 유저가 뒤로가기를 하면 포트원 V1 모듈이 다시 렌더링됩니다. 하지만 `pushReplacementNamed` 함수를 사용하면, 결제/본인인증 직전 화면으로 넘어가게 됩니다. 6 | 7 | ### 잘못된 사용 예제 8 | ```dart 9 | callback: (Map result) { 10 | Navigator.push( 11 | context, 12 | '/result', 13 | arguments: result, 14 | ); 15 | }, 16 | ``` 17 | 18 | ### 올바른 사용 예제 19 | ```dart 20 | callback: (Map result) { 21 | Navigator.pushReplacementNamed( 22 | context, 23 | '/result', 24 | arguments: result, 25 | ); 26 | }, 27 | ``` 28 | 29 | ### 결과에 따라 로직 작성하기 30 | 콜백 함수의 첫번째 인자(result)는 결제/본인인증 결과를 담고 있는 오브젝트로 아래와 같이 구성되어 있습니다. 자세한 내용은 포트원 개발자센터 [인증 결제 연동하기 - 3. 결제 결과 처리하기](https://developers.portone.io/opi/ko/integration/start/v1/auth?v=v1#3-%EA%B2%B0%EC%A0%9C-%EA%B2%B0%EA%B3%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-)를 참고해주세요. 31 | 32 | | key | Description | 33 | | ------------- | ------------------ | 34 | | success | 성공 여부 | 35 | | imp_uid | 포트원 고유 결제번호 | 36 | | merchant_uid | 주문번호 | 37 | | error_msg | 실패한 경우, 에러메시지 | 38 | 39 | response에 따라 결제/본인인증 성공/실패 여부를 판단해 아래와 같이 각기 다른 로직을 구성할 수 있습니다. 아래 코드는 예시일 뿐 실제 결제 성공/실패여부는 결제 유효성 검사 후 포트원 V1 REST API로 결제내역을 조회해 판단해야 합니다. 자세한 내용은 포트원 V1 공식 문서 [인증 결제 연동하기 - 4. 결제 완료 처리하기](https://developers.portone.io/opi/ko/integration/start/v1/auth?v=v1#4-%EA%B2%B0%EC%A0%9C-%EC%99%84%EB%A3%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-)를 참고해주세요. 40 | 41 | ```dart 42 | import 'package:flutter/material.dart'; 43 | 44 | class Result extends StatelessWidget { 45 | bool getIsSuccessed(Map result) { 46 | if (result['imp_success'] == 'true') { 47 | return true; 48 | } 49 | if (result['success'] == 'true') { 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | Map result = ModalRoute.of(context).settings.arguments; 58 | bool isSuccessed = getIsSuccessed(result); 59 | 60 | return Scaffold( 61 | appBar: new AppBar( 62 | title: Text('포트원 V1 결과'), 63 | ), 64 | body: Column( 65 | mainAxisAlignment: MainAxisAlignment.center, 66 | children: [ 67 | Text( 68 | message: isSuccessed ? '성공하였습니다' : '실패하였습니다', 69 | style: TextStyle( 70 | fontWeight: FontWeight.bold, 71 | fontSize: 20.0, 72 | ), 73 | ), 74 | Container( 75 | padding: EdgeInsets.fromLTRB(50.0, 30.0, 50.0, 50.0), 76 | child: Column( 77 | children: [ 78 | Container( 79 | padding: EdgeInsets.fromLTRB(0, 5.0, 0, 5.0), 80 | child: Row( 81 | mainAxisAlignment: MainAxisAlignment.start, 82 | children: [ 83 | Expanded( 84 | flex: 4, 85 | child: Text('포트원 고유 결제번호', style: TextStyle(color: Colors.grey)) 86 | ), 87 | Expanded( 88 | flex: 5, 89 | child: Text(result['imp_uid'] ?? '-'), 90 | ), 91 | ], 92 | ), 93 | ), 94 | isSuccessed ? Container( 95 | padding: EdgeInsets.fromLTRB(0, 5.0, 0, 5.0), 96 | child: Row( 97 | mainAxisAlignment: MainAxisAlignment.start, 98 | children: [ 99 | Expanded( 100 | flex: 4, 101 | child: Text('주문 번호', style: TextStyle(color: Colors.grey)) 102 | ), 103 | Expanded( 104 | flex: 5, 105 | child: Text(result['merchant_uid'] ?? '-'), 106 | ), 107 | ], 108 | ), 109 | ) : Container( 110 | padding: EdgeInsets.fromLTRB(0, 5.0, 0, 5.0), 111 | child: Row( 112 | mainAxisAlignment: MainAxisAlignment.start, 113 | children: [ 114 | Expanded( 115 | flex: 4, 116 | child: Text('에러 메시지', style: TextStyle(color: Colors.grey)), 117 | ), 118 | Expanded( 119 | flex: 5, 120 | child: Text(result['error_msg'] ?? '-'), 121 | ), 122 | ], 123 | ), 124 | ), 125 | ], 126 | ), 127 | ), 128 | ], 129 | ), 130 | ); 131 | } 132 | } 133 | ``` -------------------------------------------------------------------------------- /example/manuals/TRANS.md: -------------------------------------------------------------------------------- 1 | # 실시간 계좌이체 설정하기 2 | 3 | ## Android - 웹 표준 이니시스 또는 나이스 정보통신 4 | Android에서 `웹 표준 이니시스(이하 이니시스)` 또는 `나이스` `실시간 계좌이체`를 연동하는 경우 별도 설정이 요구됩니다. 이니시스는 결제완료 후 콜백이 실행되지 않고, 나이스는 결제인증 후 결제완료 처리가 되지 않기 때문입니다. 이는 이니시스와 나이스 결제모듈 자체 문제입니다. 포트원 V1 플러터 모듈은 이에 대응하기 위한 안내를 제공하고 있습니다. 5 | 6 | ### 실시간 계좌이체 결제처리 원리 7 | 먼저 뱅크페이 앱에서 귀하의 앱으로 복귀할때를 트리거해야합니다. 포트원 V1 플러터 모듈은 트리거 된 순간 이니시스의 경우 콜백을 실행시키고, 나이스의 경우 나이스로 결제정보가 담긴 POST 요청을 보내야 합니다. Java는 들어오는(`incoming`) 앱 링크를 트리거 하기 위해 `deep linking` 기능을 제공합니다. 8 | 9 | ### Intent Filter 추가하고 launchMode 설정하기 10 | deep linking 기능을 활성화하기 위해 `Intent Filter`를 추가하고 `MainActivity`의 `launchMode`를 아래와 같이 `singleTask`로 설정해야 합니다. 자세한 내용은 [Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking)를 참고하세요. 11 | 12 | ```xml 13 | ... 14 | 15 | 16 | 20 | ... 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ... 30 | ``` -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: portone_flutter_example 2 | description: Demonstrates how to use the portone_flutter plugin. 3 | publish_to: "none" 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ">=2.13.0 <4.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | # The following adds the Cupertino Icons font to your application. 14 | # Use with the CupertinoIcons class for iOS style icons. 15 | cupertino_icons: ^1.0.3 16 | get: ^4.6.0 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | portone_flutter: 23 | path: ../ 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://www.dartlang.org/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | # The following line ensures that the Material Icons font is 31 | # included with your application, so that you can use the icons in 32 | # the material Icons class. 33 | uses-material-design: true 34 | 35 | # To add assets to your application, add an assets section, like this: 36 | assets: 37 | - assets/images/iamport-logo.png 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | module: 65 | androidX: true 66 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:portone_flutter_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(IamportApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portone-io/portone_flutter/4a8c02372ab1a53fb52a4485f8c730bb138c3a2c/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/IamportFlutterPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface IamportFlutterPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/IamportFlutterPlugin.m: -------------------------------------------------------------------------------- 1 | #import "IamportFlutterPlugin.h" 2 | 3 | #if __has_include() 4 | #import 5 | #else 6 | // Support project import fallback if the generated compatibility header 7 | // is not copied when this plugin is created as a library. 8 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 9 | #import "flutter_plugin_example-Swift.h" 10 | #import "portone_flutter-Swift.h" 11 | #endif 12 | 13 | @implementation IamportFlutterPlugin 14 | + (void)registerWithRegistrar:(NSObject*)registrar { 15 | [SwiftIamportFlutterPlugin registerWithRegistrar:registrar]; 16 | } 17 | @end -------------------------------------------------------------------------------- /ios/Classes/SwiftIamportFlutterPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftIamportFlutterPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "portone_flutter", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftIamportFlutterPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/portone_flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'portone_flutter' 6 | s.version = '0.0.1' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.dependency 'Flutter' 17 | s.platform = :ios, '8.0' 18 | 19 | # Flutter.framework does not contain a i386 slice. 20 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 21 | s.swift_version = '5.0' 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/Iamport_certification.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:core'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/gestures.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:portone_flutter/model/certification_data.dart'; 9 | import 'package:portone_flutter/model/iamport_validation.dart'; 10 | import 'package:portone_flutter/model/url_data.dart'; 11 | import 'package:portone_flutter/widget/iamport_error.dart'; 12 | import 'package:portone_flutter/widget/iamport_webview.dart'; 13 | import 'package:iamport_webview_flutter/iamport_webview_flutter.dart'; 14 | 15 | class IamportCertification extends StatelessWidget { 16 | final PreferredSizeWidget? appBar; 17 | final Widget? initialChild; 18 | final String userCode; 19 | final String? tierCode; 20 | final CertificationData data; 21 | final callback; 22 | final Set>? gestureRecognizers; 23 | final String? customUserAgent; 24 | 25 | IamportCertification({ 26 | Key? key, 27 | this.appBar, 28 | this.initialChild, 29 | required this.userCode, 30 | this.tierCode, 31 | required this.data, 32 | required this.callback, 33 | this.gestureRecognizers, 34 | this.customUserAgent, 35 | }) : super(key: key); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | var redirectUrl = UrlData.redirectUrl; 40 | if (this.data.mRedirectUrl != null && this.data.mRedirectUrl!.isNotEmpty) { 41 | redirectUrl = this.data.mRedirectUrl!; 42 | } 43 | 44 | IamportValidation validation = IamportValidation.fromCertificationData( 45 | userCode, 46 | data, 47 | callback, 48 | ); 49 | 50 | var init = 51 | this.tierCode == null 52 | ? 'IMP.init("${this.userCode}");' 53 | : 'IMP.agency("${this.userCode}", "${this.tierCode}");'; 54 | 55 | if (validation.getIsValid()) { 56 | return IamportWebView( 57 | type: ActionType.auth, 58 | appBar: this.appBar, 59 | initialChild: this.initialChild, 60 | gestureRecognizers: this.gestureRecognizers, 61 | customUserAgent: this.customUserAgent, 62 | executeJS: (WebViewController controller) { 63 | controller.evaluateJavascript(''' 64 | $init 65 | IMP.certification(${jsonEncode(this.data.toJson())}, function(response) { 66 | const query = []; 67 | Object.keys(response).forEach(function(key) { 68 | query.push(key + "=" + response[key]); 69 | }); 70 | location.href = "$redirectUrl" + "?" + query.join("&"); 71 | }); 72 | '''); 73 | }, 74 | useQueryData: (Map data) { 75 | this.callback(data); 76 | }, 77 | isPaymentOver: (String url) { 78 | return url.startsWith(redirectUrl); 79 | }, 80 | // 인증에는 customPGAction 수행할 필요 없음 81 | customPGAction: (WebViewController controller) {}, 82 | ); 83 | } else { 84 | return IamportError(ActionType.auth, validation.getErrorMessage()); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/iamport_payment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/gestures.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:portone_flutter/model/iamport_validation.dart'; 10 | import 'package:portone_flutter/model/payment_data.dart'; 11 | import 'package:portone_flutter/model/url_data.dart'; 12 | import 'package:portone_flutter/widget/iamport_error.dart'; 13 | import 'package:portone_flutter/widget/iamport_webview.dart'; 14 | import 'package:iamport_webview_flutter/iamport_webview_flutter.dart'; 15 | import 'package:app_links/app_links.dart'; 16 | 17 | class IamportPayment extends StatelessWidget { 18 | final PreferredSizeWidget? appBar; 19 | final Widget? initialChild; 20 | final String userCode; 21 | final String? tierCode; 22 | final PaymentData data; 23 | final callback; 24 | final Set>? gestureRecognizers; 25 | final _appLinks = AppLinks(); 26 | 27 | IamportPayment({ 28 | Key? key, 29 | this.appBar, 30 | this.initialChild, 31 | required this.userCode, 32 | this.tierCode, 33 | required this.data, 34 | required this.callback, 35 | this.gestureRecognizers, 36 | }) : super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | IamportValidation validation = IamportValidation( 41 | this.userCode, 42 | this.data, 43 | this.callback, 44 | ); 45 | 46 | if (validation.getIsValid()) { 47 | var redirectUrl = UrlData.redirectUrl; 48 | if (this.data.mRedirectUrl != null && 49 | this.data.mRedirectUrl!.isNotEmpty) { 50 | redirectUrl = this.data.mRedirectUrl!; 51 | } 52 | 53 | var init = 54 | this.tierCode == null 55 | ? 'IMP.init("${this.userCode}");' 56 | : 'IMP.agency("${this.userCode}", "${this.tierCode}");'; 57 | 58 | return IamportWebView( 59 | type: ActionType.payment, 60 | appBar: this.appBar, 61 | initialChild: this.initialChild, 62 | gestureRecognizers: this.gestureRecognizers, 63 | executeJS: (WebViewController controller) { 64 | controller.evaluateJavascript(''' 65 | $init 66 | IMP.request_pay(${jsonEncode(this.data.toJson())}, function(response) { 67 | const query = []; 68 | Object.keys(response).forEach(function(key) { 69 | query.push(key + "=" + response[key]); 70 | }); 71 | location.href = "$redirectUrl" + "?" + query.join("&"); 72 | }); 73 | '''); 74 | }, 75 | customPGAction: (WebViewController controller) { 76 | if (this.data.pg == 'smilepay') { 77 | // webview_flutter에서 iOS는 쿠키가 기본적으로 허용되어있는 것으로 추정 78 | if (Platform.isAndroid) { 79 | controller.setAcceptThirdPartyCookies(true); 80 | } 81 | } 82 | /* [v0.9.6] niceMobileV2: true 대비 코드 작성 */ 83 | if (this.data.pg == 'nice' && this.data.payMethod == 'trans') { 84 | try { 85 | StreamSubscription sub = _appLinks.uriLinkStream.listen(( 86 | Uri? link, 87 | ) async { 88 | if (link != null) { 89 | String decodedUrl = Uri.decodeComponent(link.toString()); 90 | Uri parsedUrl = Uri.parse(decodedUrl); 91 | String scheme = parsedUrl.scheme; 92 | if (scheme == data.appScheme.toLowerCase()) { 93 | String queryToString = parsedUrl.query; 94 | String? niceTransRedirectionUrl; 95 | parsedUrl.queryParameters.forEach((key, value) { 96 | if (key == 'callbackparam1') { 97 | niceTransRedirectionUrl = value; 98 | } 99 | }); 100 | await controller.evaluateJavascript(''' 101 | location.href = "$niceTransRedirectionUrl?$queryToString"; 102 | '''); 103 | } 104 | } 105 | }); 106 | return sub; 107 | } on FormatException {} 108 | } 109 | return null; 110 | }, 111 | useQueryData: (Map data) { 112 | this.callback(data); 113 | }, 114 | isPaymentOver: (String url) { 115 | if (url.startsWith(redirectUrl)) { 116 | return true; 117 | } 118 | 119 | if (this.data.payMethod == 'trans') { 120 | /* [IOS] imp_uid와 merchant_uid값만 전달되기 때문에 결제 성공 또는 실패 구분할 수 없음 */ 121 | String decodedUrl = Uri.decodeComponent(url); 122 | Uri parsedUrl = Uri.parse(decodedUrl); 123 | String scheme = parsedUrl.scheme; 124 | if (this.data.pg == 'html5_inicis') { 125 | Map query = parsedUrl.queryParameters; 126 | if (query['m_redirect_url'] != null && 127 | scheme == this.data.appScheme.toLowerCase()) { 128 | if (query['m_redirect_url']!.contains(redirectUrl)) { 129 | return true; 130 | } 131 | } 132 | } 133 | } 134 | 135 | return false; 136 | }, 137 | ); 138 | } else { 139 | return IamportError(ActionType.payment, validation.getErrorMessage()); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/model/certification_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'certification_data.g.dart'; 4 | 5 | @JsonSerializable() 6 | class CertificationData { 7 | String? pg; 8 | 9 | @JsonKey(name: 'merchant_uid') 10 | String? merchantUid; 11 | 12 | String? company; 13 | String? carrier; 14 | String? name; 15 | String? phone; 16 | 17 | @JsonKey(name: 'min_age') 18 | int? minAge; 19 | 20 | bool? popup; 21 | 22 | @JsonKey(name: 'm_redirect_url') 23 | String? mRedirectUrl; 24 | 25 | CertificationData({ 26 | this.pg, 27 | this.merchantUid, 28 | this.company, 29 | this.carrier, 30 | this.name, 31 | this.phone, 32 | this.minAge, 33 | this.popup, 34 | this.mRedirectUrl, 35 | }); 36 | 37 | factory CertificationData.fromJson(Map json) => 38 | _$CertificationDataFromJson(json); 39 | 40 | Map toJson() => _$CertificationDataToJson(this); 41 | } 42 | -------------------------------------------------------------------------------- /lib/model/certification_data.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'certification_data.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CertificationData _$CertificationDataFromJson(Map json) => 10 | CertificationData( 11 | pg: json['pg'] as String?, 12 | merchantUid: json['merchant_uid'] as String?, 13 | company: json['company'] as String?, 14 | carrier: json['carrier'] as String?, 15 | name: json['name'] as String?, 16 | phone: json['phone'] as String?, 17 | minAge: json['min_age'] as int?, 18 | popup: json['popup'] as bool?, 19 | mRedirectUrl: json['m_redirect_url'] as String?, 20 | ); 21 | 22 | Map _$CertificationDataToJson(CertificationData instance) { 23 | final val = {}; 24 | 25 | void writeNotNull(String key, dynamic value) { 26 | if (value != null) { 27 | val[key] = value; 28 | } 29 | } 30 | 31 | writeNotNull('pg', instance.pg); 32 | writeNotNull('merchant_uid', instance.merchantUid); 33 | writeNotNull('company', instance.company); 34 | writeNotNull('carrier', instance.carrier); 35 | writeNotNull('name', instance.name); 36 | writeNotNull('phone', instance.phone); 37 | writeNotNull('min_age', instance.minAge); 38 | writeNotNull('popup', instance.popup); 39 | writeNotNull('m_redirect_url', instance.mRedirectUrl); 40 | return val; 41 | } 42 | -------------------------------------------------------------------------------- /lib/model/iamport_url.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:portone_flutter/model/url_data.dart'; 5 | import 'package:url_launcher/url_launcher_string.dart'; 6 | 7 | class IamportUrl { 8 | late String url; 9 | String? appScheme; 10 | String? appUrl; 11 | String? package; // Android only 12 | 13 | IamportUrl(String incomeUrl) { 14 | this.url = incomeUrl; 15 | 16 | List splittedUrl = 17 | this.url.replaceFirst(RegExp(r'://'), ' ').split(' '); 18 | this.appScheme = splittedUrl[0]; 19 | 20 | if (Platform.isAndroid) { 21 | /* 22 | Android scheme은 크게 3가지 형태 23 | 1. intent:// 24 | 2. [app]:// 25 | 3. intent:[app]:// 26 | 이 세가지를 정상적으로 launch가 가능한 2번 형태로 변환한다 27 | */ 28 | if (this.isAppLink()) { 29 | if (this.appScheme!.contains('intent')) { 30 | List intentUrl = splittedUrl[1].split('#Intent;'); 31 | String host = intentUrl[0]; 32 | // 농협카드 일반결제 예외처리 33 | if (host.contains(':')) { 34 | host = host.replaceAll(RegExp(r':'), '%3A'); 35 | } 36 | List arguments = intentUrl[1].split(';'); 37 | 38 | // scheme이 intent로 시작하면 뒷쪽의 정보를 통해 appscheme과 package 정보 추출 39 | if (this.appScheme! != 'intent') { 40 | // 현대카드 예외처리 41 | this.appScheme = this.appScheme!.split(':')[1]; 42 | this.appUrl = this.appScheme! + '://' + host; 43 | } 44 | arguments.forEach((s) { 45 | if (s.startsWith('scheme')) { 46 | String scheme = s.split('=')[1]; 47 | this.appUrl = scheme + '://' + host; 48 | this.appScheme = scheme; 49 | } else if (s.startsWith('package')) { 50 | String package = s.split('=')[1]; 51 | this.package = package; 52 | } 53 | }); 54 | } else { 55 | this.appUrl = this.url; 56 | } 57 | } else { 58 | this.appUrl = this.url; 59 | } 60 | } else { 61 | this.appUrl = this.url; 62 | } 63 | } 64 | 65 | bool isAppLink() { 66 | String? scheme; 67 | try { 68 | scheme = Uri.parse(this.url).scheme; 69 | } catch (e) { 70 | scheme = this.appScheme; 71 | } 72 | 73 | if (Platform.isAndroid && this.appScheme == 'https') { 74 | if (this 75 | .url 76 | .startsWith('https://play.google.com/store/apps/details?id=')) { 77 | return true; 78 | } 79 | } 80 | 81 | return !['http', 'https', 'about', 'data', ''].contains(scheme); 82 | } 83 | 84 | Future getAppUrl() async { 85 | return this.appUrl; 86 | } 87 | 88 | Future getMarketUrl() async { 89 | if (Platform.isIOS) { 90 | switch (this.appScheme) { 91 | case 'kftc-bankpay': // 뱅크페이 92 | return UrlData.IOS_MARKET_PREFIX + 'id398456030'; 93 | case 'ispmobile': // ISP/페이북 94 | return UrlData.IOS_MARKET_PREFIX + 'id369125087'; 95 | case 'hdcardappcardansimclick': // 현대카드 앱카드 96 | return UrlData.IOS_MARKET_PREFIX + 'id702653088'; 97 | case 'shinhan-sr-ansimclick': // 신한 앱카드 98 | return UrlData.IOS_MARKET_PREFIX + 'id572462317'; 99 | case 'kb-acp': // KB국민 앱카드 100 | return UrlData.IOS_MARKET_PREFIX + 'id695436326'; 101 | case 'mpocket.online.ansimclick': // 삼성앱카드 102 | return UrlData.IOS_MARKET_PREFIX + 'id535125356'; 103 | case 'lottesmartpay': // 롯데 모바일결제 104 | return UrlData.IOS_MARKET_PREFIX + 'id668497947'; 105 | case 'lotteappcard': // 롯데 앱카드 106 | return UrlData.IOS_MARKET_PREFIX + 'id688047200'; 107 | case 'cloudpay': // 하나1Q페이(앱카드) 108 | return UrlData.IOS_MARKET_PREFIX + 'id847268987'; 109 | case 'citimobileapp': // 시티은행 앱카드 110 | return UrlData.IOS_MARKET_PREFIX + 'id1179759666'; 111 | case 'payco': // 페이코 112 | return UrlData.IOS_MARKET_PREFIX + 'id924292102'; 113 | case 'kakaotalk': // 카카오톡 114 | return UrlData.IOS_MARKET_PREFIX + 'id362057947'; 115 | case 'lpayapp': // 롯데 L.pay 116 | return UrlData.IOS_MARKET_PREFIX + 'id1036098908'; 117 | case 'wooripay': // 우리페이 118 | return UrlData.IOS_MARKET_PREFIX + 'id1201113419'; 119 | case 'com.wooricard.wcard': // 우리WON카드 120 | return UrlData.IOS_MARKET_PREFIX + 'id1499598869'; 121 | case 'nhallonepayansimclick': // NH농협카드 올원페이(앱카드) 122 | return UrlData.IOS_MARKET_PREFIX + 'id1177889176'; 123 | case 'hanawalletmembers': // 하나카드(하나멤버스 월렛) 124 | return UrlData.IOS_MARKET_PREFIX + 'id1038288833'; 125 | case 'shinsegaeeasypayment': // 신세계 SSGPAY 126 | return UrlData.IOS_MARKET_PREFIX + 'id666237916'; 127 | case 'naversearchthirdlogin': // 네이버페이 앱 로그인 128 | return UrlData.IOS_MARKET_PREFIX + 'id393499958'; 129 | case 'lguthepay-xpay': // 페이나우 130 | return UrlData.IOS_MARKET_PREFIX + 'id760098906'; 131 | case 'lmslpay': // 롯데 L.POINT 132 | return UrlData.IOS_MARKET_PREFIX + 'id473250588'; 133 | case 'liivbank': // Liiv 국민 134 | return UrlData.IOS_MARKET_PREFIX + 'id1126232922'; 135 | case 'supertoss': // 토스 136 | return UrlData.IOS_MARKET_PREFIX + 'id839333328'; 137 | case 'newsmartpib': // 우리WON뱅킹 138 | return UrlData.IOS_MARKET_PREFIX + 'id1470181651'; 139 | case 'v3mobileplusweb': // V3 Mobile Plus 140 | return UrlData.IOS_MARKET_PREFIX + 'id1481938658'; 141 | case 'kbbank': // KB스타뱅킹 142 | return UrlData.IOS_MARKET_PREFIX + 'id373742138'; 143 | case 'newliiv': // 리브 Next 144 | return UrlData.IOS_MARKET_PREFIX + 'id1573528126'; 145 | default: 146 | return this.url; 147 | } 148 | } else if (Platform.isAndroid) { 149 | if (this.package != null) { 150 | // 앱이 설치되어 있지 않아 실행 불가능할 경우 추출된 package 정보를 이용해 플레이스토어 열기 151 | return UrlData.ANDROID_MARKET_PREFIX + this.package!; 152 | } 153 | switch (this.appScheme) { 154 | case UrlData.ISP: 155 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_ISP; 156 | case UrlData.BANKPAY: 157 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_BANKPAY; 158 | case UrlData.KB_BANKPAY: 159 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KB_BANKPAY; 160 | case UrlData.NH_BANKPAY: 161 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_NH_BANKPAY; 162 | case UrlData.MG_BANKPAY: 163 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_MG_BANKPAY; 164 | case UrlData.KN_BANKPAY: 165 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KN_BANKPAY; 166 | case UrlData.KAKAOPAY: 167 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KAKAOPAY; 168 | case UrlData.SMILEPAY: 169 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_SMILEPAY; 170 | case UrlData.CHAIPAY: 171 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_CHAIPAY; 172 | case UrlData.PAYCO: 173 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_PAYCO; 174 | case UrlData.HYUNDAICARD: 175 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_HYUNDAICARD; 176 | case UrlData.TOSS: 177 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_TOSS; 178 | case UrlData.SHINHANCARD: 179 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_SHINHANCARD; 180 | case UrlData.HANACARD: 181 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_HANACARD; 182 | case UrlData.SAMSUNGCARD: 183 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_SAMSUNGCARD; 184 | case UrlData.KBCARD: 185 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KBCARD; 186 | case UrlData.NHCARD: 187 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_NHCARD; 188 | case UrlData.CITICARD: 189 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_CITICARD; 190 | case UrlData.LOTTECARD: 191 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_LOTTECARD; 192 | case UrlData.LPAY: 193 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_LPAY; 194 | case UrlData.SSGPAY: 195 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_SSGPAY; 196 | case UrlData.KPAY: 197 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KPAY; 198 | case UrlData.PAYNOW: 199 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_PAYNOW; 200 | case UrlData.WOORIWONCARD: 201 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_WOORIWONCARD; 202 | case UrlData.LPOINT: 203 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_LPOINT; 204 | case UrlData.WOORIWONBANK: 205 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_WOORIWONBANK; 206 | case UrlData.KTFAUTH: 207 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KTFAUTH; 208 | case UrlData.LGTAUTH: 209 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_LGTAUTH; 210 | case UrlData.SKTAUTH: 211 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_SKTAUTH; 212 | case UrlData.V3_MOBILE_PLUS: 213 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_V3_MOBILE_PLUS; 214 | case UrlData.KBBANK: 215 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_KBBANK; 216 | case UrlData.LIIV_NEXT: 217 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_LIIV_NEXT; 218 | case UrlData.NAVER: 219 | return UrlData.ANDROID_MARKET_PREFIX + UrlData.PACKAGE_NAVER; 220 | default: 221 | return this.url; 222 | } 223 | } 224 | return null; 225 | } 226 | 227 | Future launchApp() async { 228 | bool opened = false; 229 | String appUrl = (await this.getAppUrl())!; 230 | 231 | try { 232 | opened = await launchUrlString(appUrl); 233 | } catch (e) {} 234 | 235 | if (!opened) { 236 | opened = await launchUrlString((await this.getMarketUrl())!); 237 | } 238 | 239 | return opened; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /lib/model/iamport_validation.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/certification_data.dart'; 2 | import 'package:portone_flutter/model/payment_data.dart'; 3 | 4 | class IamportValidation { 5 | bool isValid = true; 6 | String? errorMessage; 7 | 8 | IamportValidation(String userCode, PaymentData data, Function callback) { 9 | if (data.payMethod == 'vbank') { 10 | if (data.vbankDue == null) { 11 | isValid = false; 12 | errorMessage = '가상계좌 결제시 입금기한(vbankDue)은 필수입력입니다.'; 13 | return; 14 | } 15 | 16 | if (data.pg == 'danal_tpay' && data.bizNum == null) { 17 | isValid = false; 18 | errorMessage = '다날 - 가상계좌 결제시 사업자 등록번호(bizNum)은 필수입력입니다.'; 19 | return; 20 | } 21 | } 22 | 23 | if (data.payMethod == 'phone' && data.digital == null) { 24 | isValid = false; 25 | errorMessage = '휴대폰 소액결제시 실물 컨텐츠 여부(digital)는 필수입력입니다.'; 26 | return; 27 | } 28 | 29 | if (data.pg == 'kcp_billing' && data.customerUid == null) { 30 | isValid = false; 31 | errorMessage = '정기결제시 구매자 카드정보(customerUid)는 필수입력입니다.'; 32 | return; 33 | } 34 | 35 | if (data.pg == 'syrup') { 36 | isValid = false; 37 | errorMessage = '해당 모듈은 해당 PG사를 지원하지 않습니다.'; 38 | return; 39 | } 40 | 41 | if (data.pg == 'paypal' && data.popup == true) { 42 | isValid = false; 43 | errorMessage = '해당 모듈에서 페이팔 - 팝업 방식은 지원하지 않습니다.'; 44 | return; 45 | } 46 | 47 | if ((data.pg == 'naverpay' || data.pg == 'naverco') && 48 | data.naverPopupMode == true) { 49 | isValid = false; 50 | errorMessage = '해당 모듈에서 네이버페이 - 팝업 방식은 지원하지 않습니다.'; 51 | return; 52 | } 53 | 54 | if (data.popup == true) { 55 | isValid = false; 56 | errorMessage = '해당 모듈은 팝업 방식을 지원하지 않습니다.'; 57 | return; 58 | } 59 | } 60 | 61 | IamportValidation.fromCertificationData( 62 | String userCode, CertificationData data, Function callback) { 63 | if (data.popup == true) { 64 | isValid = false; 65 | errorMessage = '해당 모듈은 팝업 방식을 지원하지 않습니다.'; 66 | return; 67 | } 68 | } 69 | 70 | bool getIsValid() { 71 | return isValid; 72 | } 73 | 74 | String? getErrorMessage() { 75 | return errorMessage; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/model/payment_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/pg/bypass.dart'; 2 | import 'package:portone_flutter/model/pg/naver/naver_interface.dart'; 3 | import 'package:portone_flutter/model/pg/naver/naver_products.dart'; 4 | import 'package:portone_flutter/model/pg/kcp/kcp_products.dart'; 5 | import 'package:portone_flutter/model/url_data.dart'; 6 | import 'package:json_annotation/json_annotation.dart'; 7 | 8 | part 'payment_data.g.dart'; 9 | 10 | @JsonSerializable() 11 | class PaymentData { 12 | String? pg; // PG사 13 | 14 | @JsonKey(name: 'pay_method') 15 | String payMethod; // 결제수단 16 | 17 | bool? escrow; // 에스크로 여부 18 | 19 | @JsonKey(name: 'merchant_uid') 20 | String merchantUid; // 주문번호 21 | 22 | String? name; // 주문명 23 | num amount; // 결제금액 24 | 25 | @JsonKey(name: 'custom_data') 26 | Map? customData; // 임의 지정 데이터 27 | 28 | @JsonKey(name: 'tax_free') 29 | int? taxFree; // 면세 공급 가액 30 | 31 | int? vat; // 부가세 32 | String? currency; // 화폐단위 33 | String? language; // 언어설정 34 | 35 | @JsonKey(name: 'buyer_name') 36 | String? buyerName; // 구매자 이름 37 | 38 | @JsonKey(name: 'buyer_tel') 39 | String buyerTel; // 구매자 연락처 40 | 41 | @JsonKey(name: 'buyer_email') 42 | String? buyerEmail; // 구매자 이메일 43 | 44 | @JsonKey(name: 'buyer_addr') 45 | String? buyerAddr; // 구매자 주소 46 | 47 | @JsonKey(name: 'buyer_postcode') 48 | String? buyerPostcode; // 구매자 우편번호 49 | 50 | @JsonKey(name: 'notice_url') 51 | String? noticeUrl; 52 | 53 | @JsonKey( 54 | name: 'display', 55 | toJson: _cardQuotaToJson, 56 | fromJson: _cardQuotaFromJson, 57 | ) 58 | List? cardQuota; // 할부개월수 59 | 60 | bool? digital; // 실물컨텐츠 여부 61 | 62 | @JsonKey(name: 'vbank_due') 63 | String? vbankDue; // 가상계좌 입금기한 64 | 65 | @JsonKey(name: 'confirm_url') 66 | String? confirmUrl; 67 | 68 | @JsonKey(name: 'm_redirect_url') 69 | String? mRedirectUrl; 70 | 71 | @JsonKey(name: 'app_scheme') 72 | String appScheme; // 앱 스킴 73 | 74 | @JsonKey(name: 'biz_num') 75 | String? bizNum; // 사업자번호 76 | 77 | @JsonKey(name: 'customer_id') 78 | String? customerId; // 회원 고유 id 79 | 80 | @JsonKey(name: 'customer_uid') 81 | String? customerUid; // 정기결제 카드정보 82 | 83 | bool? popup; // 페이팔 팝업 여부 84 | String? naverUseCfm; // 네이버페이 이용완료일 85 | bool? naverPopupMode; // 네이버페이 팝업 여부 86 | List? naverProducts; // 네이버 상품정보 87 | bool? naverCultureBenefit; 88 | String? naverProductCode; 89 | String? naverActionType; 90 | String? naverPurchaserName; 91 | String? naverPurchaserBirthday; 92 | String? naverChainId; 93 | String? naverMerchantUserKey; 94 | NaverInterface? naverInterface; 95 | Map? period; // [이니시스. 다날. 나이스] 서비스 제공기간 표기 96 | String? company; // [다날 - 휴대폰 소액결제 전용] 주문명: (company) name 대비 97 | bool? niceMobileV2 = true; 98 | List? kcpProducts; // kcp 상품정보 99 | Bypass? bypass; 100 | 101 | PaymentData({ 102 | this.pg, 103 | required this.payMethod, 104 | this.escrow, 105 | required this.merchantUid, 106 | this.name, 107 | required this.amount, 108 | this.customData, 109 | this.taxFree, 110 | this.vat, 111 | this.currency, 112 | this.language, 113 | this.buyerName, 114 | required this.buyerTel, 115 | this.buyerEmail, 116 | this.buyerAddr, 117 | this.buyerPostcode, 118 | this.noticeUrl, 119 | this.cardQuota, 120 | this.digital, 121 | this.vbankDue, 122 | this.confirmUrl, 123 | this.mRedirectUrl = UrlData.redirectUrl, 124 | required this.appScheme, 125 | this.bizNum, 126 | this.customerId, 127 | this.customerUid, 128 | this.popup, 129 | this.naverUseCfm, 130 | this.naverPopupMode, 131 | this.naverProducts, 132 | this.naverCultureBenefit, 133 | this.naverProductCode, 134 | this.naverActionType, 135 | this.naverPurchaserName, 136 | this.naverPurchaserBirthday, 137 | this.naverChainId, 138 | this.naverMerchantUserKey, 139 | this.naverInterface, 140 | this.period, 141 | this.company, 142 | this.niceMobileV2, 143 | this.kcpProducts, 144 | this.bypass, 145 | }); 146 | 147 | factory PaymentData.fromJson(Map json) => 148 | _$PaymentDataFromJson(json); 149 | 150 | Map toJson() => _$PaymentDataToJson(this); 151 | 152 | static List? _cardQuotaFromJson(Map json) => 153 | (json['card_quota'] as List?)?.map((e) => e as int).toList(); 154 | 155 | static Map? _cardQuotaToJson(List? list) { 156 | if (list != null) { 157 | final val = {}; 158 | 159 | val['card_quota'] = list; 160 | 161 | return val; 162 | } else { 163 | return null; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/model/payment_data.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'payment_data.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | PaymentData _$PaymentDataFromJson(Map json) => PaymentData( 10 | pg: json['pg'] as String?, 11 | payMethod: json['pay_method'] as String, 12 | escrow: json['escrow'] as bool?, 13 | merchantUid: json['merchant_uid'] as String, 14 | name: json['name'] as String?, 15 | amount: json['amount'] as num, 16 | customData: (json['custom_data'] as Map?)?.map( 17 | (k, e) => MapEntry(k, e as String), 18 | ), 19 | taxFree: json['tax_free'] as int?, 20 | vat: json['vat'] as int?, 21 | currency: json['currency'] as String?, 22 | language: json['language'] as String?, 23 | buyerName: json['buyer_name'] as String?, 24 | buyerTel: json['buyer_tel'] as String, 25 | buyerEmail: json['buyer_email'] as String?, 26 | buyerAddr: json['buyer_addr'] as String?, 27 | buyerPostcode: json['buyer_postcode'] as String?, 28 | noticeUrl: json['notice_url'] as String?, 29 | cardQuota: PaymentData._cardQuotaFromJson( 30 | json['display'] as Map), 31 | digital: json['digital'] as bool?, 32 | vbankDue: json['vbank_due'] as String?, 33 | confirmUrl: json['confirm_url'] as String?, 34 | mRedirectUrl: json['m_redirect_url'] as String? ?? UrlData.redirectUrl, 35 | appScheme: json['app_scheme'] as String, 36 | bizNum: json['biz_num'] as String?, 37 | customerId: json['customer_id'] as String?, 38 | customerUid: json['customer_uid'] as String?, 39 | popup: json['popup'] as bool?, 40 | naverUseCfm: json['naverUseCfm'] as String?, 41 | naverPopupMode: json['naverPopupMode'] as bool?, 42 | naverProducts: (json['naverProducts'] as List?) 43 | ?.map((e) => NaverProducts.fromJson(e as Map)) 44 | .toList(), 45 | naverCultureBenefit: json['naverCultureBenefit'] as bool?, 46 | naverProductCode: json['naverProductCode'] as String?, 47 | naverActionType: json['naverActionType'] as String?, 48 | naverPurchaserName: json['naverPurchaserName'] as String?, 49 | naverPurchaserBirthday: json['naverPurchaserBirthday'] as String?, 50 | naverChainId: json['naverChainId'] as String?, 51 | naverMerchantUserKey: json['naverMerchantUserKey'] as String?, 52 | naverInterface: json['naverInterface'] == null 53 | ? null 54 | : NaverInterface.fromJson( 55 | json['naverInterface'] as Map), 56 | period: (json['period'] as Map?)?.map( 57 | (k, e) => MapEntry(k, e as String), 58 | ), 59 | company: json['company'] as String?, 60 | niceMobileV2: json['niceMobileV2'] as bool?, 61 | kcpProducts: (json['kcpProducts'] as List?) 62 | ?.map((e) => KcpProducts.fromJson(e as Map)) 63 | .toList(), 64 | bypass: json['bypass'] == null 65 | ? null 66 | : Bypass.fromJson(json['bypass'] as Map), 67 | ); 68 | 69 | Map _$PaymentDataToJson(PaymentData instance) { 70 | final val = {}; 71 | 72 | void writeNotNull(String key, dynamic value) { 73 | if (value != null) { 74 | val[key] = value; 75 | } 76 | } 77 | 78 | writeNotNull('pg', instance.pg); 79 | val['pay_method'] = instance.payMethod; 80 | writeNotNull('escrow', instance.escrow); 81 | val['merchant_uid'] = instance.merchantUid; 82 | writeNotNull('name', instance.name); 83 | val['amount'] = instance.amount; 84 | writeNotNull('custom_data', instance.customData); 85 | writeNotNull('tax_free', instance.taxFree); 86 | writeNotNull('vat', instance.vat); 87 | writeNotNull('currency', instance.currency); 88 | writeNotNull('language', instance.language); 89 | writeNotNull('buyer_name', instance.buyerName); 90 | val['buyer_tel'] = instance.buyerTel; 91 | writeNotNull('buyer_email', instance.buyerEmail); 92 | writeNotNull('buyer_addr', instance.buyerAddr); 93 | writeNotNull('buyer_postcode', instance.buyerPostcode); 94 | writeNotNull('notice_url', instance.noticeUrl); 95 | writeNotNull('display', PaymentData._cardQuotaToJson(instance.cardQuota)); 96 | writeNotNull('digital', instance.digital); 97 | writeNotNull('vbank_due', instance.vbankDue); 98 | writeNotNull('confirm_url', instance.confirmUrl); 99 | writeNotNull('m_redirect_url', instance.mRedirectUrl); 100 | val['app_scheme'] = instance.appScheme; 101 | writeNotNull('biz_num', instance.bizNum); 102 | writeNotNull('customer_id', instance.customerId); 103 | writeNotNull('customer_uid', instance.customerUid); 104 | writeNotNull('popup', instance.popup); 105 | writeNotNull('naverUseCfm', instance.naverUseCfm); 106 | writeNotNull('naverPopupMode', instance.naverPopupMode); 107 | writeNotNull('naverProducts', instance.naverProducts); 108 | writeNotNull('naverCultureBenefit', instance.naverCultureBenefit); 109 | writeNotNull('naverProductCode', instance.naverProductCode); 110 | writeNotNull('naverActionType', instance.naverActionType); 111 | writeNotNull('naverPurchaserName', instance.naverPurchaserName); 112 | writeNotNull('naverPurchaserBirthday', instance.naverPurchaserBirthday); 113 | writeNotNull('naverChainId', instance.naverChainId); 114 | writeNotNull('naverMerchantUserKey', instance.naverMerchantUserKey); 115 | writeNotNull('naverInterface', instance.naverInterface); 116 | writeNotNull('period', instance.period); 117 | writeNotNull('company', instance.company); 118 | writeNotNull('niceMobileV2', instance.niceMobileV2); 119 | writeNotNull('kcpProducts', instance.kcpProducts); 120 | writeNotNull('bypass', instance.bypass); 121 | return val; 122 | } 123 | -------------------------------------------------------------------------------- /lib/model/pg/bypass.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/pg/danal/danal.dart'; 2 | import 'package:portone_flutter/model/pg/daou/daou.dart'; 3 | import 'package:portone_flutter/model/pg/tosspayments/tosspayments.dart'; 4 | import 'package:portone_flutter/model/pg/settle/settle.dart'; 5 | import 'package:json_annotation/json_annotation.dart'; 6 | 7 | part 'bypass.g.dart'; 8 | 9 | @JsonSerializable() 10 | class Bypass { 11 | bool? isCulturalExpense; 12 | String? cashReceiptType; 13 | 14 | // https://guide.portone.io/aad7f7a4-366b-46fc-8e59-f1d79c986b3e 15 | @JsonKey(name: "acceptmethod") 16 | String? acceptMethod; 17 | @JsonKey(name: "P_RESERVED") 18 | String? pReserved; 19 | 20 | Daou? daou; 21 | Tosspayments? tosspayments; 22 | Settle? settle; 23 | Danal? danal; 24 | 25 | Bypass({ 26 | this.isCulturalExpense, 27 | this.cashReceiptType, 28 | this.acceptMethod, 29 | this.pReserved, 30 | this.daou, 31 | this.tosspayments, 32 | this.settle, 33 | this.danal, 34 | }); 35 | 36 | factory Bypass.fromJson(Map json) => _$BypassFromJson(json); 37 | 38 | Map toJson() => _$BypassToJson(this); 39 | } 40 | -------------------------------------------------------------------------------- /lib/model/pg/bypass.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'bypass.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Bypass _$BypassFromJson(Map json) => Bypass( 10 | isCulturalExpense: json['isCulturalExpense'] as bool?, 11 | cashReceiptType: json['cashReceiptType'] as String?, 12 | acceptMethod: json['acceptmethod'] as String?, 13 | pReserved: json['P_RESERVED'] as String?, 14 | daou: json['daou'] == null 15 | ? null 16 | : Daou.fromJson(json['daou'] as Map), 17 | tosspayments: json['tosspayments'] == null 18 | ? null 19 | : Tosspayments.fromJson(json['tosspayments'] as Map), 20 | settle: json['settle'] == null 21 | ? null 22 | : Settle.fromJson(json['settle'] as Map), 23 | danal: json['danal'] == null 24 | ? null 25 | : Danal.fromJson(json['danal'] as Map), 26 | ); 27 | 28 | Map _$BypassToJson(Bypass instance) { 29 | final val = {}; 30 | 31 | void writeNotNull(String key, dynamic value) { 32 | if (value != null) { 33 | val[key] = value; 34 | } 35 | } 36 | 37 | writeNotNull('isCulturalExpense', instance.isCulturalExpense); 38 | writeNotNull('cashReceiptType', instance.cashReceiptType); 39 | writeNotNull('acceptmethod', instance.acceptMethod); 40 | writeNotNull('P_RESERVED', instance.pReserved); 41 | writeNotNull('daou', instance.daou); 42 | writeNotNull('tosspayments', instance.tosspayments); 43 | writeNotNull('settle', instance.settle); 44 | writeNotNull('danal', instance.danal); 45 | return val; 46 | } 47 | -------------------------------------------------------------------------------- /lib/model/pg/danal/danal.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'danal.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Danal { 7 | // https://guide.portone.io/aad7f7a4-366b-46fc-8e59-f1d79c986b3e 8 | @JsonKey(name: "ISCASHRECEIPTUI") 9 | String? isCashReceiptUi; 10 | 11 | Danal({ 12 | this.isCashReceiptUi, 13 | }); 14 | 15 | factory Danal.fromJson(Map json) => _$DanalFromJson(json); 16 | 17 | Map toJson() => _$DanalToJson(this); 18 | } 19 | -------------------------------------------------------------------------------- /lib/model/pg/danal/danal.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'danal.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Danal _$DanalFromJson(Map json) => Danal( 10 | isCashReceiptUi: json['ISCASHRECEIPTUI'] as String?, 11 | ); 12 | 13 | Map _$DanalToJson(Danal instance) { 14 | final val = {}; 15 | 16 | void writeNotNull(String key, dynamic value) { 17 | if (value != null) { 18 | val[key] = value; 19 | } 20 | } 21 | 22 | writeNotNull('ISCASHRECEIPTUI', instance.isCashReceiptUi); 23 | return val; 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/pg/daou/daou.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'daou.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Daou { 7 | @JsonKey(name: "PRODUCTCODE") 8 | String? productCode; 9 | 10 | @JsonKey(name: "CASHRECEIPTFLAG") 11 | int cashReceiptFlag; 12 | 13 | Daou({ 14 | this.productCode, 15 | required this.cashReceiptFlag, 16 | }); 17 | 18 | factory Daou.fromJson(Map json) => _$DaouFromJson(json); 19 | 20 | Map toJson() => _$DaouToJson(this); 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/pg/daou/daou.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'daou.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Daou _$DaouFromJson(Map json) => Daou( 10 | productCode: json['PRODUCTCODE'] as String?, 11 | cashReceiptFlag: json['CASHRECEIPTFLAG'] as int, 12 | ); 13 | 14 | Map _$DaouToJson(Daou instance) { 15 | final val = {}; 16 | 17 | void writeNotNull(String key, dynamic value) { 18 | if (value != null) { 19 | val[key] = value; 20 | } 21 | } 22 | 23 | writeNotNull('PRODUCTCODE', instance.productCode); 24 | val['CASHRECEIPTFLAG'] = instance.cashReceiptFlag; 25 | return val; 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/pg/kcp/kcp_products.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'kcp_products.g.dart'; 4 | 5 | @JsonSerializable() 6 | class KcpProducts { 7 | String orderNumber; 8 | String name; 9 | int quantity; 10 | int amount; 11 | 12 | KcpProducts({ 13 | required this.orderNumber, 14 | required this.name, 15 | required this.quantity, 16 | required this.amount, 17 | }); 18 | 19 | factory KcpProducts.fromJson(Map json) => 20 | _$KcpProductsFromJson(json); 21 | 22 | Map toJson() => _$KcpProductsToJson(this); 23 | } 24 | -------------------------------------------------------------------------------- /lib/model/pg/kcp/kcp_products.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'kcp_products.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | KcpProducts _$KcpProductsFromJson(Map json) => KcpProducts( 10 | orderNumber: json['orderNumber'] as String, 11 | name: json['name'] as String, 12 | quantity: json['quantity'] as int, 13 | amount: json['amount'] as int, 14 | ); 15 | 16 | Map _$KcpProductsToJson(KcpProducts instance) => 17 | { 18 | 'orderNumber': instance.orderNumber, 19 | 'name': instance.name, 20 | 'quantity': instance.quantity, 21 | 'amount': instance.amount, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_co_products.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/pg/naver/naver_products.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'naver_co_products.g.dart'; 5 | 6 | @JsonSerializable() 7 | class NaverCoProducts implements NaverProducts { 8 | String? id; 9 | String? merchantProductId; 10 | String? ecMallProductId; 11 | String? name; 12 | int? basePrice; 13 | String? taxType; 14 | int? quantity; 15 | String? infoUrl; 16 | String? imageUrl; 17 | String? giftName; 18 | List? options; 19 | NaverCoShipping? shipping; 20 | List? supplements; 21 | 22 | NaverCoProducts({ 23 | this.id, 24 | this.merchantProductId, 25 | this.ecMallProductId, 26 | this.name, 27 | this.basePrice, 28 | this.taxType, 29 | this.quantity, 30 | this.infoUrl, 31 | this.imageUrl, 32 | this.giftName, 33 | this.options, 34 | this.shipping, 35 | this.supplements, 36 | }); 37 | 38 | factory NaverCoProducts.fromJson(Map json) => 39 | _$NaverCoProductsFromJson(json); 40 | 41 | Map toJson() => _$NaverCoProductsToJson(this); 42 | } 43 | 44 | @JsonSerializable() 45 | class NaverCoOption { 46 | int? optionQuantity; 47 | int? optionPrice; 48 | String? selectionCode; 49 | List? selections; 50 | 51 | NaverCoOption({ 52 | this.optionQuantity, 53 | this.optionPrice, 54 | this.selectionCode, 55 | this.selections, 56 | }); 57 | 58 | factory NaverCoOption.fromJson(Map json) => 59 | _$NaverCoOptionFromJson(json); 60 | 61 | Map toJson() => _$NaverCoOptionToJson(this); 62 | } 63 | 64 | @JsonSerializable() 65 | class NaverCoOptionItem { 66 | String? code; 67 | String? label; 68 | String? value; 69 | 70 | NaverCoOptionItem({ 71 | this.code, 72 | this.label, 73 | this.value, 74 | }); 75 | 76 | factory NaverCoOptionItem.fromJson(Map json) => 77 | _$NaverCoOptionItemFromJson(json); 78 | 79 | Map toJson() => _$NaverCoOptionItemToJson(this); 80 | } 81 | 82 | @JsonSerializable() 83 | class NaverCoSupplement { 84 | String? id; 85 | String? name; 86 | int? price; 87 | int? quantity; 88 | 89 | NaverCoSupplement({ 90 | this.id, 91 | this.name, 92 | this.price, 93 | this.quantity, 94 | }); 95 | 96 | factory NaverCoSupplement.fromJson(Map json) => 97 | _$NaverCoSupplementFromJson(json); 98 | 99 | Map toJson() => _$NaverCoSupplementToJson(this); 100 | } 101 | 102 | @JsonSerializable() 103 | class NaverCoShipping { 104 | String? groupId; 105 | String? method; 106 | int? baseFee; 107 | String? feePayType; 108 | NaverCoFeeRule? feeRule; 109 | 110 | NaverCoShipping({ 111 | this.groupId, 112 | this.method, 113 | this.baseFee, 114 | this.feePayType, 115 | this.feeRule, 116 | }); 117 | 118 | factory NaverCoShipping.fromJson(Map json) => 119 | _$NaverCoShippingFromJson(json); 120 | 121 | Map toJson() => _$NaverCoShippingToJson(this); 122 | } 123 | 124 | @JsonSerializable() 125 | class NaverCoFeeRule { 126 | int? freeByThreshold; 127 | int? repeatByQty; 128 | List? rangesByQty; 129 | List? surchargesByArea; 130 | 131 | NaverCoFeeRule({ 132 | this.freeByThreshold, 133 | this.repeatByQty, 134 | this.rangesByQty, 135 | this.surchargesByArea, 136 | }); 137 | 138 | factory NaverCoFeeRule.fromJson(Map json) => 139 | _$NaverCoFeeRuleFromJson(json); 140 | 141 | Map toJson() => _$NaverCoFeeRuleToJson(this); 142 | } 143 | 144 | @JsonSerializable() 145 | class NaverCoFeeRangeByQty { 146 | int? from; 147 | int? surcharge; 148 | 149 | NaverCoFeeRangeByQty({ 150 | this.from, 151 | this.surcharge, 152 | }); 153 | 154 | factory NaverCoFeeRangeByQty.fromJson(Map json) => 155 | _$NaverCoFeeRangeByQtyFromJson(json); 156 | 157 | Map toJson() => _$NaverCoFeeRangeByQtyToJson(this); 158 | } 159 | 160 | @JsonSerializable() 161 | class NaverCoFeeAreaByQty { 162 | List? from; 163 | int? surcharge; 164 | 165 | NaverCoFeeAreaByQty({ 166 | this.from, 167 | this.surcharge, 168 | }); 169 | 170 | factory NaverCoFeeAreaByQty.fromJson(Map json) => 171 | _$NaverCoFeeAreaByQtyFromJson(json); 172 | 173 | Map toJson() => _$NaverCoFeeAreaByQtyToJson(this); 174 | } 175 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_co_products.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'naver_co_products.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | NaverCoProducts _$NaverCoProductsFromJson(Map json) => 10 | NaverCoProducts( 11 | id: json['id'] as String?, 12 | merchantProductId: json['merchantProductId'] as String?, 13 | ecMallProductId: json['ecMallProductId'] as String?, 14 | name: json['name'] as String?, 15 | basePrice: json['basePrice'] as int?, 16 | taxType: json['taxType'] as String?, 17 | quantity: json['quantity'] as int?, 18 | infoUrl: json['infoUrl'] as String?, 19 | imageUrl: json['imageUrl'] as String?, 20 | giftName: json['giftName'] as String?, 21 | options: (json['options'] as List?) 22 | ?.map((e) => NaverCoOption.fromJson(e as Map)) 23 | .toList(), 24 | shipping: json['shipping'] == null 25 | ? null 26 | : NaverCoShipping.fromJson(json['shipping'] as Map), 27 | supplements: (json['supplements'] as List?) 28 | ?.map((e) => NaverCoSupplement.fromJson(e as Map)) 29 | .toList(), 30 | ); 31 | 32 | Map _$NaverCoProductsToJson(NaverCoProducts instance) { 33 | final val = {}; 34 | 35 | void writeNotNull(String key, dynamic value) { 36 | if (value != null) { 37 | val[key] = value; 38 | } 39 | } 40 | 41 | writeNotNull('id', instance.id); 42 | writeNotNull('merchantProductId', instance.merchantProductId); 43 | writeNotNull('ecMallProductId', instance.ecMallProductId); 44 | writeNotNull('name', instance.name); 45 | writeNotNull('basePrice', instance.basePrice); 46 | writeNotNull('taxType', instance.taxType); 47 | writeNotNull('quantity', instance.quantity); 48 | writeNotNull('infoUrl', instance.infoUrl); 49 | writeNotNull('imageUrl', instance.imageUrl); 50 | writeNotNull('giftName', instance.giftName); 51 | writeNotNull('options', instance.options); 52 | writeNotNull('shipping', instance.shipping); 53 | writeNotNull('supplements', instance.supplements); 54 | return val; 55 | } 56 | 57 | NaverCoOption _$NaverCoOptionFromJson(Map json) => 58 | NaverCoOption( 59 | optionQuantity: json['optionQuantity'] as int?, 60 | optionPrice: json['optionPrice'] as int?, 61 | selectionCode: json['selectionCode'] as String?, 62 | selections: (json['selections'] as List?) 63 | ?.map((e) => NaverCoOptionItem.fromJson(e as Map)) 64 | .toList(), 65 | ); 66 | 67 | Map _$NaverCoOptionToJson(NaverCoOption instance) { 68 | final val = {}; 69 | 70 | void writeNotNull(String key, dynamic value) { 71 | if (value != null) { 72 | val[key] = value; 73 | } 74 | } 75 | 76 | writeNotNull('optionQuantity', instance.optionQuantity); 77 | writeNotNull('optionPrice', instance.optionPrice); 78 | writeNotNull('selectionCode', instance.selectionCode); 79 | writeNotNull('selections', instance.selections); 80 | return val; 81 | } 82 | 83 | NaverCoOptionItem _$NaverCoOptionItemFromJson(Map json) => 84 | NaverCoOptionItem( 85 | code: json['code'] as String?, 86 | label: json['label'] as String?, 87 | value: json['value'] as String?, 88 | ); 89 | 90 | Map _$NaverCoOptionItemToJson(NaverCoOptionItem instance) { 91 | final val = {}; 92 | 93 | void writeNotNull(String key, dynamic value) { 94 | if (value != null) { 95 | val[key] = value; 96 | } 97 | } 98 | 99 | writeNotNull('code', instance.code); 100 | writeNotNull('label', instance.label); 101 | writeNotNull('value', instance.value); 102 | return val; 103 | } 104 | 105 | NaverCoSupplement _$NaverCoSupplementFromJson(Map json) => 106 | NaverCoSupplement( 107 | id: json['id'] as String?, 108 | name: json['name'] as String?, 109 | price: json['price'] as int?, 110 | quantity: json['quantity'] as int?, 111 | ); 112 | 113 | Map _$NaverCoSupplementToJson(NaverCoSupplement instance) { 114 | final val = {}; 115 | 116 | void writeNotNull(String key, dynamic value) { 117 | if (value != null) { 118 | val[key] = value; 119 | } 120 | } 121 | 122 | writeNotNull('id', instance.id); 123 | writeNotNull('name', instance.name); 124 | writeNotNull('price', instance.price); 125 | writeNotNull('quantity', instance.quantity); 126 | return val; 127 | } 128 | 129 | NaverCoShipping _$NaverCoShippingFromJson(Map json) => 130 | NaverCoShipping( 131 | groupId: json['groupId'] as String?, 132 | method: json['method'] as String?, 133 | baseFee: json['baseFee'] as int?, 134 | feePayType: json['feePayType'] as String?, 135 | feeRule: json['feeRule'] == null 136 | ? null 137 | : NaverCoFeeRule.fromJson(json['feeRule'] as Map), 138 | ); 139 | 140 | Map _$NaverCoShippingToJson(NaverCoShipping instance) { 141 | final val = {}; 142 | 143 | void writeNotNull(String key, dynamic value) { 144 | if (value != null) { 145 | val[key] = value; 146 | } 147 | } 148 | 149 | writeNotNull('groupId', instance.groupId); 150 | writeNotNull('method', instance.method); 151 | writeNotNull('baseFee', instance.baseFee); 152 | writeNotNull('feePayType', instance.feePayType); 153 | writeNotNull('feeRule', instance.feeRule); 154 | return val; 155 | } 156 | 157 | NaverCoFeeRule _$NaverCoFeeRuleFromJson(Map json) => 158 | NaverCoFeeRule( 159 | freeByThreshold: json['freeByThreshold'] as int?, 160 | repeatByQty: json['repeatByQty'] as int?, 161 | rangesByQty: (json['rangesByQty'] as List?) 162 | ?.map((e) => NaverCoFeeRangeByQty.fromJson(e as Map)) 163 | .toList(), 164 | surchargesByArea: (json['surchargesByArea'] as List?) 165 | ?.map((e) => NaverCoFeeAreaByQty.fromJson(e as Map)) 166 | .toList(), 167 | ); 168 | 169 | Map _$NaverCoFeeRuleToJson(NaverCoFeeRule instance) { 170 | final val = {}; 171 | 172 | void writeNotNull(String key, dynamic value) { 173 | if (value != null) { 174 | val[key] = value; 175 | } 176 | } 177 | 178 | writeNotNull('freeByThreshold', instance.freeByThreshold); 179 | writeNotNull('repeatByQty', instance.repeatByQty); 180 | writeNotNull('rangesByQty', instance.rangesByQty); 181 | writeNotNull('surchargesByArea', instance.surchargesByArea); 182 | return val; 183 | } 184 | 185 | NaverCoFeeRangeByQty _$NaverCoFeeRangeByQtyFromJson( 186 | Map json) => 187 | NaverCoFeeRangeByQty( 188 | from: json['from'] as int?, 189 | surcharge: json['surcharge'] as int?, 190 | ); 191 | 192 | Map _$NaverCoFeeRangeByQtyToJson( 193 | NaverCoFeeRangeByQty instance) { 194 | final val = {}; 195 | 196 | void writeNotNull(String key, dynamic value) { 197 | if (value != null) { 198 | val[key] = value; 199 | } 200 | } 201 | 202 | writeNotNull('from', instance.from); 203 | writeNotNull('surcharge', instance.surcharge); 204 | return val; 205 | } 206 | 207 | NaverCoFeeAreaByQty _$NaverCoFeeAreaByQtyFromJson(Map json) => 208 | NaverCoFeeAreaByQty( 209 | from: (json['from'] as List?)?.map((e) => e as String).toList(), 210 | surcharge: json['surcharge'] as int?, 211 | ); 212 | 213 | Map _$NaverCoFeeAreaByQtyToJson(NaverCoFeeAreaByQty instance) { 214 | final val = {}; 215 | 216 | void writeNotNull(String key, dynamic value) { 217 | if (value != null) { 218 | val[key] = value; 219 | } 220 | } 221 | 222 | writeNotNull('from', instance.from); 223 | writeNotNull('surcharge', instance.surcharge); 224 | return val; 225 | } 226 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'naver_interface.g.dart'; 4 | 5 | @JsonSerializable() 6 | class NaverInterface { 7 | String? cpaInflowCode; 8 | String? naverInflowCode; 9 | String? saClickId; 10 | String? merchantCustomCode1; 11 | String? merchantCustomCode2; 12 | 13 | NaverInterface({ 14 | this.cpaInflowCode, 15 | this.naverInflowCode, 16 | this.saClickId, 17 | this.merchantCustomCode1, 18 | this.merchantCustomCode2, 19 | }); 20 | 21 | factory NaverInterface.fromJson(Map json) => 22 | _$NaverInterfaceFromJson(json); 23 | 24 | Map toJson() => _$NaverInterfaceToJson(this); 25 | } 26 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_interface.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'naver_interface.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | NaverInterface _$NaverInterfaceFromJson(Map json) => 10 | NaverInterface( 11 | cpaInflowCode: json['cpaInflowCode'] as String?, 12 | naverInflowCode: json['naverInflowCode'] as String?, 13 | saClickId: json['saClickId'] as String?, 14 | merchantCustomCode1: json['merchantCustomCode1'] as String?, 15 | merchantCustomCode2: json['merchantCustomCode2'] as String?, 16 | ); 17 | 18 | Map _$NaverInterfaceToJson(NaverInterface instance) { 19 | final val = {}; 20 | 21 | void writeNotNull(String key, dynamic value) { 22 | if (value != null) { 23 | val[key] = value; 24 | } 25 | } 26 | 27 | writeNotNull('cpaInflowCode', instance.cpaInflowCode); 28 | writeNotNull('naverInflowCode', instance.naverInflowCode); 29 | writeNotNull('saClickId', instance.saClickId); 30 | writeNotNull('merchantCustomCode1', instance.merchantCustomCode1); 31 | writeNotNull('merchantCustomCode2', instance.merchantCustomCode2); 32 | return val; 33 | } 34 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_pay_products.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/pg/naver/naver_products.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'naver_pay_products.g.dart'; 5 | 6 | @JsonSerializable() 7 | class NaverPayProducts implements NaverProducts { 8 | String categoryType; 9 | String categoryId; 10 | String uid; 11 | String name; 12 | int count; 13 | String? payReferrer; 14 | 15 | NaverPayProducts({ 16 | required this.categoryType, 17 | required this.categoryId, 18 | required this.uid, 19 | required this.name, 20 | required this.count, 21 | this.payReferrer, 22 | }); 23 | 24 | factory NaverPayProducts.fromJson(Map json) => 25 | _$NaverPayProductsFromJson(json); 26 | 27 | Map toJson() => _$NaverPayProductsToJson(this); 28 | } 29 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_pay_products.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'naver_pay_products.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | NaverPayProducts _$NaverPayProductsFromJson(Map json) => 10 | NaverPayProducts( 11 | categoryType: json['categoryType'] as String, 12 | categoryId: json['categoryId'] as String, 13 | uid: json['uid'] as String, 14 | name: json['name'] as String, 15 | count: json['count'] as int, 16 | payReferrer: json['payReferrer'] as String?, 17 | ); 18 | 19 | Map _$NaverPayProductsToJson(NaverPayProducts instance) { 20 | final val = { 21 | 'categoryType': instance.categoryType, 22 | 'categoryId': instance.categoryId, 23 | 'uid': instance.uid, 24 | 'name': instance.name, 25 | 'count': instance.count, 26 | }; 27 | 28 | void writeNotNull(String key, dynamic value) { 29 | if (value != null) { 30 | val[key] = value; 31 | } 32 | } 33 | 34 | writeNotNull('payReferrer', instance.payReferrer); 35 | return val; 36 | } 37 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_products.dart: -------------------------------------------------------------------------------- 1 | import 'package:portone_flutter/model/pg/naver/naver_co_products.dart'; 2 | import 'package:portone_flutter/model/pg/naver/naver_pay_products.dart'; 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | part 'naver_products.g.dart'; 6 | 7 | @JsonSerializable(createFactory: false) 8 | abstract class NaverProducts { 9 | NaverProducts(); 10 | 11 | factory NaverProducts.fromJson(Map json) { 12 | if (json['id'] != null) { 13 | return NaverCoProducts.fromJson(json); 14 | } else { 15 | return NaverPayProducts.fromJson(json); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/model/pg/naver/naver_products.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'naver_products.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Map _$NaverProductsToJson(NaverProducts instance) => 10 | {}; 11 | -------------------------------------------------------------------------------- /lib/model/pg/settle/settle.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'settle.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Settle { 7 | @JsonKey(name: "criPsblYn") // bypass.settle.criPsblYn 8 | String? cashReceiptType; // Y or N (default "Y") 9 | 10 | Settle({ 11 | this.cashReceiptType, 12 | }); 13 | 14 | factory Settle.fromJson(Map json) => _$SettleFromJson(json); 15 | 16 | Map toJson() => _$SettleToJson(this); 17 | } 18 | -------------------------------------------------------------------------------- /lib/model/pg/settle/settle.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'settle.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Settle _$SettleFromJson(Map json) => Settle( 10 | cashReceiptType: json['criPsblYn'] as String?, 11 | ); 12 | 13 | Map _$SettleToJson(Settle instance) { 14 | final val = {}; 15 | 16 | void writeNotNull(String key, dynamic value) { 17 | if (value != null) { 18 | val[key] = value; 19 | } 20 | } 21 | 22 | writeNotNull('criPsblYn', instance.cashReceiptType); 23 | return val; 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/pg/tosspayments/tosspayments.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'tosspayments.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Tosspayments { 7 | bool? useInternationalCardOnly; 8 | String? discountCode; 9 | 10 | Tosspayments({ 11 | this.useInternationalCardOnly, 12 | this.discountCode, 13 | }); 14 | 15 | factory Tosspayments.fromJson(Map json) => 16 | _$TosspaymentsFromJson(json); 17 | 18 | Map toJson() => _$TosspaymentsToJson(this); 19 | } 20 | -------------------------------------------------------------------------------- /lib/model/pg/tosspayments/tosspayments.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'tosspayments.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Tosspayments _$TosspaymentsFromJson(Map json) => Tosspayments( 10 | useInternationalCardOnly: json['useInternationalCardOnly'] as bool?, 11 | discountCode: json['discountCode'] as String?, 12 | ); 13 | 14 | Map _$TosspaymentsToJson(Tosspayments instance) { 15 | final val = {}; 16 | 17 | void writeNotNull(String key, dynamic value) { 18 | if (value != null) { 19 | val[key] = value; 20 | } 21 | } 22 | 23 | writeNotNull('useInternationalCardOnly', instance.useInternationalCardOnly); 24 | writeNotNull('discountCode', instance.discountCode); 25 | return val; 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/url_data.dart: -------------------------------------------------------------------------------- 1 | class UrlData { 2 | static const String redirectUrl = 3 | 'http://detectchangingwebview/iamport/f'; // f는 flutter의 f 4 | static const String ANDROID_MARKET_PREFIX = 'market://details?id='; 5 | static const String IOS_MARKET_PREFIX = 'itms-apps://itunes.apple.com/app/'; 6 | 7 | static const String ISP = 'ispmobile'; 8 | static const String PACKAGE_ISP = 'kvp.jjy.MispAndroid320'; 9 | 10 | static const String BANKPAY = 'kftc-bankpay'; 11 | static const String PACKAGE_BANKPAY = 'com.kftc.bankpay.android'; 12 | 13 | static const String KB_BANKPAY = 'kb-bankpay'; 14 | static const String PACKAGE_KB_BANKPAY = 'com.kbstar.liivbank'; 15 | 16 | static const String NH_BANKPAY = 'nhb-bankpay'; 17 | static const String PACKAGE_NH_BANKPAY = 'com.nh.cashcardapp'; 18 | 19 | static const String MG_BANKPAY = 'mg-bankpay'; 20 | static const String PACKAGE_MG_BANKPAY = 'kr.co.kfcc.mobilebank'; 21 | 22 | static const String KN_BANKPAY = 'kn-bankpay'; 23 | static const String PACKAGE_KN_BANKPAY = 'com.knb.psb'; 24 | 25 | static const String KAKAOPAY = 'kakaotalk'; 26 | static const String PACKAGE_KAKAOPAY = 'com.kakao.talk'; 27 | 28 | static const String SMILEPAY = 'smilepayapp'; 29 | static const String PACKAGE_SMILEPAY = 'com.mysmilepay.app'; 30 | static const String SMILEPAY_BASE_URL = "https://www.mysmilepay.com/"; 31 | 32 | static const String CHAIPAY = 'chaipayment'; 33 | static const String PACKAGE_CHAIPAY = 'finance.chai.app'; 34 | 35 | static const String PAYCO = 'payco'; 36 | static const String PACKAGE_PAYCO = 'com.nhnent.payapp'; 37 | 38 | static const String HYUNDAICARD = 'hdcardappcardansimclick'; 39 | static const String PACKAGE_HYUNDAICARD = 'com.hyundaicard.appcard'; 40 | 41 | static const String TOSS = 'supertoss'; 42 | static const String PACKAGE_TOSS = 'viva.republica.toss'; 43 | 44 | static const String SHINHANCARD = 'shinhan-sr-ansimclick'; 45 | static const String PACKAGE_SHINHANCARD = 'com.shcard.smartpay'; 46 | 47 | static const String HANACARD = 'cloudpay'; 48 | static const String PACKAGE_HANACARD = 'com.hanaskcard.paycla'; 49 | 50 | static const String SAMSUNGCARD = 'mpocket.online.ansimclick'; 51 | static const String PACKAGE_SAMSUNGCARD = 'kr.co.samsungcard.mpocket'; 52 | 53 | static const String KBCARD = 'kb-acp'; 54 | static const String PACKAGE_KBCARD = 'com.kbcard.cxh.appcard'; 55 | 56 | static const String NHCARD = 'nhallonepayansimclick'; 57 | static const String PACKAGE_NHCARD = 'nh.smart.nhallonepay'; 58 | 59 | static const String CITICARD = 'citimobileapp'; 60 | static const String PACKAGE_CITICARD = 'kr.co.citibank.citimobile'; 61 | 62 | static const String LOTTECARD = 'lotteappcard'; 63 | static const String PACKAGE_LOTTECARD = 'com.lcacApp'; 64 | 65 | static const String LPAY = 'lpayapp'; 66 | static const String PACKAGE_LPAY = 'com.lotte.lpay'; 67 | 68 | static const String SSGPAY = 'shinsegaeeasypayment'; 69 | static const String PACKAGE_SSGPAY = 70 | 'com.ssg.serviceapp.android.egiftcertificate'; 71 | 72 | static const String KPAY = 'kpay'; 73 | static const String PACKAGE_KPAY = 'com.inicis.kpay'; 74 | 75 | static const String PAYNOW = 'lguthepay-xpay'; 76 | static const String PACKAGE_PAYNOW = 'com.lguplus.paynow'; 77 | 78 | static const String WOORIWONCARD = 'com.wooricard.smartapp'; 79 | static const String PACKAGE_WOORIWONCARD = 'com.wooricard.smartapp'; 80 | 81 | static const String LPOINT = 'lmslpay'; 82 | static const String PACKAGE_LPOINT = 'com.lottemembers.android'; 83 | 84 | static const String WOORIWONBANK = 'wooribank'; 85 | static const String PACKAGE_WOORIWONBANK = 'com.wooribank.smart.npib'; 86 | 87 | static const String KTFAUTH = 'ktauthexternalcall'; 88 | static const String PACKAGE_KTFAUTH = 'com.kt.ktauth'; 89 | 90 | static const String LGTAUTH = 'upluscorporation'; 91 | static const String PACKAGE_LGTAUTH = 'com.lguplus.smartotp'; 92 | 93 | static const String SKTAUTH = 'tauthlink'; 94 | static const String PACKAGE_SKTAUTH = 'com.sktelecom.tauth'; 95 | 96 | static const String V3_MOBILE_PLUS = 'v3mobileplusweb'; 97 | static const String PACKAGE_V3_MOBILE_PLUS = 'com.ahnlab.v3mobileplus'; 98 | 99 | static const String KBBANK = 'kbbank'; 100 | static const String PACKAGE_KBBANK = 'com.kbstar.kbkank'; 101 | 102 | static const String LIIV_NEXT = 'newliiv'; 103 | static const String PACKAGE_LIIV_NEXT = 'com.kbstar.reboot'; 104 | 105 | static const String NAVER = 'nidlogin'; 106 | static const String PACKAGE_NAVER = 'com.nhn.android.search'; 107 | } 108 | -------------------------------------------------------------------------------- /lib/widget/iamport_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:portone_flutter/widget/iamport_webview.dart'; 3 | 4 | class IamportError extends StatelessWidget { 5 | static final Color failureColor = Color(0xfff5222d); 6 | 7 | final ActionType actionType; 8 | final String? errorMessage; 9 | 10 | IamportError(this.actionType, this.errorMessage); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | String? actionText; 15 | if (actionType == ActionType.auth) { 16 | actionText = '본인인증'; 17 | } else if (actionType == ActionType.payment) { 18 | actionText = '결제'; 19 | } 20 | 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text('포트원 V1 $actionText 결과'), 24 | centerTitle: true, 25 | automaticallyImplyLeading: false, 26 | ), 27 | body: SafeArea( 28 | child: Row( 29 | mainAxisAlignment: MainAxisAlignment.center, 30 | children: [ 31 | Flexible( 32 | child: Column( 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | crossAxisAlignment: CrossAxisAlignment.center, 35 | children: [ 36 | Icon( 37 | Icons.error, 38 | color: failureColor, 39 | size: 200, 40 | ), 41 | Container( 42 | padding: EdgeInsets.symmetric(vertical: 10), 43 | child: Text( 44 | '포트원 V1 $actionText 파라메터 오류', 45 | style: TextStyle( 46 | fontWeight: FontWeight.bold, 47 | fontSize: 20, 48 | ), 49 | ), 50 | ), 51 | Container( 52 | padding: EdgeInsets.fromLTRB(50, 10, 50, 50), 53 | child: Text( 54 | errorMessage!, 55 | style: TextStyle( 56 | height: 1.2, 57 | fontSize: 16, 58 | ), 59 | textAlign: TextAlign.center, 60 | ), 61 | ), 62 | ElevatedButton.icon( 63 | icon: Icon(Icons.arrow_back), 64 | onPressed: () { 65 | Navigator.pop(context); 66 | }, 67 | label: Text( 68 | '돌아가기', 69 | style: TextStyle(fontSize: 16, color: failureColor), 70 | ), 71 | style: ElevatedButton.styleFrom( 72 | elevation: 0, 73 | shadowColor: Colors.transparent, 74 | ), 75 | ), 76 | ], 77 | ), 78 | ), 79 | ], 80 | ), 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/widget/iamport_webview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/gestures.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:portone_flutter/model/iamport_url.dart'; 8 | import 'package:iamport_webview_flutter/iamport_webview_flutter.dart'; 9 | 10 | enum ActionType { auth, payment } 11 | 12 | class IamportWebView extends StatefulWidget { 13 | static final Color primaryColor = Color(0xff344e81); 14 | static final String html = ''' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | '''; 25 | 26 | final ActionType type; 27 | final PreferredSizeWidget? appBar; 28 | final Widget? initialChild; 29 | final ValueSetter executeJS; 30 | final ValueSetter> useQueryData; 31 | final Function isPaymentOver; 32 | final Function customPGAction; 33 | final Set>? gestureRecognizers; 34 | final String? customUserAgent; 35 | 36 | IamportWebView({ 37 | required this.type, 38 | this.appBar, 39 | this.initialChild, 40 | required this.executeJS, 41 | required this.useQueryData, 42 | required this.isPaymentOver, 43 | required this.customPGAction, 44 | this.gestureRecognizers, 45 | this.customUserAgent, 46 | }); 47 | 48 | @override 49 | _IamportWebViewState createState() => _IamportWebViewState(); 50 | } 51 | 52 | class _IamportWebViewState extends State { 53 | late WebViewController _webViewController; 54 | StreamSubscription? _sub; 55 | late int _isWebviewLoaded; 56 | late int _isImpLoaded; 57 | 58 | @override 59 | void initState() { 60 | super.initState(); 61 | _isWebviewLoaded = 0; 62 | _isImpLoaded = 0; 63 | if (Platform.isAndroid) { 64 | WebView.platform = SurfaceAndroidWebView(); 65 | } 66 | if (widget.initialChild != null) { 67 | _isWebviewLoaded++; 68 | } 69 | } 70 | 71 | @override 72 | void dispose() { 73 | super.dispose(); 74 | if (_sub != null) _sub!.cancel(); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return Scaffold( 80 | appBar: widget.appBar, 81 | body: SafeArea( 82 | child: IndexedStack( 83 | index: _isWebviewLoaded, 84 | children: [ 85 | WebView( 86 | initialUrl: 87 | Uri.dataFromString(IamportWebView.html, mimeType: 'text/html') 88 | .toString(), 89 | javascriptMode: JavascriptMode.unrestricted, 90 | gestureRecognizers: widget.gestureRecognizers, 91 | userAgent: widget.customUserAgent, 92 | onWebViewCreated: (controller) { 93 | this._webViewController = controller; 94 | if (widget.type == ActionType.payment) { 95 | // 스마일페이, 나이스 실시간 계좌이체 96 | _sub = widget.customPGAction(this._webViewController); 97 | } 98 | }, 99 | onPageFinished: (String url) { 100 | // 웹뷰 로딩 완료시에 화면 전환 101 | if (_isWebviewLoaded == 1) { 102 | setState(() { 103 | _isWebviewLoaded = 0; 104 | }); 105 | } 106 | // 페이지 로딩 완료시 IMP 코드 실행 107 | if (_isImpLoaded == 0) { 108 | widget.executeJS(this._webViewController); 109 | _isImpLoaded++; 110 | } 111 | }, 112 | navigationDelegate: (request) async { 113 | // print("url: " + request.url); 114 | if (widget.isPaymentOver(request.url)) { 115 | String decodedUrl = Uri.decodeComponent(request.url); 116 | widget.useQueryData(Uri.parse(decodedUrl).queryParameters); 117 | 118 | return NavigationDecision.prevent; 119 | } 120 | 121 | final iamportUrl = IamportUrl(request.url); 122 | if (iamportUrl.isAppLink()) { 123 | // print("appLink: " + iamportUrl.appUrl!); 124 | // 앱 실행 로직을 iamport_url 모듈로 이동 125 | iamportUrl.launchApp(); 126 | return NavigationDecision.prevent; 127 | } 128 | 129 | return NavigationDecision.navigate; 130 | }, 131 | ), 132 | if (_isWebviewLoaded == 1) widget.initialChild!, 133 | ], 134 | ), 135 | ), 136 | ); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /portone_flutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: portone_flutter 2 | description: Plugin that allows Flutter to use PortOne V1 payment and certification functions. 3 | version: 0.12.1 4 | homepage: https://github.com/portone-io/portone_flutter 5 | 6 | environment: 7 | sdk: ">=2.17.0 <4.0.0" 8 | flutter: ">=2.5.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | iamport_webview_flutter: 3.1.0 14 | url_launcher: ^6.0.4 15 | app_links: ^6.1.1 16 | json_annotation: ^4.8.1 17 | 18 | dev_dependencies: 19 | test: ^1.19.0 20 | build_runner: ^2.0.0 21 | json_serializable: ^6.7.1 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://www.dartlang.org/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | # This section identifies this Flutter project as a plugin project. 29 | # The androidPackage and pluginClass identifiers should not ordinarily 30 | # be modified. They are used by the tooling to maintain consistency when 31 | # adding or updating assets for this project. 32 | plugin: 33 | platforms: 34 | android: 35 | package: io.portone.portone_flutter 36 | pluginClass: IamportFlutterPlugin 37 | ios: 38 | pluginClass: IamportFlutterPlugin 39 | 40 | uses-material-design: true 41 | 42 | # To add assets to your plugin package, add an assets section, like this: 43 | assets: 44 | - assets/images/iamport-logo.png 45 | 46 | # 47 | # For details regarding assets in packages, see 48 | # https://flutter.dev/assets-and-images/#from-packages 49 | # 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # To add custom fonts to your plugin package, add a fonts section here, 54 | # in this "flutter" section. Each entry in this list should have a 55 | # "family" key with the font family name, and a "fonts" key with a 56 | # list giving the asset and other descriptors for the font. For 57 | # example: 58 | # fonts: 59 | # - family: Schyler 60 | # fonts: 61 | # - asset: fonts/Schyler-Regular.ttf 62 | # - asset: fonts/Schyler-Italic.ttf 63 | # style: italic 64 | # - family: Trajan Pro 65 | # fonts: 66 | # - asset: fonts/TrajanPro.ttf 67 | # - asset: fonts/TrajanPro_Bold.ttf 68 | # weight: 700 69 | # 70 | # For details regarding fonts in packages, see 71 | # https://flutter.dev/custom-fonts/#from-packages 72 | --------------------------------------------------------------------------------