@@ -16,19 +16,24 @@ const initCurrencyFormatter = (code: string) => {
1616 });
1717};
1818
19+ const DEFAULT_PAYMENT_TYPE = ' card' ;
20+ const ARE_WE_DONE_YET_TIMER_MS = 2500 ;
21+ const SHORT_WAIT_MS = 2500 ;
22+ const EXCEPTIONS_UNTIL_ERROR_SHOWN = 3 ;
23+
24+ // We don't need these to be reactive
1925let currencyFormatter = initCurrencyFormatter (' USD' );
2026let paddle = null ;
2127let doneCheckerHandler = null ;
2228let exceptionCounter = 0 ;
23-
29+ let transactionId = null ;
30+ let paymentType = DEFAULT_PAYMENT_TYPE ;
2431
2532const planName = ref ();
2633const planSystemError = ref (false );
2734const paymentComplete = ref (false );
2835const paddleLoading = ref (true );
2936const paddleUnknownError = ref (false );
30- const ARE_WE_DONE_YET_TIMER_MS = 2500 ;
31- const EXCEPTIONS_UNTIL_ERROR_SHOWN = 3 ;
3237
3338// Placeholder information for the skeletons
3439const order_summary = ref ({
@@ -60,6 +65,8 @@ onUnmounted(() => {
6065 *
6166 * Poll the is-done api route every 2.5 seconds, and if we're doneish (paid or complete)
6267 * then reload the window to let the payment verification screen do their magic.
68+ *
69+ * Triggered by PaddleJS's checkout events. (Anyone with a transaction ID!)
6370 */
6471const areWeDoneHere = async () => {
6572 try {
@@ -76,7 +83,16 @@ const areWeDoneHere = async () => {
7683
7784 // For some reason PaddleJS has paid's constant as undefined. Hmmm...
7885 if ([' completed' , ' paid' ].indexOf (status ) > - 1 ) {
79- window .location .reload ();
86+ // Remove the Paddle checkout form, and after a short wait reload the page.
87+ paymentComplete .value = true ;
88+ }
89+
90+ if (status === ' completed' ) {
91+ // We re-use this handler since it's not currently used, and it's hooked up to unMount.
92+ doneCheckerHandler = window .setTimeout (() => {
93+ window .location .reload ();
94+ }, SHORT_WAIT_MS );
95+ return ;
8096 }
8197
8298 // Lastly clear up the exception counter, if we've reached here there's no exceptions happening and no errors need to be shown.
@@ -132,21 +148,34 @@ const onPaddleEvent = async (evt: PaddleEventData) => {
132148 // Just update the cart, every checkout.* event has all the information on it.
133149 if (evt .name .indexOf (' checkout.' ) === 0 ) {
134150 const data = evt .data ;
151+ const backendNeedsUpdate = transactionId !== (data ?.transaction_id ?? null ) || paymentType !== data .payment .method_details .type ;
152+
153+ // Transaction ID should only update if we're not falsey.
154+ if (data ?.transaction_id ) {
155+ transactionId = data ?.transaction_id ?? null ;
156+ }
135157
136- // Set the transaction id
137- if (! doneCheckerHandler && data ?.transaction_id ) {
158+ // Payment type is only reliably updated on payment selected.
159+ if (evt .name == ' checkout.payment.selected' ) {
160+ paymentType = data .payment .method_details .type ;
161+ }
162+
163+ if (backendNeedsUpdate ) {
138164 await fetch (' /api/v1/subscription/paddle/tx/set/' , {
139165 mode: ' same-origin' ,
140166 credentials: ' include' ,
141167 method: ' PUT' ,
142168 body: JSON .stringify ({
143- txid: data ?.transaction_id ,
169+ payment_type: paymentType ,
170+ txid: transactionId ,
144171 }),
145172 headers: {
146173 ' X-CSRFToken' : csrfToken ,
147174 },
148175 });
176+ }
149177
178+ if (! doneCheckerHandler && transactionId ) {
150179 // Setup our done checker
151180 doneCheckerHandler = window .setTimeout (() => areWeDoneHere (), ARE_WE_DONE_YET_TIMER_MS );
152181 }
@@ -208,8 +237,9 @@ const setupPaddle = async () => {
208237 eventCallback: onPaddleEvent ,
209238 checkout: {
210239 settings: {
240+ successUrl: ` ${window .location .origin }/subscription/paddle/complete/ ` ,
211241 displayMode: ' inline' ,
212- frameTarget: ' checkout-container ' ,
242+ frameTarget: ' paddle-checkout ' ,
213243 frameInitialHeight: 992 ,
214244 frameStyle: ' width: 100%; background-color: transparent; border: none;' ,
215245 variant: ' one-page' ,
@@ -241,10 +271,10 @@ export default {
241271 <h2 >{{ t('views.subscribe.title') }}</h2 >
242272 <notice-bar v-if =" paddleUnknownError" :type =" NoticeBarTypes.Critical" >{{
243273 t('views.subscribe.paddleUnknownError')
244- }}</notice-bar >
274+ }}</notice-bar >
245275 <notice-bar v-if =" planSystemError" :type =" NoticeBarTypes.Critical" >{{
246276 t('views.subscribe.planSystemError')
247- }}</notice-bar >
277+ }}</notice-bar >
248278 <div class =" container" >
249279 <card-container class =" summary-card" >
250280 <ul class =" summary" >
@@ -318,6 +348,7 @@ export default {
318348 <!-- Paddle's checkout will just disappear into the void once it starts redirecting us to successUrl.
319349 It looks ugly, so show a small message in its place. -->
320350 <p v-if =" paymentComplete" >{{ t('views.subscribe.paymentComplete') }}</p >
351+ <div v-else class =" paddle-checkout" ></div >
321352 </card-container >
322353 </div >
323354 </div >
@@ -350,6 +381,7 @@ export default {
350381
351382 .checkout-container {
352383 width : 100% ;
384+ min-height : 1090px ;
353385 }
354386
355387 .summary-card {
0 commit comments