@taprail/checkout 0.3.1
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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/chunk-UZGOWXDY.js +33 -0
- package/dist/index.cjs +33 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.global.js +33 -0
- package/dist/index.js +1 -0
- package/dist/react.cjs +33 -0
- package/dist/react.d.cts +22 -0
- package/dist/react.d.ts +22 -0
- package/dist/react.js +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Taprail
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# @taprail/checkout
|
|
2
|
+
|
|
3
|
+
Drop-in **Pay with Taprail** for the browser. Your customer pays inside the
|
|
4
|
+
Taprail app. Card details never touch your site, and your API key never
|
|
5
|
+
touches the browser.
|
|
6
|
+
|
|
7
|
+
The shape of it: your server creates an order with your secret key, hands the
|
|
8
|
+
result to the page, and the SDK takes it from there. On desktop that's a small
|
|
9
|
+
modal with a QR code the shopper scans. On a phone it opens the Taprail app
|
|
10
|
+
directly and shows a waiting card until they come back. The modal is themeable.
|
|
11
|
+
|
|
12
|
+
It's about 5 KB gzipped with zero dependencies, and ships ESM, CJS, a UMD
|
|
13
|
+
build for script tags, and a React subpath.
|
|
14
|
+
|
|
15
|
+
## 1. Create the order on your server
|
|
16
|
+
|
|
17
|
+
Never in the browser. This is the only call that uses your secret key.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
curl -X POST https://api.taprail.app/v1/merchant/orders \
|
|
21
|
+
-H "Authorization: Bearer $TAPRAIL_SECRET_KEY" \
|
|
22
|
+
-H "Idempotency-Key: order-1234" \
|
|
23
|
+
-H "Content-Type: application/json" \
|
|
24
|
+
-d '{"reference":"order-1234","amount_kobo":750000}'
|
|
25
|
+
# → { "id": "…", "deep_link": "https://pay.taprail.app/pay?o=…", … }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Send that JSON (or just the `id`) down to your page.
|
|
29
|
+
|
|
30
|
+
## 2. Open the checkout
|
|
31
|
+
|
|
32
|
+
With a script tag:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<script src="https://unpkg.com/@taprail/checkout"></script>
|
|
36
|
+
<script>
|
|
37
|
+
Taprail.open(intent, {
|
|
38
|
+
onSuccess: (r) => console.log("paid", r.transaction_reference),
|
|
39
|
+
onClose: () => console.log("dismissed"),
|
|
40
|
+
onError: (e) => console.error(e),
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
With a bundler:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { open } from "@taprail/checkout";
|
|
49
|
+
|
|
50
|
+
const result = await open(intent);
|
|
51
|
+
if (result.status === "approved") {
|
|
52
|
+
// show the confirmation screen
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
With React:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { TaprailButton, useTaprailCheckout } from "@taprail/checkout/react";
|
|
60
|
+
|
|
61
|
+
<TaprailButton intent={intent} onSuccess={(r) => alert("Paid!")}>
|
|
62
|
+
Pay ₦7,500
|
|
63
|
+
</TaprailButton>;
|
|
64
|
+
|
|
65
|
+
// or roll your own button:
|
|
66
|
+
const { open, pending } = useTaprailCheckout();
|
|
67
|
+
<button disabled={pending} onClick={() => open(intent)}>Pay</button>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`intent` can be the create-order response object, a bare order id, or a
|
|
71
|
+
`https://pay.taprail.app/pay?o=…` deep link. Whatever you have handy.
|
|
72
|
+
|
|
73
|
+
## What you get back
|
|
74
|
+
|
|
75
|
+
`open()` resolves with:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
{
|
|
79
|
+
status: "approved" | "declined" | "expired" | "cancelled" | "refunded" | "closed",
|
|
80
|
+
order_id: string,
|
|
81
|
+
transaction_reference: string | null, // set once approved
|
|
82
|
+
return_url: string | null // your dashboard-configured return URL
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`closed` means the shopper dismissed the modal before finishing.
|
|
87
|
+
|
|
88
|
+
If you've set a return URL in the dashboard and don't want to handle the
|
|
89
|
+
result in code, pass `redirect: true` and the SDK sends the browser there
|
|
90
|
+
itself, with `?order=<id>&status=<status>` appended.
|
|
91
|
+
|
|
92
|
+
One thing to keep straight: the browser result is for the user experience,
|
|
93
|
+
not for fulfilment. Ship goods off your `order.approved` webhook. The promise
|
|
94
|
+
resolves on both desktop and mobile, but a shopper can always close the tab or
|
|
95
|
+
the app before it does — so treat the result as a hint and let the webhook be
|
|
96
|
+
the source of truth.
|
|
97
|
+
|
|
98
|
+
## Theming
|
|
99
|
+
|
|
100
|
+
The modal chrome (backdrop, panel, the mobile waiting card, buttons) follows a
|
|
101
|
+
theme. Set a default once with `init`, or per checkout in `open`:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { init, open } from "@taprail/checkout";
|
|
105
|
+
|
|
106
|
+
// App-wide default (mode defaults to "auto" — follows the OS):
|
|
107
|
+
init({ theme: { accent: "#2f6df6" } }); // "auto" (default) | "light" | "dark"
|
|
108
|
+
|
|
109
|
+
// Or per call, with your brand:
|
|
110
|
+
await open(intent, {
|
|
111
|
+
theme: {
|
|
112
|
+
mode: "light",
|
|
113
|
+
accent: "#2f6df6", // primary button + focus rings
|
|
114
|
+
accentText: "#ffffff",
|
|
115
|
+
radius: 16, // modal corner radius, px
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Every field is optional and falls back to the monochrome default for the
|
|
121
|
+
resolved `mode`:
|
|
122
|
+
|
|
123
|
+
| Field | What it colors |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `mode` | `auto` (default — matches the OS), `light`, or `dark` |
|
|
126
|
+
| `accent` / `accentText` | The "Open Taprail" button and focus rings |
|
|
127
|
+
| `surface` | The modal / card background |
|
|
128
|
+
| `text` / `muted` | Primary and secondary text |
|
|
129
|
+
| `backdrop` | The scrim behind the modal (include your own alpha) |
|
|
130
|
+
| `radius` | Modal corner radius, in px |
|
|
131
|
+
| `fontFamily` | Font for the chrome |
|
|
132
|
+
|
|
133
|
+
The modal respects `prefers-reduced-motion`. Colors are best given as hex.
|
|
134
|
+
|
|
135
|
+
## Pointing at staging
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { init } from "@taprail/checkout";
|
|
139
|
+
|
|
140
|
+
init({
|
|
141
|
+
apiBase: "https://api.staging.taprail.app",
|
|
142
|
+
embedBase: "https://pay.staging.taprail.app",
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Skip this in production. The defaults are the live origins.
|
|
147
|
+
|
|
148
|
+
## Everything exported
|
|
149
|
+
|
|
150
|
+
| Export | What it does |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `open(intent, opts?)` | Opens the checkout, resolves with the result. `opts.theme` themes the modal |
|
|
153
|
+
| `getStatus(intent)` | One-shot status poll, no UI |
|
|
154
|
+
| `init(config)` | Override `apiBase` / `embedBase`, or set a default `theme` |
|
|
155
|
+
| `TaprailButton`, `useTaprailCheckout` | React bindings, from `@taprail/checkout/react` |
|
|
156
|
+
|
|
157
|
+
## Building from source
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm install && npm run build # → dist/ (esm, cjs, umd, .d.ts)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Full docs, including webhook verification and a sandbox testing guide, live at
|
|
164
|
+
the [Taprail developer docs](https://github.com/Taprail/docs).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var P=new Set(["approved","declined","expired","cancelled","refunded"]),d={apiBase:"https://api.taprail.app",embedBase:"https://pay.taprail.app",theme:{}};function se(e){e.apiBase&&(d.apiBase=e.apiBase.replace(/\/$/,"")),e.embedBase&&(d.embedBase=e.embedBase.replace(/\/$/,"")),e.theme&&(d.theme={...d.theme,...e.theme});}function V(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}var Z="com.taprail.consumer";function N(e){let t=encodeURIComponent(e);return typeof navigator<"u"&&/Android/i.test(navigator.userAgent)?`intent://pay?o=${t}#Intent;scheme=taprail;package=${Z};end`:`taprail://pay?o=${t}`}function R(e){try{return new URL(e).searchParams.get("o")}catch{return null}}function D(e){if(typeof e=="string"){if(/^https?:\/\//.test(e)){let i=R(e);if(!i)throw new Error("Taprail: deep link is missing the ?o=<orderId> parameter");return {id:i,deepLink:e}}return {id:e,deepLink:`${d.embedBase}/pay?o=${e}`}}let t=e.id||e.order_id||R(e.deep_link||"")||"";if(!t)throw new Error("Taprail: could not determine the order id from the intent");let n=e.deep_link||e.qr_payload||`${d.embedBase}/pay?o=${t}`;return {id:t,deepLink:n}}async function F(e){let t=await fetch(`${d.apiBase}/v1/checkout/orders/${e}`,{cache:"no-store"});if(!t.ok)return {_ok:false,status:"unknown",order_id:e,transaction_reference:null,return_url:null};let n=await t.json();return {_ok:true,status:n.status,order_id:n.id??e,transaction_reference:n.transaction_reference??null,return_url:n.return_url??null,merchant_name:n.merchant_name??void 0,amount_kobo:typeof n.amount_kobo=="number"?n.amount_kobo:void 0,currency:n.currency??void 0}}function Y(e,t){let n=e/100;return !t||t==="NGN"?"\u20A6"+n.toLocaleString("en-NG",{minimumFractionDigits:2,maximumFractionDigits:2}):`${t} ${n.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`}function W(e){if(e.return_url)try{let t=new URL(e.return_url);t.searchParams.set("order",e.order_id),t.searchParams.set("status",e.status),window.location.href=t.toString();}catch{}}var J='-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Apple Color Emoji", sans-serif',Q={surface:"#101013",text:"#f5f5f6",muted:"#8b8b93",accent:"#f5f5f6",accentText:"#0a0a0c",backdrop:"rgba(7,7,10,0.7)"},X={surface:"#ffffff",text:"#18181b",muted:"#6b7280",accent:"#18181b",accentText:"#ffffff",backdrop:"rgba(17,17,26,0.46)"};function ee(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-color-scheme: dark)").matches}function te(e){let t={...d.theme,...e},n=t.mode==="light"?"light":t.mode==="dark"||ee()?"dark":"light",i=n==="light"?X:Q;return {palette:{surface:t.surface??i.surface,text:t.text??i.text,muted:t.muted??i.muted,accent:t.accent??i.accent,accentText:t.accentText??i.accentText,backdrop:t.backdrop??i.backdrop},mode:n,radius:typeof t.radius=="number"?t.radius:20,font:t.fontFamily??J}}function re(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)return "127,127,127";let n=t[1];n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let i=parseInt(n,16);return `${i>>16&255},${i>>8&255},${i&255}`}var $="taprail-checkout-css",ne=`
|
|
2
|
+
[data-taprail="overlay"]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;background:var(--tr-backdrop);-webkit-backdrop-filter:blur(10px) saturate(1.05);backdrop-filter:blur(10px) saturate(1.05);font-family:var(--tr-font);animation:tr-fade .12s ease both;}
|
|
3
|
+
[data-taprail="frame"]{position:relative;width:min(420px,92vw);background:var(--tr-surface);border-radius:var(--tr-radius);overflow:hidden;box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.08),0 24px 70px -20px rgba(0,0,0,.55),0 8px 24px -16px rgba(0,0,0,.5);animation:tr-pop .19s cubic-bezier(.2,.9,.25,1) both;}
|
|
4
|
+
[data-taprail="frame"].tr-iframe{height:min(620px,90vh);}
|
|
5
|
+
[data-taprail="frame"] iframe{position:relative;z-index:1;width:100%;height:100%;border:0;display:block;background:transparent;}
|
|
6
|
+
[data-taprail="loader"]{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;color:var(--tr-text);}
|
|
7
|
+
.tr-logo{display:flex;align-items:center;justify-content:center;gap:7px;color:var(--tr-text);}
|
|
8
|
+
.tr-logo svg{display:block;flex:none;}
|
|
9
|
+
.tr-word{font-size:16px;font-weight:650;letter-spacing:-.02em;}
|
|
10
|
+
.tr-ring{width:26px;height:26px;border-radius:50%;border:2.5px solid rgba(var(--tr-text-rgb),.16);border-top-color:var(--tr-text);animation:tr-spin .7s linear infinite;}
|
|
11
|
+
.tr-ring.tr-sm{width:15px;height:15px;border-width:2px;}
|
|
12
|
+
[data-taprail="card"]{padding:38px 30px 30px;text-align:center;color:var(--tr-text);}
|
|
13
|
+
[data-taprail="card"] .tr-logo{margin:0 auto 22px;}
|
|
14
|
+
.tr-amount{font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:1.1;}
|
|
15
|
+
.tr-merchant{font-size:13px;color:var(--tr-muted);margin-top:3px;}
|
|
16
|
+
.tr-title{font-size:17px;font-weight:650;letter-spacing:-.01em;}
|
|
17
|
+
.tr-sub{font-size:13.5px;line-height:1.5;color:var(--tr-muted);margin:10px auto 22px;max-width:30ch;}
|
|
18
|
+
.tr-btn{display:block;width:100%;box-sizing:border-box;background:var(--tr-accent);color:var(--tr-accent-text);border:0;border-radius:999px;padding:14px 18px;font:inherit;font-size:15px;font-weight:650;text-decoration:none;cursor:pointer;transition:transform .1s ease,filter .12s ease;}
|
|
19
|
+
.tr-btn:hover{filter:brightness(1.06);}
|
|
20
|
+
.tr-btn:active{transform:scale(.975);}
|
|
21
|
+
.tr-wait{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12.5px;color:var(--tr-muted);margin-top:20px;}
|
|
22
|
+
.tr-cap{font-size:12.5px;color:var(--tr-muted);}
|
|
23
|
+
[data-taprail="close"]{position:absolute;top:12px;right:12px;z-index:3;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:50%;cursor:pointer;background:var(--tr-surface);color:var(--tr-text);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.14),0 2px 10px -2px rgba(0,0,0,.4);transition:transform .12s ease,box-shadow .12s ease;}
|
|
24
|
+
[data-taprail="close"] svg{display:block;opacity:.7;transition:opacity .12s ease;}
|
|
25
|
+
[data-taprail="close"]:hover{transform:scale(1.07);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.24),0 4px 14px -3px rgba(0,0,0,.5);}
|
|
26
|
+
[data-taprail="close"]:hover svg{opacity:1;}
|
|
27
|
+
[data-taprail="close"]:active{transform:scale(.93);}
|
|
28
|
+
[data-taprail="close"]:focus-visible{outline:2px solid var(--tr-accent);outline-offset:2px;}
|
|
29
|
+
@keyframes tr-fade{from{opacity:0}to{opacity:1}}
|
|
30
|
+
@keyframes tr-pop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
|
|
31
|
+
@keyframes tr-spin{to{transform:rotate(360deg)}}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){[data-taprail="overlay"],[data-taprail="frame"]{animation:none}.tr-ring{animation-duration:1.4s}.tr-btn,[data-taprail="close"]{transition:none}}
|
|
33
|
+
`;function ae(){if(typeof document>"u"||document.getElementById($))return;let e=document.createElement("style");e.id=$,e.textContent=ne,document.head.appendChild(e);}var ie='<svg viewBox="0 0 500 500" width="22" height="22" fill="none" aria-hidden="true" focusable="false"><path d="M147.789 394.059C214.872 439.472 307.74 425.816 359.214 362.946L371.749 347.654L437.045 393.713L423.39 410.225C385.37 456.004 331.287 486.708 271.399 496.516C211.059 506.557 151.221 494.72 102.868 463.262C50.9889 429.616 15.9579 377.523 4.20937 316.703C-1.75472 286.404 -1.3776 255.258 5.31847 225.103C12.0145 194.947 24.8939 166.394 43.1963 141.127L55.4452 124.132L120.503 170.19L109.04 186.311C61.1408 253.994 78.4657 347.194 147.789 394.059Z" fill="currentColor"/><path d="M500 310.578L428.222 321.724L386.423 70.7041L126.55 111.075L114.992 41.7101L377.391 0.879109C385.717 -0.444061 394.224 -0.272305 402.485 1.38576L405.059 1.91543C418.429 4.93491 430.565 11.7347 439.92 21.4473C449.274 31.16 455.423 43.3453 457.581 56.4489L500 310.578Z" fill="currentColor"/><path d="M257.046 329.232C306.454 329.232 346.506 290.526 346.506 242.779C346.506 195.033 306.454 156.327 257.046 156.327C207.639 156.327 167.586 195.033 167.586 242.779C167.586 290.526 207.639 329.232 257.046 329.232Z" fill="currentColor"/></svg>';function M(){let e=document.createElement("div");return e.className="tr-logo",e.innerHTML=`${ie}<span class="tr-word">Taprail</span>`,e}function O(e=false){let t=document.createElement("div");return t.className=e?"tr-ring tr-sm":"tr-ring",t}var oe='<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M3.5 3.5l8 8M11.5 3.5l-8 8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>';function ce(e,t={}){let{id:n}=D(e);return V()?(window.location.href=N(n),z(n,t,"app")):z(n,t,"iframe")}function z(e,t,n){return new Promise(i=>{let f=false,b=false,h,x=0,g=null,E=false,I=document.activeElement,j=document.body.style.overflow;ae();let{palette:l,mode:H,radius:G,font:U}=te(t.theme??{});document.querySelector('[data-taprail="overlay"]')?.remove();let c=document.createElement("div");c.setAttribute("data-taprail","overlay"),c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label","Pay with Taprail");let T={"--tr-surface":l.surface,"--tr-text":l.text,"--tr-text-rgb":re(l.text),"--tr-muted":l.muted,"--tr-accent":l.accent,"--tr-accent-text":l.accentText,"--tr-backdrop":l.backdrop,"--tr-radius":`${G}px`,"--tr-font":U};for(let r in T)c.style.setProperty(r,T[r]);let p=document.createElement("div");p.setAttribute("data-taprail","frame");let v;if(n==="iframe"){p.classList.add("tr-iframe");let r=document.createElement("div");r.setAttribute("data-taprail","loader");let a=document.createElement("div");a.className="tr-cap",a.textContent="Loading secure checkout\u2026",r.append(M(),O(),a);let s=document.createElement("iframe"),o=new URL(`${d.embedBase}/pay`);o.searchParams.set("o",e),o.searchParams.set("theme",H),o.searchParams.set("accent",l.accent),s.src=o.toString(),s.allow="clipboard-write",s.addEventListener("load",()=>r.remove()),p.appendChild(r),v=s;}else {let r=document.createElement("div");r.setAttribute("data-taprail","card");let a=document.createElement("div"),s=document.createElement("div");s.className="tr-title",s.textContent="Continue in the Taprail app",a.appendChild(s),g=a;let o=document.createElement("div");o.className="tr-sub",o.textContent="Approve this payment in the Taprail app, then come back here.";let u=document.createElement("a");u.className="tr-btn",u.href=N(e),u.textContent="Open Taprail";let C=document.createElement("div");C.className="tr-wait";let B=document.createElement("span");B.textContent="Waiting for approval",C.append(O(true),B),r.append(M(),a,o,u,C),v=r;}let m=document.createElement("button");m.setAttribute("data-taprail","close"),m.setAttribute("aria-label","Close"),m.innerHTML=oe;function K(){clearTimeout(h),window.removeEventListener("message",L),document.removeEventListener("keydown",A),document.removeEventListener("visibilitychange",S),window.removeEventListener("focus",_),c.remove(),document.body.style.overflow=j,I?.focus?.();}function q(r,a,s){if(!g||E||r==null)return;E=true,g.textContent="";let o=document.createElement("div");if(o.className="tr-amount",o.textContent=Y(r,a),g.appendChild(o),s){let u=document.createElement("div");u.className="tr-merchant",u.textContent=s,g.appendChild(u);}}function y(r){f||(f=true,K(),r.status==="approved"?t.onSuccess?.(r):r.status==="closed"&&t.onClose?.(),i(r),t.redirect&&r.status!=="closed"&&W(r));}function L(r){if(r.origin!==d.embedBase)return;let a=r.data;!a||a.source!=="taprail-checkout"||a.status&&P.has(a.status)&&y({status:a.status,order_id:a.order_id||e,transaction_reference:a.transaction_reference??null,return_url:a.return_url??null});}function A(r){r.key==="Escape"&&k();}function k(){y({status:"closed",order_id:e,transaction_reference:null,return_url:null});}async function w(){if(!(f||b)){b=true;try{let r=await F(e);if(x=0,n==="app"&&q(r.amount_kobo,r.currency,r.merchant_name),r._ok&&P.has(r.status)){y({status:r.status,order_id:r.order_id,transaction_reference:r.transaction_reference,return_url:r.return_url});return}}catch(r){x+=1,x===4&&t.onError?.(r instanceof Error?r:new Error(String(r)));}finally{b=false;}f||(h=setTimeout(w,n==="app"?1200:2e3));}}function _(){f||(clearTimeout(h),w());}function S(){document.visibilityState==="visible"&&_();}m.addEventListener("click",k),c.addEventListener("click",r=>{r.target===c&&k();}),document.addEventListener("keydown",A),window.addEventListener("message",L),document.addEventListener("visibilitychange",S),window.addEventListener("focus",_),p.appendChild(v),p.appendChild(m),c.appendChild(p),document.body.appendChild(c),document.body.style.overflow="hidden",m.focus(),h=setTimeout(w,n==="app"?450:800);})}async function de(e){let{id:t}=D(e),n=await F(t);return {status:n.status,order_id:n.order_id,transaction_reference:n.transaction_reference,return_url:n.return_url}}export{se as a,ce as b,de as c};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';var P=new Set(["approved","declined","expired","cancelled","refunded"]),d={apiBase:"https://api.taprail.app",embedBase:"https://pay.taprail.app",theme:{}};function se(e){e.apiBase&&(d.apiBase=e.apiBase.replace(/\/$/,"")),e.embedBase&&(d.embedBase=e.embedBase.replace(/\/$/,"")),e.theme&&(d.theme={...d.theme,...e.theme});}function V(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}var Z="com.taprail.consumer";function N(e){let t=encodeURIComponent(e);return typeof navigator<"u"&&/Android/i.test(navigator.userAgent)?`intent://pay?o=${t}#Intent;scheme=taprail;package=${Z};end`:`taprail://pay?o=${t}`}function R(e){try{return new URL(e).searchParams.get("o")}catch{return null}}function D(e){if(typeof e=="string"){if(/^https?:\/\//.test(e)){let i=R(e);if(!i)throw new Error("Taprail: deep link is missing the ?o=<orderId> parameter");return {id:i,deepLink:e}}return {id:e,deepLink:`${d.embedBase}/pay?o=${e}`}}let t=e.id||e.order_id||R(e.deep_link||"")||"";if(!t)throw new Error("Taprail: could not determine the order id from the intent");let n=e.deep_link||e.qr_payload||`${d.embedBase}/pay?o=${t}`;return {id:t,deepLink:n}}async function F(e){let t=await fetch(`${d.apiBase}/v1/checkout/orders/${e}`,{cache:"no-store"});if(!t.ok)return {_ok:false,status:"unknown",order_id:e,transaction_reference:null,return_url:null};let n=await t.json();return {_ok:true,status:n.status,order_id:n.id??e,transaction_reference:n.transaction_reference??null,return_url:n.return_url??null,merchant_name:n.merchant_name??void 0,amount_kobo:typeof n.amount_kobo=="number"?n.amount_kobo:void 0,currency:n.currency??void 0}}function Y(e,t){let n=e/100;return !t||t==="NGN"?"\u20A6"+n.toLocaleString("en-NG",{minimumFractionDigits:2,maximumFractionDigits:2}):`${t} ${n.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`}function W(e){if(e.return_url)try{let t=new URL(e.return_url);t.searchParams.set("order",e.order_id),t.searchParams.set("status",e.status),window.location.href=t.toString();}catch{}}var J='-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Apple Color Emoji", sans-serif',Q={surface:"#101013",text:"#f5f5f6",muted:"#8b8b93",accent:"#f5f5f6",accentText:"#0a0a0c",backdrop:"rgba(7,7,10,0.7)"},X={surface:"#ffffff",text:"#18181b",muted:"#6b7280",accent:"#18181b",accentText:"#ffffff",backdrop:"rgba(17,17,26,0.46)"};function ee(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-color-scheme: dark)").matches}function te(e){let t={...d.theme,...e},n=t.mode==="light"?"light":t.mode==="dark"||ee()?"dark":"light",i=n==="light"?X:Q;return {palette:{surface:t.surface??i.surface,text:t.text??i.text,muted:t.muted??i.muted,accent:t.accent??i.accent,accentText:t.accentText??i.accentText,backdrop:t.backdrop??i.backdrop},mode:n,radius:typeof t.radius=="number"?t.radius:20,font:t.fontFamily??J}}function re(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)return "127,127,127";let n=t[1];n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let i=parseInt(n,16);return `${i>>16&255},${i>>8&255},${i&255}`}var $="taprail-checkout-css",ne=`
|
|
2
|
+
[data-taprail="overlay"]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;background:var(--tr-backdrop);-webkit-backdrop-filter:blur(10px) saturate(1.05);backdrop-filter:blur(10px) saturate(1.05);font-family:var(--tr-font);animation:tr-fade .12s ease both;}
|
|
3
|
+
[data-taprail="frame"]{position:relative;width:min(420px,92vw);background:var(--tr-surface);border-radius:var(--tr-radius);overflow:hidden;box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.08),0 24px 70px -20px rgba(0,0,0,.55),0 8px 24px -16px rgba(0,0,0,.5);animation:tr-pop .19s cubic-bezier(.2,.9,.25,1) both;}
|
|
4
|
+
[data-taprail="frame"].tr-iframe{height:min(620px,90vh);}
|
|
5
|
+
[data-taprail="frame"] iframe{position:relative;z-index:1;width:100%;height:100%;border:0;display:block;background:transparent;}
|
|
6
|
+
[data-taprail="loader"]{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;color:var(--tr-text);}
|
|
7
|
+
.tr-logo{display:flex;align-items:center;justify-content:center;gap:7px;color:var(--tr-text);}
|
|
8
|
+
.tr-logo svg{display:block;flex:none;}
|
|
9
|
+
.tr-word{font-size:16px;font-weight:650;letter-spacing:-.02em;}
|
|
10
|
+
.tr-ring{width:26px;height:26px;border-radius:50%;border:2.5px solid rgba(var(--tr-text-rgb),.16);border-top-color:var(--tr-text);animation:tr-spin .7s linear infinite;}
|
|
11
|
+
.tr-ring.tr-sm{width:15px;height:15px;border-width:2px;}
|
|
12
|
+
[data-taprail="card"]{padding:38px 30px 30px;text-align:center;color:var(--tr-text);}
|
|
13
|
+
[data-taprail="card"] .tr-logo{margin:0 auto 22px;}
|
|
14
|
+
.tr-amount{font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:1.1;}
|
|
15
|
+
.tr-merchant{font-size:13px;color:var(--tr-muted);margin-top:3px;}
|
|
16
|
+
.tr-title{font-size:17px;font-weight:650;letter-spacing:-.01em;}
|
|
17
|
+
.tr-sub{font-size:13.5px;line-height:1.5;color:var(--tr-muted);margin:10px auto 22px;max-width:30ch;}
|
|
18
|
+
.tr-btn{display:block;width:100%;box-sizing:border-box;background:var(--tr-accent);color:var(--tr-accent-text);border:0;border-radius:999px;padding:14px 18px;font:inherit;font-size:15px;font-weight:650;text-decoration:none;cursor:pointer;transition:transform .1s ease,filter .12s ease;}
|
|
19
|
+
.tr-btn:hover{filter:brightness(1.06);}
|
|
20
|
+
.tr-btn:active{transform:scale(.975);}
|
|
21
|
+
.tr-wait{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12.5px;color:var(--tr-muted);margin-top:20px;}
|
|
22
|
+
.tr-cap{font-size:12.5px;color:var(--tr-muted);}
|
|
23
|
+
[data-taprail="close"]{position:absolute;top:12px;right:12px;z-index:3;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:50%;cursor:pointer;background:var(--tr-surface);color:var(--tr-text);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.14),0 2px 10px -2px rgba(0,0,0,.4);transition:transform .12s ease,box-shadow .12s ease;}
|
|
24
|
+
[data-taprail="close"] svg{display:block;opacity:.7;transition:opacity .12s ease;}
|
|
25
|
+
[data-taprail="close"]:hover{transform:scale(1.07);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.24),0 4px 14px -3px rgba(0,0,0,.5);}
|
|
26
|
+
[data-taprail="close"]:hover svg{opacity:1;}
|
|
27
|
+
[data-taprail="close"]:active{transform:scale(.93);}
|
|
28
|
+
[data-taprail="close"]:focus-visible{outline:2px solid var(--tr-accent);outline-offset:2px;}
|
|
29
|
+
@keyframes tr-fade{from{opacity:0}to{opacity:1}}
|
|
30
|
+
@keyframes tr-pop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
|
|
31
|
+
@keyframes tr-spin{to{transform:rotate(360deg)}}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){[data-taprail="overlay"],[data-taprail="frame"]{animation:none}.tr-ring{animation-duration:1.4s}.tr-btn,[data-taprail="close"]{transition:none}}
|
|
33
|
+
`;function ae(){if(typeof document>"u"||document.getElementById($))return;let e=document.createElement("style");e.id=$,e.textContent=ne,document.head.appendChild(e);}var ie='<svg viewBox="0 0 500 500" width="22" height="22" fill="none" aria-hidden="true" focusable="false"><path d="M147.789 394.059C214.872 439.472 307.74 425.816 359.214 362.946L371.749 347.654L437.045 393.713L423.39 410.225C385.37 456.004 331.287 486.708 271.399 496.516C211.059 506.557 151.221 494.72 102.868 463.262C50.9889 429.616 15.9579 377.523 4.20937 316.703C-1.75472 286.404 -1.3776 255.258 5.31847 225.103C12.0145 194.947 24.8939 166.394 43.1963 141.127L55.4452 124.132L120.503 170.19L109.04 186.311C61.1408 253.994 78.4657 347.194 147.789 394.059Z" fill="currentColor"/><path d="M500 310.578L428.222 321.724L386.423 70.7041L126.55 111.075L114.992 41.7101L377.391 0.879109C385.717 -0.444061 394.224 -0.272305 402.485 1.38576L405.059 1.91543C418.429 4.93491 430.565 11.7347 439.92 21.4473C449.274 31.16 455.423 43.3453 457.581 56.4489L500 310.578Z" fill="currentColor"/><path d="M257.046 329.232C306.454 329.232 346.506 290.526 346.506 242.779C346.506 195.033 306.454 156.327 257.046 156.327C207.639 156.327 167.586 195.033 167.586 242.779C167.586 290.526 207.639 329.232 257.046 329.232Z" fill="currentColor"/></svg>';function M(){let e=document.createElement("div");return e.className="tr-logo",e.innerHTML=`${ie}<span class="tr-word">Taprail</span>`,e}function O(e=false){let t=document.createElement("div");return t.className=e?"tr-ring tr-sm":"tr-ring",t}var oe='<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M3.5 3.5l8 8M11.5 3.5l-8 8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>';function ce(e,t={}){let{id:n}=D(e);return V()?(window.location.href=N(n),z(n,t,"app")):z(n,t,"iframe")}function z(e,t,n){return new Promise(i=>{let f=false,b=false,h,x=0,g=null,E=false,I=document.activeElement,j=document.body.style.overflow;ae();let{palette:l,mode:H,radius:G,font:U}=te(t.theme??{});document.querySelector('[data-taprail="overlay"]')?.remove();let c=document.createElement("div");c.setAttribute("data-taprail","overlay"),c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label","Pay with Taprail");let T={"--tr-surface":l.surface,"--tr-text":l.text,"--tr-text-rgb":re(l.text),"--tr-muted":l.muted,"--tr-accent":l.accent,"--tr-accent-text":l.accentText,"--tr-backdrop":l.backdrop,"--tr-radius":`${G}px`,"--tr-font":U};for(let r in T)c.style.setProperty(r,T[r]);let p=document.createElement("div");p.setAttribute("data-taprail","frame");let v;if(n==="iframe"){p.classList.add("tr-iframe");let r=document.createElement("div");r.setAttribute("data-taprail","loader");let a=document.createElement("div");a.className="tr-cap",a.textContent="Loading secure checkout\u2026",r.append(M(),O(),a);let s=document.createElement("iframe"),o=new URL(`${d.embedBase}/pay`);o.searchParams.set("o",e),o.searchParams.set("theme",H),o.searchParams.set("accent",l.accent),s.src=o.toString(),s.allow="clipboard-write",s.addEventListener("load",()=>r.remove()),p.appendChild(r),v=s;}else {let r=document.createElement("div");r.setAttribute("data-taprail","card");let a=document.createElement("div"),s=document.createElement("div");s.className="tr-title",s.textContent="Continue in the Taprail app",a.appendChild(s),g=a;let o=document.createElement("div");o.className="tr-sub",o.textContent="Approve this payment in the Taprail app, then come back here.";let u=document.createElement("a");u.className="tr-btn",u.href=N(e),u.textContent="Open Taprail";let C=document.createElement("div");C.className="tr-wait";let B=document.createElement("span");B.textContent="Waiting for approval",C.append(O(true),B),r.append(M(),a,o,u,C),v=r;}let m=document.createElement("button");m.setAttribute("data-taprail","close"),m.setAttribute("aria-label","Close"),m.innerHTML=oe;function K(){clearTimeout(h),window.removeEventListener("message",L),document.removeEventListener("keydown",A),document.removeEventListener("visibilitychange",S),window.removeEventListener("focus",_),c.remove(),document.body.style.overflow=j,I?.focus?.();}function q(r,a,s){if(!g||E||r==null)return;E=true,g.textContent="";let o=document.createElement("div");if(o.className="tr-amount",o.textContent=Y(r,a),g.appendChild(o),s){let u=document.createElement("div");u.className="tr-merchant",u.textContent=s,g.appendChild(u);}}function y(r){f||(f=true,K(),r.status==="approved"?t.onSuccess?.(r):r.status==="closed"&&t.onClose?.(),i(r),t.redirect&&r.status!=="closed"&&W(r));}function L(r){if(r.origin!==d.embedBase)return;let a=r.data;!a||a.source!=="taprail-checkout"||a.status&&P.has(a.status)&&y({status:a.status,order_id:a.order_id||e,transaction_reference:a.transaction_reference??null,return_url:a.return_url??null});}function A(r){r.key==="Escape"&&k();}function k(){y({status:"closed",order_id:e,transaction_reference:null,return_url:null});}async function w(){if(!(f||b)){b=true;try{let r=await F(e);if(x=0,n==="app"&&q(r.amount_kobo,r.currency,r.merchant_name),r._ok&&P.has(r.status)){y({status:r.status,order_id:r.order_id,transaction_reference:r.transaction_reference,return_url:r.return_url});return}}catch(r){x+=1,x===4&&t.onError?.(r instanceof Error?r:new Error(String(r)));}finally{b=false;}f||(h=setTimeout(w,n==="app"?1200:2e3));}}function _(){f||(clearTimeout(h),w());}function S(){document.visibilityState==="visible"&&_();}m.addEventListener("click",k),c.addEventListener("click",r=>{r.target===c&&k();}),document.addEventListener("keydown",A),window.addEventListener("message",L),document.addEventListener("visibilitychange",S),window.addEventListener("focus",_),p.appendChild(v),p.appendChild(m),c.appendChild(p),document.body.appendChild(c),document.body.style.overflow="hidden",m.focus(),h=setTimeout(w,n==="app"?450:800);})}async function de(e){let{id:t}=D(e),n=await F(t);return {status:n.status,order_id:n.order_id,transaction_reference:n.transaction_reference,return_url:n.return_url}}exports.getStatus=de;exports.init=se;exports.open=ce;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @taprail/checkout — drop-in "Pay with Taprail" for the browser.
|
|
3
|
+
*
|
|
4
|
+
* The SDK never sees card details or your API key. Your server creates the
|
|
5
|
+
* order with your secret key, then hands the SDK the resulting intent (or its
|
|
6
|
+
* order id / deep link). On desktop it opens a QR modal; on mobile it opens
|
|
7
|
+
* the Taprail app directly (no intermediate page) and shows a small waiting
|
|
8
|
+
* overlay. Either way the promise resolves with the terminal result.
|
|
9
|
+
*
|
|
10
|
+
* The modal chrome is themeable — set a default with `init({ theme })` or
|
|
11
|
+
* per checkout with `open(intent, { theme })`.
|
|
12
|
+
*/
|
|
13
|
+
/** Look-and-feel for the SDK-rendered modal chrome. */
|
|
14
|
+
interface TaprailTheme {
|
|
15
|
+
/** "auto" (default — follows the OS color scheme), "dark", or "light". */
|
|
16
|
+
mode?: "dark" | "light" | "auto";
|
|
17
|
+
/** Primary action color — the mobile "Open Taprail" button + focus rings. */
|
|
18
|
+
accent?: string;
|
|
19
|
+
/** Text color on the accent button. */
|
|
20
|
+
accentText?: string;
|
|
21
|
+
/** Panel / card background. */
|
|
22
|
+
surface?: string;
|
|
23
|
+
/** Primary text color. */
|
|
24
|
+
text?: string;
|
|
25
|
+
/** Secondary (muted) text color. */
|
|
26
|
+
muted?: string;
|
|
27
|
+
/** Backdrop scrim behind the modal — include your own alpha. */
|
|
28
|
+
backdrop?: string;
|
|
29
|
+
/** Corner radius of the modal, in px (default 20). */
|
|
30
|
+
radius?: number;
|
|
31
|
+
/** Font family for the chrome (defaults to the system UI stack). */
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
}
|
|
34
|
+
interface TaprailConfig {
|
|
35
|
+
/** API origin. Defaults to https://api.taprail.app */
|
|
36
|
+
apiBase?: string;
|
|
37
|
+
/** Where the embed page lives. Defaults to https://pay.taprail.app */
|
|
38
|
+
embedBase?: string;
|
|
39
|
+
/** Default theme for every checkout; override per call in `open()`. */
|
|
40
|
+
theme?: TaprailTheme;
|
|
41
|
+
}
|
|
42
|
+
interface CheckoutResult {
|
|
43
|
+
/** Terminal status: approved | declined | expired | cancelled | refunded | closed */
|
|
44
|
+
status: string;
|
|
45
|
+
order_id: string;
|
|
46
|
+
transaction_reference: string | null;
|
|
47
|
+
/** The merchant's dashboard-configured browser return URL, when set. */
|
|
48
|
+
return_url: string | null;
|
|
49
|
+
}
|
|
50
|
+
interface OpenOptions {
|
|
51
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
52
|
+
onError?: (error: Error) => void;
|
|
53
|
+
/** Fires when the shopper dismisses the modal before a terminal state. */
|
|
54
|
+
onClose?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* When true and the merchant has a return URL configured, navigate the page
|
|
57
|
+
* to it after a terminal status, appending ?order=<id>&status=<status>.
|
|
58
|
+
* Defaults to false: with the SDK you usually handle the result yourself.
|
|
59
|
+
*/
|
|
60
|
+
redirect?: boolean;
|
|
61
|
+
/** Theme for this checkout, merged over any theme set via `init()`. */
|
|
62
|
+
theme?: TaprailTheme;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What `Taprail.open` accepts:
|
|
66
|
+
* - the intent object returned by `POST /v1/merchant/orders` (`{ id, deep_link }`)
|
|
67
|
+
* - an order id string (`"6f1e…"`)
|
|
68
|
+
* - a deep link string (`"https://taprail.app/pay?o=6f1e…"`)
|
|
69
|
+
*/
|
|
70
|
+
type Intent = string | {
|
|
71
|
+
id?: string;
|
|
72
|
+
order_id?: string;
|
|
73
|
+
deep_link?: string;
|
|
74
|
+
qr_payload?: string;
|
|
75
|
+
};
|
|
76
|
+
declare function init(cfg: TaprailConfig): void;
|
|
77
|
+
/**
|
|
78
|
+
* Open the checkout and resolve with the terminal result.
|
|
79
|
+
*
|
|
80
|
+
* Desktop: QR modal (embed iframe). Mobile: opens the Taprail app directly —
|
|
81
|
+
* no intermediate web page — and shows a small waiting overlay on the
|
|
82
|
+
* merchant page; when the shopper approves in the app and switches back, the
|
|
83
|
+
* promise has resolved and the merchant UI can route to its receipt.
|
|
84
|
+
*/
|
|
85
|
+
declare function open(intent: Intent, opts?: OpenOptions): Promise<CheckoutResult>;
|
|
86
|
+
/** Imperative poll helper, if you want to track status without the modal. */
|
|
87
|
+
declare function getStatus(intent: Intent): Promise<CheckoutResult>;
|
|
88
|
+
|
|
89
|
+
export { type CheckoutResult, type Intent, type OpenOptions, type TaprailConfig, type TaprailTheme, getStatus, init, open };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @taprail/checkout — drop-in "Pay with Taprail" for the browser.
|
|
3
|
+
*
|
|
4
|
+
* The SDK never sees card details or your API key. Your server creates the
|
|
5
|
+
* order with your secret key, then hands the SDK the resulting intent (or its
|
|
6
|
+
* order id / deep link). On desktop it opens a QR modal; on mobile it opens
|
|
7
|
+
* the Taprail app directly (no intermediate page) and shows a small waiting
|
|
8
|
+
* overlay. Either way the promise resolves with the terminal result.
|
|
9
|
+
*
|
|
10
|
+
* The modal chrome is themeable — set a default with `init({ theme })` or
|
|
11
|
+
* per checkout with `open(intent, { theme })`.
|
|
12
|
+
*/
|
|
13
|
+
/** Look-and-feel for the SDK-rendered modal chrome. */
|
|
14
|
+
interface TaprailTheme {
|
|
15
|
+
/** "auto" (default — follows the OS color scheme), "dark", or "light". */
|
|
16
|
+
mode?: "dark" | "light" | "auto";
|
|
17
|
+
/** Primary action color — the mobile "Open Taprail" button + focus rings. */
|
|
18
|
+
accent?: string;
|
|
19
|
+
/** Text color on the accent button. */
|
|
20
|
+
accentText?: string;
|
|
21
|
+
/** Panel / card background. */
|
|
22
|
+
surface?: string;
|
|
23
|
+
/** Primary text color. */
|
|
24
|
+
text?: string;
|
|
25
|
+
/** Secondary (muted) text color. */
|
|
26
|
+
muted?: string;
|
|
27
|
+
/** Backdrop scrim behind the modal — include your own alpha. */
|
|
28
|
+
backdrop?: string;
|
|
29
|
+
/** Corner radius of the modal, in px (default 20). */
|
|
30
|
+
radius?: number;
|
|
31
|
+
/** Font family for the chrome (defaults to the system UI stack). */
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
}
|
|
34
|
+
interface TaprailConfig {
|
|
35
|
+
/** API origin. Defaults to https://api.taprail.app */
|
|
36
|
+
apiBase?: string;
|
|
37
|
+
/** Where the embed page lives. Defaults to https://pay.taprail.app */
|
|
38
|
+
embedBase?: string;
|
|
39
|
+
/** Default theme for every checkout; override per call in `open()`. */
|
|
40
|
+
theme?: TaprailTheme;
|
|
41
|
+
}
|
|
42
|
+
interface CheckoutResult {
|
|
43
|
+
/** Terminal status: approved | declined | expired | cancelled | refunded | closed */
|
|
44
|
+
status: string;
|
|
45
|
+
order_id: string;
|
|
46
|
+
transaction_reference: string | null;
|
|
47
|
+
/** The merchant's dashboard-configured browser return URL, when set. */
|
|
48
|
+
return_url: string | null;
|
|
49
|
+
}
|
|
50
|
+
interface OpenOptions {
|
|
51
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
52
|
+
onError?: (error: Error) => void;
|
|
53
|
+
/** Fires when the shopper dismisses the modal before a terminal state. */
|
|
54
|
+
onClose?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* When true and the merchant has a return URL configured, navigate the page
|
|
57
|
+
* to it after a terminal status, appending ?order=<id>&status=<status>.
|
|
58
|
+
* Defaults to false: with the SDK you usually handle the result yourself.
|
|
59
|
+
*/
|
|
60
|
+
redirect?: boolean;
|
|
61
|
+
/** Theme for this checkout, merged over any theme set via `init()`. */
|
|
62
|
+
theme?: TaprailTheme;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What `Taprail.open` accepts:
|
|
66
|
+
* - the intent object returned by `POST /v1/merchant/orders` (`{ id, deep_link }`)
|
|
67
|
+
* - an order id string (`"6f1e…"`)
|
|
68
|
+
* - a deep link string (`"https://taprail.app/pay?o=6f1e…"`)
|
|
69
|
+
*/
|
|
70
|
+
type Intent = string | {
|
|
71
|
+
id?: string;
|
|
72
|
+
order_id?: string;
|
|
73
|
+
deep_link?: string;
|
|
74
|
+
qr_payload?: string;
|
|
75
|
+
};
|
|
76
|
+
declare function init(cfg: TaprailConfig): void;
|
|
77
|
+
/**
|
|
78
|
+
* Open the checkout and resolve with the terminal result.
|
|
79
|
+
*
|
|
80
|
+
* Desktop: QR modal (embed iframe). Mobile: opens the Taprail app directly —
|
|
81
|
+
* no intermediate web page — and shows a small waiting overlay on the
|
|
82
|
+
* merchant page; when the shopper approves in the app and switches back, the
|
|
83
|
+
* promise has resolved and the merchant UI can route to its receipt.
|
|
84
|
+
*/
|
|
85
|
+
declare function open(intent: Intent, opts?: OpenOptions): Promise<CheckoutResult>;
|
|
86
|
+
/** Imperative poll helper, if you want to track status without the modal. */
|
|
87
|
+
declare function getStatus(intent: Intent): Promise<CheckoutResult>;
|
|
88
|
+
|
|
89
|
+
export { type CheckoutResult, type Intent, type OpenOptions, type TaprailConfig, type TaprailTheme, getStatus, init, open };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var Taprail=(function(exports){'use strict';var P=new Set(["approved","declined","expired","cancelled","refunded"]),d={apiBase:"https://api.taprail.app",embedBase:"https://pay.taprail.app",theme:{}};function se(e){e.apiBase&&(d.apiBase=e.apiBase.replace(/\/$/,"")),e.embedBase&&(d.embedBase=e.embedBase.replace(/\/$/,"")),e.theme&&(d.theme={...d.theme,...e.theme});}function V(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}var Z="com.taprail.consumer";function N(e){let t=encodeURIComponent(e);return typeof navigator<"u"&&/Android/i.test(navigator.userAgent)?`intent://pay?o=${t}#Intent;scheme=taprail;package=${Z};end`:`taprail://pay?o=${t}`}function R(e){try{return new URL(e).searchParams.get("o")}catch{return null}}function D(e){if(typeof e=="string"){if(/^https?:\/\//.test(e)){let i=R(e);if(!i)throw new Error("Taprail: deep link is missing the ?o=<orderId> parameter");return {id:i,deepLink:e}}return {id:e,deepLink:`${d.embedBase}/pay?o=${e}`}}let t=e.id||e.order_id||R(e.deep_link||"")||"";if(!t)throw new Error("Taprail: could not determine the order id from the intent");let n=e.deep_link||e.qr_payload||`${d.embedBase}/pay?o=${t}`;return {id:t,deepLink:n}}async function F(e){let t=await fetch(`${d.apiBase}/v1/checkout/orders/${e}`,{cache:"no-store"});if(!t.ok)return {_ok:false,status:"unknown",order_id:e,transaction_reference:null,return_url:null};let n=await t.json();return {_ok:true,status:n.status,order_id:n.id??e,transaction_reference:n.transaction_reference??null,return_url:n.return_url??null,merchant_name:n.merchant_name??void 0,amount_kobo:typeof n.amount_kobo=="number"?n.amount_kobo:void 0,currency:n.currency??void 0}}function Y(e,t){let n=e/100;return !t||t==="NGN"?"\u20A6"+n.toLocaleString("en-NG",{minimumFractionDigits:2,maximumFractionDigits:2}):`${t} ${n.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`}function W(e){if(e.return_url)try{let t=new URL(e.return_url);t.searchParams.set("order",e.order_id),t.searchParams.set("status",e.status),window.location.href=t.toString();}catch{}}var J='-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Apple Color Emoji", sans-serif',Q={surface:"#101013",text:"#f5f5f6",muted:"#8b8b93",accent:"#f5f5f6",accentText:"#0a0a0c",backdrop:"rgba(7,7,10,0.7)"},X={surface:"#ffffff",text:"#18181b",muted:"#6b7280",accent:"#18181b",accentText:"#ffffff",backdrop:"rgba(17,17,26,0.46)"};function ee(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-color-scheme: dark)").matches}function te(e){let t={...d.theme,...e},n=t.mode==="light"?"light":t.mode==="dark"||ee()?"dark":"light",i=n==="light"?X:Q;return {palette:{surface:t.surface??i.surface,text:t.text??i.text,muted:t.muted??i.muted,accent:t.accent??i.accent,accentText:t.accentText??i.accentText,backdrop:t.backdrop??i.backdrop},mode:n,radius:typeof t.radius=="number"?t.radius:20,font:t.fontFamily??J}}function re(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)return "127,127,127";let n=t[1];n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let i=parseInt(n,16);return `${i>>16&255},${i>>8&255},${i&255}`}var $="taprail-checkout-css",ne=`
|
|
2
|
+
[data-taprail="overlay"]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;background:var(--tr-backdrop);-webkit-backdrop-filter:blur(10px) saturate(1.05);backdrop-filter:blur(10px) saturate(1.05);font-family:var(--tr-font);animation:tr-fade .12s ease both;}
|
|
3
|
+
[data-taprail="frame"]{position:relative;width:min(420px,92vw);background:var(--tr-surface);border-radius:var(--tr-radius);overflow:hidden;box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.08),0 24px 70px -20px rgba(0,0,0,.55),0 8px 24px -16px rgba(0,0,0,.5);animation:tr-pop .19s cubic-bezier(.2,.9,.25,1) both;}
|
|
4
|
+
[data-taprail="frame"].tr-iframe{height:min(620px,90vh);}
|
|
5
|
+
[data-taprail="frame"] iframe{position:relative;z-index:1;width:100%;height:100%;border:0;display:block;background:transparent;}
|
|
6
|
+
[data-taprail="loader"]{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;color:var(--tr-text);}
|
|
7
|
+
.tr-logo{display:flex;align-items:center;justify-content:center;gap:7px;color:var(--tr-text);}
|
|
8
|
+
.tr-logo svg{display:block;flex:none;}
|
|
9
|
+
.tr-word{font-size:16px;font-weight:650;letter-spacing:-.02em;}
|
|
10
|
+
.tr-ring{width:26px;height:26px;border-radius:50%;border:2.5px solid rgba(var(--tr-text-rgb),.16);border-top-color:var(--tr-text);animation:tr-spin .7s linear infinite;}
|
|
11
|
+
.tr-ring.tr-sm{width:15px;height:15px;border-width:2px;}
|
|
12
|
+
[data-taprail="card"]{padding:38px 30px 30px;text-align:center;color:var(--tr-text);}
|
|
13
|
+
[data-taprail="card"] .tr-logo{margin:0 auto 22px;}
|
|
14
|
+
.tr-amount{font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:1.1;}
|
|
15
|
+
.tr-merchant{font-size:13px;color:var(--tr-muted);margin-top:3px;}
|
|
16
|
+
.tr-title{font-size:17px;font-weight:650;letter-spacing:-.01em;}
|
|
17
|
+
.tr-sub{font-size:13.5px;line-height:1.5;color:var(--tr-muted);margin:10px auto 22px;max-width:30ch;}
|
|
18
|
+
.tr-btn{display:block;width:100%;box-sizing:border-box;background:var(--tr-accent);color:var(--tr-accent-text);border:0;border-radius:999px;padding:14px 18px;font:inherit;font-size:15px;font-weight:650;text-decoration:none;cursor:pointer;transition:transform .1s ease,filter .12s ease;}
|
|
19
|
+
.tr-btn:hover{filter:brightness(1.06);}
|
|
20
|
+
.tr-btn:active{transform:scale(.975);}
|
|
21
|
+
.tr-wait{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12.5px;color:var(--tr-muted);margin-top:20px;}
|
|
22
|
+
.tr-cap{font-size:12.5px;color:var(--tr-muted);}
|
|
23
|
+
[data-taprail="close"]{position:absolute;top:12px;right:12px;z-index:3;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:50%;cursor:pointer;background:var(--tr-surface);color:var(--tr-text);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.14),0 2px 10px -2px rgba(0,0,0,.4);transition:transform .12s ease,box-shadow .12s ease;}
|
|
24
|
+
[data-taprail="close"] svg{display:block;opacity:.7;transition:opacity .12s ease;}
|
|
25
|
+
[data-taprail="close"]:hover{transform:scale(1.07);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.24),0 4px 14px -3px rgba(0,0,0,.5);}
|
|
26
|
+
[data-taprail="close"]:hover svg{opacity:1;}
|
|
27
|
+
[data-taprail="close"]:active{transform:scale(.93);}
|
|
28
|
+
[data-taprail="close"]:focus-visible{outline:2px solid var(--tr-accent);outline-offset:2px;}
|
|
29
|
+
@keyframes tr-fade{from{opacity:0}to{opacity:1}}
|
|
30
|
+
@keyframes tr-pop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
|
|
31
|
+
@keyframes tr-spin{to{transform:rotate(360deg)}}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){[data-taprail="overlay"],[data-taprail="frame"]{animation:none}.tr-ring{animation-duration:1.4s}.tr-btn,[data-taprail="close"]{transition:none}}
|
|
33
|
+
`;function ae(){if(typeof document>"u"||document.getElementById($))return;let e=document.createElement("style");e.id=$,e.textContent=ne,document.head.appendChild(e);}var ie='<svg viewBox="0 0 500 500" width="22" height="22" fill="none" aria-hidden="true" focusable="false"><path d="M147.789 394.059C214.872 439.472 307.74 425.816 359.214 362.946L371.749 347.654L437.045 393.713L423.39 410.225C385.37 456.004 331.287 486.708 271.399 496.516C211.059 506.557 151.221 494.72 102.868 463.262C50.9889 429.616 15.9579 377.523 4.20937 316.703C-1.75472 286.404 -1.3776 255.258 5.31847 225.103C12.0145 194.947 24.8939 166.394 43.1963 141.127L55.4452 124.132L120.503 170.19L109.04 186.311C61.1408 253.994 78.4657 347.194 147.789 394.059Z" fill="currentColor"/><path d="M500 310.578L428.222 321.724L386.423 70.7041L126.55 111.075L114.992 41.7101L377.391 0.879109C385.717 -0.444061 394.224 -0.272305 402.485 1.38576L405.059 1.91543C418.429 4.93491 430.565 11.7347 439.92 21.4473C449.274 31.16 455.423 43.3453 457.581 56.4489L500 310.578Z" fill="currentColor"/><path d="M257.046 329.232C306.454 329.232 346.506 290.526 346.506 242.779C346.506 195.033 306.454 156.327 257.046 156.327C207.639 156.327 167.586 195.033 167.586 242.779C167.586 290.526 207.639 329.232 257.046 329.232Z" fill="currentColor"/></svg>';function M(){let e=document.createElement("div");return e.className="tr-logo",e.innerHTML=`${ie}<span class="tr-word">Taprail</span>`,e}function O(e=false){let t=document.createElement("div");return t.className=e?"tr-ring tr-sm":"tr-ring",t}var oe='<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M3.5 3.5l8 8M11.5 3.5l-8 8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>';function ce(e,t={}){let{id:n}=D(e);return V()?(window.location.href=N(n),z(n,t,"app")):z(n,t,"iframe")}function z(e,t,n){return new Promise(i=>{let f=false,b=false,h,x=0,g=null,E=false,I=document.activeElement,j=document.body.style.overflow;ae();let{palette:l,mode:H,radius:G,font:U}=te(t.theme??{});document.querySelector('[data-taprail="overlay"]')?.remove();let c=document.createElement("div");c.setAttribute("data-taprail","overlay"),c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label","Pay with Taprail");let T={"--tr-surface":l.surface,"--tr-text":l.text,"--tr-text-rgb":re(l.text),"--tr-muted":l.muted,"--tr-accent":l.accent,"--tr-accent-text":l.accentText,"--tr-backdrop":l.backdrop,"--tr-radius":`${G}px`,"--tr-font":U};for(let r in T)c.style.setProperty(r,T[r]);let p=document.createElement("div");p.setAttribute("data-taprail","frame");let v;if(n==="iframe"){p.classList.add("tr-iframe");let r=document.createElement("div");r.setAttribute("data-taprail","loader");let a=document.createElement("div");a.className="tr-cap",a.textContent="Loading secure checkout\u2026",r.append(M(),O(),a);let s=document.createElement("iframe"),o=new URL(`${d.embedBase}/pay`);o.searchParams.set("o",e),o.searchParams.set("theme",H),o.searchParams.set("accent",l.accent),s.src=o.toString(),s.allow="clipboard-write",s.addEventListener("load",()=>r.remove()),p.appendChild(r),v=s;}else {let r=document.createElement("div");r.setAttribute("data-taprail","card");let a=document.createElement("div"),s=document.createElement("div");s.className="tr-title",s.textContent="Continue in the Taprail app",a.appendChild(s),g=a;let o=document.createElement("div");o.className="tr-sub",o.textContent="Approve this payment in the Taprail app, then come back here.";let u=document.createElement("a");u.className="tr-btn",u.href=N(e),u.textContent="Open Taprail";let C=document.createElement("div");C.className="tr-wait";let B=document.createElement("span");B.textContent="Waiting for approval",C.append(O(true),B),r.append(M(),a,o,u,C),v=r;}let m=document.createElement("button");m.setAttribute("data-taprail","close"),m.setAttribute("aria-label","Close"),m.innerHTML=oe;function K(){clearTimeout(h),window.removeEventListener("message",L),document.removeEventListener("keydown",A),document.removeEventListener("visibilitychange",S),window.removeEventListener("focus",_),c.remove(),document.body.style.overflow=j,I?.focus?.();}function q(r,a,s){if(!g||E||r==null)return;E=true,g.textContent="";let o=document.createElement("div");if(o.className="tr-amount",o.textContent=Y(r,a),g.appendChild(o),s){let u=document.createElement("div");u.className="tr-merchant",u.textContent=s,g.appendChild(u);}}function y(r){f||(f=true,K(),r.status==="approved"?t.onSuccess?.(r):r.status==="closed"&&t.onClose?.(),i(r),t.redirect&&r.status!=="closed"&&W(r));}function L(r){if(r.origin!==d.embedBase)return;let a=r.data;!a||a.source!=="taprail-checkout"||a.status&&P.has(a.status)&&y({status:a.status,order_id:a.order_id||e,transaction_reference:a.transaction_reference??null,return_url:a.return_url??null});}function A(r){r.key==="Escape"&&k();}function k(){y({status:"closed",order_id:e,transaction_reference:null,return_url:null});}async function w(){if(!(f||b)){b=true;try{let r=await F(e);if(x=0,n==="app"&&q(r.amount_kobo,r.currency,r.merchant_name),r._ok&&P.has(r.status)){y({status:r.status,order_id:r.order_id,transaction_reference:r.transaction_reference,return_url:r.return_url});return}}catch(r){x+=1,x===4&&t.onError?.(r instanceof Error?r:new Error(String(r)));}finally{b=false;}f||(h=setTimeout(w,n==="app"?1200:2e3));}}function _(){f||(clearTimeout(h),w());}function S(){document.visibilityState==="visible"&&_();}m.addEventListener("click",k),c.addEventListener("click",r=>{r.target===c&&k();}),document.addEventListener("keydown",A),window.addEventListener("message",L),document.addEventListener("visibilitychange",S),window.addEventListener("focus",_),p.appendChild(v),p.appendChild(m),c.appendChild(p),document.body.appendChild(c),document.body.style.overflow="hidden",m.focus(),h=setTimeout(w,n==="app"?450:800);})}async function de(e){let{id:t}=D(e),n=await F(t);return {status:n.status,order_id:n.order_id,transaction_reference:n.transaction_reference,return_url:n.return_url}}exports.getStatus=de;exports.init=se;exports.open=ce;return exports;})({});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{c as getStatus,a as init,b as open}from'./chunk-UZGOWXDY.js';
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';var y=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var y__namespace=/*#__PURE__*/_interopNamespace(y);var A=new Set(["approved","declined","expired","cancelled","refunded"]),h={apiBase:"https://api.taprail.app",embedBase:"https://pay.taprail.app",theme:{}};function W(){return typeof navigator<"u"&&/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)}var J="com.taprail.consumer";function D(e){let t=encodeURIComponent(e);return typeof navigator<"u"&&/Android/i.test(navigator.userAgent)?`intent://pay?o=${t}#Intent;scheme=taprail;package=${J};end`:`taprail://pay?o=${t}`}function M(e){try{return new URL(e).searchParams.get("o")}catch{return null}}function F(e){if(typeof e=="string"){if(/^https?:\/\//.test(e)){let a=M(e);if(!a)throw new Error("Taprail: deep link is missing the ?o=<orderId> parameter");return {id:a,deepLink:e}}return {id:e,deepLink:`${h.embedBase}/pay?o=${e}`}}let t=e.id||e.order_id||M(e.deep_link||"")||"";if(!t)throw new Error("Taprail: could not determine the order id from the intent");let r=e.deep_link||e.qr_payload||`${h.embedBase}/pay?o=${t}`;return {id:t,deepLink:r}}async function I(e){let t=await fetch(`${h.apiBase}/v1/checkout/orders/${e}`,{cache:"no-store"});if(!t.ok)return {_ok:false,status:"unknown",order_id:e,transaction_reference:null,return_url:null};let r=await t.json();return {_ok:true,status:r.status,order_id:r.id??e,transaction_reference:r.transaction_reference??null,return_url:r.return_url??null,merchant_name:r.merchant_name??void 0,amount_kobo:typeof r.amount_kobo=="number"?r.amount_kobo:void 0,currency:r.currency??void 0}}function Q(e,t){let r=e/100;return !t||t==="NGN"?"\u20A6"+r.toLocaleString("en-NG",{minimumFractionDigits:2,maximumFractionDigits:2}):`${t} ${r.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`}function X(e){if(e.return_url)try{let t=new URL(e.return_url);t.searchParams.set("order",e.order_id),t.searchParams.set("status",e.status),window.location.href=t.toString();}catch{}}var ee='-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Apple Color Emoji", sans-serif',te={surface:"#101013",text:"#f5f5f6",muted:"#8b8b93",accent:"#f5f5f6",accentText:"#0a0a0c",backdrop:"rgba(7,7,10,0.7)"},re={surface:"#ffffff",text:"#18181b",muted:"#6b7280",accent:"#18181b",accentText:"#ffffff",backdrop:"rgba(17,17,26,0.46)"};function ne(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-color-scheme: dark)").matches}function ae(e){let t={...h.theme,...e},r=t.mode==="light"?"light":t.mode==="dark"||ne()?"dark":"light",a=r==="light"?re:te;return {palette:{surface:t.surface??a.surface,text:t.text??a.text,muted:t.muted??a.muted,accent:t.accent??a.accent,accentText:t.accentText??a.accentText,backdrop:t.backdrop??a.backdrop},mode:r,radius:typeof t.radius=="number"?t.radius:20,font:t.fontFamily??ee}}function oe(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)return "127,127,127";let r=t[1];r.length===3&&(r=r[0]+r[0]+r[1]+r[1]+r[2]+r[2]);let a=parseInt(r,16);return `${a>>16&255},${a>>8&255},${a&255}`}var O="taprail-checkout-css",ie=`
|
|
2
|
+
[data-taprail="overlay"]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;background:var(--tr-backdrop);-webkit-backdrop-filter:blur(10px) saturate(1.05);backdrop-filter:blur(10px) saturate(1.05);font-family:var(--tr-font);animation:tr-fade .12s ease both;}
|
|
3
|
+
[data-taprail="frame"]{position:relative;width:min(420px,92vw);background:var(--tr-surface);border-radius:var(--tr-radius);overflow:hidden;box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.08),0 24px 70px -20px rgba(0,0,0,.55),0 8px 24px -16px rgba(0,0,0,.5);animation:tr-pop .19s cubic-bezier(.2,.9,.25,1) both;}
|
|
4
|
+
[data-taprail="frame"].tr-iframe{height:min(620px,90vh);}
|
|
5
|
+
[data-taprail="frame"] iframe{position:relative;z-index:1;width:100%;height:100%;border:0;display:block;background:transparent;}
|
|
6
|
+
[data-taprail="loader"]{position:absolute;inset:0;z-index:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;color:var(--tr-text);}
|
|
7
|
+
.tr-logo{display:flex;align-items:center;justify-content:center;gap:7px;color:var(--tr-text);}
|
|
8
|
+
.tr-logo svg{display:block;flex:none;}
|
|
9
|
+
.tr-word{font-size:16px;font-weight:650;letter-spacing:-.02em;}
|
|
10
|
+
.tr-ring{width:26px;height:26px;border-radius:50%;border:2.5px solid rgba(var(--tr-text-rgb),.16);border-top-color:var(--tr-text);animation:tr-spin .7s linear infinite;}
|
|
11
|
+
.tr-ring.tr-sm{width:15px;height:15px;border-width:2px;}
|
|
12
|
+
[data-taprail="card"]{padding:38px 30px 30px;text-align:center;color:var(--tr-text);}
|
|
13
|
+
[data-taprail="card"] .tr-logo{margin:0 auto 22px;}
|
|
14
|
+
.tr-amount{font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:1.1;}
|
|
15
|
+
.tr-merchant{font-size:13px;color:var(--tr-muted);margin-top:3px;}
|
|
16
|
+
.tr-title{font-size:17px;font-weight:650;letter-spacing:-.01em;}
|
|
17
|
+
.tr-sub{font-size:13.5px;line-height:1.5;color:var(--tr-muted);margin:10px auto 22px;max-width:30ch;}
|
|
18
|
+
.tr-btn{display:block;width:100%;box-sizing:border-box;background:var(--tr-accent);color:var(--tr-accent-text);border:0;border-radius:999px;padding:14px 18px;font:inherit;font-size:15px;font-weight:650;text-decoration:none;cursor:pointer;transition:transform .1s ease,filter .12s ease;}
|
|
19
|
+
.tr-btn:hover{filter:brightness(1.06);}
|
|
20
|
+
.tr-btn:active{transform:scale(.975);}
|
|
21
|
+
.tr-wait{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12.5px;color:var(--tr-muted);margin-top:20px;}
|
|
22
|
+
.tr-cap{font-size:12.5px;color:var(--tr-muted);}
|
|
23
|
+
[data-taprail="close"]{position:absolute;top:12px;right:12px;z-index:3;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:50%;cursor:pointer;background:var(--tr-surface);color:var(--tr-text);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.14),0 2px 10px -2px rgba(0,0,0,.4);transition:transform .12s ease,box-shadow .12s ease;}
|
|
24
|
+
[data-taprail="close"] svg{display:block;opacity:.7;transition:opacity .12s ease;}
|
|
25
|
+
[data-taprail="close"]:hover{transform:scale(1.07);box-shadow:0 0 0 1px rgba(var(--tr-text-rgb),.24),0 4px 14px -3px rgba(0,0,0,.5);}
|
|
26
|
+
[data-taprail="close"]:hover svg{opacity:1;}
|
|
27
|
+
[data-taprail="close"]:active{transform:scale(.93);}
|
|
28
|
+
[data-taprail="close"]:focus-visible{outline:2px solid var(--tr-accent);outline-offset:2px;}
|
|
29
|
+
@keyframes tr-fade{from{opacity:0}to{opacity:1}}
|
|
30
|
+
@keyframes tr-pop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
|
|
31
|
+
@keyframes tr-spin{to{transform:rotate(360deg)}}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){[data-taprail="overlay"],[data-taprail="frame"]{animation:none}.tr-ring{animation-duration:1.4s}.tr-btn,[data-taprail="close"]{transition:none}}
|
|
33
|
+
`;function se(){if(typeof document>"u"||document.getElementById(O))return;let e=document.createElement("style");e.id=O,e.textContent=ie,document.head.appendChild(e);}var ce='<svg viewBox="0 0 500 500" width="22" height="22" fill="none" aria-hidden="true" focusable="false"><path d="M147.789 394.059C214.872 439.472 307.74 425.816 359.214 362.946L371.749 347.654L437.045 393.713L423.39 410.225C385.37 456.004 331.287 486.708 271.399 496.516C211.059 506.557 151.221 494.72 102.868 463.262C50.9889 429.616 15.9579 377.523 4.20937 316.703C-1.75472 286.404 -1.3776 255.258 5.31847 225.103C12.0145 194.947 24.8939 166.394 43.1963 141.127L55.4452 124.132L120.503 170.19L109.04 186.311C61.1408 253.994 78.4657 347.194 147.789 394.059Z" fill="currentColor"/><path d="M500 310.578L428.222 321.724L386.423 70.7041L126.55 111.075L114.992 41.7101L377.391 0.879109C385.717 -0.444061 394.224 -0.272305 402.485 1.38576L405.059 1.91543C418.429 4.93491 430.565 11.7347 439.92 21.4473C449.274 31.16 455.423 43.3453 457.581 56.4489L500 310.578Z" fill="currentColor"/><path d="M257.046 329.232C306.454 329.232 346.506 290.526 346.506 242.779C346.506 195.033 306.454 156.327 257.046 156.327C207.639 156.327 167.586 195.033 167.586 242.779C167.586 290.526 207.639 329.232 257.046 329.232Z" fill="currentColor"/></svg>';function $(){let e=document.createElement("div");return e.className="tr-logo",e.innerHTML=`${ce}<span class="tr-word">Taprail</span>`,e}function N(e=false){let t=document.createElement("div");return t.className=e?"tr-ring tr-sm":"tr-ring",t}var le='<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M3.5 3.5l8 8M11.5 3.5l-8 8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>';function H(e,t={}){let{id:r}=F(e);return W()?(window.location.href=D(r),z(r,t,"app")):z(r,t,"iframe")}function z(e,t,r){return new Promise(a=>{let l=false,b=false,m,x=0,p=null,v=false,G=document.activeElement,U=document.body.style.overflow;se();let{palette:d,mode:K,radius:q,font:V}=ae(t.theme??{});document.querySelector('[data-taprail="overlay"]')?.remove();let c=document.createElement("div");c.setAttribute("data-taprail","overlay"),c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label","Pay with Taprail");let L={"--tr-surface":d.surface,"--tr-text":d.text,"--tr-text-rgb":oe(d.text),"--tr-muted":d.muted,"--tr-accent":d.accent,"--tr-accent-text":d.accentText,"--tr-backdrop":d.backdrop,"--tr-radius":`${q}px`,"--tr-font":V};for(let n in L)c.style.setProperty(n,L[n]);let f=document.createElement("div");f.setAttribute("data-taprail","frame");let k;if(r==="iframe"){f.classList.add("tr-iframe");let n=document.createElement("div");n.setAttribute("data-taprail","loader");let o=document.createElement("div");o.className="tr-cap",o.textContent="Loading secure checkout\u2026",n.append($(),N(),o);let s=document.createElement("iframe"),i=new URL(`${h.embedBase}/pay`);i.searchParams.set("o",e),i.searchParams.set("theme",K),i.searchParams.set("accent",d.accent),s.src=i.toString(),s.allow="clipboard-write",s.addEventListener("load",()=>n.remove()),f.appendChild(n),k=s;}else {let n=document.createElement("div");n.setAttribute("data-taprail","card");let o=document.createElement("div"),s=document.createElement("div");s.className="tr-title",s.textContent="Continue in the Taprail app",o.appendChild(s),p=o;let i=document.createElement("div");i.className="tr-sub",i.textContent="Approve this payment in the Taprail app, then come back here.";let u=document.createElement("a");u.className="tr-btn",u.href=D(e),u.textContent="Open Taprail";let E=document.createElement("div");E.className="tr-wait";let S=document.createElement("span");S.textContent="Waiting for approval",E.append(N(true),S),n.append($(),o,i,u,E),k=n;}let g=document.createElement("button");g.setAttribute("data-taprail","close"),g.setAttribute("aria-label","Close"),g.innerHTML=le;function Z(){clearTimeout(m),window.removeEventListener("message",R),document.removeEventListener("keydown",B),document.removeEventListener("visibilitychange",P),window.removeEventListener("focus",_),c.remove(),document.body.style.overflow=U,G?.focus?.();}function Y(n,o,s){if(!p||v||n==null)return;v=true,p.textContent="";let i=document.createElement("div");if(i.className="tr-amount",i.textContent=Q(n,o),p.appendChild(i),s){let u=document.createElement("div");u.className="tr-merchant",u.textContent=s,p.appendChild(u);}}function w(n){l||(l=true,Z(),n.status==="approved"?t.onSuccess?.(n):n.status==="closed"&&t.onClose?.(),a(n),t.redirect&&n.status!=="closed"&&X(n));}function R(n){if(n.origin!==h.embedBase)return;let o=n.data;!o||o.source!=="taprail-checkout"||o.status&&A.has(o.status)&&w({status:o.status,order_id:o.order_id||e,transaction_reference:o.transaction_reference??null,return_url:o.return_url??null});}function B(n){n.key==="Escape"&&C();}function C(){w({status:"closed",order_id:e,transaction_reference:null,return_url:null});}async function T(){if(!(l||b)){b=true;try{let n=await I(e);if(x=0,r==="app"&&Y(n.amount_kobo,n.currency,n.merchant_name),n._ok&&A.has(n.status)){w({status:n.status,order_id:n.order_id,transaction_reference:n.transaction_reference,return_url:n.return_url});return}}catch(n){x+=1,x===4&&t.onError?.(n instanceof Error?n:new Error(String(n)));}finally{b=false;}l||(m=setTimeout(T,r==="app"?1200:2e3));}}function _(){l||(clearTimeout(m),T());}function P(){document.visibilityState==="visible"&&_();}g.addEventListener("click",C),c.addEventListener("click",n=>{n.target===c&&C();}),document.addEventListener("keydown",B),window.addEventListener("message",R),document.addEventListener("visibilitychange",P),window.addEventListener("focus",_),f.appendChild(k),f.appendChild(g),c.appendChild(f),document.body.appendChild(c),document.body.style.overflow="hidden",g.focus(),m=setTimeout(T,r==="app"?450:800);})}async function j(e){let{id:t}=F(e),r=await I(t);return {status:r.status,order_id:r.order_id,transaction_reference:r.transaction_reference,return_url:r.return_url}}function de(){let[e,t]=y__namespace.useState(false);return {open:y__namespace.useCallback((a,l={})=>(t(true),H(a,l).finally(()=>t(false))),[]),getStatus:j,pending:e}}function fe({intent:e,onSuccess:t,onError:r,onClose:a,theme:l,children:b,disabled:m,...x}){let{open:p,pending:v}=de();return jsxRuntime.jsx("button",{type:"button",disabled:m||v,onClick:()=>p(e,{onSuccess:t,onError:r,onClose:a,theme:l}),...x,children:b??"Pay with Taprail"})}exports.TaprailButton=fe;exports.useTaprailCheckout=de;
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Intent, CheckoutResult, TaprailTheme, OpenOptions, getStatus } from './index.cjs';
|
|
3
|
+
|
|
4
|
+
/** React bindings for @taprail/checkout. React is an optional peer dependency. */
|
|
5
|
+
|
|
6
|
+
declare function useTaprailCheckout(): {
|
|
7
|
+
open: (intent: Intent, opts?: OpenOptions) => Promise<CheckoutResult>;
|
|
8
|
+
getStatus: typeof getStatus;
|
|
9
|
+
pending: boolean;
|
|
10
|
+
};
|
|
11
|
+
interface TaprailButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onError"> {
|
|
12
|
+
intent: Intent;
|
|
13
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
/** Theme the checkout modal for this button. */
|
|
17
|
+
theme?: TaprailTheme;
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
declare function TaprailButton({ intent, onSuccess, onError, onClose, theme, children, disabled, ...rest }: TaprailButtonProps): React.JSX.Element;
|
|
21
|
+
|
|
22
|
+
export { TaprailButton, type TaprailButtonProps, useTaprailCheckout };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Intent, CheckoutResult, TaprailTheme, OpenOptions, getStatus } from './index.js';
|
|
3
|
+
|
|
4
|
+
/** React bindings for @taprail/checkout. React is an optional peer dependency. */
|
|
5
|
+
|
|
6
|
+
declare function useTaprailCheckout(): {
|
|
7
|
+
open: (intent: Intent, opts?: OpenOptions) => Promise<CheckoutResult>;
|
|
8
|
+
getStatus: typeof getStatus;
|
|
9
|
+
pending: boolean;
|
|
10
|
+
};
|
|
11
|
+
interface TaprailButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onError"> {
|
|
12
|
+
intent: Intent;
|
|
13
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
/** Theme the checkout modal for this button. */
|
|
17
|
+
theme?: TaprailTheme;
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
declare function TaprailButton({ intent, onSuccess, onError, onClose, theme, children, disabled, ...rest }: TaprailButtonProps): React.JSX.Element;
|
|
21
|
+
|
|
22
|
+
export { TaprailButton, type TaprailButtonProps, useTaprailCheckout };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {c,b}from'./chunk-UZGOWXDY.js';import*as e from'react';import {jsx}from'react/jsx-runtime';function T(){let[n,t]=e.useState(false);return {open:e.useCallback((o,r={})=>(t(true),b(o,r).finally(()=>t(false))),[]),getStatus:c,pending:n}}function R({intent:n,onSuccess:t,onError:a,onClose:o,theme:r,children:s,disabled:u,...l}){let{open:c,pending:d}=T();return jsx("button",{type:"button",disabled:u||d,onClick:()=>c(n,{onSuccess:t,onError:a,onClose:o,theme:r}),...l,children:s??"Pay with Taprail"})}export{R as TaprailButton,T as useTaprailCheckout};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taprail/checkout",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Drop-in Pay with Taprail checkout for the browser. Never sees card details or API keys.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"unpkg": "./dist/index.global.js",
|
|
15
|
+
"jsdelivr": "./dist/index.global.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./react": {
|
|
23
|
+
"types": "./dist/react.d.ts",
|
|
24
|
+
"import": "./dist/react.js",
|
|
25
|
+
"require": "./dist/react.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"taprail",
|
|
39
|
+
"checkout",
|
|
40
|
+
"payments",
|
|
41
|
+
"tap-to-pay",
|
|
42
|
+
"nigeria"
|
|
43
|
+
],
|
|
44
|
+
"homepage": "https://github.com/Taprail/checkout-js#readme",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/Taprail/checkout-js.git",
|
|
48
|
+
"directory": "checkout"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/Taprail/checkout-js/issues"
|
|
52
|
+
},
|
|
53
|
+
"author": "Taprail <hello@taprail.app>",
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"react": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/react": "^18.3.12",
|
|
64
|
+
"react": "^18.3.1",
|
|
65
|
+
"tsup": "^8.3.5",
|
|
66
|
+
"typescript": "^5.6.3"
|
|
67
|
+
}
|
|
68
|
+
}
|