Skip to content

Commit 96e02b7

Browse files
committed
fix(payments): Upgrade to latest version of Stripe SDK
Fixes: #1448
1 parent f69e717 commit 96e02b7

File tree

6 files changed

+92
-176
lines changed

6 files changed

+92
-176
lines changed

src/app/account-app/credit-cards/stripe-add-card-dialog.component.html

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,31 +42,8 @@ <h1 mat-dialog-title>Credit card setup via Stripe</h1>
4242
</ng-template>
4343

4444
<div mat-dialog-content style="max-width: 500px;">
45-
<div style="width: 100%; text-align: center">
46-
<img src="/_img/pay/stripe-logo.png" alt="VISA, MasterCard, AmEx" style="width: inherit; height: auto" />
47-
</div>
4845
<div [style.display]="state === 'initial' ? 'block' : 'none'">
49-
<div class="payment-row">
50-
<div class="payment-field payment-field-big">
51-
<div #cardNumber></div>
52-
<label for="cardNumber">Card number</label>
53-
</div>
54-
</div>
55-
<div class="payment-row">
56-
<div class="payment-field payment-field-small">
57-
<div #cardExpiry></div>
58-
<label for="cardExpiry">Expiration date</label>
59-
</div>
60-
<div class="payment-field payment-field-small">
61-
<div #cardCvc></div>
62-
<label for="cardCvc">CVC code</label>
63-
</div>
64-
<div class="payment-field payment-field-small StripeElement">
65-
<mat-form-field>
66-
<input matInput [(ngModel)]="zipCode" placeholder="ZIP code">
67-
</mat-form-field>
68-
</div>
69-
</div>
46+
<div #paymentElement id="payment-element"></div>
7047

7148
<div id="card-errors" role="alert" style="display: flex; justify-content: center;">
7249
<strong> {{ stripeError }} </strong>

src/app/account-app/credit-cards/stripe-add-card-dialog.component.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angula
2121
import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog';
2222

2323
import { PaymentsService } from '../payments.service';
24-
import { AsyncSubject } from 'rxjs';
24+
import { firstValueFrom, AsyncSubject } from 'rxjs';
2525

2626
let stripeLoader: AsyncSubject<void> = null;
2727
declare var Stripe: any;
@@ -35,15 +35,15 @@ export class StripeAddCardDialogComponent implements AfterViewInit {
3535
clientSecret: string;
3636

3737
stripe: any;
38+
elements: any;
39+
payment: any;
3840
card: any;
3941

4042
stripeError: string;
4143
zipCode: string;
4244
failure: string;
4345

44-
@ViewChild('cardNumber') cardNumber: ElementRef;
45-
@ViewChild('cardExpiry') cardExpiry: ElementRef;
46-
@ViewChild('cardCvc') cardCvc: ElementRef;
46+
@ViewChild('paymentElement') paymentElement: ElementRef;
4747

4848
constructor(
4949
private paymentsservice: PaymentsService,
@@ -66,29 +66,32 @@ export class StripeAddCardDialogComponent implements AfterViewInit {
6666
async ngAfterViewInit() {
6767
await stripeLoader.toPromise();
6868
const stripePubkey = await this.paymentsservice.stripePubkey.toPromise();
69+
const customerSession = await firstValueFrom(this.paymentsservice.customerSession());
6970

70-
const stripeStyle = {
71-
base: {
72-
fontSize: '18px',
73-
color: '#32325d',
74-
textAlign: 'center',
71+
this.stripe = Stripe(stripePubkey);
72+
const options = {
73+
clientSecret: this.clientSecret,
74+
appearance: {
75+
rules: {
76+
'.CheckboxInput': {
77+
border: '1px solid #32325d'
78+
}
79+
},
80+
variables: { colorPrimaryText: '#32325d'},
7581
}
7682
};
83+
if (Object.keys(customerSession).includes('customer_session_client_secret')) {
84+
options['customerSessionClientSecret'] = customerSession['customer_session_client_secret'];
85+
}
86+
this.elements = this.stripe.elements(options);
7787

78-
this.stripe = Stripe(stripePubkey);
79-
const elements = this.stripe.elements();
80-
81-
this.card = elements.create('cardNumber', {style: stripeStyle});
82-
this.card.mount(this.cardNumber.nativeElement);
83-
this.card.addEventListener('change', e => this.errorHandler(e));
84-
85-
const expiry = elements.create('cardExpiry', {style: stripeStyle});
86-
expiry.mount(this.cardExpiry.nativeElement);
87-
expiry.addEventListener('change', e => this.errorHandler(e));
88-
89-
const cvc = elements.create('cardCvc', {style: stripeStyle});
90-
cvc.mount(this.cardCvc.nativeElement);
91-
cvc.addEventListener('change', e => this.errorHandler(e));
88+
this.payment = this.elements.create('payment', {
89+
layout: { 'type': 'accordion',
90+
'defaultCollapsed': false,
91+
'radios': true
92+
}
93+
});
94+
this.payment.mount(this.paymentElement.nativeElement);
9295

9396
this.state = 'initial';
9497
}
@@ -104,15 +107,15 @@ export class StripeAddCardDialogComponent implements AfterViewInit {
104107
submitCardDetails() {
105108
this.state = 'processing';
106109

107-
this.stripe.confirmCardSetup(this.clientSecret, {
108-
payment_method: {
109-
card: this.card,
110-
billing_details: {
111-
// 'name': 'John Nhoj'
112-
address: { postal_code: this.zipCode },
113-
},
114-
},
115-
}).then((result: any) => {
110+
this.stripe.confirmSetup(
111+
{
112+
elements: this.elements,
113+
redirect: 'if_required',
114+
// confirmParams: {
115+
// return_url: '',
116+
// }
117+
}
118+
).then((result: any) => {
116119
console.log(result);
117120
if (result.setupIntent.status === 'succeeded') {
118121
this.stripeError = '';

src/app/account-app/payments.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export class PaymentsService {
5050
});
5151
}
5252

53+
customerSession(): Observable<any> {
54+
return this.rmmapi.createCustomerSession();
55+
}
56+
5357
submitStripePayment(tid: number, token: string): Observable<any> {
5458
return this.rmmapi.payWithStripe(tid, token);
5559
}

src/app/account-app/stripe-payment-dialog.component.html

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,8 @@
1-
<style>
2-
.payment-row {
3-
display: -ms-flexbox;
4-
display: flex;
5-
justify-content: space-between;
6-
flex-wrap: wrap;
7-
gap: 5px;
8-
margin: 0 5px 10px;
9-
}
10-
11-
.payment-field {
12-
position: relative;
13-
height: 50px;
14-
padding: 5px;
15-
text-align: center;
16-
}
17-
18-
@media only screen and (max-width: 767px) {
19-
.payment-field {
20-
font-size: 14px;
21-
}
22-
}
23-
24-
.payment-field-big {
25-
width: 100%;
26-
}
27-
28-
.payment-field-small {
29-
flex: 1 0 25%;
30-
}
31-
</style>
32-
331
<h1 mat-dialog-title>Card payment</h1>
34-
<div style="width: 95%; text-align: center; margin: auto;">
35-
<img src="/_img/pay/stripe-logo.png" alt="VISA, MasterCard, AmEx" style="width: inherit; height: auto" />
36-
</div>
372
<div [style.display]="state === 'initial' ? 'block' : 'none'">
38-
<div id="payment-button-row" class="payment-row" [style.display]="paymentRequestsSupported ? 'block' : 'none'">
39-
<div class="payment-field payment-field-big">
40-
<div #paymentRequestButton></div>
41-
</div>
42-
</div>
43-
<div class="payment-row" autofocus>
44-
<div class="payment-field payment-field-big">
45-
<label for="cardNumber">Card number</label>
46-
<div #cardNumber id="cardNumber"></div>
47-
</div>
48-
</div>
49-
<div class="payment-row">
50-
<div class="payment-field payment-field-small">
51-
<label for="cardExpiry">Expiration date</label>
52-
<div #cardExpiry></div>
53-
</div>
54-
<div class="payment-field payment-field-small">
55-
<label for="cardCvc">CVC code</label>
56-
<div #cardCvc></div>
57-
</div>
58-
<div class="payment-field payment-field-small StripeElement">
59-
<mat-form-field>
60-
<input matInput [(ngModel)]="zipCode" placeholder="ZIP/postal code">
61-
</mat-form-field>
62-
</div>
63-
</div>
3+
<div >
4+
<div #paymentElement id="payment-element"></div>
5+
</div>
646

657
<div id="card-errors" role="alert" style="display: flex; justify-content: center;">
668
<strong> {{ stripeError }} </strong>

src/app/account-app/stripe-payment-dialog.component.ts

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { MatLegacyDialogRef as MatDialogRef, MAT_LEGACY_DIALOG_DATA as MAT_DIALO
2323
import { HttpErrorResponse } from '@angular/common/http';
2424

2525
import { PaymentsService } from './payments.service';
26-
import { AsyncSubject } from 'rxjs';
26+
import { firstValueFrom, AsyncSubject } from 'rxjs';
2727

2828
let stripeLoader: AsyncSubject<void> = null;
2929
declare var Stripe: any;
@@ -38,20 +38,18 @@ export class StripePaymentDialogComponent implements AfterViewInit {
3838
total: number;
3939
currency: string;
4040

41+
elements: any;
4142
paymentRequestsSupported = false;
4243
state = 'loading';
4344

4445
stripe: any;
45-
card: any;
46+
payment: any;
4647

4748
stripeError: string;
4849
zipCode: string;
4950
failure: string;
5051

51-
@ViewChild('paymentRequestButton') paymentRequestButton: ElementRef;
52-
@ViewChild('cardNumber') cardNumber: ElementRef;
53-
@ViewChild('cardExpiry') cardExpiry: ElementRef;
54-
@ViewChild('cardCvc') cardCvc: ElementRef;
52+
@ViewChild('paymentElement') paymentElement: ElementRef;
5553

5654
constructor(
5755
private paymentsservice: PaymentsService,
@@ -77,55 +75,37 @@ export class StripePaymentDialogComponent implements AfterViewInit {
7775

7876
async ngAfterViewInit() {
7977
await stripeLoader.toPromise();
80-
const stripePubkey = await this.paymentsservice.stripePubkey.toPromise();
81-
82-
const stripeStyle = {
83-
base: {
84-
fontSize: '18px',
85-
color: '#32325d',
86-
textAlign: 'center',
87-
}
88-
};
78+
const stripePubkey = await firstValueFrom(this.paymentsservice.stripePubkey);
79+
const customerSession = await firstValueFrom(this.paymentsservice.customerSession());
8980

9081
this.stripe = Stripe(stripePubkey);
91-
const elements = this.stripe.elements();
92-
93-
const paymentRequest = this.stripe.paymentRequest({
94-
country: 'NO',
82+
const options = {
83+
mode: 'payment',
84+
amount: Math.trunc(this.total * 100),
9585
currency: this.currency.toLowerCase(),
96-
total: {
97-
label: 'Runbox purchase #' + this.tid,
98-
amount: Math.trunc(this.total * 100),
99-
},
100-
});
101-
102-
const prButton = elements.create('paymentRequestButton', {
103-
paymentRequest: paymentRequest,
104-
});
105-
paymentRequest.canMakePayment().then(result => {
106-
if (result) {
107-
prButton.mount(this.paymentRequestButton.nativeElement);
108-
this.paymentRequestsSupported = true;
109-
paymentRequest.on('paymentmethod', (ev: any) => {
110-
this.handlePaymentMethod(ev.paymentMethod).then(
111-
() => ev.complete('success'),
112-
() => ev.complete('fail'),
113-
);
114-
});
86+
// setup_future_usage: 'off_session',
87+
'paymentMethodOptions[require_cvc_recollection]': true,
88+
appearance: {
89+
rules: {
90+
'.CheckboxInput': {
91+
border: '1px solid #32325d'
92+
}
93+
},
94+
variables: { colorPrimaryText: '#32325d'},
11595
}
116-
});
117-
118-
this.card = elements.create('cardNumber', {style: stripeStyle});
119-
this.card.mount(this.cardNumber.nativeElement);
120-
this.card.addEventListener('change', e => this.errorHandler(e));
121-
122-
const expiry = elements.create('cardExpiry', {style: stripeStyle});
123-
expiry.mount(this.cardExpiry.nativeElement);
124-
expiry.addEventListener('change', e => this.errorHandler(e));
96+
};
97+
if (Object.keys(customerSession).includes('customer_session_client_secret')) {
98+
options['customerSessionClientSecret'] = customerSession['customer_session_client_secret'];
99+
}
100+
this.elements = this.stripe.elements(options);
125101

126-
const cvc = elements.create('cardCvc', {style: stripeStyle});
127-
cvc.mount(this.cardCvc.nativeElement);
128-
cvc.addEventListener('change', e => this.errorHandler(e));
102+
this.payment = this.elements.create('payment', {
103+
layout: { 'type': 'tabs',
104+
'defaultCollapsed': false,
105+
'radios': true
106+
}
107+
});
108+
this.payment.mount(this.paymentElement.nativeElement);
129109

130110
this.state = 'initial';
131111
}
@@ -138,26 +118,30 @@ export class StripePaymentDialogComponent implements AfterViewInit {
138118
}
139119
}
140120

141-
submitPayment() {
121+
async submitPayment() {
142122
this.state = 'processing';
143123

144-
const additionals = { billing_details: { address: { postal_code: this.zipCode } } };
124+
await this.elements.submit();
125+
126+
// const additionals = { billing_details: { address: { postal_code: this.zipCode } } };
145127

146-
this.stripe.createPaymentMethod('card', this.card, additionals).then(result => {
128+
this.stripe.createConfirmationToken({
129+
'elements': this.elements}
130+
).then(result => {
147131
if (result.error) {
148132
this.stripeError = result.error.message;
149133
this.state = 'initial';
150134
} else {
151135
console.log(result);
152-
this.handlePaymentMethod(result.paymentMethod);
136+
this.handleConfirmationToken(result.confirmationToken.id);
153137
}
154138
});
155139
}
156140

157-
handlePaymentMethod(paymentMethod: any) {
141+
handleConfirmationToken(cId: string) {
158142
return new Promise<void>((resolve, reject) => {
159-
this.paymentsservice.submitStripePayment(this.tid, paymentMethod.id).subscribe(res => {
160-
if (res.status === 'requires_source_action') {
143+
this.paymentsservice.submitStripePayment(this.tid, cId).subscribe(res => {
144+
if (res.status === 'requires_action') {
161145
const client_secret = res.client_secret;
162146
this.stripe.handleCardAction(client_secret).then(actionRes => {
163147
if (actionRes.error) {

src/app/rmmapi/rbwebmail.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,12 @@ export class RunboxWebmailAPI {
824824
);
825825
}
826826

827+
public createCustomerSession(): Observable<string> {
828+
return this.http.get('/rest/v1/account_product/stripe/session').pipe(
829+
map((res: HttpResponse<any>) => res['result'])
830+
);
831+
}
832+
827833
public payWithBitpay(tid: number, return_url: string, cancel_url: string): Observable<any> {
828834
return this.http.post('/rest/v1/account_product/crypto/pay', {
829835
tid, return_url, cancel_url
@@ -840,9 +846,9 @@ export class RunboxWebmailAPI {
840846
);
841847
}
842848

843-
public payWithStripe(tid: number, paymentMethod: string): Observable<any> {
849+
public payWithStripe(tid: number, confirmation_id: string): Observable<any> {
844850
return this.http.post('/rest/v1/account_product/stripe/pay', {
845-
tid: tid, payment_method: paymentMethod
851+
tid: tid, confirmation_id: confirmation_id
846852
}).pipe(
847853
map((res: HttpResponse<any>) => res['result'])
848854
);

0 commit comments

Comments
 (0)