NestJs - 결제 프로세스 구현 / 포트원 & 카카오페이 본문
미래의 나에게
포트원 SDK
<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
PG사 & 카드사 프로세스에 대한 간단한 이해
웹 결제에 대한 프로세스는 다음과 같이 이루어진다.
- 1. 구매자가 구매할 상품에 대한 정보와 금액을 판매자에게 전달
- 2. 판매자는 전달받은 금액을 PG사에게 결제해줄 것을 요청
- 3. PG사는 요청받은 정보를 은행사에게 다시 결제 요청
- 4. 은행사는 요청받은 금액을 구매자의 계좌에서 출금 후 PG사로 전달
- 5. PG사는 판매자에게 금액을 전달 (일정량의 수수료를 제외)
- 6. 판매자는 금액 확인 후, 구매자에게 상품 배송
**PG사 : Payment Gateway 의 준말로, 구매자와 판매자 사이의 결제를 대행해주는 역할을 담당한다.
대표적으로 KG이니시스 / NHN / KCP , 모바일 환경으로는 KG모빌리언스, 다날, 카카오페이 등이 있다.
결제 솔루션 이해
결제 솔루션은 PG사와의 결제 시스템을 연결해주는 API 서비스다.
위와 같은 서비스는 포트원(아임포트) / 부트페이가 있는데 포스팅에서는 포트원에 대해 다룬다.
결제 솔루션을 사용할 경우, 개발자 입장에서 복잡한 결제 환경을 직접 구현할 필요 없다는 장점이 있다.
** 모두 할 필요 없을 뿐이지, 사실 신경쓸 것이 많다...
포트원 사용하기
포트원 관리자 콘솔에서는 기본적인 설정을 포함하여 결제와 관련된 모든 API 를 확인할 수 있다.
먼저, 로그인 후 결제 연동을 설정한다.
테스트 환경에서 카카오 페이로 진행해보기로 했다.
결제에 관해서는 관련 테이블을 만들어 다룰 생각이니 먼저 entity를 만든다.
@Entity()
@ObjectType()
export class Payment {
@PrimaryGeneratedColumn('uuid')
@Field(() => String)
id: string;
@Column()
@Field(() => String)
impUid: string;
@Column()
@Field(() => Int)
amount: number;
// 결제 상태는 enum 으로 다루어, 잘못된 값이 들어오지 않도록 한다.
@Column({ type: 'enum', enum: POINT_TRANSACTION_STATUS_ENUM })
@Field(() => POINT_TRANSACTION_STATUS_ENUM)
status: POINT_TRANSACTION_STATUS_ENUM;
@CreateDateColumn()
createAt: Date;
@ManyToOne(() => Product)
@Field(() => Product)
products: Product;
// 회원
@ManyToOne(() => User, (users) => users.payment)
@Field(() => User)
users: User;
}
이후 dto를 작성하는 것이 조금 더 좋겠지만, 아직 어떤 데이터를 받아올지 정해두진 않았으니 생략한다.
resolver & service
// payments.resolver.ts
@Resolver()
export class PaymentResolver {
constructor(
private readonly paymentService: PaymentService, // DI
) {}
@UseGuards(GqlAuthGuard('access')) // 사용자를 확인하기 위한 guard
@Mutation(() => Payment) // 리턴으로 받을 값
createPayment(
@Args('impUid') impUid: string, // 결제 정보
@Args({ name: 'amount', type: () => Int }) amount: number, // 금액
@Context() context: IContext, // req 받을 context
): Promise<Payment> {
const user = context.req.user;
return this.paymentService.createPayment({ impUid, amount, user });
}
}
// payments.service.ts
@Injectable()
export class PaymentService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>, // user DB
@InjectRepository(Payment)
private readonly paymentRepository: Repository<Payment>, // payment DB
) {}
async createPayment({
impUid,
amount: _amount, // 동일 변수명이 다른 의미로 등장할 경우, 언더바를 활용하여 구분한다
// 여기서 _amount는 사용 금액, 아래 amount 는 userRepository 콜론인 내 총 사용금액을 뜻한다.
user: _user,
}: IPaymentServiceCreate): Promise<Payment> {
// 1. 테이블에 거래 기록 1줄 생성
const payment = this.paymentRepository.create({
impUid,
amount: _amount,
users: _user,
status: POINT_TRANSACTION_STATUS_ENUM.PAYMENT, // 테스트 환경이기에 결제 처리로 처리
});
await this.paymentRepository.save(payment);
// 2. 유저의 여태 사용한 금액 파악
const user = this.userRepository.findOne({
where: { id: _user.id },
});
// 3. 유저의 사용한 금액 업데이트
// 이떄 TypeORM update에는 {조건식}, {업뎃할 내용} 이 들어간다.
await this.userRepository.update(
{ id: _user.id },
{ amount: (await user).amount + _amount },
);
// 4. 최종 결과 브라우저에 돌려주기
return payment;
}
}
이후 payments.module.ts에서 resolver & service를 providers로 추가하고
app.module.ts에 payments.module.ts 를 imports 로 추가해주면 된다.
이때 결제는 rest-API로 진행하는데, graphQL 작성한 로직이 graphQL이라고 하더라도 아래와 같이 가능하다.
const data = await axios.post("http://localhost:3000/graphql",
{query: `mutation {createPayment(
impUid: "${rsp.imp_uid}", amount: ${rsp.paid_amount}) {id}}`,},
{ headers: { authorization: "Bearer 리프래쉬토큰",},});
return data
위와 같이 처리하면 graphQL, HTML 내에서 API 호출을 하더라도 잘 출력되는 것을 확인할 수 있다.
'개발 > nest.js' 카테고리의 다른 글
class-validator 커스터마이징 (0) | 2023.11.30 |
---|---|
NestJs - 로그아웃 프로세스 구현 / docker를 사용한 redis (0) | 2023.02.22 |
NestJs - 소셜로그인 구현(google login) (0) | 2023.02.12 |
NestJS - login process 구현 (refresh Token) (0) | 2023.02.12 |
NestJs - JWT 사용한 인증 & 인가 처리 (access token) (0) | 2023.02.12 |