
Multi Payment Providers

- #payment provider
- #timeout
- #runtime crash
- #high availability
- #fromm
๋ค์ด๊ฐ๋ฉฐ
์ ํฌ๋ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ฅผ A์ฌ๋ง ์ฌ์ฉํ์๋๋ฐ, ์ด๋ฒ์ ์ถ๊ฐ๋ก B์ฌ์ ํจ๊ป ๋ณตํฉ์ ์ผ๋ก ์ฌ์ฉํ๊ฒ ๋์์ต๋๋ค.
๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ์์ํ์๊ฒ ์ง๋ง, ์ ํฌ ๊ฒฐ์ ๋ก์ง์ A์ฌ์ ํนํ๋์ด ์์ฑ๋์ด ์์๊ณ , B์ฌ์ ๋ง๊ฒ ๋ณ๊ฒฝํ๋ ์์
์ด ํ์ํ์ต๋๋ค.
Multi Payment Providers๋ฅผ ๊ตฌํํ๋ฉด์ ์ป์ ์ค๊ณ ๊ฒฝํ์ ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค.
๊ฒฐ์ ํ๋ฆ
๊ณ ๊ฐ โ> e-commerce(๋ ธ๋จธ์ค) โ> ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ(A์ฌ, B์ฌ) โ> PG์ฌ(๊ฒฐ์ ๋ํ์ฌ) โ> ์นด๋์ฌ/์ํ
๊ธฐ์กด์ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ A์ฌ๋ง ์ฌ์ฉํ์ ๋์, ๊ฒฐ์ ๋ฐฉ์์ด ์ถ๊ฐ๋ ๋๋ง๋ค PG์ฌ๊ฐ ์ถ๊ฐ๋๋ ํํ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ถ๊ฐ๋ก ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ B์ฌ๋ฅผ ๊ฐ์ด ์ฌ์ฉํ๊ฒ ๋๋ฉด์, ์ ํฌ ๊ฒฐ์ ๋ก์ง์์๋ A์ฌ์ B์ฌ๋ฅผ ๊ตฌ๋ถํ๋ ์ผ์ข
์ router ์ญํ ์ด ํ์ํ๊ฒ ๋์์ต๋๋ค.
๊ตฌํ
๊ฒฐ์ ์์๋ ๋จ๊ฑด ๊ฒฐ์ , ์ ๊ธฐ ๊ฒฐ์ (๋น๋งํค), ๊ฒฐ์ ์ทจ์(ํ๋ถ), ์นํ
๊ตฌํ์ด ์ฃผ์ interface ๊ฐ์ต๋๋ค.
๋จ๊ฑด ๊ฒฐ์ (์ธ์ฆ ๊ฒฐ์ )
๋จ๊ฑด ๊ฒฐ์ ์์ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์ ๋ฐ๋ผ ๊ฒฐ์ ๐์น์ธ๐ ํ์ ์์น๊ฐ ๋ค๋ฆ
๋๋ค.
์ ๊ธฐ ๊ฒฐ์ (๋น๋งํค)
์ ๊ธฐ ๊ฒฐ์ ๋ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์ ๋ฐ๋ผ, ๋น๋งํค ๐์น์ธ๐ ํ์ ์์น๊ฐ ๋ค๋ฆ
๋๋ค.
๊ฒฐ์ ์ทจ์(ํ๋ถ)
๊ฒฐ์ ์ทจ์ flow๋ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์ ์ฐจ์ด๊ฐ ์์ต๋๋ค.
์นํ (Webhook)
์นํ
๋ฑ๋ก ๋ฐฉ์์ ๊ฐ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ง๋ค ์คํ์ผ์ด ๋ค๋ฆ
๋๋ค.
์คํ์ผ1
- ๊ฒฐ์ ์์ฒญ์ webhook URL์ ๊ฐ์ด ์ ๋ฌํฉ๋๋ค.
- webhook response ์์ Billing Key ์ ๋ณด๊ฐ ์ ํ์ ์ผ๋ก ํฌํจ๋์ด ์์ต๋๋ค.
์คํ์ผ2
- ๋ค์์ webhook URL์ ๋ณ๋๋ก ๋ฑ๋กํฉ๋๋ค.
- ๋จ๊ฑด ๊ฒฐ์
- ํ๋ถ
- ์ ๊ธฐ ๊ฒฐ์
์คํ์ผ3
- ์์ (mid)๋ณ๋ก webhook URL์ ๋ฑ๋กํฉ๋๋ค.
- ๊ฐ ์์ ์์ ๋ฐ์ํ ์ ์๋ ์ด๋ฒคํธ๋ฅผ ์ ํ์ ์ผ๋ก ๊ตฌ๋ ํฉ๋๋ค.
์ํคํ ์ฒ ์ค๊ณ
ํ๊ฒฝ๋ณ์๋ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ณ๋ก ๋ค๋ฅธ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๋๋ค:
- A์ฌ: ํ๋์ ํตํฉ ํ๊ฒฝ๋ณ์
- B์ฌ: mid(์์ ID)๋ณ๋ก ๊ฐ๊ฐ์ ํ๊ฒฝ๋ณ์
PG ๋ผ์ฐํฐ ๊ตฌํ
API interface์์ <pg>.<mid>
ํํ๋ก ์์ฒญ์ ๋ฐ๊ณ , ์ด ๊ฐ์ผ๋ก ์ด๋ค ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์ธ์ง ๊ตฌ๋ถํฉ๋๋ค.
routePaymentProvider({ pg, mid, currency }: { pg: string; mid: string; currency: string }): PaymentProvider {
switch (pg) {
case 'PayLetter':
return currency === 'KRW' ? PaymentProvider.A : PaymentProvider.A;
case 'TossPayments':
return this.hasTossPaymentsMid(mid) ? PaymentProvider.B : PaymentProvider.A;
case 'Naver':
return PaymentProvider.B;
case 'PaymentWall':
case 'Kakao':
case 'Paypal':
return PaymentProvider.A;
default:
throw new Error(`Unsupported payment provider: ${pg}`);
}
}
PG ๋ผ์ฐํฐ๋ pg, mid, currency ์ ๋ณด๋ฅผ ๋ฐ์์ ์ด๋ค ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ layered architecture๋ก ์ค๊ณํ์ต๋๋ค.
์๋ฌ ์ฒ๋ฆฌ
์ ๋ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ง๋ค Custom Error๋ฅผ ์ ์ํ์์ต๋๋ค.
export class PaymentProviderAError extends Error {
constructor(
message: string,
public readonly status?: number,
public readonly code?: string,
public readonly contextMethod?: string,
public readonly errorDetails?: string
) {
super(message);
this.name = 'PaymentProviderAError';
}
}
export class PaymentProviderBError extends Error {
...
}
๊ฐ ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์ ์๋ต ํฌ๋ฉง์ ๊ฐ๊ธฐ ๋ค๋ฅด๊ณ , ์๋ฌ ์ฝ๋ ๋ํ ๋ค๋ฆ
๋๋ค.
ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ์์ HTTP ํต์ ์์ ์๋ฌ๋ก ํํ๋๋ ์ฌ๋ฌ๊ฐ์ง ์ค์
๊ณ ๊ฐ์ ์ํ๊ฐ ์ ์์ ์ด์ง ์์ ๊ฒฐ์ ๊ฐ ์คํจํ ๊ฒฝ์ฐ๋, ๊ณ ๊ฐ์๊ฒ ์๋ฌ ๋ฉ์์ง๋ฅผ ๊ทธ๋๋ก ์ ๋ฌํ๊ณ ,
์ ํฌ ์์คํ
์์ ์ธ์งํด์ผํ ์๋ฌ๋ ๋ด๋ถ ์๋ ์์คํ
์ผ๋ก ์ ์กํ๊ธฐ ์ํด Custom Error๋ฅผ ์ ์ํ์์ต๋๋ค.
๋ง์น๋ฉฐ
ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ฅผ 1๊ฐ๋ง ์ฌ์ฉํ ๋น์์, 0์ ๊ฒฐ์ ์ํ์ด ๋ฑ์ฅํ์๋๋ฐ, 0์์ด๊ธฐ ๋๋ฌธ์, ํตํฉ ๊ฒฐ์ ์ค๊ณ์ฌ๋ฅผ ํธ์ถํ ํ์๊ฐ ์์๊ณ , ์ ํฌ ๋ด๋ถ์ ์ผ๋ก๋ง ์ฒ๋ฆฌํ๋ฉด ๋์์ต๋๋ค.
์ด๋ฅผ ์ถ๊ฐ์ ์ธ argument๋ก ์ฒ๋ฆฌํ๋ ๊ฒ๋ณด๋ค, ๊ฐ์์ Knowmerce Payment Provider๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ค๋ฉด ์ข ๋ ๊น๋ํ ๊ตฌ์กฐ๊ฐ ๋์์ ๊ฒ ๊ฐ์ต๋๋ค.
๋
ธ๋จธ์ค ๋ฐฑ์๋ ์ ์ฒด ๋ก์ง์ try catch๋ฅผ ์ฆ๊ฒจ ์ฌ์ฉํ์ง ์๊ณ , global exception filter์ ์ฃผ๋ก ์์กดํ๋๋ฐ,
๊ฐ PG์ฌ์ ์๋ฌ๋ฅผ ํด์ํ์ฌ ๋ณํ์ฒ๋ฆฌํด์ผ ํ๋ ๋ถ๋ถ์ด ์์ด, ๊ฒฐ์ ๋ถ๋ถ์๋ try catch๊ฐ ์ฌ์ฉ๋๋ ๊ณณ์ด ์์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค.