@onborn/billing 0.1.0-beta.1 → 0.1.0-beta.3
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/CHANGELOG.md +15 -0
- package/README.md +5 -0
- package/dist/adapters/nativeStores.d.ts +1 -0
- package/dist/adapters/nativeStores.js +2 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.js +31 -4
- package/dist/useOnbornOffering.d.ts +1 -2
- package/dist/useOnbornOffering.js +3 -5
- package/dist/validation.js +13 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.3
|
|
4
|
+
|
|
5
|
+
- Fixed StoreKit 2 purchases incorrectly using the signed transaction JWS as
|
|
6
|
+
the transaction identifier.
|
|
7
|
+
- Bounded purchase idempotency keys when only a signed purchase token is
|
|
8
|
+
available.
|
|
9
|
+
- Added structured billing request errors with backend messages and codes.
|
|
10
|
+
|
|
11
|
+
## 0.1.0-beta.2
|
|
12
|
+
|
|
13
|
+
- Removed `offeringId` from `useOnbornOffering` and
|
|
14
|
+
`BillingClient.loadOffering`.
|
|
15
|
+
- Billing now loads only the offering marked Current for the project
|
|
16
|
+
environment.
|
|
17
|
+
|
|
3
18
|
## 0.1.0-beta.1
|
|
4
19
|
|
|
5
20
|
- Added headless offering, paywall, purchase, restore, and entitlement APIs.
|
package/README.md
CHANGED
|
@@ -36,6 +36,11 @@ function PremiumGate() {
|
|
|
36
36
|
The package owns the Onborn API URL. Configure credentials and user context
|
|
37
37
|
once through `Onborn.init`; hooks and adapters do not accept an API key.
|
|
38
38
|
|
|
39
|
+
`useOnbornOffering()` and `BillingClient.loadOffering()` always load the
|
|
40
|
+
offering marked **Current** for the active project and environment. The public
|
|
41
|
+
API accepts no offering ID. Select the current offering in Dashboard → Billing
|
|
42
|
+
before loading a custom paywall.
|
|
43
|
+
|
|
39
44
|
Installing `@onborn/billing` also installs its analytics and public-contract
|
|
40
45
|
dependencies. You do not need `@onborn/rn-sdk` unless you render Onborn-built
|
|
41
46
|
flows or paywalls.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NativeStoreRestoredPurchase } from "@onborn/sdk-contracts";
|
|
2
2
|
import type { OnbornBillingAdapter, OnbornLoadProductsInput, OnbornPurchaseInput, OnbornRestoreInput } from "../types";
|
|
3
3
|
type NativeStoresPurchase = {
|
|
4
|
+
id?: string;
|
|
4
5
|
productId?: string;
|
|
5
6
|
storeProductId?: string;
|
|
6
7
|
transactionId?: string;
|
|
@@ -143,6 +143,7 @@ function normalizeNativeRestoredPurchase(value) {
|
|
|
143
143
|
store,
|
|
144
144
|
storeProductId,
|
|
145
145
|
transactionId: readString(record.transactionId) ??
|
|
146
|
+
readString(record.id) ??
|
|
146
147
|
readString(record.originalTransactionIdentifierIOS),
|
|
147
148
|
purchaseToken: readString(record.purchaseToken),
|
|
148
149
|
raw: record.raw ?? value,
|
|
@@ -150,7 +151,7 @@ function normalizeNativeRestoredPurchase(value) {
|
|
|
150
151
|
}
|
|
151
152
|
function readTransactionId(purchase) {
|
|
152
153
|
return (purchase?.transactionId ??
|
|
153
|
-
purchase?.
|
|
154
|
+
purchase?.id ??
|
|
154
155
|
purchase?.originalTransactionIdentifierIOS);
|
|
155
156
|
}
|
|
156
157
|
function readString(value) {
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { type CustomerEntitlementsResponse, type GetOfferingResponse, type GetPa
|
|
|
2
2
|
export type BillingClientOptions = {
|
|
3
3
|
sourceId?: string;
|
|
4
4
|
};
|
|
5
|
+
export declare class OnbornBillingRequestError extends Error {
|
|
6
|
+
readonly status: number;
|
|
7
|
+
readonly code?: string | undefined;
|
|
8
|
+
constructor(message: string, status: number, code?: string | undefined);
|
|
9
|
+
}
|
|
5
10
|
export declare class BillingClient {
|
|
6
11
|
private readonly options;
|
|
7
12
|
private readonly config;
|
|
@@ -10,7 +15,7 @@ export declare class BillingClient {
|
|
|
10
15
|
private readonly emitAnalyticsEvents;
|
|
11
16
|
constructor(options?: BillingClientOptions);
|
|
12
17
|
loadPaywall(paywallId: string): Promise<GetPaywallResponse>;
|
|
13
|
-
loadOffering(
|
|
18
|
+
loadOffering(): Promise<GetOfferingResponse>;
|
|
14
19
|
validatePurchase(input: Omit<ValidatePurchaseRequest, "userId">): Promise<PurchaseValidationResponse>;
|
|
15
20
|
restorePurchases(input: Omit<RestorePurchasesRequest, "userId">): Promise<PurchaseValidationResponse>;
|
|
16
21
|
loadCustomerEntitlements(): Promise<CustomerEntitlementsResponse>;
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,16 @@ import { Onborn, } from "@onborn/analytics";
|
|
|
2
2
|
import { resolveOnbornBillingConfig } from "./runtime";
|
|
3
3
|
import { CustomerEntitlementsResponseSchema, GetOfferingResponseSchema, GetPaywallResponseSchema, PurchaseValidationResponseSchema, } from "@onborn/sdk-contracts";
|
|
4
4
|
const ONBORN_API_BASE_URL = "https://api.testing.onborn.app";
|
|
5
|
+
export class OnbornBillingRequestError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, status, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = "OnbornBillingRequestError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
5
15
|
export class BillingClient {
|
|
6
16
|
options;
|
|
7
17
|
config;
|
|
@@ -24,9 +34,9 @@ export class BillingClient {
|
|
|
24
34
|
}
|
|
25
35
|
return parsed.data;
|
|
26
36
|
}
|
|
27
|
-
async loadOffering(
|
|
28
|
-
const url = this.runtimeUrl(
|
|
29
|
-
const payload = await this.getJson(url,
|
|
37
|
+
async loadOffering() {
|
|
38
|
+
const url = this.runtimeUrl("/offerings/current");
|
|
39
|
+
const payload = await this.getJson(url, "current offering");
|
|
30
40
|
const parsed = GetOfferingResponseSchema.safeParse(payload);
|
|
31
41
|
if (!parsed.success) {
|
|
32
42
|
throw new Error("Invalid offering response payload");
|
|
@@ -113,7 +123,8 @@ export class BillingClient {
|
|
|
113
123
|
body: JSON.stringify(payload),
|
|
114
124
|
});
|
|
115
125
|
if (!response.ok) {
|
|
116
|
-
|
|
126
|
+
const failure = await readBillingFailure(response);
|
|
127
|
+
throw new OnbornBillingRequestError(failure.message ?? `Purchase request failed (${response.status})`, response.status, failure.code);
|
|
117
128
|
}
|
|
118
129
|
const parsed = PurchaseValidationResponseSchema.safeParse(await response.json());
|
|
119
130
|
if (!parsed.success) {
|
|
@@ -135,6 +146,22 @@ export class BillingClient {
|
|
|
135
146
|
});
|
|
136
147
|
}
|
|
137
148
|
}
|
|
149
|
+
async function readBillingFailure(response) {
|
|
150
|
+
try {
|
|
151
|
+
const payload = (await response.json());
|
|
152
|
+
return {
|
|
153
|
+
message: typeof payload.error === "string"
|
|
154
|
+
? payload.error
|
|
155
|
+
: typeof payload.message === "string"
|
|
156
|
+
? payload.message
|
|
157
|
+
: undefined,
|
|
158
|
+
code: typeof payload.code === "string" ? payload.code : undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
138
165
|
export function createBillingClient(options = {}) {
|
|
139
166
|
return new BillingClient(options);
|
|
140
167
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type CustomerEntitlement, type GetOfferingResponse } from "@onborn/sdk-contracts";
|
|
2
2
|
import type { OnbornBillingAdapter, OnbornPackageWithProduct, OnbornPurchaseResult, OnbornRestoreResult } from "./types";
|
|
3
3
|
export type UseOnbornOfferingOptions = {
|
|
4
|
-
offeringId: string;
|
|
5
4
|
initialPackageId?: string;
|
|
6
5
|
billingAdapter?: OnbornBillingAdapter;
|
|
7
6
|
onPurchaseStarted?: (item: OnbornPackageWithProduct) => void;
|
|
@@ -26,4 +25,4 @@ export type UseOnbornOfferingState = {
|
|
|
26
25
|
restorePurchases: () => Promise<OnbornRestoreResult>;
|
|
27
26
|
refetchCustomerEntitlements: () => Promise<OnbornRestoreResult>;
|
|
28
27
|
};
|
|
29
|
-
export declare function useOnbornOffering(options
|
|
28
|
+
export declare function useOnbornOffering(options?: UseOnbornOfferingOptions): UseOnbornOfferingState;
|
|
@@ -3,7 +3,7 @@ import { createBillingClient } from "./client";
|
|
|
3
3
|
import { useOnbornBillingConfig } from "./runtime";
|
|
4
4
|
import { findPackageWithProduct, getPackagesWithProducts, resolveDefaultPackageId, } from "./utils";
|
|
5
5
|
import { validateBillingPurchase, validateBillingRestore } from "./validation";
|
|
6
|
-
export function useOnbornOffering(options) {
|
|
6
|
+
export function useOnbornOffering(options = {}) {
|
|
7
7
|
const runtimeOptions = useOnbornBillingConfig(options);
|
|
8
8
|
const [data, setData] = useState(null);
|
|
9
9
|
const [selectedPackageId, setSelectedPackageId] = useState(runtimeOptions.initialPackageId ?? null);
|
|
@@ -12,7 +12,7 @@ export function useOnbornOffering(options) {
|
|
|
12
12
|
const [restoring, setRestoring] = useState(false);
|
|
13
13
|
const [error, setError] = useState(null);
|
|
14
14
|
const client = useMemo(() => createBillingClient({
|
|
15
|
-
sourceId:
|
|
15
|
+
sourceId: "offering:current",
|
|
16
16
|
}), [
|
|
17
17
|
runtimeOptions.apiKey,
|
|
18
18
|
runtimeOptions.appId,
|
|
@@ -23,7 +23,6 @@ export function useOnbornOffering(options) {
|
|
|
23
23
|
runtimeOptions.emitSdkConnectionSignal,
|
|
24
24
|
runtimeOptions.fetchImpl,
|
|
25
25
|
runtimeOptions.locale,
|
|
26
|
-
runtimeOptions.offeringId,
|
|
27
26
|
runtimeOptions.platform,
|
|
28
27
|
runtimeOptions.sdkVersion,
|
|
29
28
|
runtimeOptions.userId,
|
|
@@ -38,7 +37,7 @@ export function useOnbornOffering(options) {
|
|
|
38
37
|
setLoading(true);
|
|
39
38
|
setError(null);
|
|
40
39
|
try {
|
|
41
|
-
const response = await client.loadOffering(
|
|
40
|
+
const response = await client.loadOffering();
|
|
42
41
|
const products = await loadLocalizedProducts(runtimeOptions.billingAdapter, {
|
|
43
42
|
offering: response.offering,
|
|
44
43
|
products: response.products,
|
|
@@ -62,7 +61,6 @@ export function useOnbornOffering(options) {
|
|
|
62
61
|
}, [
|
|
63
62
|
client,
|
|
64
63
|
runtimeOptions.billingAdapter,
|
|
65
|
-
runtimeOptions.offeringId,
|
|
66
64
|
runtimeOptions.userId,
|
|
67
65
|
]);
|
|
68
66
|
useEffect(() => {
|
package/dist/validation.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function validateBillingRestore(input) {
|
|
|
44
44
|
}
|
|
45
45
|
function createPurchaseIdempotencyKey(input) {
|
|
46
46
|
const stablePart = input.result.transactionId ??
|
|
47
|
-
input.result.purchaseToken ??
|
|
47
|
+
fingerprintIdempotencyValue(input.result.purchaseToken) ??
|
|
48
48
|
input.result.productId ??
|
|
49
49
|
input.item.product?.storeProductId ??
|
|
50
50
|
input.item.package.id;
|
|
@@ -55,6 +55,18 @@ function createPurchaseIdempotencyKey(input) {
|
|
|
55
55
|
stablePart,
|
|
56
56
|
].join(":");
|
|
57
57
|
}
|
|
58
|
+
function fingerprintIdempotencyValue(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return undefined;
|
|
61
|
+
let first = 0x811c9dc5;
|
|
62
|
+
let second = 0x9e3779b9;
|
|
63
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
64
|
+
const code = value.charCodeAt(index);
|
|
65
|
+
first = Math.imul(first ^ code, 0x01000193);
|
|
66
|
+
second = Math.imul(second ^ code, 0x85ebca6b);
|
|
67
|
+
}
|
|
68
|
+
return `${(first >>> 0).toString(36)}${(second >>> 0).toString(36)}`;
|
|
69
|
+
}
|
|
58
70
|
function createRestoreIdempotencyKey(input) {
|
|
59
71
|
return ["restore", input.offering?.id ?? "unknown", String(Date.now())].join(":");
|
|
60
72
|
}
|