@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.
@@ -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: boolean;
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
- checkout.on("change", (session: any) => {
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
- console.log("session created:", sessionResponse.value, checkout);
196
+ loadActionsResult = await checkout.loadActions();
157
197
 
158
- loadActionsResult = await checkout.loadActions();
159
-
160
- if (
161
- loadActionsResult.type === "error" &&
162
- /^No such payment_page: /.test(loadActionsResult.error.message)
163
- ) {
164
- } else if (loadActionsResult.type !== "success") {
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
- if (paymentElement) {
169
- await paymentElement.destroy();
170
- await nextTick();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virgodev/iap",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "main": "./index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
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 offer = product.getOffer();
204
- return offer.order();
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,