@virgodev/iap 1.0.5 → 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,17 +139,18 @@ 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
- watch(
114
- () => props.cart,
115
- () => {
116
- getSession();
117
- }
118
- );
151
+ watch([() => props.cart, () => iap.stripe.coupon], () => {
152
+ getSession();
153
+ });
119
154
 
120
155
  onMounted(async () => {
121
156
  getSession();
@@ -123,6 +158,7 @@ onMounted(async () => {
123
158
 
124
159
  async function getSession() {
125
160
  if (iap.stripe.client) {
161
+ messageText.value = "";
126
162
  try {
127
163
  // Fetch client secret from backend
128
164
  const clientSecretResponse = await api({
@@ -131,8 +167,9 @@ async function getSession() {
131
167
  json: {
132
168
  items: props.cart.items,
133
169
  sub: props.cart.items.some(
134
- (i) => i.product.type === ProductType.PAID_SUBSCRIPTION
170
+ (i) => i.product.type === ProductType.PAID_SUBSCRIPTION,
135
171
  ),
172
+ coupon_code: iap.stripe.coupon,
136
173
  },
137
174
  });
138
175
  if (clientSecretResponse.ok) {
@@ -142,24 +179,39 @@ async function getSession() {
142
179
  clientSecret: sessionResponse.value.client_secret,
143
180
  elementsOptions: { appearance: { theme: "stripe" } },
144
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
+ });
145
193
 
146
- checkout.on("change", (session: any) => {
147
- // Handle changes to the checkout session
148
- console.log("session changed:", session);
149
- });
194
+ console.log("session created:", sessionResponse.value, checkout);
150
195
 
151
- loadActionsResult = await checkout.loadActions();
196
+ loadActionsResult = await checkout.loadActions();
152
197
 
153
- if (loadActionsResult.type !== "success") {
154
- showPaymentMessage("Failed to load payment actions");
155
- }
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
+ }
156
205
 
157
- if (paymentElement) {
158
- await paymentElement.destroy();
159
- 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");
160
214
  }
161
- paymentElement = checkout.createPaymentElement();
162
- paymentElement.mount("#payment-element");
163
215
  } else {
164
216
  const errorContent = clientSecretResponse.body.error;
165
217
  iap.stripe.error = new Error(errorContent);
@@ -173,6 +225,34 @@ async function getSession() {
173
225
  }
174
226
  }
175
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
+
176
256
  function showPaymentMessage(messageContent: string) {
177
257
  messageText.value = messageContent;
178
258
 
@@ -186,7 +266,6 @@ function setLoading(loading: boolean) {
186
266
  }
187
267
 
188
268
  async function handleSubmit() {
189
- console.log("Submitting payment");
190
269
  setLoading(true);
191
270
  emits("start");
192
271
 
@@ -210,7 +289,6 @@ async function handleSubmit() {
210
289
  }
211
290
 
212
291
  setLoading(false);
213
- console.log("done Submitting payment");
214
292
  }
215
293
  </script>
216
294
 
@@ -223,4 +301,7 @@ async function handleSubmit() {
223
301
  .cart {
224
302
  min-width: 250px;
225
303
  }
304
+ .coupon-applied {
305
+ color: green;
306
+ }
226
307
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virgodev/iap",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "./index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -9,9 +9,11 @@
9
9
  "license": "ISC",
10
10
  "description": "",
11
11
  "dependencies": {
12
+ "@capacitor/core": "^7.4.4",
12
13
  "@stripe/stripe-js": "^8.2.0",
13
14
  "@virgodev/iap": "^1.0.1",
14
- "cordova-plugin-purchase": "^13.12.1"
15
+ "cordova-plugin-purchase": "^13.12.1",
16
+ "localforage": "^1.10.0"
15
17
  },
16
18
  "peerDependencies": {
17
19
  "@capacitor/app": "^7.1.0",
package/stores/iap.ts CHANGED
@@ -129,6 +129,9 @@ export const useIapStore = defineStore("iap", () => {
129
129
  const stripeCallback =
130
130
  ref<(t: Transaction | undefined) => Transaction | undefined>();
131
131
  const stripeError = ref<undefined | string>();
132
+ const stripeCoupon = ref("");
133
+
134
+ const mobileCoupon = ref("");
132
135
 
133
136
  const storePlatform = computed(() => {
134
137
  if (platform.value === "android") {
@@ -199,8 +202,9 @@ export const useIapStore = defineStore("iap", () => {
199
202
  const { store } = window.CdvPurchase;
200
203
  const product = store.get(code, storePlatform.value);
201
204
  if (product) {
202
- const offer = product.getOffer();
203
- 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);
204
208
  } else {
205
209
  console.error("Product not found");
206
210
  }
@@ -258,6 +262,7 @@ export const useIapStore = defineStore("iap", () => {
258
262
  transaction: p,
259
263
  platform: p.platform,
260
264
  subscription: sub,
265
+ coupon_code: mobileCoupon.value || undefined,
261
266
  },
262
267
  });
263
268
 
@@ -267,6 +272,21 @@ export const useIapStore = defineStore("iap", () => {
267
272
  }
268
273
  }
269
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
+
270
290
  function getCdvProducts(): any[] {
271
291
  const { store } = window.CdvPurchase;
272
292
  return store.products;
@@ -309,6 +329,11 @@ export const useIapStore = defineStore("iap", () => {
309
329
  purchases,
310
330
  ready,
311
331
 
332
+ mobile: {
333
+ coupon: mobileCoupon,
334
+ redeemAppleCode,
335
+ },
336
+
312
337
  stripe: {
313
338
  key: STRIP_KEY,
314
339
  client: stripe,
@@ -318,6 +343,7 @@ export const useIapStore = defineStore("iap", () => {
318
343
  result: stripeResult,
319
344
  callback: stripeCallback,
320
345
  error: stripeError,
346
+ coupon: stripeCoupon,
321
347
  reset,
322
348
  },
323
349
  };