Загружаем каталог…
Загружаем каталог…
기획 피벗: "인앱 결제(PG), 꼭 지금 붙여야 할까?" 앱 기획 초기에는 당연히 앱 내에서 회원들이 신용카드로 PT 수강권을 직접 결제하는 그림을 그렸습니다. 하지만 PG(결제 대행사) 연동을 알아보며 현실적인 벽에 부딪혔습니다. 높은 수수료와 복잡한 심사 : 앱 내 결제를 붙이려면 사업자 등록, 통신판매업 신고는 기본이고, 구글/애플의 무자비한 인앱 결제 수수려(최대 30%) 정책까지 얽혀 있었습니다. PT샵의 실제 비즈니스 환경 : 현장을 분석해 보니 회원들은 대부분 샵에 방문해서 현장 단말기(카드)로 긁거나 계좌 이체 로 큰 금액을 결제하고 있었습니다. 굳이 수수료를 떼며 앱에서 결제할 이유가 없었던 것입니다. 그래서 과감하게 기획을 피벗했습니다. " 복잡한 외부 PG 연동은 걷어내고, 관리자가 현장 결제를 확인한 뒤 앱에서 '수동으로 수강권을 발급'하는 기능에 집중하자! " 배보다 배꼽이 커지는 것을 막고, MVP(최소 기능 제품)의 핵심에 집중하기 위한 현실적인 선택이었습니다. RDBMS 핵심 스키마 설계 결제 방식이 '관리자 수동 발급'으로 바뀌었어도, 백엔드에서 처리해야 할 데이터의 무결성은 똑같이 중요합니다. 시스템의 근간을 위해 MySQL 을 채택하고 핵심 테이블을 설계했습니다. users (사용자): 관리자, 트레이너, 회원 등 권한별 계정 정보 branches (지점): 운영 중인 PT 지점 정보 tickets (수강권): 회원별 수강권 잔여 횟수( remaining_sessions ) payments (매출/결제 내역): 현장 결제 및 수강권 발급에 따른 매출 기록 트랙잭션 설계 관리자가 앱에서 특정 회원에게 '10회권 발급' 버튼을 누르면, Node.js 백엔드에서는 반드시 두 가지 작업이 동시에 일어나야 합니다. tickets 테이블에 회원의 수강권 횟수(10회)를 INSERT payments 테이블에 해당 상품 금액만큼의 매출 기록을 INSERT 둘 중 하나라도 실패하면(수강권만 생기고 매출이 안 잡히거나, 매출만 잡히고 수강권이 안 생기거나) 큰일이 나기 때문에 이 과정은 하나의 트랜잭션(Transaction)으로 묶어주었습니다. // Node.js (Express) - 관리자 수강권 수동 발급 트랜잭션 const connection = await pool.getConnection(); await connection.beginTransaction(); try { // 1. tickets 테이블에 수강권 발급 await connection.query( 'INSERT INTO tickets (user_id, ticket_name, total_count, used_count, remaining_sessions) VALUES (?, ?, ?, 0, ?)', [member_id, product.name, product.total_count, product.total_count] ); // 2. payments 테이블에 매출 기록 await connection.query( 'INSERT INTO payments (branch_id, amount, payment_date) VALUES (?, ?, NOW())', [branch_id, product.price] ); await connection.commit(); // 모두 성공 시 확정 } catch (error) { await connection.rollback(); // 실패 시 원상 복구 throw error; } 트러블슈팅 관리자 대시보드 화면에 '지점별 총매출'을 띄우는 테스트를 하던 중, 치명적인 함정을 발견했습니다. UI 구현에 너무 매몰된 나머지 payments 테이블을 아래처럼 짰던 것이빈다. 초기 payments 테이블: id , branch_id (지점), amount (금액), payment_date (일시) 당장 총매출액을 구하는 데는 문제가 없었지만, 가상의 회원이 "어제 결제한 거 환불할게요"라고 하면? 이라는 생각이 들었습니다. " 이 결제 내역, 관리자가 누구한테 발급해 준 거고, 회원이 현장 카드로 한 건지 계좌이체로 한 건지 어떻게 알지? " 지점별 전체 매출액만 신경 쓰느라 가장 중요한 user_id 와 payment_method 를 테이블에 넣지 않은 것입니다. 돈이 들어오긴 했는데 출처와 수단이 없는, 상용 앱에서는 절대로 있어서는 안 되는 '반쪽짜리' 영수증이었습니다. 해결: 설계 고도화와 롤백 실수를 깨닫자마자 DBeaver를 열어 즉시 테이블 스키마를 뜯어고쳤습니다. ALTER TABLE payments ADD COLUMN user_id INT AFTER id, ADD COLUMN payment_method VARCHAR(50) AFTER amount; 테이블 구조를 바꾼 후, 관리자 발급용 API 쿼리도 전면 수정했습니다. 관리자가 발급 버튼을 누를 때 [어떤 회원에게 / 무슨 지점에서 / 얼마를 / 현장 카드인지 이체인지 / 언제] 발급했는지 꼼꼼하게 기록하는 완전체 쿼리로 고도화했습니다. await connection.query( 'INSERT INTO payments (user_id, branch_id, amount, payment_method, payment_date) VALUES (?, ?, ?, ?, NOW())', [member_id, branch_id, product.price, payment_method] ); 이제 완벽한 매출/발급 추적 데이터를 쌓을 수 있게 되었습니다. 회고 이번 과정을 통해 두 가지 큰 레슨을 얻었습니다. 첫째, 기술적으로 멋져 보이는 기능(PG 연동)이라도 실제 비즈니스 환경(현장 결제 위주)에 맞지 않으면 과감히 덜어내는 것이 맞다. 둘째, UI에 띄울 '결과물'만 생각해서 DB를 설계하면 안 된다. 데이터베이스는 비즈니스의 모든 예외 상황(환불, 영수증, 수단별 통계)을 위한 '추적 가능성'을 완벽히 보장해야 한다. 다음 편 예고 결제 시스템과 DB의 뼈대가 탄탄하게 잡혔으니, 이제 이 데이터를 바탕으로 최고 관리자가 시스템을 어떻게 쥐락펴락하는지 보여드릴 차례입니다. 다음 편에서는 관리자 포털의 세부 기능과 UI/UX 구현 과정 을 다뤄보겠습니다. 지점과 직원을 관리하는 로직, 복잡한 2-Depth 하단/상단 탭 네비게이션 라우팅 설계, 그리고 이 과정에서 마주쳤던 플러터 생명주기(Lifecycle) 에러 해결기까지 낱낱이 파헤쳐 보겠습니다.
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
📱 O2O PT샵 관리 시스템 개발기 #2. DB 설계: 결제 시스템 피벗과 DB 설계의 함정. 기획 피벗: "인앱 결제(PG), 꼭 지금 붙여야 할까?" 앱 기획 초기에는 당연히 앱 내에서 회원들이 신용카드로 PT 수강권을 직접 결제하는 그림을 그렸습니다. 하지만 PG(결제 대행사) 연동을 알아보며 현실적인 벽에 부딪혔습니다. 높은 수수료와 복잡한 심사 : 앱 내 결제를 붙이려면 사업자 등록, 통신판매업 신고는 기본이고, 구글/애플의 무자비한 인앱 결제 수수려(최대 30%) 정책까지 얽혀 있었습니다. PT샵의 실제 비즈니스 환경 : 현장을 분석해 보니 회원들은 대부분 샵에 방문해서 현장 단말기(카드)로 긁거나 계좌 이체 로 큰 금액을 결제하고 있었습니다. 굳이 수수료를 떼며 앱에서 결제할 이유가 없었던…
Открыть источник