어제는 결제 연동 프로세스 구현에 대해 배웠다면 오늘은
포트원 연동 결제 프로세스를 배웠다.
결제 API도 마찬가지로 KEY를 연동시켜서 API 를 구현해줘야 하는데 대표적인 사이트로는 포트원이 있다.
자기가 구현하고 싶은 결제대행사 선택후
왼쪽 결제연동을 들어가 식별코드 키를 받아주면 된다.
자세한 것은 콘솔 가이드를 보면서 참고해보자.
imp_uid
솔루션사랑 백엔드에 서버연결API를 하나 만들어줘야 한다. 웹훅 연동하기
웹훅
: 특정 이벤트가 발생하였을 댸 서비스나 응용프로그램으로 알림을 보내는 기능
imp_uid
: 포트원을 통해 받는 고유키로,
uid키를 통해 해당 결제에 관련된 모든 정보들을 조회해 볼 수 있으며, 결제 취소도 가능하다.
이와 같은 구현을 할 경우 복잡한 개발 과정을 거치지 않고 포트원에 있는 결제 과정을 사용해
보다 편리한 개발 구현을 만들 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>결제하기</title>
// 포트원을 사요하기 위해 해당 라이브러리를 페이지에 추가해야하한다.
<script
type="text/javascript"
src="https://code.jquery.com/jquery-1.12.4.min.js"
></script>
<script
type="text/javascript" // 최신버전 사용 권장
src="https://cdn.iamport.kr/js/iamport.{SDK-최신버전}-1.2.0.js"
></script>
<script
type="text/javascript"
src="https://unpkg.com/axios/dist/axios.min.js"
></script>
<script>
function mypayment() {
const myAmount = Number(document.getElementById("amount").value);
const IMP = window.IMP; // 생략 가능
IMP.init("나만의 식별키"); // Example: imp00000000
IMP.request_pay(
{
// param
pg: "kakaopay", <<== 포트원 등록한 결제 대행사
pay_method: "card",
name: "마우스",
amount: myAmount,
buyer_email: "gildong@gmail.com",
buyer_name: "홍길동",
buyer_tel: "010-4242-4242",
buyer_addr: "서울특별시 강남구 신사동",
buyer_postcode: "01181",
m_redirect_url: "", // 모바일 결제후 리다이렉트될 주소!!
},
async (rsp) => {
// callback
if (rsp.success) {
// 결제 성공시
console.log(rsp);
const data = await axios.post(
"http://localhost:3000/graphql",
{ // graphql createpoint 부분
query: `
mutation{
createPointTransaction(impUid:"${rsp.imp_uid}",amount:${rsp.paid_amount}){
id // 템플릿 사용으로 안에 변수 값 넣어주기
impUid
amount
status
}
}
`,
},
{
headers: {
// graphql 에서 로그인 하고 받은 인가 토큰
authorization:
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NzhhZWE3NS1kMmIyLTRjZjEtYWYwZi1hYjc5Zjg5NThmNDUiLCJpYXQiOjE2NzYzNzQ2ODksImV4cCI6MTY3NjM3ODI4OX0.yQwfzaJmWvna5Ew05viIwo0_YPwTFr3ZOBLCGSEuTrE",
},
}
);
console.log(data);
} else {
// 결제 실패시
}
}
);
}
</script>
</head>
<body>
결제할 금액: <input type="text" id="amount" />
<button onclick="mypayment()">결제하기</button>
</body>
</html>
IMP.init("{가맹점 식별코드}")
: 해당 가맹점 식별코드를 등록해 주는 것IMP.request_pay
: 포트원이 연결해주는 PG사의 결제페이지를 사용할 수 있게 된다.pg
: 어떤 PG사를 이용할 건지 선택 가능 ⇒ 저는 카카오 선택했으니 카카오 적어준거 처럼?pay_method
: 어떤 결제 방식을 사용할 건지 선택 가능rsp
(respose) 안에는 결제 정보들이 들어있습니다.작성하다보니 양식들이 포트원 콘솔가이드에 자세하게 나와있어서 따른 기존 docs보다 많이 편리했다.
import { Field, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Product } from 'src/apis/products/entities/product.entity';
import { ProductRoomType } from 'src/apis/productsRoomTypes/entities/productsRoomTypes.entity';
import { User } from 'src/apis/users/entities/user.entity';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
// 결제 API 추가
// status 결제완료 취소완료를 해주기 위해 만들어줌
export enum POINT_TRANSACTION_STATUS_ENUM {
PAYMENT = 'PAYMENT',
CANCEL = 'CANCEL',
}
// 결제 API 추가
registerEnumType(POINT_TRANSACTION_STATUS_ENUM, {
name: 'POINT_TRANSACTION_STATUS_ENUM',
});
@Entity()
@ObjectType()
export class Payment {
@PrimaryGeneratedColumn('uuid')
@Field(() => String)
id: string;
// 결제 API 추가
@Column()
@Field(() => String)
impUid: string;
// @Column()
// @Field(() => Date)
// stay: Date;
// 결제 API 추가
@Column()
@Field(() => Int)
amount: number;
// 결제 API 추가
// 결제 타입은 enum 으로 다루어 정해진 문자열만 들어가게 해준다.
@Column({ type: 'enum', enum: POINT_TRANSACTION_STATUS_ENUM })
@Field(() => POINT_TRANSACTION_STATUS_ENUM)
status: POINT_TRANSACTION_STATUS_ENUM;
// @Column()
// @Field(() => String)
// method: string;
// 유저 한명당 여러 번 결제를 할 수 있기에 ManyToOne 연결
@ManyToOne(() => User, (users) => users.payment)
@Field(() => User)
users: User;
@ManyToOne(() => Product, (products) => products.payment)
@Field(() => [Product])
products: Product[];
@JoinColumn()
@OneToOne(() => ProductRoomType)
@Field(() => [ProductRoomType])
productsRoomType: ProductRoomType[];
// 결제 API 추가
// 결제가 이루어질때 자동으로 시간 생성 저장
@CreateDateColumn()
@Field(() => Date)
createdAt: Date;
}
🐙 3. Resolve, Service.ts 작성
// payments.resolver.ts
@Resolver()
export class PaymentsResolver {
constructor(
private readonly paymentsService: PaymentsService, //
) {}
@UseGuards(GqlAuthGuard('access')) => 로그인 한 유저만 가능하게 guard 사용
@Mutation(() => Payment)
createPayment(
@Args('impUid') impUid: string, //
@Args({ name: 'amount', type: () => Int }) amount: number,
// 로그인 한 유저의 정보는 Context에 내장되어 있다.
@Context() context: IContext,
): Promise<Payment> {
const user = context.req.user;
return this.paymentsService.create({ impUid, amount, user });
}
}
// payments.service.ts
@Injectable()
export class PaymentsService {
constructor(
@InjectRepository(Payment)
private readonly paymentsRepository: Repository<Payment>,
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async create({
impUid,
amount,
user: _user, ==> 2번의 user와 변수가 겹쳐 에러 발생 _user로 변경
}: IPaymentsServiceCreate): Promise<Payment> {
// this.payment.create() // 등록을 위한 빈 객체 만들기
// this.payment.insert() // 결과는 못 받는 등록 방법
// this.payment.update() // 결과는 못 받는 수정 방법
// 1. payment 테이블에 거래기록 1줄 생성
const payment = this.paymentsRepository.create({
impUid: impUid,
amount: amount,
users: _user,
status: POINT_TRANSACTION_STATUS_ENUM.PAYMENT, //안전한 코드 만들기의 한 방향
// 포인트를 충전하는 것 이기에 결제상태를 PAYMENT 를 작성
});
await this.paymentsRepository.save(payment);
// 2. 유저의 돈 찾아오기
const user = await this.usersRepository.findOne({
where: { id: _user.id },
});
// 3. 유저의 돈 업데이트
await this.usersRepository.update(
{ id: _user.id },
{ point: user.point + amount },
);
// 4. 최종결과 브라우저에 돌려주기
return payment;
}
}
이후 module.ts 와 app.module 추가하여 정상적으로 구현할 수있도록 해준다.
// patments.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([
Payment, //
User,
]),
],
providers: [
PaymentsResolver, //
PaymentsService,
],
})
export class PaymentsModule {}
// app.module.ts
// 모듈 부분 imports 에 PaymentsModule 넣어주기