@khaime/react 0.1.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/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @khaime/react
2
+
3
+ Khaime Checkout SDK for React. Accept payments globally with a single component.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @khaime/react
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { KhaimeCheckout } from '@khaime/react';
15
+
16
+ function CheckoutPage({ token }) {
17
+ return (
18
+ <KhaimeCheckout
19
+ token={token}
20
+ onSuccess={(result) => {
21
+ console.log('Payment successful!', result);
22
+ window.location.href = '/success';
23
+ }}
24
+ onError={(error) => {
25
+ console.error('Payment failed:', error);
26
+ }}
27
+ onClose={() => {
28
+ console.log('Checkout closed');
29
+ }}
30
+ />
31
+ );
32
+ }
33
+ ```
34
+
35
+ ## How It Works
36
+
37
+ 1. Your backend calls Khaime API to create a payment intent
38
+ 2. Khaime returns a `token` containing gateway configuration
39
+ 3. Pass the `token` to `<KhaimeCheckout />`
40
+ 4. The component renders the correct payment UI automatically
41
+
42
+ ```
43
+ Your Backend Khaime API
44
+ │ │
45
+ │ POST /payment/intent │
46
+ │ { amount, currency, ... } │
47
+ │ ─────────────────────────────►
48
+ │ │
49
+ │ { token: "eyJ..." } │
50
+ │ ◄─────────────────────────────
51
+ │ │
52
+
53
+ <KhaimeCheckout token={token} />
54
+ ```
55
+
56
+ ## Supported Payment Gateways
57
+
58
+ | Currency | Gateway | UI |
59
+ |----------|---------|-----|
60
+ | USD, EUR, GBP, CAD | Stripe | Embedded card form |
61
+ | NGN, ZAR, KES | Paystack | Popup |
62
+ | GHS | StartButton | Redirect |
63
+
64
+ The SDK automatically renders the correct UI based on the token. Your code never needs to know about specific gateways.
65
+
66
+ ## Props
67
+
68
+ | Prop | Type | Required | Description |
69
+ |------|------|----------|-------------|
70
+ | `token` | `string` | Yes | Payment token from Khaime API |
71
+ | `onSuccess` | `(result) => void` | No | Called on successful payment |
72
+ | `onError` | `(error) => void` | No | Called on payment error |
73
+ | `onClose` | `() => void` | No | Called when checkout is closed |
74
+ | `onReady` | `() => void` | No | Called when checkout is ready |
75
+ | `productName` | `string` | No | Override product name display |
76
+ | `productImage` | `string` | No | Override product image |
77
+ | `showOrderSummary` | `boolean` | No | Show/hide order summary (default: true) |
78
+ | `submitButtonText` | `string` | No | Custom button text |
79
+ | `returnUrl` | `string` | No | URL to redirect after payment |
80
+ | `appearance` | `Appearance` | No | Stripe appearance customization |
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,144 @@
1
+ import React from 'react';
2
+ import { Appearance } from '@stripe/stripe-js';
3
+
4
+ type PaymentGateway = 'stripe' | 'paystack' | 'startbutton';
5
+ declare const PAYSTACK_CURRENCIES: readonly ["NGN", "GHS", "ZAR", "KES"];
6
+ interface PaymentResult {
7
+ success: boolean;
8
+ paymentIntentId?: string;
9
+ reference?: string;
10
+ gateway: PaymentGateway;
11
+ error?: PaymentError;
12
+ }
13
+ interface PaymentError {
14
+ code: string;
15
+ message: string;
16
+ }
17
+ interface DecodedPaymentToken {
18
+ intent_id: string;
19
+ merchant_id: number;
20
+ payment_gateway: PaymentGateway;
21
+ publishable_key?: string;
22
+ client_secret?: string;
23
+ public_key?: string;
24
+ access_code?: string;
25
+ authorization_url?: string;
26
+ redirect_url?: string;
27
+ merchant?: {
28
+ business_name: string;
29
+ logo: string | null;
30
+ };
31
+ product?: {
32
+ id: number;
33
+ name: string;
34
+ description: string | null;
35
+ type: string;
36
+ thumbnail: string | null;
37
+ currency: string;
38
+ };
39
+ amount: number;
40
+ currency: string;
41
+ customer_email?: string;
42
+ payment_type: 'one_time' | 'subscription';
43
+ metadata?: {
44
+ stripe_account_id?: string;
45
+ is_direct_charge?: boolean;
46
+ [key: string]: any;
47
+ };
48
+ iat?: number;
49
+ exp?: number;
50
+ }
51
+ interface KhaimeCheckoutProps {
52
+ token: string;
53
+ productName?: string;
54
+ productImage?: string;
55
+ showOrderSummary?: boolean;
56
+ submitButtonText?: string;
57
+ onSuccess?: (result: PaymentResult) => void;
58
+ onError?: (error: PaymentError) => void;
59
+ onReady?: () => void;
60
+ onClose?: () => void;
61
+ returnUrl?: string;
62
+ appearance?: Appearance;
63
+ className?: string;
64
+ }
65
+ interface KhaimePaymentElementProps {
66
+ token: string;
67
+ onSuccess?: (result: PaymentResult) => void;
68
+ onError?: (error: PaymentError) => void;
69
+ onReady?: () => void;
70
+ onClose?: () => void;
71
+ returnUrl?: string;
72
+ appearance?: Appearance;
73
+ className?: string;
74
+ }
75
+ interface KhaimeRedirectProps {
76
+ redirectUrl: string;
77
+ buttonText?: string;
78
+ onRedirect?: () => void;
79
+ className?: string;
80
+ }
81
+ /**
82
+ * Decode and verify payment token
83
+ *
84
+ * Token format: base64(payload).signature
85
+ * - Checks for valid signature format
86
+ * - Validates expiration time
87
+ * - Returns null if invalid or expired
88
+ */
89
+ declare function decodePaymentToken(token: string): DecodedPaymentToken | null;
90
+ declare function detectPaymentGateway(currency: string): PaymentGateway;
91
+
92
+ declare function KhaimeCheckout({ token, productName, productImage, showOrderSummary, submitButtonText, onSuccess, onError, onReady, onClose, returnUrl, appearance, }: KhaimeCheckoutProps): React.JSX.Element;
93
+
94
+ interface PaystackPaymentProps {
95
+ publicKey?: string;
96
+ accessCode?: string;
97
+ authorizationUrl?: string;
98
+ email?: string;
99
+ amount?: number;
100
+ currency?: string;
101
+ reference?: string;
102
+ onSuccess?: (result: PaymentResult) => void;
103
+ onError?: (error: PaymentError) => void;
104
+ onClose?: () => void;
105
+ onReady?: () => void;
106
+ submitButtonText?: string;
107
+ className?: string;
108
+ }
109
+ declare global {
110
+ interface Window {
111
+ PaystackPop?: {
112
+ setup: (config: {
113
+ key: string;
114
+ email: string;
115
+ amount: number;
116
+ currency?: string;
117
+ ref?: string;
118
+ onClose: () => void;
119
+ callback: (response: {
120
+ reference: string;
121
+ status: string;
122
+ }) => void;
123
+ }) => {
124
+ openIframe: () => void;
125
+ };
126
+ };
127
+ }
128
+ }
129
+ declare function PaystackPayment({ publicKey, accessCode, authorizationUrl, email, amount, currency, reference, onSuccess, onError, onClose, onReady, submitButtonText, className, }: PaystackPaymentProps): React.JSX.Element;
130
+
131
+ interface RedirectPaymentProps {
132
+ redirectUrl: string;
133
+ buttonText?: string;
134
+ productName?: string;
135
+ productImage?: string;
136
+ amount?: number;
137
+ currency?: string;
138
+ showOrderSummary?: boolean;
139
+ onRedirect?: () => void;
140
+ className?: string;
141
+ }
142
+ declare function RedirectPayment({ redirectUrl, buttonText, productName, productImage, amount, currency, showOrderSummary, onRedirect, className, }: RedirectPaymentProps): React.JSX.Element;
143
+
144
+ export { type DecodedPaymentToken, KhaimeCheckout, type KhaimeCheckoutProps, type KhaimePaymentElementProps, type KhaimeRedirectProps, PAYSTACK_CURRENCIES, type PaymentError, type PaymentGateway, type PaymentResult, PaystackPayment, RedirectPayment, decodePaymentToken, detectPaymentGateway };
@@ -0,0 +1,144 @@
1
+ import React from 'react';
2
+ import { Appearance } from '@stripe/stripe-js';
3
+
4
+ type PaymentGateway = 'stripe' | 'paystack' | 'startbutton';
5
+ declare const PAYSTACK_CURRENCIES: readonly ["NGN", "GHS", "ZAR", "KES"];
6
+ interface PaymentResult {
7
+ success: boolean;
8
+ paymentIntentId?: string;
9
+ reference?: string;
10
+ gateway: PaymentGateway;
11
+ error?: PaymentError;
12
+ }
13
+ interface PaymentError {
14
+ code: string;
15
+ message: string;
16
+ }
17
+ interface DecodedPaymentToken {
18
+ intent_id: string;
19
+ merchant_id: number;
20
+ payment_gateway: PaymentGateway;
21
+ publishable_key?: string;
22
+ client_secret?: string;
23
+ public_key?: string;
24
+ access_code?: string;
25
+ authorization_url?: string;
26
+ redirect_url?: string;
27
+ merchant?: {
28
+ business_name: string;
29
+ logo: string | null;
30
+ };
31
+ product?: {
32
+ id: number;
33
+ name: string;
34
+ description: string | null;
35
+ type: string;
36
+ thumbnail: string | null;
37
+ currency: string;
38
+ };
39
+ amount: number;
40
+ currency: string;
41
+ customer_email?: string;
42
+ payment_type: 'one_time' | 'subscription';
43
+ metadata?: {
44
+ stripe_account_id?: string;
45
+ is_direct_charge?: boolean;
46
+ [key: string]: any;
47
+ };
48
+ iat?: number;
49
+ exp?: number;
50
+ }
51
+ interface KhaimeCheckoutProps {
52
+ token: string;
53
+ productName?: string;
54
+ productImage?: string;
55
+ showOrderSummary?: boolean;
56
+ submitButtonText?: string;
57
+ onSuccess?: (result: PaymentResult) => void;
58
+ onError?: (error: PaymentError) => void;
59
+ onReady?: () => void;
60
+ onClose?: () => void;
61
+ returnUrl?: string;
62
+ appearance?: Appearance;
63
+ className?: string;
64
+ }
65
+ interface KhaimePaymentElementProps {
66
+ token: string;
67
+ onSuccess?: (result: PaymentResult) => void;
68
+ onError?: (error: PaymentError) => void;
69
+ onReady?: () => void;
70
+ onClose?: () => void;
71
+ returnUrl?: string;
72
+ appearance?: Appearance;
73
+ className?: string;
74
+ }
75
+ interface KhaimeRedirectProps {
76
+ redirectUrl: string;
77
+ buttonText?: string;
78
+ onRedirect?: () => void;
79
+ className?: string;
80
+ }
81
+ /**
82
+ * Decode and verify payment token
83
+ *
84
+ * Token format: base64(payload).signature
85
+ * - Checks for valid signature format
86
+ * - Validates expiration time
87
+ * - Returns null if invalid or expired
88
+ */
89
+ declare function decodePaymentToken(token: string): DecodedPaymentToken | null;
90
+ declare function detectPaymentGateway(currency: string): PaymentGateway;
91
+
92
+ declare function KhaimeCheckout({ token, productName, productImage, showOrderSummary, submitButtonText, onSuccess, onError, onReady, onClose, returnUrl, appearance, }: KhaimeCheckoutProps): React.JSX.Element;
93
+
94
+ interface PaystackPaymentProps {
95
+ publicKey?: string;
96
+ accessCode?: string;
97
+ authorizationUrl?: string;
98
+ email?: string;
99
+ amount?: number;
100
+ currency?: string;
101
+ reference?: string;
102
+ onSuccess?: (result: PaymentResult) => void;
103
+ onError?: (error: PaymentError) => void;
104
+ onClose?: () => void;
105
+ onReady?: () => void;
106
+ submitButtonText?: string;
107
+ className?: string;
108
+ }
109
+ declare global {
110
+ interface Window {
111
+ PaystackPop?: {
112
+ setup: (config: {
113
+ key: string;
114
+ email: string;
115
+ amount: number;
116
+ currency?: string;
117
+ ref?: string;
118
+ onClose: () => void;
119
+ callback: (response: {
120
+ reference: string;
121
+ status: string;
122
+ }) => void;
123
+ }) => {
124
+ openIframe: () => void;
125
+ };
126
+ };
127
+ }
128
+ }
129
+ declare function PaystackPayment({ publicKey, accessCode, authorizationUrl, email, amount, currency, reference, onSuccess, onError, onClose, onReady, submitButtonText, className, }: PaystackPaymentProps): React.JSX.Element;
130
+
131
+ interface RedirectPaymentProps {
132
+ redirectUrl: string;
133
+ buttonText?: string;
134
+ productName?: string;
135
+ productImage?: string;
136
+ amount?: number;
137
+ currency?: string;
138
+ showOrderSummary?: boolean;
139
+ onRedirect?: () => void;
140
+ className?: string;
141
+ }
142
+ declare function RedirectPayment({ redirectUrl, buttonText, productName, productImage, amount, currency, showOrderSummary, onRedirect, className, }: RedirectPaymentProps): React.JSX.Element;
143
+
144
+ export { type DecodedPaymentToken, KhaimeCheckout, type KhaimeCheckoutProps, type KhaimePaymentElementProps, type KhaimeRedirectProps, PAYSTACK_CURRENCIES, type PaymentError, type PaymentGateway, type PaymentResult, PaystackPayment, RedirectPayment, decodePaymentToken, detectPaymentGateway };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var D=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var Y=(e,t)=>{for(var a in t)D(e,a,{get:t[a],enumerable:!0})},Z=(e,t,a,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of $(t))!H.call(e,r)&&r!==a&&D(e,r,{get:()=>t[r],enumerable:!(s=O(t,r))||s.enumerable});return e};var q=e=>Z(D({},"__esModule",{value:!0}),e);var Q={};Y(Q,{KhaimeCheckout:()=>M,PAYSTACK_CURRENCIES:()=>F,PaystackPayment:()=>A,RedirectPayment:()=>L,decodePaymentToken:()=>N,detectPaymentGateway:()=>B});module.exports=q(Q);var I=require("react"),G=require("@stripe/stripe-js"),w=require("@stripe/react-stripe-js");var v=require("react"),d=require("react/jsx-runtime");function A({publicKey:e,accessCode:t,authorizationUrl:a,email:s="customer@example.com",amount:r,currency:y="NGN",reference:g,onSuccess:u,onError:c,onClose:i,onReady:m,submitButtonText:o,className:S}){let[f,p]=(0,v.useState)(!1),[h,R]=(0,v.useState)(!1),P=(0,v.useRef)(null);(0,v.useEffect)(()=>{if(e&&!P.current){let x=document.createElement("script");x.src="https://js.paystack.co/v1/inline.js",x.async=!0,x.onload=()=>{R(!0),m?.()},x.onerror=()=>{c?.({code:"script_load_error",message:"Failed to load Paystack"})},document.body.appendChild(x),P.current=x}else e||m?.();return()=>{}},[e,m,c]);let _=r?new Intl.NumberFormat("en-US",{style:"currency",currency:y}).format(r/100):null,E=(0,v.useCallback)(()=>{if(!window.PaystackPop){c?.({code:"paystack_not_loaded",message:"Paystack is not loaded yet"});return}if(!e||!r){c?.({code:"missing_credentials",message:"Payment credentials are missing"});return}p(!0),window.PaystackPop.setup({key:e,email:s,amount:r,currency:y,ref:g||`ref_${Date.now()}_${Math.random().toString(36).substring(2,8)}`,onClose:()=>{p(!1),i?.()},callback:T=>{p(!1),u?.({success:!0,reference:T.reference,gateway:"paystack"})}}).openIframe()},[e,s,r,y,g,u,c,i]),b=(0,v.useCallback)(()=>{let x=a||`https://checkout.paystack.com/${t}`;if(!x||!t&&!a){c?.({code:"missing_credentials",message:"Payment credentials are missing"});return}p(!0);let T=window.open(x,"paystack_payment","width=500,height=600,scrollbars=yes,resizable=yes");if(!T){window.location.href=x;return}let K=!1,W=null,U=z=>{z.data?.type==="khaime-checkout-return"&&(K=!0,W=z.data.reference)};window.addEventListener("message",U);let j=setInterval(()=>{T.closed&&(clearInterval(j),window.removeEventListener("message",U),p(!1),K&&W?u?.({success:!0,reference:W,gateway:"paystack"}):i?.())},500)},[t,a,u,c,i]),k=e?E:b,C=e?h:!0;return(0,d.jsxs)("div",{className:S,children:[_&&(0,d.jsxs)("div",{style:{textAlign:"center",marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:[(0,d.jsx)("div",{style:{fontSize:"14px",color:"#6b7280"},children:"Amount to pay"}),(0,d.jsx)("div",{style:{fontSize:"28px",fontWeight:700,color:"#1a1a1a"},children:_})]}),(0,d.jsx)("button",{type:"button",onClick:k,disabled:f||!C,style:{width:"100%",padding:"14px 16px",backgroundColor:"#0BA4DB",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:f||!C?"not-allowed":"pointer",opacity:f||!C?.7:1,display:"flex",alignItems:"center",justifyContent:"center",gap:"8px"},children:C?f?"Processing...":(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,d.jsx)("path",{d:"M12 2L2 7L12 12L22 7L12 2Z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),(0,d.jsx)("path",{d:"M2 17L12 22L22 17",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),(0,d.jsx)("path",{d:"M2 12L12 17L22 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})]}),o||(_?`Pay ${_}`:"Pay with Paystack")]}):"Loading..."}),(0,d.jsx)("p",{style:{marginTop:"12px",fontSize:"12px",color:"#9ca3af",textAlign:"center"},children:"Secured by Paystack"})]})}var l=require("react/jsx-runtime");function L({redirectUrl:e,buttonText:t="Continue to Payment",productName:a,productImage:s,amount:r,currency:y="USD",showOrderSummary:g=!0,onRedirect:u,className:c}){let i=r?new Intl.NumberFormat("en-US",{style:"currency",currency:y}).format(r/100):null;return(0,l.jsxs)("div",{className:c,style:{maxWidth:"480px",margin:"0 auto"},children:[g&&(a||i)&&(0,l.jsx)("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:(0,l.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[s&&(0,l.jsx)("img",{src:s,alt:a||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),(0,l.jsxs)("div",{style:{flex:1},children:[a&&(0,l.jsx)("div",{style:{fontWeight:600,color:"#1a1a1a"},children:a}),i&&(0,l.jsx)("div",{style:{fontSize:"18px",fontWeight:700,color:"#0070f3"},children:i})]})]})}),(0,l.jsxs)("button",{type:"button",onClick:()=>{u?.(),window.location.href=e},style:{width:"100%",padding:"14px 16px",backgroundColor:"#0070f3",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",gap:"8px"},children:[t,(0,l.jsxs)("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,l.jsx)("path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}),(0,l.jsx)("polyline",{points:"15 3 21 3 21 9"}),(0,l.jsx)("line",{x1:"10",y1:"14",x2:"21",y2:"3"})]})]}),(0,l.jsx)("p",{style:{marginTop:"12px",fontSize:"12px",color:"#9ca3af",textAlign:"center"},children:"You will be redirected to complete your payment"})]})}var F=["NGN","GHS","ZAR","KES"];function N(e){try{let t;if(e.includes(".")){let r=e.split(".");if(r.length!==2)return console.error("[KhaimeSDK] Invalid token format"),null;t=r[0]}else t=e;let a=atob(t),s=JSON.parse(a);if(s.exp){let r=Math.floor(Date.now()/1e3);if(s.exp<r)return console.error("[KhaimeSDK] Token expired"),null}return s}catch(t){return console.error("[KhaimeSDK] Failed to decode token:",t),null}}function B(e){return F.includes(e.toUpperCase())?"paystack":"stripe"}var n=require("react/jsx-runtime");function J({data:e,productName:t,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:y,onError:g,onReady:u,returnUrl:c}){let i=(0,w.useStripe)(),m=(0,w.useElements)(),[o,S]=(0,I.useState)(!1),[f,p]=(0,I.useState)(null),h=t||e.product?.name,R=a||e.product?.thumbnail,P=e.amount?new Intl.NumberFormat("en-US",{style:"currency",currency:e.currency}).format(e.amount/100):null;return(0,n.jsxs)("div",{style:{maxWidth:"480px",margin:"0 auto"},children:[s&&(h||P)&&(0,n.jsx)("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[R&&(0,n.jsx)("img",{src:R,alt:h||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),(0,n.jsxs)("div",{style:{flex:1},children:[h&&(0,n.jsx)("div",{style:{fontWeight:600,color:"#1a1a1a"},children:h}),P&&(0,n.jsx)("div",{style:{fontSize:"18px",fontWeight:700,color:"#0070f3"},children:P})]})]})}),(0,n.jsxs)("form",{onSubmit:async E=>{if(E.preventDefault(),!(!i||!m)){S(!0),p(null);try{let{error:b,paymentIntent:k}=await i.confirmPayment({elements:m,confirmParams:{return_url:c||window.location.href},redirect:"if_required"});if(b){let C={code:b.code||"unknown_error",message:b.message||"An unexpected error occurred"};p(C.message),g?.(C)}else k&&k.status==="succeeded"&&y?.({success:!0,paymentIntentId:k.id,gateway:"stripe"})}catch(b){let k={code:"unexpected_error",message:b instanceof Error?b.message:"An unexpected error occurred"};p(k.message),g?.(k)}finally{S(!1)}}},children:[(0,n.jsx)(w.PaymentElement,{onReady:u}),f&&(0,n.jsx)("div",{style:{color:"#df1b41",marginTop:"12px",fontSize:"14px"},children:f}),(0,n.jsx)("button",{type:"submit",disabled:!i||o,style:{marginTop:"24px",width:"100%",padding:"12px 16px",backgroundColor:"#0070f3",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:o||!i?"not-allowed":"pointer",opacity:o||!i?.7:1},children:o?"Processing...":r||(P?`Pay ${P}`:"Pay Now")})]})]})}function V({data:e,productName:t,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:y,onError:g,onReady:u,onClose:c}){let i=t||e.product?.name,m=a||e.product?.thumbnail,o=e.amount?new Intl.NumberFormat("en-US",{style:"currency",currency:e.currency}).format(e.amount/100):null;return(0,n.jsxs)("div",{style:{maxWidth:"480px",margin:"0 auto"},children:[s&&(i||o)&&(0,n.jsx)("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[m&&(0,n.jsx)("img",{src:m,alt:i||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),(0,n.jsxs)("div",{style:{flex:1},children:[i&&(0,n.jsx)("div",{style:{fontWeight:600,color:"#1a1a1a"},children:i}),o&&(0,n.jsx)("div",{style:{fontSize:"18px",fontWeight:700,color:"#0BA4DB"},children:o})]})]})}),(0,n.jsx)(A,{publicKey:e.public_key,accessCode:e.access_code,authorizationUrl:e.authorization_url,email:e.customer_email,amount:e.amount,currency:e.currency,reference:e.intent_id,onSuccess:y,onError:g,onClose:c,onReady:u,submitButtonText:r})]})}function M({token:e,productName:t,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:y,onError:g,onReady:u,onClose:c,returnUrl:i,appearance:m}){let o=(0,I.useMemo)(()=>N(e),[e]),S=o?.payment_gateway,f=o?.metadata?.stripe_account_id,p=o?.publishable_key,h=o?.client_secret,R=(0,I.useMemo)(()=>p?(0,G.loadStripe)(p,f?{stripeAccount:f}:void 0):null,[p,f]),P=(0,I.useMemo)(()=>h?{clientSecret:h,appearance:m,loader:"auto"}:null,[h,m]);if(!o)return(0,n.jsx)("div",{style:{color:"#df1b41",padding:"16px",textAlign:"center"},children:"Invalid payment token"});if(S==="startbutton"||o.redirect_url)return(0,n.jsx)(L,{redirectUrl:o.redirect_url||o.authorization_url||"",buttonText:r||"Continue to Payment",productName:t||o.product?.name,productImage:a||o.product?.thumbnail||void 0,amount:o.amount,currency:o.currency,showOrderSummary:s});if(S==="paystack")return(0,n.jsx)(V,{data:o,productName:t,productImage:a,showOrderSummary:s,submitButtonText:r,onSuccess:y,onError:g,onReady:u,onClose:c});if(!R||!P)return(0,n.jsx)("div",{style:{color:"#df1b41",padding:"16px",textAlign:"center"},children:"Invalid payment configuration"});let _=`${h}-${f||"no-account"}`;return(0,n.jsx)(w.Elements,{stripe:R,options:P,children:(0,n.jsx)(J,{data:o,productName:t,productImage:a,showOrderSummary:s,submitButtonText:r,onSuccess:y,onError:g,onReady:u,returnUrl:i})},_)}0&&(module.exports={KhaimeCheckout,PAYSTACK_CURRENCIES,PaystackPayment,RedirectPayment,decodePaymentToken,detectPaymentGateway});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/KhaimeCheckout.tsx","../src/PaystackPayment.tsx","../src/RedirectPayment.tsx","../src/types.ts"],"sourcesContent":["// Main component\nexport { KhaimeCheckout } from './KhaimeCheckout';\n\n// Sub-components (for advanced use cases)\nexport { PaystackPayment } from './PaystackPayment';\nexport { RedirectPayment } from './RedirectPayment';\n\n// Types\nexport type {\n KhaimeCheckoutProps,\n KhaimePaymentElementProps,\n KhaimeRedirectProps,\n PaymentResult,\n PaymentError,\n PaymentGateway,\n DecodedPaymentToken,\n} from './types';\n\n// Utilities\nexport { decodePaymentToken, detectPaymentGateway, PAYSTACK_CURRENCIES } from './types';\n","import React, { useState, useMemo } from 'react';\nimport { loadStripe } from '@stripe/stripe-js';\nimport {\n Elements,\n PaymentElement,\n useStripe,\n useElements,\n} from '@stripe/react-stripe-js';\nimport { PaystackPayment } from './PaystackPayment';\nimport { RedirectPayment } from './RedirectPayment';\nimport type {\n KhaimeCheckoutProps,\n PaymentError,\n PaymentGateway,\n DecodedPaymentToken,\n} from './types';\nimport { decodePaymentToken } from './types';\n\ninterface StripeCheckoutFormProps {\n data: DecodedPaymentToken;\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n onSuccess?: (result: { success: boolean; paymentIntentId?: string; gateway: PaymentGateway }) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n returnUrl?: string;\n}\n\nfunction StripeCheckoutForm({\n data,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n returnUrl,\n}: StripeCheckoutFormProps) {\n const stripe = useStripe();\n const elements = useElements();\n const [isLoading, setIsLoading] = useState(false);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const displayName = productName || data.product?.name;\n const displayImage = productImage || data.product?.thumbnail;\n\n const formattedAmount = data.amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: data.currency,\n }).format(data.amount / 100)\n : null;\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!stripe || !elements) {\n return;\n }\n\n setIsLoading(true);\n setErrorMessage(null);\n\n try {\n const { error, paymentIntent } = await stripe.confirmPayment({\n elements,\n confirmParams: {\n return_url: returnUrl || window.location.href,\n },\n redirect: 'if_required',\n });\n\n if (error) {\n const paymentError: PaymentError = {\n code: error.code || 'unknown_error',\n message: error.message || 'An unexpected error occurred',\n };\n setErrorMessage(paymentError.message);\n onError?.(paymentError);\n } else if (paymentIntent && paymentIntent.status === 'succeeded') {\n onSuccess?.({\n success: true,\n paymentIntentId: paymentIntent.id,\n gateway: 'stripe',\n });\n }\n } catch (err) {\n const paymentError: PaymentError = {\n code: 'unexpected_error',\n message: err instanceof Error ? err.message : 'An unexpected error occurred',\n };\n setErrorMessage(paymentError.message);\n onError?.(paymentError);\n } finally {\n setIsLoading(false);\n }\n };\n\n return (\n <div style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (displayName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {displayImage && (\n <img\n src={displayImage}\n alt={displayName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {displayName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{displayName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0070f3' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <form onSubmit={handleSubmit}>\n <PaymentElement onReady={onReady} />\n {errorMessage && (\n <div style={{ color: '#df1b41', marginTop: '12px', fontSize: '14px' }}>\n {errorMessage}\n </div>\n )}\n <button\n type=\"submit\"\n disabled={!stripe || isLoading}\n style={{\n marginTop: '24px',\n width: '100%',\n padding: '12px 16px',\n backgroundColor: '#0070f3',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: isLoading || !stripe ? 'not-allowed' : 'pointer',\n opacity: isLoading || !stripe ? 0.7 : 1,\n }}\n >\n {isLoading\n ? 'Processing...'\n : submitButtonText || (formattedAmount ? `Pay ${formattedAmount}` : 'Pay Now')}\n </button>\n </form>\n </div>\n );\n}\n\ninterface PaystackCheckoutFormProps {\n data: DecodedPaymentToken;\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n onSuccess?: (result: { success: boolean; reference?: string; gateway: PaymentGateway }) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n}\n\nfunction PaystackCheckoutForm({\n data,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n onClose,\n}: PaystackCheckoutFormProps) {\n const displayName = productName || data.product?.name;\n const displayImage = productImage || data.product?.thumbnail;\n\n const formattedAmount = data.amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: data.currency,\n }).format(data.amount / 100)\n : null;\n\n return (\n <div style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (displayName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {displayImage && (\n <img\n src={displayImage}\n alt={displayName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {displayName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{displayName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0BA4DB' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <PaystackPayment\n publicKey={data.public_key}\n accessCode={data.access_code}\n authorizationUrl={data.authorization_url}\n email={data.customer_email}\n amount={data.amount}\n currency={data.currency}\n reference={data.intent_id}\n onSuccess={onSuccess}\n onError={onError}\n onClose={onClose}\n onReady={onReady}\n submitButtonText={submitButtonText}\n />\n </div>\n );\n}\n\nexport function KhaimeCheckout({\n token,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n onClose,\n returnUrl,\n appearance,\n}: KhaimeCheckoutProps) {\n // Decode the token - all hooks must be at the top, before any conditionals\n const data = useMemo(() => decodePaymentToken(token), [token]);\n\n // Extract values needed for hooks (with fallbacks for when data is null)\n const gateway = data?.payment_gateway;\n const stripeAccountId = data?.metadata?.stripe_account_id;\n const publishableKey = data?.publishable_key;\n const clientSecret = data?.client_secret;\n\n // All useMemo hooks must be called unconditionally (React rules of hooks)\n const stripePromise = useMemo(\n () => {\n if (!publishableKey) return null;\n const options = stripeAccountId ? { stripeAccount: stripeAccountId } : undefined;\n return loadStripe(publishableKey, options);\n },\n [publishableKey, stripeAccountId]\n );\n\n const stripeOptions = useMemo(\n () => clientSecret ? {\n clientSecret,\n appearance,\n loader: 'auto' as const,\n } : null,\n [clientSecret, appearance]\n );\n\n // Now handle the different flows with conditional rendering\n if (!data) {\n return (\n <div style={{ color: '#df1b41', padding: '16px', textAlign: 'center' }}>\n Invalid payment token\n </div>\n );\n }\n\n // STARTBUTTON / REDIRECT-ONLY FLOW\n if (gateway === 'startbutton' || data.redirect_url) {\n return (\n <RedirectPayment\n redirectUrl={data.redirect_url || data.authorization_url || ''}\n buttonText={submitButtonText || 'Continue to Payment'}\n productName={productName || data.product?.name}\n productImage={productImage || data.product?.thumbnail || undefined}\n amount={data.amount}\n currency={data.currency}\n showOrderSummary={showOrderSummary}\n />\n );\n }\n\n // PAYSTACK FLOW\n if (gateway === 'paystack') {\n return (\n <PaystackCheckoutForm\n data={data}\n productName={productName}\n productImage={productImage}\n showOrderSummary={showOrderSummary}\n submitButtonText={submitButtonText}\n onSuccess={onSuccess}\n onError={onError}\n onReady={onReady}\n onClose={onClose}\n />\n );\n }\n\n // STRIPE FLOW\n if (!stripePromise || !stripeOptions) {\n return (\n <div style={{ color: '#df1b41', padding: '16px', textAlign: 'center' }}>\n Invalid payment configuration\n </div>\n );\n }\n\n // Key includes stripeAccountId to force remount when account changes\n const elementsKey = `${clientSecret}-${stripeAccountId || 'no-account'}`;\n\n return (\n <Elements key={elementsKey} stripe={stripePromise} options={stripeOptions}>\n <StripeCheckoutForm\n data={data}\n productName={productName}\n productImage={productImage}\n showOrderSummary={showOrderSummary}\n submitButtonText={submitButtonText}\n onSuccess={onSuccess}\n onError={onError}\n onReady={onReady}\n returnUrl={returnUrl}\n />\n </Elements>\n );\n}\n","import React, { useState, useCallback, useEffect, useRef } from 'react';\nimport type { PaymentResult, PaymentError } from './types';\n\ninterface PaystackPaymentProps {\n publicKey?: string;\n accessCode?: string;\n authorizationUrl?: string;\n email?: string;\n amount?: number;\n currency?: string;\n reference?: string;\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onClose?: () => void;\n onReady?: () => void;\n submitButtonText?: string;\n className?: string;\n}\n\n// Declare Paystack types\ndeclare global {\n interface Window {\n PaystackPop?: {\n setup: (config: {\n key: string;\n email: string;\n amount: number;\n currency?: string;\n ref?: string;\n onClose: () => void;\n callback: (response: { reference: string; status: string }) => void;\n }) => {\n openIframe: () => void;\n };\n };\n }\n}\n\nexport function PaystackPayment({\n publicKey,\n accessCode,\n authorizationUrl,\n email = 'customer@example.com',\n amount,\n currency = 'NGN',\n reference,\n onSuccess,\n onError,\n onClose,\n onReady,\n submitButtonText,\n className,\n}: PaystackPaymentProps) {\n const [isProcessing, setIsProcessing] = useState(false);\n const [scriptLoaded, setScriptLoaded] = useState(false);\n const scriptRef = useRef<HTMLScriptElement | null>(null);\n\n // Load Paystack inline script\n useEffect(() => {\n if (publicKey && !scriptRef.current) {\n const script = document.createElement('script');\n script.src = 'https://js.paystack.co/v1/inline.js';\n script.async = true;\n script.onload = () => {\n setScriptLoaded(true);\n onReady?.();\n };\n script.onerror = () => {\n onError?.({\n code: 'script_load_error',\n message: 'Failed to load Paystack',\n });\n };\n document.body.appendChild(script);\n scriptRef.current = script;\n } else if (!publicKey) {\n // No public key, using authorization URL approach\n onReady?.();\n }\n\n return () => {\n // Cleanup is handled on unmount of the whole component\n };\n }, [publicKey, onReady, onError]);\n\n const formattedAmount = amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency,\n }).format(amount / 100)\n : null;\n\n const handleInlinePayment = useCallback(() => {\n if (!window.PaystackPop) {\n onError?.({\n code: 'paystack_not_loaded',\n message: 'Paystack is not loaded yet',\n });\n return;\n }\n\n if (!publicKey || !amount) {\n onError?.({\n code: 'missing_credentials',\n message: 'Payment credentials are missing',\n });\n return;\n }\n\n setIsProcessing(true);\n\n const handler = window.PaystackPop.setup({\n key: publicKey,\n email: email,\n amount: amount, // Amount in kobo/cents\n currency: currency,\n ref: reference || `ref_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n onClose: () => {\n setIsProcessing(false);\n onClose?.();\n },\n callback: (response) => {\n setIsProcessing(false);\n onSuccess?.({\n success: true,\n reference: response.reference,\n gateway: 'paystack',\n });\n },\n });\n\n handler.openIframe();\n }, [publicKey, email, amount, currency, reference, onSuccess, onError, onClose]);\n\n const handlePopupPayment = useCallback(() => {\n const paymentUrl =\n authorizationUrl || `https://checkout.paystack.com/${accessCode}`;\n\n if (!paymentUrl || (!accessCode && !authorizationUrl)) {\n onError?.({\n code: 'missing_credentials',\n message: 'Payment credentials are missing',\n });\n return;\n }\n\n setIsProcessing(true);\n\n // Open Paystack popup\n const popup = window.open(\n paymentUrl,\n 'paystack_payment',\n 'width=500,height=600,scrollbars=yes,resizable=yes'\n );\n\n if (!popup) {\n // Popup blocked - fallback to redirect\n window.location.href = paymentUrl;\n return;\n }\n\n let paymentReturned = false;\n let paystackReference: string | null = null;\n\n // Listen for payment completion message from popup\n const messageHandler = (event: MessageEvent) => {\n if (event.data?.type === 'khaime-checkout-return') {\n paymentReturned = true;\n paystackReference = event.data.reference;\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n // Poll for popup closure\n const pollInterval = setInterval(() => {\n if (popup.closed) {\n clearInterval(pollInterval);\n window.removeEventListener('message', messageHandler);\n setIsProcessing(false);\n\n if (paymentReturned && paystackReference) {\n onSuccess?.({\n success: true,\n reference: paystackReference,\n gateway: 'paystack',\n });\n } else {\n // User closed popup without completing payment\n onClose?.();\n }\n }\n }, 500);\n }, [accessCode, authorizationUrl, onSuccess, onError, onClose]);\n\n // Determine which payment method to use\n const handlePayment = publicKey ? handleInlinePayment : handlePopupPayment;\n const isReady = publicKey ? scriptLoaded : true;\n\n return (\n <div className={className}>\n {formattedAmount && (\n <div\n style={{\n textAlign: 'center',\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ fontSize: '14px', color: '#6b7280' }}>Amount to pay</div>\n <div style={{ fontSize: '28px', fontWeight: 700, color: '#1a1a1a' }}>\n {formattedAmount}\n </div>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handlePayment}\n disabled={isProcessing || !isReady}\n style={{\n width: '100%',\n padding: '14px 16px',\n backgroundColor: '#0BA4DB',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: isProcessing || !isReady ? 'not-allowed' : 'pointer',\n opacity: isProcessing || !isReady ? 0.7 : 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n }}\n >\n {!isReady ? (\n 'Loading...'\n ) : isProcessing ? (\n 'Processing...'\n ) : (\n <>\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 2L2 7L12 12L22 7L12 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 17L12 22L22 17\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 12L12 17L22 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n {submitButtonText || (formattedAmount ? `Pay ${formattedAmount}` : 'Pay with Paystack')}\n </>\n )}\n </button>\n\n <p\n style={{\n marginTop: '12px',\n fontSize: '12px',\n color: '#9ca3af',\n textAlign: 'center',\n }}\n >\n Secured by Paystack\n </p>\n </div>\n );\n}\n","import React from 'react';\n\ninterface RedirectPaymentProps {\n redirectUrl: string;\n buttonText?: string;\n productName?: string;\n productImage?: string;\n amount?: number;\n currency?: string;\n showOrderSummary?: boolean;\n onRedirect?: () => void;\n className?: string;\n}\n\nexport function RedirectPayment({\n redirectUrl,\n buttonText = 'Continue to Payment',\n productName,\n productImage,\n amount,\n currency = 'USD',\n showOrderSummary = true,\n onRedirect,\n className,\n}: RedirectPaymentProps) {\n const formattedAmount = amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency,\n }).format(amount / 100)\n : null;\n\n const handleRedirect = () => {\n onRedirect?.();\n window.location.href = redirectUrl;\n };\n\n return (\n <div className={className} style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (productName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {productImage && (\n <img\n src={productImage}\n alt={productName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {productName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{productName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0070f3' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handleRedirect}\n style={{\n width: '100%',\n padding: '14px 16px',\n backgroundColor: '#0070f3',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n }}\n >\n {buttonText}\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n </button>\n\n <p\n style={{\n marginTop: '12px',\n fontSize: '12px',\n color: '#9ca3af',\n textAlign: 'center',\n }}\n >\n You will be redirected to complete your payment\n </p>\n </div>\n );\n}\n","import type { Appearance } from '@stripe/stripe-js';\n\nexport type PaymentGateway = 'stripe' | 'paystack' | 'startbutton';\n\nexport const PAYSTACK_CURRENCIES = ['NGN', 'GHS', 'ZAR', 'KES'] as const;\n\nexport interface PaymentResult {\n success: boolean;\n paymentIntentId?: string;\n reference?: string;\n gateway: PaymentGateway;\n error?: PaymentError;\n}\n\nexport interface PaymentError {\n code: string;\n message: string;\n}\n\n// Internal decoded token structure (developers never see this)\nexport interface DecodedPaymentToken {\n intent_id: string;\n merchant_id: number;\n payment_gateway: PaymentGateway;\n // Stripe fields\n publishable_key?: string;\n client_secret?: string;\n // Paystack fields\n public_key?: string;\n access_code?: string;\n authorization_url?: string;\n // Redirect fields\n redirect_url?: string;\n merchant?: {\n business_name: string;\n logo: string | null;\n };\n product?: {\n id: number;\n name: string;\n description: string | null;\n type: string;\n thumbnail: string | null;\n currency: string;\n };\n amount: number;\n currency: string;\n customer_email?: string;\n payment_type: 'one_time' | 'subscription';\n // Internal metadata (for connected accounts, etc.)\n metadata?: {\n stripe_account_id?: string;\n is_direct_charge?: boolean;\n [key: string]: any;\n };\n // Security fields\n iat?: number; // Issued at (Unix timestamp)\n exp?: number; // Expiration (Unix timestamp)\n}\n\n// What developers pass - simple props\nexport interface KhaimeCheckoutProps {\n token: string;\n // Optional product display overrides\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n // Callbacks\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n // Optional customization\n returnUrl?: string;\n appearance?: Appearance;\n className?: string;\n}\n\nexport interface KhaimePaymentElementProps {\n token: string;\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n returnUrl?: string;\n appearance?: Appearance;\n className?: string;\n}\n\n// For redirect-only flows (Startbutton, etc.)\nexport interface KhaimeRedirectProps {\n redirectUrl: string;\n buttonText?: string;\n onRedirect?: () => void;\n className?: string;\n}\n\n/**\n * Decode and verify payment token\n *\n * Token format: base64(payload).signature\n * - Checks for valid signature format\n * - Validates expiration time\n * - Returns null if invalid or expired\n */\nexport function decodePaymentToken(token: string): DecodedPaymentToken | null {\n try {\n // Handle signed token format: data.signature\n let data: string;\n\n if (token.includes('.')) {\n // Signed token format\n const parts = token.split('.');\n if (parts.length !== 2) {\n console.error('[KhaimeSDK] Invalid token format');\n return null;\n }\n data = parts[0];\n // Note: Signature verification happens server-side\n // Client just decodes and checks expiration\n } else {\n // Legacy unsigned token (backwards compatibility)\n data = token;\n }\n\n const decoded = atob(data);\n const payload = JSON.parse(decoded) as DecodedPaymentToken;\n\n // Check expiration if present\n if (payload.exp) {\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n console.error('[KhaimeSDK] Token expired');\n return null;\n }\n }\n\n return payload;\n } catch (error) {\n console.error('[KhaimeSDK] Failed to decode token:', error);\n return null;\n }\n}\n\nexport function detectPaymentGateway(currency: string): PaymentGateway {\n return PAYSTACK_CURRENCIES.includes(currency.toUpperCase() as any)\n ? 'paystack'\n : 'stripe';\n}\n"],"mappings":"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","names":["index_exports","__export","KhaimeCheckout","PAYSTACK_CURRENCIES","PaystackPayment","RedirectPayment","decodePaymentToken","detectPaymentGateway","__toCommonJS","import_react","import_stripe_js","import_react_stripe_js","import_react","import_jsx_runtime","PaystackPayment","publicKey","accessCode","authorizationUrl","email","amount","currency","reference","onSuccess","onError","onClose","onReady","submitButtonText","className","isProcessing","setIsProcessing","scriptLoaded","setScriptLoaded","scriptRef","script","formattedAmount","handleInlinePayment","response","handlePopupPayment","paymentUrl","popup","paymentReturned","paystackReference","messageHandler","event","pollInterval","handlePayment","isReady","import_jsx_runtime","RedirectPayment","redirectUrl","buttonText","productName","productImage","amount","currency","showOrderSummary","onRedirect","className","formattedAmount","PAYSTACK_CURRENCIES","decodePaymentToken","token","data","parts","decoded","payload","now","error","detectPaymentGateway","currency","import_jsx_runtime","StripeCheckoutForm","data","productName","productImage","showOrderSummary","submitButtonText","onSuccess","onError","onReady","returnUrl","stripe","elements","isLoading","setIsLoading","errorMessage","setErrorMessage","displayName","displayImage","formattedAmount","event","error","paymentIntent","paymentError","err","PaystackCheckoutForm","onClose","PaystackPayment","KhaimeCheckout","token","appearance","decodePaymentToken","gateway","stripeAccountId","publishableKey","clientSecret","stripePromise","stripeOptions","RedirectPayment","elementsKey"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{useState as M,useMemo as D}from"react";import{loadStripe as Z}from"@stripe/stripe-js";import{Elements as q,PaymentElement as J,useStripe as V,useElements as Q}from"@stripe/react-stripe-js";import{useState as z,useCallback as B,useEffect as O,useRef as $}from"react";import{Fragment as H,jsx as R,jsxs as T}from"react/jsx-runtime";function L({publicKey:e,accessCode:o,authorizationUrl:a,email:s="customer@example.com",amount:r,currency:m="NGN",reference:p,onSuccess:d,onError:c,onClose:n,onReady:l,submitButtonText:t,className:b}){let[y,u]=z(!1),[g,k]=z(!1),f=$(null);O(()=>{if(e&&!f.current){let h=document.createElement("script");h.src="https://js.paystack.co/v1/inline.js",h.async=!0,h.onload=()=>{k(!0),l?.()},h.onerror=()=>{c?.({code:"script_load_error",message:"Failed to load Paystack"})},document.body.appendChild(h),f.current=h}else e||l?.();return()=>{}},[e,l,c]);let w=r?new Intl.NumberFormat("en-US",{style:"currency",currency:m}).format(r/100):null,I=B(()=>{if(!window.PaystackPop){c?.({code:"paystack_not_loaded",message:"Paystack is not loaded yet"});return}if(!e||!r){c?.({code:"missing_credentials",message:"Payment credentials are missing"});return}u(!0),window.PaystackPop.setup({key:e,email:s,amount:r,currency:m,ref:p||`ref_${Date.now()}_${Math.random().toString(36).substring(2,8)}`,onClose:()=>{u(!1),n?.()},callback:E=>{u(!1),d?.({success:!0,reference:E.reference,gateway:"paystack"})}}).openIframe()},[e,s,r,m,p,d,c,n]),P=B(()=>{let h=a||`https://checkout.paystack.com/${o}`;if(!h||!o&&!a){c?.({code:"missing_credentials",message:"Payment credentials are missing"});return}u(!0);let E=window.open(h,"paystack_payment","width=500,height=600,scrollbars=yes,resizable=yes");if(!E){window.location.href=h;return}let F=!1,A=null,K=U=>{U.data?.type==="khaime-checkout-return"&&(F=!0,A=U.data.reference)};window.addEventListener("message",K);let j=setInterval(()=>{E.closed&&(clearInterval(j),window.removeEventListener("message",K),u(!1),F&&A?d?.({success:!0,reference:A,gateway:"paystack"}):n?.())},500)},[o,a,d,c,n]),x=e?I:P,S=e?g:!0;return T("div",{className:b,children:[w&&T("div",{style:{textAlign:"center",marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:[R("div",{style:{fontSize:"14px",color:"#6b7280"},children:"Amount to pay"}),R("div",{style:{fontSize:"28px",fontWeight:700,color:"#1a1a1a"},children:w})]}),R("button",{type:"button",onClick:x,disabled:y||!S,style:{width:"100%",padding:"14px 16px",backgroundColor:"#0BA4DB",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:y||!S?"not-allowed":"pointer",opacity:y||!S?.7:1,display:"flex",alignItems:"center",justifyContent:"center",gap:"8px"},children:S?y?"Processing...":T(H,{children:[T("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[R("path",{d:"M12 2L2 7L12 12L22 7L12 2Z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),R("path",{d:"M2 17L12 22L22 17",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),R("path",{d:"M2 12L12 17L22 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})]}),t||(w?`Pay ${w}`:"Pay with Paystack")]}):"Loading..."}),R("p",{style:{marginTop:"12px",fontSize:"12px",color:"#9ca3af",textAlign:"center"},children:"Secured by Paystack"})]})}import{jsx as v,jsxs as C}from"react/jsx-runtime";function N({redirectUrl:e,buttonText:o="Continue to Payment",productName:a,productImage:s,amount:r,currency:m="USD",showOrderSummary:p=!0,onRedirect:d,className:c}){let n=r?new Intl.NumberFormat("en-US",{style:"currency",currency:m}).format(r/100):null;return C("div",{className:c,style:{maxWidth:"480px",margin:"0 auto"},children:[p&&(a||n)&&v("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:C("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[s&&v("img",{src:s,alt:a||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),C("div",{style:{flex:1},children:[a&&v("div",{style:{fontWeight:600,color:"#1a1a1a"},children:a}),n&&v("div",{style:{fontSize:"18px",fontWeight:700,color:"#0070f3"},children:n})]})]})}),C("button",{type:"button",onClick:()=>{d?.(),window.location.href=e},style:{width:"100%",padding:"14px 16px",backgroundColor:"#0070f3",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",gap:"8px"},children:[o,C("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[v("path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}),v("polyline",{points:"15 3 21 3 21 9"}),v("line",{x1:"10",y1:"14",x2:"21",y2:"3"})]})]}),v("p",{style:{marginTop:"12px",fontSize:"12px",color:"#9ca3af",textAlign:"center"},children:"You will be redirected to complete your payment"})]})}var G=["NGN","GHS","ZAR","KES"];function W(e){try{let o;if(e.includes(".")){let r=e.split(".");if(r.length!==2)return console.error("[KhaimeSDK] Invalid token format"),null;o=r[0]}else o=e;let a=atob(o),s=JSON.parse(a);if(s.exp){let r=Math.floor(Date.now()/1e3);if(s.exp<r)return console.error("[KhaimeSDK] Token expired"),null}return s}catch(o){return console.error("[KhaimeSDK] Failed to decode token:",o),null}}function Y(e){return G.includes(e.toUpperCase())?"paystack":"stripe"}import{jsx as i,jsxs as _}from"react/jsx-runtime";function X({data:e,productName:o,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:m,onError:p,onReady:d,returnUrl:c}){let n=V(),l=Q(),[t,b]=M(!1),[y,u]=M(null),g=o||e.product?.name,k=a||e.product?.thumbnail,f=e.amount?new Intl.NumberFormat("en-US",{style:"currency",currency:e.currency}).format(e.amount/100):null;return _("div",{style:{maxWidth:"480px",margin:"0 auto"},children:[s&&(g||f)&&i("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:_("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[k&&i("img",{src:k,alt:g||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),_("div",{style:{flex:1},children:[g&&i("div",{style:{fontWeight:600,color:"#1a1a1a"},children:g}),f&&i("div",{style:{fontSize:"18px",fontWeight:700,color:"#0070f3"},children:f})]})]})}),_("form",{onSubmit:async I=>{if(I.preventDefault(),!(!n||!l)){b(!0),u(null);try{let{error:P,paymentIntent:x}=await n.confirmPayment({elements:l,confirmParams:{return_url:c||window.location.href},redirect:"if_required"});if(P){let S={code:P.code||"unknown_error",message:P.message||"An unexpected error occurred"};u(S.message),p?.(S)}else x&&x.status==="succeeded"&&m?.({success:!0,paymentIntentId:x.id,gateway:"stripe"})}catch(P){let x={code:"unexpected_error",message:P instanceof Error?P.message:"An unexpected error occurred"};u(x.message),p?.(x)}finally{b(!1)}}},children:[i(J,{onReady:d}),y&&i("div",{style:{color:"#df1b41",marginTop:"12px",fontSize:"14px"},children:y}),i("button",{type:"submit",disabled:!n||t,style:{marginTop:"24px",width:"100%",padding:"12px 16px",backgroundColor:"#0070f3",color:"white",border:"none",borderRadius:"6px",fontSize:"16px",fontWeight:600,cursor:t||!n?"not-allowed":"pointer",opacity:t||!n?.7:1},children:t?"Processing...":r||(f?`Pay ${f}`:"Pay Now")})]})]})}function ee({data:e,productName:o,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:m,onError:p,onReady:d,onClose:c}){let n=o||e.product?.name,l=a||e.product?.thumbnail,t=e.amount?new Intl.NumberFormat("en-US",{style:"currency",currency:e.currency}).format(e.amount/100):null;return _("div",{style:{maxWidth:"480px",margin:"0 auto"},children:[s&&(n||t)&&i("div",{style:{marginBottom:"24px",padding:"16px",backgroundColor:"#f9fafb",borderRadius:"8px"},children:_("div",{style:{display:"flex",alignItems:"center",gap:"12px"},children:[l&&i("img",{src:l,alt:n||"Product",style:{width:"48px",height:"48px",objectFit:"cover",borderRadius:"6px"}}),_("div",{style:{flex:1},children:[n&&i("div",{style:{fontWeight:600,color:"#1a1a1a"},children:n}),t&&i("div",{style:{fontSize:"18px",fontWeight:700,color:"#0BA4DB"},children:t})]})]})}),i(L,{publicKey:e.public_key,accessCode:e.access_code,authorizationUrl:e.authorization_url,email:e.customer_email,amount:e.amount,currency:e.currency,reference:e.intent_id,onSuccess:m,onError:p,onClose:c,onReady:d,submitButtonText:r})]})}function te({token:e,productName:o,productImage:a,showOrderSummary:s=!0,submitButtonText:r,onSuccess:m,onError:p,onReady:d,onClose:c,returnUrl:n,appearance:l}){let t=D(()=>W(e),[e]),b=t?.payment_gateway,y=t?.metadata?.stripe_account_id,u=t?.publishable_key,g=t?.client_secret,k=D(()=>u?Z(u,y?{stripeAccount:y}:void 0):null,[u,y]),f=D(()=>g?{clientSecret:g,appearance:l,loader:"auto"}:null,[g,l]);if(!t)return i("div",{style:{color:"#df1b41",padding:"16px",textAlign:"center"},children:"Invalid payment token"});if(b==="startbutton"||t.redirect_url)return i(N,{redirectUrl:t.redirect_url||t.authorization_url||"",buttonText:r||"Continue to Payment",productName:o||t.product?.name,productImage:a||t.product?.thumbnail||void 0,amount:t.amount,currency:t.currency,showOrderSummary:s});if(b==="paystack")return i(ee,{data:t,productName:o,productImage:a,showOrderSummary:s,submitButtonText:r,onSuccess:m,onError:p,onReady:d,onClose:c});if(!k||!f)return i("div",{style:{color:"#df1b41",padding:"16px",textAlign:"center"},children:"Invalid payment configuration"});let w=`${g}-${y||"no-account"}`;return i(q,{stripe:k,options:f,children:i(X,{data:t,productName:o,productImage:a,showOrderSummary:s,submitButtonText:r,onSuccess:m,onError:p,onReady:d,returnUrl:n})},w)}export{te as KhaimeCheckout,G as PAYSTACK_CURRENCIES,L as PaystackPayment,N as RedirectPayment,W as decodePaymentToken,Y as detectPaymentGateway};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/KhaimeCheckout.tsx","../src/PaystackPayment.tsx","../src/RedirectPayment.tsx","../src/types.ts"],"sourcesContent":["import React, { useState, useMemo } from 'react';\nimport { loadStripe } from '@stripe/stripe-js';\nimport {\n Elements,\n PaymentElement,\n useStripe,\n useElements,\n} from '@stripe/react-stripe-js';\nimport { PaystackPayment } from './PaystackPayment';\nimport { RedirectPayment } from './RedirectPayment';\nimport type {\n KhaimeCheckoutProps,\n PaymentError,\n PaymentGateway,\n DecodedPaymentToken,\n} from './types';\nimport { decodePaymentToken } from './types';\n\ninterface StripeCheckoutFormProps {\n data: DecodedPaymentToken;\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n onSuccess?: (result: { success: boolean; paymentIntentId?: string; gateway: PaymentGateway }) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n returnUrl?: string;\n}\n\nfunction StripeCheckoutForm({\n data,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n returnUrl,\n}: StripeCheckoutFormProps) {\n const stripe = useStripe();\n const elements = useElements();\n const [isLoading, setIsLoading] = useState(false);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const displayName = productName || data.product?.name;\n const displayImage = productImage || data.product?.thumbnail;\n\n const formattedAmount = data.amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: data.currency,\n }).format(data.amount / 100)\n : null;\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!stripe || !elements) {\n return;\n }\n\n setIsLoading(true);\n setErrorMessage(null);\n\n try {\n const { error, paymentIntent } = await stripe.confirmPayment({\n elements,\n confirmParams: {\n return_url: returnUrl || window.location.href,\n },\n redirect: 'if_required',\n });\n\n if (error) {\n const paymentError: PaymentError = {\n code: error.code || 'unknown_error',\n message: error.message || 'An unexpected error occurred',\n };\n setErrorMessage(paymentError.message);\n onError?.(paymentError);\n } else if (paymentIntent && paymentIntent.status === 'succeeded') {\n onSuccess?.({\n success: true,\n paymentIntentId: paymentIntent.id,\n gateway: 'stripe',\n });\n }\n } catch (err) {\n const paymentError: PaymentError = {\n code: 'unexpected_error',\n message: err instanceof Error ? err.message : 'An unexpected error occurred',\n };\n setErrorMessage(paymentError.message);\n onError?.(paymentError);\n } finally {\n setIsLoading(false);\n }\n };\n\n return (\n <div style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (displayName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {displayImage && (\n <img\n src={displayImage}\n alt={displayName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {displayName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{displayName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0070f3' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <form onSubmit={handleSubmit}>\n <PaymentElement onReady={onReady} />\n {errorMessage && (\n <div style={{ color: '#df1b41', marginTop: '12px', fontSize: '14px' }}>\n {errorMessage}\n </div>\n )}\n <button\n type=\"submit\"\n disabled={!stripe || isLoading}\n style={{\n marginTop: '24px',\n width: '100%',\n padding: '12px 16px',\n backgroundColor: '#0070f3',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: isLoading || !stripe ? 'not-allowed' : 'pointer',\n opacity: isLoading || !stripe ? 0.7 : 1,\n }}\n >\n {isLoading\n ? 'Processing...'\n : submitButtonText || (formattedAmount ? `Pay ${formattedAmount}` : 'Pay Now')}\n </button>\n </form>\n </div>\n );\n}\n\ninterface PaystackCheckoutFormProps {\n data: DecodedPaymentToken;\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n onSuccess?: (result: { success: boolean; reference?: string; gateway: PaymentGateway }) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n}\n\nfunction PaystackCheckoutForm({\n data,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n onClose,\n}: PaystackCheckoutFormProps) {\n const displayName = productName || data.product?.name;\n const displayImage = productImage || data.product?.thumbnail;\n\n const formattedAmount = data.amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: data.currency,\n }).format(data.amount / 100)\n : null;\n\n return (\n <div style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (displayName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {displayImage && (\n <img\n src={displayImage}\n alt={displayName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {displayName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{displayName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0BA4DB' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <PaystackPayment\n publicKey={data.public_key}\n accessCode={data.access_code}\n authorizationUrl={data.authorization_url}\n email={data.customer_email}\n amount={data.amount}\n currency={data.currency}\n reference={data.intent_id}\n onSuccess={onSuccess}\n onError={onError}\n onClose={onClose}\n onReady={onReady}\n submitButtonText={submitButtonText}\n />\n </div>\n );\n}\n\nexport function KhaimeCheckout({\n token,\n productName,\n productImage,\n showOrderSummary = true,\n submitButtonText,\n onSuccess,\n onError,\n onReady,\n onClose,\n returnUrl,\n appearance,\n}: KhaimeCheckoutProps) {\n // Decode the token - all hooks must be at the top, before any conditionals\n const data = useMemo(() => decodePaymentToken(token), [token]);\n\n // Extract values needed for hooks (with fallbacks for when data is null)\n const gateway = data?.payment_gateway;\n const stripeAccountId = data?.metadata?.stripe_account_id;\n const publishableKey = data?.publishable_key;\n const clientSecret = data?.client_secret;\n\n // All useMemo hooks must be called unconditionally (React rules of hooks)\n const stripePromise = useMemo(\n () => {\n if (!publishableKey) return null;\n const options = stripeAccountId ? { stripeAccount: stripeAccountId } : undefined;\n return loadStripe(publishableKey, options);\n },\n [publishableKey, stripeAccountId]\n );\n\n const stripeOptions = useMemo(\n () => clientSecret ? {\n clientSecret,\n appearance,\n loader: 'auto' as const,\n } : null,\n [clientSecret, appearance]\n );\n\n // Now handle the different flows with conditional rendering\n if (!data) {\n return (\n <div style={{ color: '#df1b41', padding: '16px', textAlign: 'center' }}>\n Invalid payment token\n </div>\n );\n }\n\n // STARTBUTTON / REDIRECT-ONLY FLOW\n if (gateway === 'startbutton' || data.redirect_url) {\n return (\n <RedirectPayment\n redirectUrl={data.redirect_url || data.authorization_url || ''}\n buttonText={submitButtonText || 'Continue to Payment'}\n productName={productName || data.product?.name}\n productImage={productImage || data.product?.thumbnail || undefined}\n amount={data.amount}\n currency={data.currency}\n showOrderSummary={showOrderSummary}\n />\n );\n }\n\n // PAYSTACK FLOW\n if (gateway === 'paystack') {\n return (\n <PaystackCheckoutForm\n data={data}\n productName={productName}\n productImage={productImage}\n showOrderSummary={showOrderSummary}\n submitButtonText={submitButtonText}\n onSuccess={onSuccess}\n onError={onError}\n onReady={onReady}\n onClose={onClose}\n />\n );\n }\n\n // STRIPE FLOW\n if (!stripePromise || !stripeOptions) {\n return (\n <div style={{ color: '#df1b41', padding: '16px', textAlign: 'center' }}>\n Invalid payment configuration\n </div>\n );\n }\n\n // Key includes stripeAccountId to force remount when account changes\n const elementsKey = `${clientSecret}-${stripeAccountId || 'no-account'}`;\n\n return (\n <Elements key={elementsKey} stripe={stripePromise} options={stripeOptions}>\n <StripeCheckoutForm\n data={data}\n productName={productName}\n productImage={productImage}\n showOrderSummary={showOrderSummary}\n submitButtonText={submitButtonText}\n onSuccess={onSuccess}\n onError={onError}\n onReady={onReady}\n returnUrl={returnUrl}\n />\n </Elements>\n );\n}\n","import React, { useState, useCallback, useEffect, useRef } from 'react';\nimport type { PaymentResult, PaymentError } from './types';\n\ninterface PaystackPaymentProps {\n publicKey?: string;\n accessCode?: string;\n authorizationUrl?: string;\n email?: string;\n amount?: number;\n currency?: string;\n reference?: string;\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onClose?: () => void;\n onReady?: () => void;\n submitButtonText?: string;\n className?: string;\n}\n\n// Declare Paystack types\ndeclare global {\n interface Window {\n PaystackPop?: {\n setup: (config: {\n key: string;\n email: string;\n amount: number;\n currency?: string;\n ref?: string;\n onClose: () => void;\n callback: (response: { reference: string; status: string }) => void;\n }) => {\n openIframe: () => void;\n };\n };\n }\n}\n\nexport function PaystackPayment({\n publicKey,\n accessCode,\n authorizationUrl,\n email = 'customer@example.com',\n amount,\n currency = 'NGN',\n reference,\n onSuccess,\n onError,\n onClose,\n onReady,\n submitButtonText,\n className,\n}: PaystackPaymentProps) {\n const [isProcessing, setIsProcessing] = useState(false);\n const [scriptLoaded, setScriptLoaded] = useState(false);\n const scriptRef = useRef<HTMLScriptElement | null>(null);\n\n // Load Paystack inline script\n useEffect(() => {\n if (publicKey && !scriptRef.current) {\n const script = document.createElement('script');\n script.src = 'https://js.paystack.co/v1/inline.js';\n script.async = true;\n script.onload = () => {\n setScriptLoaded(true);\n onReady?.();\n };\n script.onerror = () => {\n onError?.({\n code: 'script_load_error',\n message: 'Failed to load Paystack',\n });\n };\n document.body.appendChild(script);\n scriptRef.current = script;\n } else if (!publicKey) {\n // No public key, using authorization URL approach\n onReady?.();\n }\n\n return () => {\n // Cleanup is handled on unmount of the whole component\n };\n }, [publicKey, onReady, onError]);\n\n const formattedAmount = amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency,\n }).format(amount / 100)\n : null;\n\n const handleInlinePayment = useCallback(() => {\n if (!window.PaystackPop) {\n onError?.({\n code: 'paystack_not_loaded',\n message: 'Paystack is not loaded yet',\n });\n return;\n }\n\n if (!publicKey || !amount) {\n onError?.({\n code: 'missing_credentials',\n message: 'Payment credentials are missing',\n });\n return;\n }\n\n setIsProcessing(true);\n\n const handler = window.PaystackPop.setup({\n key: publicKey,\n email: email,\n amount: amount, // Amount in kobo/cents\n currency: currency,\n ref: reference || `ref_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n onClose: () => {\n setIsProcessing(false);\n onClose?.();\n },\n callback: (response) => {\n setIsProcessing(false);\n onSuccess?.({\n success: true,\n reference: response.reference,\n gateway: 'paystack',\n });\n },\n });\n\n handler.openIframe();\n }, [publicKey, email, amount, currency, reference, onSuccess, onError, onClose]);\n\n const handlePopupPayment = useCallback(() => {\n const paymentUrl =\n authorizationUrl || `https://checkout.paystack.com/${accessCode}`;\n\n if (!paymentUrl || (!accessCode && !authorizationUrl)) {\n onError?.({\n code: 'missing_credentials',\n message: 'Payment credentials are missing',\n });\n return;\n }\n\n setIsProcessing(true);\n\n // Open Paystack popup\n const popup = window.open(\n paymentUrl,\n 'paystack_payment',\n 'width=500,height=600,scrollbars=yes,resizable=yes'\n );\n\n if (!popup) {\n // Popup blocked - fallback to redirect\n window.location.href = paymentUrl;\n return;\n }\n\n let paymentReturned = false;\n let paystackReference: string | null = null;\n\n // Listen for payment completion message from popup\n const messageHandler = (event: MessageEvent) => {\n if (event.data?.type === 'khaime-checkout-return') {\n paymentReturned = true;\n paystackReference = event.data.reference;\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n // Poll for popup closure\n const pollInterval = setInterval(() => {\n if (popup.closed) {\n clearInterval(pollInterval);\n window.removeEventListener('message', messageHandler);\n setIsProcessing(false);\n\n if (paymentReturned && paystackReference) {\n onSuccess?.({\n success: true,\n reference: paystackReference,\n gateway: 'paystack',\n });\n } else {\n // User closed popup without completing payment\n onClose?.();\n }\n }\n }, 500);\n }, [accessCode, authorizationUrl, onSuccess, onError, onClose]);\n\n // Determine which payment method to use\n const handlePayment = publicKey ? handleInlinePayment : handlePopupPayment;\n const isReady = publicKey ? scriptLoaded : true;\n\n return (\n <div className={className}>\n {formattedAmount && (\n <div\n style={{\n textAlign: 'center',\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ fontSize: '14px', color: '#6b7280' }}>Amount to pay</div>\n <div style={{ fontSize: '28px', fontWeight: 700, color: '#1a1a1a' }}>\n {formattedAmount}\n </div>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handlePayment}\n disabled={isProcessing || !isReady}\n style={{\n width: '100%',\n padding: '14px 16px',\n backgroundColor: '#0BA4DB',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: isProcessing || !isReady ? 'not-allowed' : 'pointer',\n opacity: isProcessing || !isReady ? 0.7 : 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n }}\n >\n {!isReady ? (\n 'Loading...'\n ) : isProcessing ? (\n 'Processing...'\n ) : (\n <>\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 2L2 7L12 12L22 7L12 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 17L12 22L22 17\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 12L12 17L22 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n {submitButtonText || (formattedAmount ? `Pay ${formattedAmount}` : 'Pay with Paystack')}\n </>\n )}\n </button>\n\n <p\n style={{\n marginTop: '12px',\n fontSize: '12px',\n color: '#9ca3af',\n textAlign: 'center',\n }}\n >\n Secured by Paystack\n </p>\n </div>\n );\n}\n","import React from 'react';\n\ninterface RedirectPaymentProps {\n redirectUrl: string;\n buttonText?: string;\n productName?: string;\n productImage?: string;\n amount?: number;\n currency?: string;\n showOrderSummary?: boolean;\n onRedirect?: () => void;\n className?: string;\n}\n\nexport function RedirectPayment({\n redirectUrl,\n buttonText = 'Continue to Payment',\n productName,\n productImage,\n amount,\n currency = 'USD',\n showOrderSummary = true,\n onRedirect,\n className,\n}: RedirectPaymentProps) {\n const formattedAmount = amount\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency,\n }).format(amount / 100)\n : null;\n\n const handleRedirect = () => {\n onRedirect?.();\n window.location.href = redirectUrl;\n };\n\n return (\n <div className={className} style={{ maxWidth: '480px', margin: '0 auto' }}>\n {showOrderSummary && (productName || formattedAmount) && (\n <div\n style={{\n marginBottom: '24px',\n padding: '16px',\n backgroundColor: '#f9fafb',\n borderRadius: '8px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n {productImage && (\n <img\n src={productImage}\n alt={productName || 'Product'}\n style={{\n width: '48px',\n height: '48px',\n objectFit: 'cover',\n borderRadius: '6px',\n }}\n />\n )}\n <div style={{ flex: 1 }}>\n {productName && (\n <div style={{ fontWeight: 600, color: '#1a1a1a' }}>{productName}</div>\n )}\n {formattedAmount && (\n <div style={{ fontSize: '18px', fontWeight: 700, color: '#0070f3' }}>\n {formattedAmount}\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={handleRedirect}\n style={{\n width: '100%',\n padding: '14px 16px',\n backgroundColor: '#0070f3',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n }}\n >\n {buttonText}\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n </button>\n\n <p\n style={{\n marginTop: '12px',\n fontSize: '12px',\n color: '#9ca3af',\n textAlign: 'center',\n }}\n >\n You will be redirected to complete your payment\n </p>\n </div>\n );\n}\n","import type { Appearance } from '@stripe/stripe-js';\n\nexport type PaymentGateway = 'stripe' | 'paystack' | 'startbutton';\n\nexport const PAYSTACK_CURRENCIES = ['NGN', 'GHS', 'ZAR', 'KES'] as const;\n\nexport interface PaymentResult {\n success: boolean;\n paymentIntentId?: string;\n reference?: string;\n gateway: PaymentGateway;\n error?: PaymentError;\n}\n\nexport interface PaymentError {\n code: string;\n message: string;\n}\n\n// Internal decoded token structure (developers never see this)\nexport interface DecodedPaymentToken {\n intent_id: string;\n merchant_id: number;\n payment_gateway: PaymentGateway;\n // Stripe fields\n publishable_key?: string;\n client_secret?: string;\n // Paystack fields\n public_key?: string;\n access_code?: string;\n authorization_url?: string;\n // Redirect fields\n redirect_url?: string;\n merchant?: {\n business_name: string;\n logo: string | null;\n };\n product?: {\n id: number;\n name: string;\n description: string | null;\n type: string;\n thumbnail: string | null;\n currency: string;\n };\n amount: number;\n currency: string;\n customer_email?: string;\n payment_type: 'one_time' | 'subscription';\n // Internal metadata (for connected accounts, etc.)\n metadata?: {\n stripe_account_id?: string;\n is_direct_charge?: boolean;\n [key: string]: any;\n };\n // Security fields\n iat?: number; // Issued at (Unix timestamp)\n exp?: number; // Expiration (Unix timestamp)\n}\n\n// What developers pass - simple props\nexport interface KhaimeCheckoutProps {\n token: string;\n // Optional product display overrides\n productName?: string;\n productImage?: string;\n showOrderSummary?: boolean;\n submitButtonText?: string;\n // Callbacks\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n // Optional customization\n returnUrl?: string;\n appearance?: Appearance;\n className?: string;\n}\n\nexport interface KhaimePaymentElementProps {\n token: string;\n onSuccess?: (result: PaymentResult) => void;\n onError?: (error: PaymentError) => void;\n onReady?: () => void;\n onClose?: () => void;\n returnUrl?: string;\n appearance?: Appearance;\n className?: string;\n}\n\n// For redirect-only flows (Startbutton, etc.)\nexport interface KhaimeRedirectProps {\n redirectUrl: string;\n buttonText?: string;\n onRedirect?: () => void;\n className?: string;\n}\n\n/**\n * Decode and verify payment token\n *\n * Token format: base64(payload).signature\n * - Checks for valid signature format\n * - Validates expiration time\n * - Returns null if invalid or expired\n */\nexport function decodePaymentToken(token: string): DecodedPaymentToken | null {\n try {\n // Handle signed token format: data.signature\n let data: string;\n\n if (token.includes('.')) {\n // Signed token format\n const parts = token.split('.');\n if (parts.length !== 2) {\n console.error('[KhaimeSDK] Invalid token format');\n return null;\n }\n data = parts[0];\n // Note: Signature verification happens server-side\n // Client just decodes and checks expiration\n } else {\n // Legacy unsigned token (backwards compatibility)\n data = token;\n }\n\n const decoded = atob(data);\n const payload = JSON.parse(decoded) as DecodedPaymentToken;\n\n // Check expiration if present\n if (payload.exp) {\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n console.error('[KhaimeSDK] Token expired');\n return null;\n }\n }\n\n return payload;\n } catch (error) {\n console.error('[KhaimeSDK] Failed to decode token:', error);\n return null;\n }\n}\n\nexport function detectPaymentGateway(currency: string): PaymentGateway {\n return PAYSTACK_CURRENCIES.includes(currency.toUpperCase() as any)\n ? 'paystack'\n : 'stripe';\n}\n"],"mappings":"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","names":["useState","useMemo","loadStripe","Elements","PaymentElement","useStripe","useElements","useState","useCallback","useEffect","useRef","Fragment","jsx","jsxs","PaystackPayment","publicKey","accessCode","authorizationUrl","email","amount","currency","reference","onSuccess","onError","onClose","onReady","submitButtonText","className","isProcessing","setIsProcessing","scriptLoaded","setScriptLoaded","scriptRef","script","formattedAmount","handleInlinePayment","response","handlePopupPayment","paymentUrl","popup","paymentReturned","paystackReference","messageHandler","event","pollInterval","handlePayment","isReady","jsx","jsxs","RedirectPayment","redirectUrl","buttonText","productName","productImage","amount","currency","showOrderSummary","onRedirect","className","formattedAmount","PAYSTACK_CURRENCIES","decodePaymentToken","token","data","parts","decoded","payload","now","error","detectPaymentGateway","currency","jsx","jsxs","StripeCheckoutForm","data","productName","productImage","showOrderSummary","submitButtonText","onSuccess","onError","onReady","returnUrl","stripe","useStripe","elements","useElements","isLoading","setIsLoading","useState","errorMessage","setErrorMessage","displayName","displayImage","formattedAmount","event","error","paymentIntent","paymentError","err","PaymentElement","PaystackCheckoutForm","onClose","PaystackPayment","KhaimeCheckout","token","appearance","useMemo","decodePaymentToken","gateway","stripeAccountId","publishableKey","clientSecret","stripePromise","loadStripe","stripeOptions","RedirectPayment","elementsKey","Elements"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@khaime/react",
3
+ "version": "0.1.0",
4
+ "description": "Khaime Checkout SDK for React - Accept payments globally with a single component",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=17.0.0",
26
+ "react-dom": ">=17.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@stripe/react-stripe-js": "^2.4.0",
30
+ "@stripe/stripe-js": "^2.2.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "^18.2.0",
34
+ "@types/react-dom": "^18.2.0",
35
+ "react": "^18.2.0",
36
+ "react-dom": "^18.2.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.3.0"
39
+ },
40
+ "keywords": [
41
+ "khaime",
42
+ "checkout",
43
+ "payments",
44
+ "stripe",
45
+ "paystack",
46
+ "react",
47
+ "sdk"
48
+ ],
49
+ "author": "Khaime <support@khaime.com>",
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/khaime/khaime-react.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/khaime/khaime-react/issues"
57
+ },
58
+ "homepage": "https://docs.khaime.com/sdks/react"
59
+ }