Skip to content

Commit e355726

Browse files
committed
feat: portokasse top up implementation
1 parent 5863d65 commit e355726

File tree

5 files changed

+117
-24
lines changed

5 files changed

+117
-24
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,31 @@ from the Portokasse service.
321321

322322
### Top Up Account
323323

324+
Top up is the main method of the Portokasse service. There are two different
325+
payment methods: PayPal and Giropay. Giropay expects a BIC string to be also
326+
passed to the method. Both methods will result in a `redirect` link that should
327+
be called by the user to finish the top up request.
328+
329+
**Info:** The minimum amount to top up is EUR 10,00 which can be defined as an
330+
`Amount` object or a raw number in Euro Cents.
331+
332+
**PayPal top up**
333+
334+
```typescript
335+
const amount = { value: 10, currency: 'EUR' }; // or: const amount = 1000;
336+
const payment = await internetmarke.topUp(amount, PaymentMethod.PayPal);
337+
// payment: { code: 'OK', redirect: 'https://paypal.com/...' }
338+
```
339+
340+
**GiroPay top up**
341+
342+
```typescript
343+
const amount = { value: 10, currency: 'EUR' }; // or: const amount = 1000;
344+
const bic = 'HOLVDEB1XXX';
345+
const payment = await internetmarke.topUp(amount, PaymentMethod.GiroPay, bic);
346+
// payment: { code: 'OK', redirect: 'https://giropay.de/...' }
347+
```
348+
324349
## ProdWS (Product Service)
325350

326351
The product list contains all available vouchers that can be ordered. They are

src/Internetmarke.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Amount, Product } from './prodWs/product';
2323
import { ProductService, ProductServiceOptions, ProdWS } from './prodWs/Service';
2424
import {
2525
PaymentMethod,
26+
PaymentResponse,
2627
Portokasse,
2728
PortokasseService,
2829
PortokasseServiceOptions
@@ -318,13 +319,17 @@ export class Internetmarke implements OneClickForApp, Portokasse, ProdWS {
318319
return this.portokasseService;
319320
}
320321

321-
public topUp(amount: Amount | number, paymentMethod: PaymentMethod): Promise<Amount | false> {
322+
public topUp(
323+
amount: Amount | number,
324+
paymentMethod: PaymentMethod,
325+
bic?: string
326+
): Promise<PaymentResponse> {
322327
this.checkServiceInit(
323328
this.portokasseService,
324329
'Cannot get balance before initializing portokasse service'
325330
);
326331

327-
return this.portokasseService.topUp(amount, paymentMethod);
332+
return this.portokasseService.topUp(amount, paymentMethod, bic);
328333
}
329334

330335
protected init(): void {

src/portokasse/Error.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { InternetmarkeError } from '../Error';
2+
3+
export class PortokasseError extends InternetmarkeError {}

src/portokasse/Service.ts

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { inject, injectable } from 'inversify';
44
import { CookieJar } from 'tough-cookie';
55
import { TYPES } from '../di/types';
66
import { UserError } from '../Error';
7+
import { PortokasseError } from './Error';
78
import { Amount } from '../prodWs/product';
89
import { RestService } from '../services/Rest';
910
import { User, UserCredentials, UserInfo } from '../User';
@@ -13,15 +14,25 @@ export enum PaymentMethod {
1314
Paypal = 'PAYPAL'
1415
}
1516

17+
export interface PaymentResponse {
18+
code: string; // 'OK'
19+
redirect: string; // paypal url
20+
}
21+
1622
export interface PortokasseServiceOptions {
1723
user: UserCredentials;
1824
}
1925

2026
export interface Portokasse {
21-
topUp(amount: Amount | number, paymentMethod: PaymentMethod): Promise<Amount | false>;
27+
getUserInfo(): Promise<UserInfo>;
28+
topUp(
29+
amount: Amount | number,
30+
paymentMethod: PaymentMethod,
31+
bic?: string
32+
): Promise<PaymentResponse>;
2233
}
2334

24-
export const BASE_URL = 'https://portokasse.deutschepost.de/portokasse';
35+
const BASE_URL = 'https://portokasse.deutschepost.de/portokasse';
2536

2637
@injectable()
2738
export class PortokasseService extends RestService implements Portokasse {
@@ -45,7 +56,7 @@ export class PortokasseService extends RestService implements Portokasse {
4556
}
4657

4758
public isInitialized(): boolean {
48-
return !!this.cookieJar;
59+
return this.user.isAuthenticated();
4960
}
5061

5162
public async getUserInfo(): Promise<UserInfo> {
@@ -61,10 +72,20 @@ export class PortokasseService extends RestService implements Portokasse {
6172
}
6273

6374
public async topUp(
64-
_amount: Amount | number,
65-
_paymentMethod: PaymentMethod
66-
): Promise<Amount | false> {
67-
return false;
75+
amount: Amount | number,
76+
paymentMethod: PaymentMethod,
77+
bic?: string
78+
): Promise<PaymentResponse> {
79+
const data: any = {
80+
amount: 'number' === typeof amount ? amount : (amount as Amount).value * 100,
81+
paymentMethod
82+
};
83+
84+
if (PaymentMethod.GiroPay === paymentMethod) {
85+
data.bic = bic;
86+
}
87+
88+
return this.request('POST', '/api/v1/payments', data);
6889
}
6990

7091
private async login(): Promise<boolean> {
@@ -84,19 +105,30 @@ export class PortokasseService extends RestService implements Portokasse {
84105
withCredentials: true
85106
};
86107

108+
const isLogin = '/login' === path;
109+
87110
if (data) {
88-
const encodedData: string[] = [];
89-
for (let prop in data) {
90-
encodedData.push(`${prop}=${encodeURIComponent(data[prop])}`);
111+
if (isLogin) {
112+
const encodedData: string[] = [];
113+
for (let prop in data) {
114+
encodedData.push(`${prop}=${encodeURIComponent(data[prop])}`);
115+
}
116+
117+
options.data = encodedData.join('&');
118+
} else {
119+
options.data = data;
91120
}
92-
93-
options.data = encodedData.join('&');
94121
}
95122
if (this.csrf) {
96123
options.headers = {
97-
'X-CSRF-TOKEN': this.csrf,
98-
'Content-Type': 'application/x-www-form-urlencoded'
124+
'X-CSRF-TOKEN': this.csrf
99125
};
126+
127+
if (data) {
128+
options.headers['Content-Type'] = isLogin
129+
? 'application/x-www-form-urlencoded'
130+
: 'application/json';
131+
}
100132
}
101133

102134
try {
@@ -112,7 +144,12 @@ export class PortokasseService extends RestService implements Portokasse {
112144

113145
return res.data;
114146
} catch (e) {
115-
return e.response?.data || null;
147+
const error = new PortokasseError(
148+
`Error from Portokasse: ${e.response?.data.code || 'Unknown'}`
149+
);
150+
(error as any).response = e.response || e.message;
151+
152+
throw error;
116153
}
117154
}
118155
}

test/portokasse/Service.spec.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { UserError } from '../../src/Error';
44
import { PaymentMethod, PortokasseService } from '../../src/portokasse/Service';
55
import { userCredentials } from '../1c4a/helper';
66
import { User } from '../../src/User';
7+
import { PortokasseError } from '../../src/portokasse/Error';
78

89
describe('Portokasse Service', () => {
910
let service: PortokasseService;
@@ -38,7 +39,7 @@ describe('Portokasse Service', () => {
3839
});
3940

4041
it('should init with minimal options', async () => {
41-
expect(service.init({ user: userCredentials })).to.eventually.be.fulfilled;
42+
await service.init({ user: userCredentials });
4243
expect(service.isInitialized()).to.be.true;
4344
});
4445
});
@@ -53,9 +54,7 @@ describe('Portokasse Service', () => {
5354
}
5455
});
5556

56-
const info = await service.getUserInfo();
57-
58-
expect(info.isAuthenticated).to.be.false;
57+
expect(service.getUserInfo()).to.eventually.be.rejectedWith(PortokasseError);
5958
});
6059

6160
it('should retrieve user balance', async () => {
@@ -77,10 +76,34 @@ describe('Portokasse Service', () => {
7776
});
7877

7978
describe('topUp', () => {
80-
it('should add tests once topup is implemented', async () => {
81-
const res = await service.topUp(1000, PaymentMethod.GiroPay);
79+
it('should top up with GiroPay', async () => {
80+
moxios.stubOnce('post', /\/payments$/, {
81+
status: 200,
82+
headers: {},
83+
response: {
84+
code: 'OK',
85+
redirect: 'http://localhost'
86+
}
87+
});
88+
89+
const res = await service.topUp(1000, PaymentMethod.GiroPay, 'XXXXDEXXXX');
90+
91+
expect(res).to.exist;
92+
});
93+
94+
it('should top up with Paypal', async () => {
95+
moxios.stubOnce('post', /\/payments$/, {
96+
status: 200,
97+
headers: {},
98+
response: {
99+
code: 'OK',
100+
redirect: 'http://localhost'
101+
}
102+
});
103+
104+
const res = await service.topUp(1000, PaymentMethod.Paypal);
82105

83-
expect(res).to.be.false;
106+
expect(res).to.exist;
84107
});
85108
});
86109
});

0 commit comments

Comments
 (0)