@bitpal/checkout 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -67,6 +67,17 @@ interface CreateSessionParams {
67
67
  * 예: ['eip155:84532', 'eip155:11155420', 'eip155:80002', 'eip155:421614']
68
68
  */
69
69
  allowed_pay_chains?: string[];
70
+ /**
71
+ * chain×token cell 허용 목록(**결제 수단** 표준 입력) — buyer 가 (체인, 토큰) 조합을 고른다.
72
+ * allowed_pay_chains(×단일 currency shorthand)보다 우선. 서로 다른 토큰을 섞을 때 사용.
73
+ * `chain` 은 short-name('base','tron','solana') 또는 CAIP-2 둘 다 허용(서버가 mode-relative 로 정규화).
74
+ * paymentLinks.allowed_assets 와 동일 형태(SDK 일관).
75
+ * 예: [{ chain: 'base', token: 'USDC' }, { chain: 'tron', token: 'USDT' }]
76
+ */
77
+ allowed_assets?: Array<{
78
+ chain: string;
79
+ token: string;
80
+ }>;
70
81
  /** 결제 자산 (예: USDC) */
71
82
  pay_asset?: string;
72
83
  /** 허용 오차 (%, 기본값: 2.00) */
@@ -677,6 +688,386 @@ declare function toAtomicUSDC(decimal: string): string;
677
688
  */
678
689
  declare function fromAtomicUSDC(atomic: string): string;
679
690
 
691
+ /**
692
+ * x402 — 라우트 호출 1건당 과금하는 머천트 미들웨어.
693
+ *
694
+ * 머천트 코드는 "이 라우트는 $1" 만 선언한다. 402 조립, `X-PAYMENT` 파싱, verify → settle 순서,
695
+ * 에러 매핑, 재사용 차단은 전부 여기 안에 있다. 수수료는 BitPal이 계산해 402에 실어주므로
696
+ * 머천트는 **자기가 받고 싶은 총액(gross) 하나만** 넘긴다(체크아웃 `line_items[].amount` 와 동일 UX).
697
+ *
698
+ * 두 가지를 특히 조심해서 다룬다:
699
+ *
700
+ * 1. **"결제 1건 = 응답 1건" 은 미들웨어 책임이다.** BitPal `/settle` 의 멱등성은 "같은 nonce로 두 번
701
+ * 정산 요청해도 자금은 한 번만 움직인다" 만 보장한다. 같은 `X-PAYMENT` 를 100번 보내면 100번
702
+ * 성공 응답이 오므로, 그것만 믿으면 결제 1건으로 API를 무한 호출할 수 있다. 그래서 이미 리소스를
703
+ * 내준 authorization 을 로컬에 기록해 재사용을 막는다(`X402ReplayStore`).
704
+ *
705
+ * 2. **HTTP 200 은 정산 성공이 아니다.** 이미 종결된 결제를 다시 settle 하면 서버는 그 결과를 그대로
706
+ * 돌려준다 — `reverted` 여도 200 이다. 리소스는 `status === 'confirmed'` 일 때만 내준다.
707
+ */
708
+ /** payer → resource server. base64(JSON) 결제 payload. */
709
+ declare const X402_PAYMENT_HEADER = "x-payment";
710
+ /** resource server → payer. base64(JSON) 정산 결과(txHash 포함). */
711
+ declare const X402_PAYMENT_RESPONSE_HEADER = "X-PAYMENT-RESPONSE";
712
+ interface X402FeeBreakdown {
713
+ netAmount: string;
714
+ feeAmount: string;
715
+ feeBps: number;
716
+ feeRecipient: string | null;
717
+ }
718
+ interface X402Accept {
719
+ scheme: 'exact';
720
+ network: string;
721
+ asset: string;
722
+ payTo: string;
723
+ maxAmountRequired: string;
724
+ resource: string;
725
+ description: string;
726
+ mimeType: string;
727
+ maxTimeoutSeconds: number;
728
+ /** payer 가 EIP-712 domain 을 그대로 만들 수 있는 값. */
729
+ extra: {
730
+ name: string;
731
+ version: string;
732
+ chainId: number;
733
+ };
734
+ /** BitPal 확장 — payer 가 merchant/fee 두 authorization 을 만들 수 있도록 서버가 확정한 분해. */
735
+ feeBreakdown: X402FeeBreakdown;
736
+ }
737
+ interface X402Requirements {
738
+ x402Version: 1;
739
+ accepts: X402Accept[];
740
+ }
741
+ interface Eip3009Authorization {
742
+ from: string;
743
+ to: string;
744
+ value: string;
745
+ validAfter: string;
746
+ validBefore: string;
747
+ nonce: string;
748
+ signature: {
749
+ v: number;
750
+ r: string;
751
+ s: string;
752
+ };
753
+ }
754
+ interface X402PaymentPayload {
755
+ x402Version: 1;
756
+ scheme: 'exact';
757
+ /** x402 wire 이름 — 'base' / 'base-sepolia'. */
758
+ network: string;
759
+ payload: {
760
+ merchant: Eip3009Authorization;
761
+ /** 서버가 계산한 feeAmount > 0 이면 필수. */
762
+ fee?: Eip3009Authorization;
763
+ };
764
+ }
765
+ /** `ok` = 이번 호출이 최초 점유 / `inflight` = 다른 요청이 처리 중 / `used` = 이미 리소스를 내줌. */
766
+ type X402ClaimState = 'ok' | 'inflight' | 'used';
767
+ /**
768
+ * authorization 재사용 차단 저장소.
769
+ *
770
+ * 기본 구현은 프로세스 메모리다 — 단일 인스턴스에선 충분하지만, 여러 인스턴스로 스케일 아웃하면
771
+ * 인스턴스마다 기억이 따로라 결제 1건으로 인스턴스 수만큼 호출할 수 있다. 다중 인스턴스 배포라면
772
+ * Redis/DB 기반 구현을 넘겨야 한다(키는 `network:from:nonce` 문자열 하나).
773
+ */
774
+ interface X402ReplayStore {
775
+ claim(key: string, inflightTtlSeconds: number): X402ClaimState | Promise<X402ClaimState>;
776
+ /** 리소스를 내줬다 — 이 키는 영구 소진. */
777
+ commit(key: string): void | Promise<void>;
778
+ /** 정산이 안 끝났다 — 점유만 풀고 재시도를 허용(소진 처리 아님). */
779
+ release(key: string): void | Promise<void>;
780
+ }
781
+ /** 프로세스 메모리 기반 기본 store. */
782
+ declare function createMemoryReplayStore(): X402ReplayStore;
783
+ interface X402GateOptions {
784
+ /** 머천트가 이미 쓰고 있는 `bp_test_…` / `bp_live_…` 키. prefix 가 곧 네트워크(테스트/라이브)다. */
785
+ apiKey: string;
786
+ /** 정산 지갑. 콘솔에 등록한 payout 주소와 같은 값이어야 한다. */
787
+ payTo: string;
788
+ /** 호출 1건당 받을 총액(atomic). USDC 6dp 기준 $1 = `'1000000'`. 수수료는 여기서 차감된다. */
789
+ amount: string;
790
+ /** 생략 시 API 키 모드의 기본 자산(현재 Base / Base Sepolia). */
791
+ network?: string;
792
+ /** 기본 `'USDC'`. */
793
+ asset?: string;
794
+ description?: string;
795
+ mimeType?: string;
796
+ /** payer 가 서명을 만들어 재요청하기까지 주는 시간(초). 기본 300. */
797
+ maxTimeoutSeconds?: number;
798
+ baseUrl?: string;
799
+ /** BitPal API 호출 타임아웃(ms). settle 은 영수증까지 기다리므로 기본 60초. */
800
+ timeout?: number;
801
+ /** 다중 인스턴스 배포라면 공유 store 를 넘긴다. 기본은 프로세스 메모리. */
802
+ replayStore?: X402ReplayStore;
803
+ }
804
+ interface X402SettledPayment {
805
+ paymentId: string;
806
+ txHash: string | null;
807
+ /** 플랫폼 수수료 leg 의 tx. best-effort 라 merchant leg 가 확정돼도 null 일 수 있다. */
808
+ feeTxHash: string | null;
809
+ network: string;
810
+ /** payer 주소(merchant authorization 의 from). */
811
+ payer: string;
812
+ amount: string;
813
+ /** 이미 정산돼 있던 건을 그대로 돌려받았는지(감사·디버깅용). */
814
+ idempotent: boolean;
815
+ }
816
+ type X402GateResult =
817
+ /** 결제가 없거나 유효하지 않다 — 402 와 결제 조건을 그대로 내려준다. */
818
+ {
819
+ kind: 'payment_required';
820
+ status: 402;
821
+ body: X402Requirements & {
822
+ error?: string;
823
+ };
824
+ }
825
+ /** 이 authorization 으로는 이미 리소스를 내줬다 — 재사용 거부. */
826
+ | {
827
+ kind: 'already_used';
828
+ status: 409;
829
+ body: {
830
+ error: string;
831
+ message: string;
832
+ };
833
+ }
834
+ /** 자금이 움직였는지 아직 확정 못 했다 — **같은** `X-PAYMENT` 로 재시도해야 한다(재서명 금지). */
835
+ | {
836
+ kind: 'unconfirmed';
837
+ status: 503;
838
+ body: {
839
+ error: string;
840
+ message: string;
841
+ paymentId?: string;
842
+ };
843
+ }
844
+ /**
845
+ * 정산 확정 — 리소스를 내주면 된다.
846
+ *
847
+ * 이 시점에 결제는 아직 **점유(inflight)** 상태다. 응답을 실제로 내보낸 뒤 `commit()` 을,
848
+ * 핸들러가 실패해 리소스를 못 줬으면 `release()` 를 불러야 한다. 둘 다 안 부르면 점유가
849
+ * TTL 까지 유지돼 그 사이 재시도가 503 으로 막힌다(이중 지급보다 안전한 방향).
850
+ */
851
+ | {
852
+ kind: 'settled';
853
+ status: 200;
854
+ payment: X402SettledPayment;
855
+ paymentResponseHeader: string;
856
+ commit: () => Promise<void>;
857
+ release: () => Promise<void>;
858
+ };
859
+ /**
860
+ * 프레임워크에 묶이지 않은 x402 게이트.
861
+ *
862
+ * Express 는 아래 `x402()` 어댑터를 쓰면 되고, Hono/Fastify/Next 등 다른 런타임은 이 클래스를 직접
863
+ * 감싸면 된다 — 헤더 하나와 resource URL 만 넘기면 "402 를 내려라 / 리소스를 내줘라" 를 돌려준다.
864
+ */
865
+ declare class X402Gate {
866
+ private readonly opts;
867
+ private readonly baseUrl;
868
+ private readonly timeout;
869
+ private readonly store;
870
+ private readonly maxTimeoutSeconds;
871
+ constructor(opts: X402GateOptions);
872
+ /** 이 리소스의 402 바디를 서버에서 받아온다(수수료 분해 포함). */
873
+ requirements(resource: string): Promise<X402Requirements>;
874
+ /**
875
+ * `X-PAYMENT` 헤더 1건을 처리한다. 헤더가 없거나 깨졌으면 402, 정산까지 끝나면 `settled`.
876
+ *
877
+ * 순서가 중요하다: **점유 → verify → settle → 소진 기록**. 점유를 verify 앞에 두어야 같은
878
+ * authorization 을 동시에 밀어 넣어도 한 요청만 리소스를 받는다.
879
+ */
880
+ collect(input: {
881
+ header?: string | null;
882
+ resource: string;
883
+ }): Promise<X402GateResult>;
884
+ private firstAccept;
885
+ private paymentRequired;
886
+ private request;
887
+ }
888
+ /** Express 요청에서 이 미들웨어가 실제로 읽는 필드만 추린 구조적 타입. */
889
+ interface X402Request {
890
+ headers: Record<string, string | string[] | undefined>;
891
+ protocol?: string;
892
+ baseUrl?: string;
893
+ path?: string;
894
+ url?: string;
895
+ originalUrl?: string;
896
+ get?(name: string): string | undefined;
897
+ /** 정산 정보를 핸들러에서 쓸 수 있게 여기에 붙여준다. */
898
+ x402Payment?: X402SettledPayment;
899
+ }
900
+ /** Express 응답에서 이 미들웨어가 실제로 쓰는 메서드만 추린 구조적 타입. */
901
+ interface X402Response {
902
+ status(code: number): X402Response;
903
+ json(body: unknown): unknown;
904
+ setHeader(name: string, value: string): unknown;
905
+ /** 응답이 실제로 나갔는지 보고 결제를 소진 처리한다. 없으면 즉시 소진 처리로 폴백. */
906
+ once?(event: string, listener: () => void): unknown;
907
+ statusCode?: number;
908
+ writableEnded?: boolean;
909
+ }
910
+ interface X402MiddlewareOptions extends X402GateOptions {
911
+ /**
912
+ * 이 결제가 묶일 리소스 URL. 기본값은 요청의 절대 URL(쿼리 제외).
913
+ * 결제는 이 값에 묶이므로 402 와 재요청에서 같은 값이 나와야 한다.
914
+ */
915
+ resource?: string | ((req: X402Request) => string);
916
+ /** 정산 확정 직후(핸들러 실행 전) 호출 — 로깅/사용량 집계용. */
917
+ onSettled?: (payment: X402SettledPayment, req: X402Request) => void | Promise<void>;
918
+ }
919
+ /**
920
+ * Express 라우트를 호출당 과금으로 감싼다.
921
+ *
922
+ * @example
923
+ * ```ts
924
+ * import { x402 } from '@bitpal/checkout';
925
+ *
926
+ * // 호출 1건당 $1.00 USDC
927
+ * app.get('/report', x402({
928
+ * apiKey: process.env.BITPAL_API_KEY!,
929
+ * payTo: '0xYourPayoutWallet',
930
+ * network: 'base',
931
+ * amount: '1000000',
932
+ * }), (req, res) => {
933
+ * res.json({ report: '…' });
934
+ * });
935
+ * ```
936
+ */
937
+ declare function x402(options: X402MiddlewareOptions): (req: X402Request, res: X402Response, next: (err?: unknown) => void) => Promise<void>;
938
+ /**
939
+ * base64(JSON) 이 표준이지만, 평문 JSON 을 보내는 클라이언트도 받아준다.
940
+ *
941
+ * 인증 전에 임의의 외부 입력을 파싱하는 자리라 크기 상한과 형태 검증을 여기서 끝낸다 —
942
+ * 아래를 통과한 값만 replay key 와 BitPal API 바디로 들어간다. (프로토타입 오염은 이 payload 를
943
+ * 어디에도 merge 하지 않아 직접 경로는 없지만, `__proto__` 같은 키를 그대로 통과시키지 않는다.)
944
+ */
945
+ declare function decodePaymentHeader(header: string): X402PaymentPayload;
946
+ /** payer 헬퍼가 만든 payload 를 `X-PAYMENT` 헤더 값으로 인코딩한다. */
947
+ declare function encodePaymentHeader(payload: X402PaymentPayload): string;
948
+
949
+ /**
950
+ * x402-pay — payer 측 헬퍼. 402 응답을 받아 `X-PAYMENT` 헤더를 만들어 준다.
951
+ *
952
+ * BitPal 의 402 는 수수료를 forwarder 컨트랙트 없이 나누기 위해 authorization 을 두 개 요구한다
953
+ * (머천트 몫 + 플랫폼 수수료 몫). 순정 x402 클라이언트는 그 확장(`feeBreakdown`)을 모르므로,
954
+ * payer 쪽에서 그 차이를 흡수하는 게 이 파일의 존재 이유다. `feeBreakdown` 이 없는 평범한 402 도
955
+ * 그대로 처리한다 — 그 경우 authorization 하나만 만든다.
956
+ *
957
+ * 서명은 직접 하지 않는다. 지갑/키가 있는 쪽에서 `signTypedData` 하나만 넘기면 되고, 그래서 이
958
+ * SDK 는 암호 라이브러리 의존이 없다(viem / ethers / 브라우저 지갑 전부 몇 줄로 연결된다).
959
+ */
960
+
961
+ /** EIP-3009 `TransferWithAuthorization` 의 EIP-712 타입 정의. */
962
+ declare const TRANSFER_WITH_AUTHORIZATION_TYPES: {
963
+ readonly TransferWithAuthorization: readonly [{
964
+ readonly name: "from";
965
+ readonly type: "address";
966
+ }, {
967
+ readonly name: "to";
968
+ readonly type: "address";
969
+ }, {
970
+ readonly name: "value";
971
+ readonly type: "uint256";
972
+ }, {
973
+ readonly name: "validAfter";
974
+ readonly type: "uint256";
975
+ }, {
976
+ readonly name: "validBefore";
977
+ readonly type: "uint256";
978
+ }, {
979
+ readonly name: "nonce";
980
+ readonly type: "bytes32";
981
+ }];
982
+ };
983
+ interface Eip712Domain {
984
+ name: string;
985
+ version: string;
986
+ chainId: number;
987
+ verifyingContract: string;
988
+ }
989
+ interface X402TypedDataRequest {
990
+ domain: Eip712Domain;
991
+ types: typeof TRANSFER_WITH_AUTHORIZATION_TYPES;
992
+ primaryType: 'TransferWithAuthorization';
993
+ message: {
994
+ from: string;
995
+ to: string;
996
+ value: string;
997
+ validAfter: string;
998
+ validBefore: string;
999
+ nonce: string;
1000
+ };
1001
+ }
1002
+ /**
1003
+ * EIP-712 서명자. 65바이트 `0x…` 서명 문자열을 돌려주면 된다.
1004
+ *
1005
+ * @example viem
1006
+ * ```ts
1007
+ * const sign: X402Signer = (req) => walletClient.signTypedData({ account, ...req });
1008
+ * ```
1009
+ * @example ethers v6
1010
+ * ```ts
1011
+ * const sign: X402Signer = (req) => wallet.signTypedData(req.domain, req.types, req.message);
1012
+ * ```
1013
+ */
1014
+ type X402Signer = (request: X402TypedDataRequest) => Promise<string> | string;
1015
+ interface CreateX402PaymentOptions {
1016
+ /** 402 응답 바디 전체, 또는 그 안의 `accepts[]` 한 칸. */
1017
+ requirements: X402Requirements | X402Accept;
1018
+ /** 지불자 주소. 서명자와 같은 주소여야 한다(서버가 recover 로 대조). */
1019
+ from: string;
1020
+ signTypedData: X402Signer;
1021
+ /**
1022
+ * 서명 유효시간(초). 기본은 402 의 `maxTimeoutSeconds`.
1023
+ * BitPal 은 60초 미만·3600초 초과를 거부한다.
1024
+ */
1025
+ validForSeconds?: number;
1026
+ /** 테스트에서 고정하기 위한 훅. 기본은 `Date.now()`. */
1027
+ now?: () => number;
1028
+ /** 테스트에서 고정하기 위한 훅. 기본은 CSPRNG 32바이트. */
1029
+ randomNonce?: () => string;
1030
+ }
1031
+ interface X402PaymentResult {
1032
+ /** 그대로 `X-PAYMENT` 헤더에 넣으면 되는 base64 문자열. */
1033
+ header: string;
1034
+ payload: X402PaymentPayload;
1035
+ /** 지갑에서 실제로 빠져나가는 총액(머천트 몫 + 수수료 몫). */
1036
+ totalAmount: string;
1037
+ }
1038
+ /**
1039
+ * 402 응답 → 서명된 `X-PAYMENT` 헤더.
1040
+ *
1041
+ * 수수료가 있으면 서명 프롬프트가 두 번 뜬다(머천트 몫 1건, 수수료 몫 1건). 두 authorization 은
1042
+ * 같은 유효창을 쓴다 — 수수료 쪽이 먼저 만료되면 머천트 정산 뒤 수수료만 못 걷히기 때문에
1043
+ * 서버가 "수수료 창이 결제 창을 덮을 것"을 요구한다.
1044
+ *
1045
+ * @example
1046
+ * ```ts
1047
+ * const res = await fetch(url);
1048
+ * if (res.status === 402) {
1049
+ * const { header } = await createX402Payment({
1050
+ * requirements: await res.json(),
1051
+ * from: account.address,
1052
+ * signTypedData: (req) => walletClient.signTypedData({ account, ...req }),
1053
+ * });
1054
+ * const paid = await fetch(url, { headers: { 'X-PAYMENT': header } });
1055
+ * }
1056
+ * ```
1057
+ */
1058
+ declare function createX402Payment(options: CreateX402PaymentOptions): Promise<X402PaymentResult>;
1059
+ /**
1060
+ * 서명 → `{v, r, s}`. BitPal API 가 요구하는 분해 형태.
1061
+ *
1062
+ * 65바이트(r‖s‖v)와 64바이트 EIP-2098 compact(r‖yParityAndS) 둘 다 받는다 — viem 의
1063
+ * `signTypedData` 는 65바이트를 주지만 일부 지갑·라이브러리는 compact 를 돌려준다.
1064
+ */
1065
+ declare function splitSignature(signature: string): {
1066
+ v: number;
1067
+ r: string;
1068
+ s: string;
1069
+ };
1070
+
680
1071
  /**
681
1072
  * @bitpal/checkout — BitPal Checkout SDK
682
1073
  *
@@ -705,4 +1096,4 @@ declare class BitPal {
705
1096
  constructor(config: BitPalConfig | string);
706
1097
  }
707
1098
 
708
- export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type DepositAddress, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPaymentLinksParams, type ListPricesParams, type ListProductsParams, type ListResponse, PaymentLinksResource, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, fromAtomic, fromAtomicUSDC, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature };
1099
+ export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type CreateX402PaymentOptions, type DepositAddress, type Eip3009Authorization, type Eip712Domain, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPaymentLinksParams, type ListPricesParams, type ListProductsParams, type ListResponse, PaymentLinksResource, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, TRANSFER_WITH_AUTHORIZATION_TYPES, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, type X402Accept, type X402ClaimState, type X402FeeBreakdown, X402Gate, type X402GateOptions, type X402GateResult, type X402MiddlewareOptions, type X402PaymentPayload, type X402PaymentResult, type X402ReplayStore, type X402Request, type X402Requirements, type X402Response, type X402SettledPayment, type X402Signer, type X402TypedDataRequest, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER, createMemoryReplayStore, createX402Payment, decodePaymentHeader, encodePaymentHeader, fromAtomic, fromAtomicUSDC, parseWebhookEvent, splitSignature, toAtomic, toAtomicUSDC, verifyWebhookSignature, x402 };