@virgodev/iap 1.0.6 → 1.0.7
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/components/StripeCheckout.vue +102 -30
- package/package.json +1 -1
- package/stores/iap.ts +26 -2
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<span
|
|
47
47
|
v-if="
|
|
48
48
|
cart.items.some(
|
|
49
|
-
(i) => i.product.type === ProductType.PAID_SUBSCRIPTION
|
|
49
|
+
(i) => i.product.type === ProductType.PAID_SUBSCRIPTION,
|
|
50
50
|
)
|
|
51
51
|
"
|
|
52
52
|
>
|
|
@@ -54,6 +54,38 @@
|
|
|
54
54
|
</span>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
|
+
<div v-if="showCoupon" class="coupon-row flex-row p-1 gap-1 align-center">
|
|
58
|
+
<template v-if="appliedCoupon">
|
|
59
|
+
<div class="flex-stretch coupon-applied">
|
|
60
|
+
Coupon <b>{{ appliedCoupon.id }}</b
|
|
61
|
+
>:
|
|
62
|
+
{{
|
|
63
|
+
appliedCoupon.percent_off
|
|
64
|
+
? appliedCoupon.percent_off + "% off"
|
|
65
|
+
: "$" + appliedCoupon.amount_off + " off"
|
|
66
|
+
}}
|
|
67
|
+
</div>
|
|
68
|
+
<Button size="small" severity="secondary" @click="removeCoupon"
|
|
69
|
+
>Remove</Button
|
|
70
|
+
>
|
|
71
|
+
</template>
|
|
72
|
+
<template v-else>
|
|
73
|
+
<InputText
|
|
74
|
+
v-model="couponInput"
|
|
75
|
+
placeholder="Coupon code"
|
|
76
|
+
size="small"
|
|
77
|
+
class="flex-stretch"
|
|
78
|
+
:disabled="couponLoading"
|
|
79
|
+
@keyup.enter="applyCoupon"
|
|
80
|
+
/>
|
|
81
|
+
<Button size="small" :loading="couponLoading" @click="applyCoupon">
|
|
82
|
+
Apply
|
|
83
|
+
</Button>
|
|
84
|
+
</template>
|
|
85
|
+
</div>
|
|
86
|
+
<div v-if="couponError" class="p-1-x">
|
|
87
|
+
<Message severity="error" size="small">{{ couponError }}</Message>
|
|
88
|
+
</div>
|
|
57
89
|
</div>
|
|
58
90
|
|
|
59
91
|
<div class="payments flex-stretch">
|
|
@@ -84,15 +116,17 @@ import { ProductType, useIapStore } from "../index";
|
|
|
84
116
|
import type { StripeCart } from "../stores/iap";
|
|
85
117
|
import Message from "primevue/message";
|
|
86
118
|
import Button from "primevue/button";
|
|
119
|
+
import InputText from "primevue/inputtext";
|
|
87
120
|
|
|
88
121
|
const props = withDefaults(
|
|
89
122
|
defineProps<{
|
|
90
123
|
cart: StripeCart;
|
|
91
124
|
returnUrl: string;
|
|
92
125
|
email: string;
|
|
93
|
-
vertical
|
|
126
|
+
vertical?: boolean;
|
|
127
|
+
showCoupon?: boolean;
|
|
94
128
|
}>(),
|
|
95
|
-
{ vertical: false }
|
|
129
|
+
{ vertical: false, showCoupon: true },
|
|
96
130
|
);
|
|
97
131
|
|
|
98
132
|
const emits = defineEmits<{
|
|
@@ -105,10 +139,14 @@ const iap = useIapStore();
|
|
|
105
139
|
const isLoading = ref<boolean>(false);
|
|
106
140
|
const messageText = ref("");
|
|
107
141
|
const sessionResponse: any = ref<any>();
|
|
142
|
+
const couponInput = ref("");
|
|
143
|
+
const couponLoading = ref(false);
|
|
144
|
+
const couponError = ref("");
|
|
145
|
+
const appliedCoupon = ref<any>(null);
|
|
108
146
|
|
|
109
147
|
let checkout: StripeCheckout | null = null;
|
|
110
148
|
let loadActionsResult: StripeCheckoutLoadActionsResult | null = null;
|
|
111
|
-
let paymentElement = undefined;
|
|
149
|
+
let paymentElement: any | undefined = undefined;
|
|
112
150
|
|
|
113
151
|
watch([() => props.cart, () => iap.stripe.coupon], () => {
|
|
114
152
|
getSession();
|
|
@@ -129,7 +167,7 @@ async function getSession() {
|
|
|
129
167
|
json: {
|
|
130
168
|
items: props.cart.items,
|
|
131
169
|
sub: props.cart.items.some(
|
|
132
|
-
(i) => i.product.type === ProductType.PAID_SUBSCRIPTION
|
|
170
|
+
(i) => i.product.type === ProductType.PAID_SUBSCRIPTION,
|
|
133
171
|
),
|
|
134
172
|
coupon_code: iap.stripe.coupon,
|
|
135
173
|
},
|
|
@@ -141,36 +179,39 @@ async function getSession() {
|
|
|
141
179
|
clientSecret: sessionResponse.value.client_secret,
|
|
142
180
|
elementsOptions: { appearance: { theme: "stripe" } },
|
|
143
181
|
});
|
|
182
|
+
if (checkout && checkout !== null) {
|
|
183
|
+
checkout.on("change", (session: any) => {
|
|
184
|
+
// Handle changes to the checkout session
|
|
185
|
+
console.log("session changed:", session);
|
|
186
|
+
});
|
|
187
|
+
checkout.on("error", (error: any) => {
|
|
188
|
+
console.error("Error initializing checkout:", error);
|
|
189
|
+
});
|
|
190
|
+
checkout.on("ready", (...args: any) => {
|
|
191
|
+
console.info("ready?:", args);
|
|
192
|
+
});
|
|
144
193
|
|
|
145
|
-
|
|
146
|
-
// Handle changes to the checkout session
|
|
147
|
-
console.log("session changed:", session);
|
|
148
|
-
});
|
|
149
|
-
checkout.on("error", (error: any) => {
|
|
150
|
-
console.error("Error initializing checkout:", error);
|
|
151
|
-
});
|
|
152
|
-
checkout.on("ready", (...args: any) => {
|
|
153
|
-
console.info("ready?:", args);
|
|
154
|
-
});
|
|
194
|
+
console.log("session created:", sessionResponse.value, checkout);
|
|
155
195
|
|
|
156
|
-
|
|
196
|
+
loadActionsResult = await checkout.loadActions();
|
|
157
197
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
showPaymentMessage("Failed to load payment actions");
|
|
166
|
-
}
|
|
198
|
+
if (
|
|
199
|
+
loadActionsResult.type === "error" &&
|
|
200
|
+
/^No such payment_page: /.test(loadActionsResult.error.message)
|
|
201
|
+
) {
|
|
202
|
+
} else if (loadActionsResult.type !== "success") {
|
|
203
|
+
showPaymentMessage("Failed to load payment actions");
|
|
204
|
+
}
|
|
167
205
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
if (paymentElement) {
|
|
207
|
+
await paymentElement.destroy();
|
|
208
|
+
await nextTick();
|
|
209
|
+
}
|
|
210
|
+
paymentElement = checkout.createPaymentElement();
|
|
211
|
+
paymentElement.mount("#payment-element");
|
|
212
|
+
} else {
|
|
213
|
+
console.error("failed to create checkout");
|
|
171
214
|
}
|
|
172
|
-
paymentElement = checkout.createPaymentElement();
|
|
173
|
-
paymentElement.mount("#payment-element");
|
|
174
215
|
} else {
|
|
175
216
|
const errorContent = clientSecretResponse.body.error;
|
|
176
217
|
iap.stripe.error = new Error(errorContent);
|
|
@@ -184,6 +225,34 @@ async function getSession() {
|
|
|
184
225
|
}
|
|
185
226
|
}
|
|
186
227
|
|
|
228
|
+
async function applyCoupon() {
|
|
229
|
+
if (!couponInput.value.trim()) return;
|
|
230
|
+
couponLoading.value = true;
|
|
231
|
+
couponError.value = "";
|
|
232
|
+
try {
|
|
233
|
+
const response = await api({
|
|
234
|
+
url: `purchases/coupon/?code=${encodeURIComponent(couponInput.value.trim())}`,
|
|
235
|
+
method: "GET",
|
|
236
|
+
});
|
|
237
|
+
if (response.ok) {
|
|
238
|
+
appliedCoupon.value = response.body;
|
|
239
|
+
iap.stripe.coupon = couponInput.value.trim();
|
|
240
|
+
couponInput.value = "";
|
|
241
|
+
} else {
|
|
242
|
+
couponError.value = response.body.error || "Invalid coupon";
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.log("e", e);
|
|
246
|
+
couponError.value = "Failed to validate coupon";
|
|
247
|
+
}
|
|
248
|
+
couponLoading.value = false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function removeCoupon() {
|
|
252
|
+
appliedCoupon.value = null;
|
|
253
|
+
iap.stripe.coupon = "";
|
|
254
|
+
}
|
|
255
|
+
|
|
187
256
|
function showPaymentMessage(messageContent: string) {
|
|
188
257
|
messageText.value = messageContent;
|
|
189
258
|
|
|
@@ -232,4 +301,7 @@ async function handleSubmit() {
|
|
|
232
301
|
.cart {
|
|
233
302
|
min-width: 250px;
|
|
234
303
|
}
|
|
304
|
+
.coupon-applied {
|
|
305
|
+
color: green;
|
|
306
|
+
}
|
|
235
307
|
</style>
|
package/package.json
CHANGED
package/stores/iap.ts
CHANGED
|
@@ -131,6 +131,8 @@ export const useIapStore = defineStore("iap", () => {
|
|
|
131
131
|
const stripeError = ref<undefined | string>();
|
|
132
132
|
const stripeCoupon = ref("");
|
|
133
133
|
|
|
134
|
+
const mobileCoupon = ref("");
|
|
135
|
+
|
|
134
136
|
const storePlatform = computed(() => {
|
|
135
137
|
if (platform.value === "android") {
|
|
136
138
|
return Platform.GOOGLE_PLAY;
|
|
@@ -200,8 +202,9 @@ export const useIapStore = defineStore("iap", () => {
|
|
|
200
202
|
const { store } = window.CdvPurchase;
|
|
201
203
|
const product = store.get(code, storePlatform.value);
|
|
202
204
|
if (product) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
+
const coupon = mobileCoupon.value || undefined;
|
|
206
|
+
const offer = product.getOffer(coupon) ?? product.getOffer();
|
|
207
|
+
return offer.order(coupon ? { applicationUsername: coupon } : undefined);
|
|
205
208
|
} else {
|
|
206
209
|
console.error("Product not found");
|
|
207
210
|
}
|
|
@@ -259,6 +262,7 @@ export const useIapStore = defineStore("iap", () => {
|
|
|
259
262
|
transaction: p,
|
|
260
263
|
platform: p.platform,
|
|
261
264
|
subscription: sub,
|
|
265
|
+
coupon_code: mobileCoupon.value || undefined,
|
|
262
266
|
},
|
|
263
267
|
});
|
|
264
268
|
|
|
@@ -268,6 +272,21 @@ export const useIapStore = defineStore("iap", () => {
|
|
|
268
272
|
}
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
async function redeemAppleCode(): Promise<void> {
|
|
276
|
+
if (platform.value !== "ios") {
|
|
277
|
+
throw new Error("Offer code redemption is only supported on iOS");
|
|
278
|
+
}
|
|
279
|
+
if (!window.CdvPurchase) {
|
|
280
|
+
throw new Error("CdvPurchase not available");
|
|
281
|
+
}
|
|
282
|
+
const { store } = window.CdvPurchase;
|
|
283
|
+
if (typeof store.presentCodeRedemptionSheet === "function") {
|
|
284
|
+
await store.presentCodeRedemptionSheet();
|
|
285
|
+
} else {
|
|
286
|
+
throw new Error("presentCodeRedemptionSheet is not supported in this version of CdvPurchase");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
271
290
|
function getCdvProducts(): any[] {
|
|
272
291
|
const { store } = window.CdvPurchase;
|
|
273
292
|
return store.products;
|
|
@@ -310,6 +329,11 @@ export const useIapStore = defineStore("iap", () => {
|
|
|
310
329
|
purchases,
|
|
311
330
|
ready,
|
|
312
331
|
|
|
332
|
+
mobile: {
|
|
333
|
+
coupon: mobileCoupon,
|
|
334
|
+
redeemAppleCode,
|
|
335
|
+
},
|
|
336
|
+
|
|
313
337
|
stripe: {
|
|
314
338
|
key: STRIP_KEY,
|
|
315
339
|
client: stripe,
|