ReactNative)Expo 인앱결제 (I'mport이용)

2022. 4. 15. 01:39프로그래밍/개인프로젝트

Expo를 이용해서 개발을 진행해왔고 인앱 결제를 구현해보려고 하는데 제약사항이 많았다

I'mport를 이용하면 기능 구현이 가능하다는 정보를 입수했고 문서를 먼저 정독했다

그동안 실제 결제 관련해서 프로젝트를 진행해본 적이 없어서 접근하기가 어려웠지만 테스트 결제는 성공했고

추후에 다시 볼 수 있도록 정리를 할 예정이다 


 

아임포트 홈페이지

 

온라인 비즈니스의 모든 결제를 한곳에서, 아임포트

결제의 시작부터 비즈니스의 성장까지 아임포트와 함께하세요

www.iamport.kr

아임포트 깃허브

 

GitHub - iamport/iamport-react-native: React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다.

React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다. Contribute to iamport/iamport-react-native development by creating an account on GitHub.

github.com


1. 설치하기

$ npm install iamport-react-native --save
$ npm install react-native-webview --save

2. iOS 설정하기

https://github.com/iamport/iamport-react-native/blob/main/manuals/SETTING.md

 

GitHub - iamport/iamport-react-native: React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다.

React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다. Contribute to iamport/iamport-react-native development by creating an account on GitHub.

github.com

 

3. Expo에서 아임포트 연동하기

https://github.com/iamport/iamport-react-native/blob/main/manuals/EXPO.md

 

GitHub - iamport/iamport-react-native: React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다.

React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다. Contribute to iamport/iamport-react-native development by creating an account on GitHub.

github.com

내용 중에 managed 프로젝트 부분만 진행했고 Expo CLI를 설치했을 때 기본 설치되는 폴더구조가  달라서 다른대로 진행했음

(app.json 내부에 icon부분이 없어서 없는 대로 진행했음)

// app.json
{
  "expo": {
    "name": "taptapcard",
    "version": "1.0.0",
    "slug": "taptapcard",
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "bundleIdentifier": "org.name.taptapcard",
      "buildNumber": "1.0.0",
      "infoPlist": {
        "LSApplicationQueriesSchemes": [
          "kftc-bankpay",
          "ispmobile",
          "itms-apps",
          "hdcardappcardansimclick",
          "smhyundaiansimclick",
          "shinhan-sr-ansimclick",
          "smshinhanansimclick",
          "kb-acp",
          "mpocket.online.ansimclick",
          "ansimclickscard",
          "ansimclickipcollect",
          "vguardstart",
          "samsungpay",
          "scardcertiapp",
          "lottesmartpay",
          "lotteappcard",
          "cloudpay",
          "nhappcardansimclick",
          "nonghyupcardansimclick",
          "citispay",
          "citicardappkr",
          "citimobileapp",
          "kakaotalk",
          "payco",
          "chaipayment",
          "kb-auth",
          "hyundaicardappcardid",
          "com.wooricard.wcard",
          "lmslpay",
          "lguthepay-xpay",
          "liivbank",
          "supertoss"
        ],
        "NSAppTransportSecurity": {
          "NSAllowsArbitraryLoads": true,
          "NSAllowsArbitraryLoadsInWebContent": true
        }
      }
    },
    "android": {
      "package": "com.taptapcard",
      "versionCode": 1,
      "intentFilters": [
        {
          "action": "VIEW",
          "category": [
            "DEFAULT",
            "BROWSABLE"
          ],
          "data": {
            "scheme": "taptapScheme"
          }
        }
      ]
    }
  },
  "name": "taptapcard"
}

4. 예제 코드를 다운로드, exampleForExpo 폴더 내부 파일들을 내 프로젝트에 복사

https://github.com/iamport/iamport-react-native/blob/main/manuals/EXAMPLE.md

 

GitHub - iamport/iamport-react-native: React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다.

React Native용 아임포트 일반.결제 및 휴대폰 본인인증 모듈입니다. Contribute to iamport/iamport-react-native development by creating an account on GitHub.

github.com

5. 생성한 파일들?

screen에 해당하는 PaymentTest.js, Payment.js, PaymentResult.js

component에 해당하는 Loading.js, constants.js, Pickers.js utils.js

 

PaymentTest.js

// PaymentTest.js
import React, { useState } from 'react';
import {
  Button,
  FormControl,
  Input,
  ScrollView,
  Stack,
  Switch,
  Text,
} from 'native-base';
import Picker from '../component/paymentComponent/Pickers';
import { PGS, TIER_CODES } from '../component/paymentComponent/constants';
import { getMethods, getQuotas } from '../component/paymentComponent/utils';
// import { IMPConst } from 'lib/module';
import { IMPConst } from 'iamport-react-native';

export default function PaymentTest({ navigation }) {
  const [pg, setPg] = useState('html5_inicis');
  const [tierCode, setTierCode] = useState(undefined);
  const [method, setMethod] = useState('card');
  const [cardQuota, setCardQuota] = useState(0);
  const [merchantUid, setMerchantUid] = useState(`mid_${new Date().getTime()}`);
  const [name, setName] = useState('아임포트 결제데이터분석');
  const [amount, setAmount] = useState('3333');
  const [buyerName, setBuyerName] = useState('최병민');
  const [buyerTel, setBuyerTel] = useState('01012341234');
  const [buyerEmail, setBuyerEmail] = useState('example@example.com');
  const [vbankDue, setVbankDue] = useState('');
  const [bizNum, setBizNum] = useState('');
  const [escrow, setEscrow] = useState(false);
  const [digital, setDigital] = useState(false);

  return (
    <ScrollView>
      <FormControl>
        <Stack>
          <FormControl.Label>PG사</FormControl.Label>
          <Picker
            data={PGS}
            selectedValue={pg}
            onValueChange={(value) => {
              setPg(value);
              const methods = getMethods(value);
              setMethod(methods[0].value);
            }}
          />
        </Stack>
        <Stack>
          <FormControl.Label>티어 코드</FormControl.Label>
          <Picker
            data={TIER_CODES}
            selectedValue={tierCode}
            onValueChange={(value) => setTierCode(value)}
          />
        </Stack>
        <Stack>
          <FormControl.Label>결제수단</FormControl.Label>
          <Picker
            data={getMethods(pg)}
            selectedValue={method}
            onValueChange={(value) => setMethod(value)}
          />
        </Stack>
        {method === 'card' && (
          <Stack>
            <FormControl.Label>할부개월수</FormControl.Label>
            <Picker
              data={getQuotas(pg)}
              selectedValue={cardQuota}
              onValueChange={(value) => setCardQuota(parseInt(value, 10))}
            />
          </Stack>
        )}
        {method === 'vbank' && (
          <Stack>
            <FormControl.Label>입금기한</FormControl.Label>
            <Input
              value={vbankDue}
              onChangeText={(value) => setVbankDue(value)}
            />
          </Stack>
        )}
        {method === 'vbank' && pg === 'danal_tpay' && (
          <Stack>
            <FormControl.Label>사업자번호</FormControl.Label>
            <Input
              value={bizNum}
              keyboardType="number-pad"
              onChangeText={(value) => setBizNum(value)}
            />
          </Stack>
        )}
        {method === 'phone' && (
          <Stack>
            <FormControl.Label>실물컨텐츠</FormControl.Label>
            <Switch
              value={digital}
              onValueChange={(value) => setDigital(value)}
            />
          </Stack>
        )}
        <Stack>
          <FormControl.Label>에스크로</FormControl.Label>
          <Switch value={escrow} onValueChange={(value) => setEscrow(value)} />
        </Stack>
        <Stack>
          <FormControl.Label>주문명</FormControl.Label>
          <Input value={name} onChangeText={(value) => setName(value)} />
        </Stack>
        <Stack>
          <FormControl.Label>결제금액</FormControl.Label>
          <Input
            value={amount}
            keyboardType="number-pad"
            onChangeText={(value) => setAmount(value)}
          />
        </Stack>
        <Stack>
          <FormControl.Label>주문번호</FormControl.Label>
          <Input
            value={merchantUid}
            onChangeText={(value) => setMerchantUid(value)}
          />
        </Stack>
        <Stack>
          <FormControl.Label>이름</FormControl.Label>
          <Input
            value={buyerName}
            onChangeText={(value) => setBuyerName(value)}
          />
        </Stack>
        <Stack>
          <FormControl.Label>전화번호</FormControl.Label>
          <Input
            value={buyerTel}
            keyboardType="number-pad"
            onChangeText={(value) => setBuyerTel(value)}
          />
        </Stack>
        <Stack>
          <FormControl.Label>이메일</FormControl.Label>
          <Input
            value={buyerEmail}
            onChangeText={(value) => setBuyerEmail(value)}
          />
        </Stack>
        <Button
          onPress={() => {
            const data = {
              params: {
                pg,
                pay_method: method,
                currency: undefined,
                notice_url: undefined,
                display: undefined,
                merchant_uid: merchantUid,
                name,
                amount,
                app_scheme: 'exampleformanagedexpo',
                tax_free: undefined,
                buyer_name: buyerName,
                buyer_tel: buyerTel,
                buyer_email: buyerEmail,
                buyer_addr: undefined,
                buyer_postcode: undefined,
                custom_data: undefined,
                vbank_due: undefined,
                popup: undefined,
                digital: undefined,
                language: undefined,
                biz_num: undefined,
                customer_uid: undefined,
                naverPopupMode: undefined,
                naverUseCfm: undefined,
                naverProducts: undefined,
                m_redirect_url: IMPConst.M_REDIRECT_URL,
                escrow,
              },
              tierCode,
            };

            // 신용카드의 경우, 할부기한 추가
            if (method === 'card' && cardQuota !== 0) {
              data.params.display = {
                card_quota: cardQuota === 1 ? [] : [cardQuota],
              };
            }

            // 가상계좌의 경우, 입금기한 추가
            if (method === 'vbank' && vbankDue) {
              data.params.vbank_due = vbankDue;
            }

            // 다날 && 가상계좌의 경우, 사업자 등록번호 10자리 추가
            if (method === 'vbank' && pg === 'danal_tpay') {
              data.params.biz_num = bizNum;
            }

            // 휴대폰 소액결제의 경우, 실물 컨텐츠 여부 추가
            if (method === 'phone') {
              data.params.digital = digital;
            }

            // 정기결제의 경우, customer_uid 추가
            if (pg === 'kcp_billing') {
              data.params.customer_uid = `cuid_${new Date().getTime()}`;
            }

            if (pg === 'naverpay') {
              const today = new Date();
              const oneMonthLater = new Date(
                today.setMonth(today.getMonth() + 1)
              );
              const dd = String(oneMonthLater.getDate()).padStart(2, '0');
              const mm = String(oneMonthLater.getMonth() + 1).padStart(2, '0'); // January is 0!
              const yyyy = oneMonthLater.getFullYear();

              data.params.naverPopupMode = false;
              data.params.naverUseCfm = `${yyyy}${mm}${dd}`;
              data.params.naverProducts = [
                {
                  categoryType: 'BOOK',
                  categoryId: 'GENERAL',
                  uid: '107922211',
                  name: '한국사',
                  payReferrer: 'NAVER_BOOK',
                  count: 10,
                },
              ];
            }

            navigation.navigate('Payment', data);
          }}
        >
          <Text>결제하기</Text>
        </Button>
      </FormControl>
    </ScrollView>
  );
}

Payment.js

//Payment.js
import React from 'react';
import IMP from 'iamport-react-native';
import { getUserCode } from '../component/paymentComponent/utils';
import Loading from './Loading';

export default function Payment({ route, navigation }) {
  const params = route.params.params;
  const tierCode = route.params.tierCode;
  const userCode = getUserCode(params.pg, tierCode);
  {console.log(userCode)}
  return (
    <IMP.Payment
      userCode={userCode}
      tierCode={tierCode}
      loading={<Loading />}
      data={params}
      callback={(response) => navigation.replace('PaymentResult', response)}
    />
  );
}

PaymentResult.js

// PaymentResult.js
import React from 'react';
import {
  ArrowBackIcon,
  CheckCircleIcon,
  IconButton,
  List,
  Text,
  View,
  WarningIcon,
} from 'native-base';

export default function PaymentResult({ route, navigation }) {
  const imp_success = route.params.imp_success;
  const success = route.params.success;
  const imp_uid = route.params.imp_uid;
  const merchant_uid = route.params.merchant_uid;
  const error_msg = route.params.error_msg;

  // [WARNING: 이해를 돕기 위한 것일 뿐, imp_success 또는 success 파라미터로 결제 성공 여부를 장담할 수 없습니다.]
  // 아임포트 서버로 결제내역 조회(GET /payments/${imp_uid})를 통해 그 응답(status)에 따라 결제 성공 여부를 판단하세요.
  const isSuccess = !(
    imp_success === 'false' ||
    imp_success === false ||
    success === 'false' ||
    success === false
  );

  return (
    <View>
      {isSuccess ? <CheckCircleIcon /> : <WarningIcon />}
      <Text>{`결제에 ${isSuccess ? '성공' : '실패'}하였습니다`}</Text>
      <List>
        <List.Item>
          <Text>아임포트 번호</Text>
          <Text>{imp_uid}</Text>
        </List.Item>
        {isSuccess ? (
          <List.Item>
            <Text>주문번호</Text>
            <Text>{merchant_uid}</Text>
          </List.Item>
        ) : (
          <List.Item>
            <Text>에러메시지</Text>
            <Text>{error_msg}</Text>
          </List.Item>
        )}
      </List>
      <IconButton
        icon={<ArrowBackIcon />}
        onPress={() => navigation.navigate('Menu')}
      >
        <Text>돌아가기</Text>
      </IconButton>
      {/* {console.log(route)} */}
      {/* <Text>{imp_success}</Text>
      <Text>{success}</Text>
      <Text>{imp_uid}</Text>
      <Text>{merchant_uid}</Text>
      <Text>{error_msg}</Text> */}
    </View>
  );
}

Loading.js

//Loading.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export function Loading() {
  const { container } = styles;
  return (
    <View style={container}>
      <Text>잠시만 기다려주세요...</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
  },
});

export default Loading;

constants.js

//constants.js
const PGS = [
    {
      value: 'html5_inicis',
      label: '웹 표준 이니시스',
    },
    {
      value: 'kcp',
      label: 'NHN KCP',
    },
    {
      value: 'kcp_billing',
      label: 'NHN KCP 정기결제',
    },
    {
      value: 'uplus',
      label: '토스페이먼츠 - (구)LG 유플러스',
    },
    {
      value: 'jtnet',
      label: 'JTNET',
    },
    {
      value: 'nice',
      label: '나이스 정보통신',
    },
    {
      value: 'kakaopay',
      label: '신 - 카카오페이',
    },
    {
      value: 'kakao',
      label: '구 - LG CNS 카카오페이',
    },
    {
      value: 'danal',
      label: '다날 휴대폰 소액결제',
    },
    {
      value: 'danal_tpay',
      label: '다날 일반결제',
    },
    {
      value: 'kicc',
      label: '한국정보통신',
    },
    {
      value: 'paypal',
      label: '페이팔',
    },
    {
      value: 'mobilians',
      label: '모빌리언스',
    },
    {
      value: 'payco',
      label: '페이코',
    },
    {
      value: 'eximbay',
      label: '엑심베이',
    },
    {
      value: 'settle',
      label: '세틀뱅크 가상계좌',
    },
    {
      value: 'naverco',
      label: '네이버 체크아웃',
    },
    {
      value: 'naverpay',
      label: '네이버페이',
    },
    {
      value: 'smilepay',
      label: '스마일페이',
    },
    {
      value: 'chai',
      label: '차이페이',
    },
    {
      value: 'payple',
      label: '페이플',
    },
    {
      value: 'alipay',
      label: '알리페이',
    },
    {
      value: 'bluewalnut',
      label: '블루월넛',
    },
    {
      value: 'tosspay',
      label: '토스 - 간편결제',
    },
  ];
  
  const TIER_CODES = [
    {
      value: 'ADD',
      label: '아디다스',
    },
    {
      value: 'RBK',
      label: '리복 - 복수PG 사용중',
    },
    {
      value: 'HKT',
      label: '에어텔닷컴 렌트카 테스트',
    },
    {
      value: 'HKK',
      label: 'YES Angel',
    },
    {
      value: 'DGA',
      label: 'CAMPVR대구',
    },
    {
      value: 'SCH',
      label: 'CAMPVR광화문',
    },
    {
      value: 'SNL',
      label: '토즈_선릉점',
    },
    {
      value: '111',
      label: '여기어때',
    },
    {
      value: 'ABC',
      label: '삼성점',
    },
    {
      value: 'XYZ',
      label: '삼성점',
    },
    {
      value: '123',
      label: 'aaaaaaa',
    },
    {
      value: 'AAZ',
      label: '테스트하위가맹',
    },
    {
      value: '001',
      label: '테스트',
    },
    {
      value: 'A01',
      label: '행복쇼핑',
    },
    {
      value: 'T11',
      label: 'test11',
    },
  ];
  
  const METHODS = [
    {
      value: 'card',
      label: '신용카드',
    },
    {
      value: 'vbank',
      label: '가상계좌',
    },
    {
      value: 'trans',
      label: '실시간 계좌이체',
    },
    {
      value: 'phone',
      label: '휴대폰 소액결제',
    },
  ];
  
  const METHODS_FOR_INICIS = METHODS.concat([
    {
      value: 'samsung',
      label: '삼성페이',
    },
    {
      value: 'kapy',
      label: 'KPAY',
    },
    {
      value: 'cultureland',
      label: '문화상품권',
    },
    {
      value: 'smartculture',
      label: '스마트문상',
    },
    {
      value: 'happymoney',
      label: '해피머니',
    },
  ]);
  
  const METHODS_FOR_UPLUS = METHODS.concat([
    {
      value: 'cultureland',
      label: '문화상품권',
    },
    {
      value: 'smartculture',
      label: '스마트문상',
    },
    {
      value: 'booknlife',
      label: '도서상품권',
    },
  ]);
  
  const METHODS_FOR_KCP = METHODS.concat([
    {
      value: 'samsung',
      label: '삼성페이',
    },
  ]);
  
  const METHODS_FOR_MOBILIANS = [
    {
      value: 'card',
      label: '신용카드',
    },
    {
      value: 'phone',
      label: '휴대폰 소액결제',
    },
  ];
  
  const METHOD_FOR_CARD = [
    {
      value: 'card',
      label: '신용카드',
    },
  ];
  
  const METHOD_FOR_PHONE = [
    {
      value: 'phone',
      label: '휴대폰 소액결제',
    },
  ];
  
  const METHOD_FOR_VBANK = [
    {
      value: 'vbank',
      label: '가상계좌',
    },
  ];
  
  const METHOD_FOR_TRANS = [
    {
      value: 'trans',
      label: '실시간 계좌이체',
    },
  ];
  
  const QUOTAS = [
    {
      value: 0,
      label: 'PG사 기본 제공',
    },
    {
      value: 1,
      label: '일시불',
    },
  ];
  
  const CARRIERS = [
    {
      value: 'SKT',
      label: 'SKT',
    },
    {
      value: 'KTF',
      label: 'KT',
    },
    {
      value: 'LGT',
      label: 'LGU+',
    },
    {
      value: 'MVNO',
      label: '알뜰폰',
    },
  ];
  
  export {
    PGS,
    TIER_CODES,
    METHODS,
    METHODS_FOR_INICIS,
    METHODS_FOR_UPLUS,
    METHODS_FOR_KCP,
    METHODS_FOR_MOBILIANS,
    METHOD_FOR_CARD,
    METHOD_FOR_PHONE,
    METHOD_FOR_VBANK,
    METHOD_FOR_TRANS,
    QUOTAS,
    CARRIERS,
  };

Pickers.js

// Pickers.js
import React from 'react';
import { Select } from 'native-base';

export default function Picker(props) {
  return (
    <Select
      selectedValue={props.selectedValue}
      onValueChange={props.onValueChange}
    >
      {props.data.map((e, index) => {
        const { label, value } = e;
        return <Select.Item label={label} value={value} key={index} />;
      })}
    </Select>
  );
}

utils.js

//utils.js
import {
    QUOTAS,
    METHODS,
    METHODS_FOR_KCP,
    METHODS_FOR_UPLUS,
    METHODS_FOR_INICIS,
    METHODS_FOR_MOBILIANS,
    METHOD_FOR_CARD,
    METHOD_FOR_PHONE,
    METHOD_FOR_VBANK,
    METHOD_FOR_TRANS,
  } from './constants';
  
  function getQuotas(pg) {
    switch (pg) {
      case 'html5_inicis':
      case 'kcp': {
        return QUOTAS.concat([
          {
            value: 2,
            label: '2개월',
          },
          {
            value: 3,
            label: '3개월',
          },
          {
            value: 4,
            label: '4개월',
          },
          {
            value: 5,
            label: '5개월',
          },
          {
            value: 6,
            label: '6개월',
          },
        ]);
      }
      default:
        return QUOTAS;
    }
  }
  
  function getMethods(pg) {
    switch (pg) {
      case 'html5_inicis': {
        return METHODS_FOR_INICIS;
      }
      case 'kcp': {
        return METHODS_FOR_KCP;
      }
      case 'kcp_billing':
      case 'kakaopay':
      case 'kakao':
      case 'paypal':
      case 'payco':
      case 'smilepay':
      case 'chai':
      case 'alipay':
      case 'tosspay': {
        return METHOD_FOR_CARD;
      }
      case 'uplus': {
        return METHODS_FOR_UPLUS;
      }
      case 'danal': {
        return METHOD_FOR_PHONE;
      }
      case 'mobilians': {
        return METHODS_FOR_MOBILIANS;
      }
      case 'settle': {
        return METHOD_FOR_VBANK;
      }
      case 'payple': {
        return METHOD_FOR_TRANS;
      }
      default:
        return METHODS;
    }
  }
  
  function getUserCode(pg, tierCode, type = 'payment') {
    if (tierCode) {
      return 'imp19464697';
    }
    if (type === 'certification') {
      return 'imp10391932';
    }
  
    switch (pg) {
      case 'kakao':
        return 'imp19464697';
      case 'paypal':
        return 'imp19464697';
      case 'mobilians':
        return 'imp19464697';
      case 'naverco':
      case 'naverpay':
        return 'imp19464697';
      case 'smilepay':
        return 'imp19464697';
      case 'chai':
        return 'imp19464697';
      case 'alipay':
        return 'imp19464697';
      case 'payple':
        return 'imp19464697';
      default:
        return 'imp19464697';
    }
  }
  
  export { getQuotas, getMethods, getUserCode };

그 외 설정

1. native-base 설치

npm install native-base --save
npm react-native link

1-1. native-base 에러 발생 / 해결

- useTheme 관련 에러 발생

해결법 :

App.js에 native-base를 import해서 전체 컴포넌트를 감싸줌

// App.js
import { NavigationContainer } from '@react-navigation/native';
import * as Font from "expo-font"
import AppLoading from 'expo-app-loading';
import React, { useState, useEffect } from 'react';
import InStack from './app/navigators/InStack';
import OutStack from './app/navigators/OutStack'
import { SafeAreaView } from 'react-native-safe-area-context';
import auth from '@react-native-firebase/auth';

import { NativeBaseProvider } from 'native-base';

export default function App() {
  const [ready, setReady] = useState(false);
  const onFinish = () => setReady(true);
  const[isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(async()=>{
    await auth().onAuthStateChanged((user)=>{
      if(user){
        setIsLoggedIn(true)
      }else{
        setIsLoggedIn(false)
      }
    })
  },[])

  const startLoading = async () =>{
    // 로딩하고 싶은 것들을 담는 공간 
    // (ex. API호출 혹은 정보를 받거나 video요소를 미리 받아놓거나, DB를 미리 열거나, 아이콘을 미리준비)
    await Font.loadAsync({
        "SDChild": require("./app/asset/fonts/SDChildfundkorea.otf")
    })
  };



  if(!ready){
    return (
    <AppLoading
      startAsync={startLoading}
      onFinish={onFinish}
      onError={console.log} />
      // ready가 안되어있으면 AppLoading은 splash screen을 비추도록 강제하고 startAsync를 호출,
      // startAsync가 완료되면 AppLoading은 onFinish함수를 호출, 
      // onFinish는 state를 변경시키고 state가 변경되면 조건문 else에 해당하는 부분을 render한다
    );
  }

  return (
    <NativeBaseProvider>
      <NavigationContainer>
        {ready && (
          <SafeAreaView style={{flex:1}}>
            {isLoggedIn ? <InStack />: <OutStack />}
          </SafeAreaView>
        )}
      </NavigationContainer>
    </NativeBaseProvider>
  )
}

 

- Invariant Violation: requireNativeComponent: "RNSVGPath" was not found in the UIManage

해결법

1. npm install react-native-svg 

2. npm install react-native-svg-transformer.
3. Uninstall your app from mobile.
4. npm start
5. npm run ios / npm run android
If linking required then react-native link react-native-svg.

 

2. 아임포트 관련 설정

2-1 아임포트 가입

2-2 관리자 콘솔로 이동

2-3 시스템 설정 - pg설정으로 가서 테스트 모드 on 한 뒤 전체 저장

2-4 시스템 설정 - 내 정보 탭에서 가맹점 식별코드 복사,  프로젝트의 utils.js 파일로 가서 가맹점 식별코드 넣어줌

 

3. 결제 모듈의 흐름

navigation.navigate("PaymentTest")를 통해 스크린을 이동하게 되면 Payment를 거쳐 PaymentResult로 이동하기 때문에

처음에 버튼을 통해 PaymentTest로 이동시켜주는 작업이 필요하다

 

내 프로젝트에서 스택네비게이터를 담당하는 MenuStack.js에서 각 스크린들을 넣어주고

//MenuStack.js
import React from "react";
import {createNativeStackNavigator} from "@react-navigation/native-stack" 

import Menu from "../screens/Menu";
import WordPlay from "../screens/WordPlay";
import Payment from "../screens/Payment";
import PaymentResult from "../screens/PaymentResult";
import PaymentTest from "../screens/PaymentTest";

const NativeStack = createNativeStackNavigator();

//  Menu와 WordPlay를 Screen으로 가진다, MathPlay 추가할예정
const MenuStack = () => {
    return(
    <NativeStack.Navigator initialRouteName="Menu" screenOptions={{headerShown: false}} >
        <NativeStack.Screen name="Menu" component={Menu} />
        <NativeStack.Screen name="WordPlay" component={WordPlay} />
        
        <NativeStack.Screen name="PaymentTest" component={PaymentTest} />
        <NativeStack.Screen name="Payment" component={Payment} />
        <NativeStack.Screen name="PaymentResult" component={PaymentResult} />
    </NativeStack.Navigator>
    )
}
export default MenuStack;

PaymentTest로 스크린을 이동시키는 버튼은 화면을 담당하는 WordPlay.js 파일에 작성해준다 

// WordPlay.js
...

<TouchableOpacity onPress={()=>{navigation.navigate("PaymentTest")}}>
	<Text>결제</Text>
</TouchableOpacity>

...

 

4. 결과

임의로 생성한 결제 버튼을 누르면 결제를 진행할 수 있다

테스트 결제라서 자정이 되기 전 자동으로 취소된다

 

 

220519 추가 : 결제검증구현

앞으로 해야 할 일?

1. 아임포트에서 제공하는 예제 코드를 그대로 복사해왔기 때문에 하나씩 뜯어보면서 내가 알기 쉽도록 수정해야 한다

2. 결제창 디자인을 손봐서 어플과 잘 어울리도록 해야 한다

3. 사업자 등록을 하고 실결제될 수 있도록 아임포트쪽에서 pg사와 계약을 진행한다

4. 그 외 잡다한 결제코드 관련 에러들을 해결한다

(예를 들어 When server rendering, you must wrap your application in an <SSRProvider> to ensure consistent ids are generated between the client and server. 이런 것들)

5. 프로젝트 자체에 모자란 부분들을 채운다(소리 관련 에러 해결하기, asset 모아서 앱의 볼륨 키우기, 안드로이드/iOS 교차 테스트하기, AppLoading관련 정리하기, 컴포넌트 정리하기 등등 할 일이 산더미)

6. 그 후에 배포를 진행한다