NodeJS 서버에서 애플 인앱결제 유효성 검증을 하는 방법

  • 지난 시간에 구글 안드로이드 인앱결제 유효성 검증을 알아봤습니다.
  • 오늘은 애플 아이폰용 앱에서 인앱결제를 했을 경우에, 결제정보의 유효성 검증을 알아보겠습니다.
  • 2024년 5월 현재, 지금 소개하는 방법은 사용가능한 것입니다.
  • 사전에 처리해야 하는 과정은 생략할게요, 그 부분은 다른데서 검색하셔도 됩니다.
  • 저는 iap 패키지를 이용했습니다.
const iap = require('iap')

const platform = 'apple';
const payment = {
    receipt: "애플로부터 받은 pay receipt",
    productId: "애플에 등록해둔 상품ID",
    packageName: '앱 패키지 네임',
    excludeOldTransactions: true,
};
try {
    const resp = await new Promise((resolve, reject) => {
        iap.verifyPayment(platform, payment, (err, response) => {
            if (err) {
                reject(err)
            }
            resolve(response)
        })
    })
    if (resp.transactionId === "애플에서 보내온 결제ID") {
        return true
    }
} catch (e) {
    console.error(e)
}
  • secret을 넣으라고 되어 있는 글들이 있던데요, 제가 확인한 바로는 오히려 에러가 납니다.
  • iap 패키지의 함수가 async/await을 지원하지 않아서 직접 구현했습니다.

NodeJS 서버에서 안드로이드 인앱결제 유효성 검증을 하는 방법

  • 구글 안드로이드용 앱에서 인앱결제를 했을 경우에, 결제정보가 리턴되는데요.
  • 리턴된 값이 제대로된 값인지 혹시 위조된 것은 아닌지 검증할 필요가 있습니다.
  • 제가 여기저기 검색을 해본 결과, 안되는 내용을 올려놓은 포스팅들이 있더군요.
  • 2024년 4월 현재, 지금 소개하는 방법은 사용가능한 것입니다.
  • 사전에 처리해야 하는 과정은 생략할게요, 그 부분은 다른데서 검색하셔도 됩니다.
  • 서비스계정 만들고, 플레이스토어에서 계정 연동 시키는 등의 과정은 같아요.
const {google} = require('googleapis')
const path = require('path')

// 서비스 계정 만들면서 다운로드 받은 json 파일이 필요합니다
const realPath = path.join(__dirname, '../XXXXXXX.json')
const auth = new google.auth.JWT(
    "서비스계정 이메일 주소",
    null,
    require(realPath).private_key,
    // 아래 scopes 꼭 지정해야 하구요
    ['https://www.googleapis.com/auth/androidpublisher'],
    null
)

google.options({auth: auth})

const iap = google.androidpublisher('v3')
const packageName = "앱의 패키지 네임이 들어갑니다"
try {
    const resp = await iap.purchases.products.get({
        packageName: packageName,
        productId: "플레이스토어에서 등록한 상품ID",
        token: "검증하고자 하는 결제 정보의 purchaseToken 값",
    })
    if (resp.data.orderId === "결제ID") {
        // 올바른 결제 정보
    }
} catch (e) {
    console.error(e)
}

Flutter에서 google_sign_in 라이브러리 이용해서 캘린더 API 연동하기(1)

  • 구글 캘린더 많이 사용하시나요? 저는 그것 만큼 직관적인 UI를 가진 스케쥴 앱을 못 찾겠더군요.
  • 그래서 몇 년째 애용하고 있는데요. 회사에서는 팀장, 블로그와 유튜버 크리에이터 그리고 육아대디로 활동하다보니 스케쥴 만들고 수정하는 것도 하나의 스케쥴이 되어 버렸어요.
  • 저의 이런 고충을 이야기해 본 포스팅이 아래 링크로 있구요.
  • 위 포스팅에서도 이야기 했지만, 구글 캘린더에 음성인식 기술을 이용해서 이벤트 추가하는게 생각보다 어렵더군요.
  • 그래서 제가 직접 만들어보려고 합니다. 오늘은 그 중 첫번째 시간으로 제가 만들어서 관리중인 캘린더 리스트를 뽑아오는 것 까지 해보겠습니다.
  • 아래 코드를 보시면 되겠구요. 생각보다 간단합니다. 앱을 시작하면 구글 로그인을 해서 권한 설정을 하고, 모두 완료되면 콘솔에 프린트를 하도록 해놨습니다.
import 'dart:convert';


import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;


void main() {
 runApp(const MyApp());
}
class MyApp extends StatelessWidget {
 const MyApp({super.key});

 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
       useMaterial3: true,
     ),
     home: const MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}


class MyHomePage extends StatefulWidget {
 const MyHomePage({super.key, required this.title});

 final String title;


 @override
 State<MyHomePage> createState() => _MyHomePageState();
}


class _MyHomePageState extends State<MyHomePage> {
 int _counter = 0;
 List<String> items = [];
 final _googleSignIn = GoogleSignIn(
   scopes: [
     'https://www.googleapis.com/auth/calendar',
   ],
 );
 GoogleSignInAccount? _currentUser;
 late Map<String, dynamic> cals;


 @override
 void initState() {
   super.initState();
   _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {
     setState(() {
       _currentUser = account;
       if (_currentUser != null) {
         _getCalendarList();
       }
     });
   });
   _googleSignIn.signIn().then((GoogleSignInAccount? account) {
     if (account != null) {
       setState(() {
         _currentUser = account;
         _getCalendarList();
       });
     }
   });
 }


 void _getCalendarList() async {
   http.Client client = http.Client();
   var headers = await _currentUser?.authHeaders;
   var resp = await client.get(Uri.parse("https://www.googleapis.com/calendar/v3/users/me/calendarList"), headers: headers);
   cals = jsonDecode(resp.body);
   for(var i=0;i<cals['items'].length;i++) {
     print(cals['items'][i]['summary']);
   }
 }


 void _incrementCounter() {
   setState(() {
     for(var i=0;i<cals['items'].length;i++) {
       print(cals['items'][i]['summary']);
     }
   });
 }


 @override
 Widget build(BuildContext context) {   
   return Scaffold(
     appBar: AppBar(       
       title: Text(widget.title),
     ),
     body: Center(
       child: Column(         
         mainAxisAlignment: MainAxisAlignment.center,
         children: <Widget>[
           const Text(
             'You have pushed the button this many times:',
           ),
           Text(
             '$_counter',
             style: Theme.of(context).textTheme.headlineMedium,
           ),
         ],
       ),
     ),
     floatingActionButton: FloatingActionButton(
       onPressed: _incrementCounter,
       tooltip: 'Increment',
       child: const Icon(Icons.add),
     ),
   );
 }
}
  • 여기서 주목할 점은 우리가 사용하는 캘린더 상의 이름은 summary 라는 점 이에요.
  • package.yaml 파일에 아래 의존성 정의 추가하세요.
    • google_sign_in: ^6.2.1
  • 이러면 아래처럼 출력이 됩니다.
    • 이미지1