Skip to content

Commit 8bc6437

Browse files
authored
Merge pull request #8 from Ficodes/slu/redsys
Slu/redsys
2 parents afecb04 + 84380a6 commit 8bc6437

23 files changed

Lines changed: 386 additions & 82 deletions

billing-server/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ app.post('/v1/checkout/sessions', (req, res) => {
160160
status: 'open',
161161
customer: customerId,
162162
payment_intent: paymentIntentId,
163-
url: `http://localhost:${PORT}/stripe-checkout?session_id=${sessionId}`,
163+
url: `http://localhost:${PORT}/stripe/checkin`,
164164
line_items: lineItems,
165165
}
166166
stripeSessions.set(sessionId, session)

charging-docker/docker-compose.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ services:
3333
# Service Configuration
3434
- BAE_SERVICE_HOST=http://proxy.docker:8004/
3535
- BAE_CB_LOCAL_SITE=http://charging.docker:8006/
36+
- GUNICORN_CMD_ARGS=--timeout 120
3637

3738
# TMForum APIs - all-in-one (port 8633)
3839
- BAE_CB_CATALOG=http://host.docker.internal:8633/tmf-api/productCatalogManagement/v4
@@ -58,7 +59,10 @@ services:
5859
- BAE_LP_OAUTH2_CUSTOMER_ROLE=customer
5960
- BAE_LP_OAUTH2_ORG_ADMIN_ROLE=orgAdmin
6061
# Payment Configuration
61-
- BAE_CB_PAYMENT_METHOD=stripe
62+
- BAE_CB_PAYMENT_METHOD=redsys
63+
- BAE_CB_REDSYS_MERCHANT_CODE=263100000
64+
- BAE_CB_REDSYS_TERMINAL=005
65+
- BAE_CB_REDSYS_SECRET_KEY=sq7HjrUOBfKmC576ILgskD5srU870gJ7
6266
- BAE_CB_BILLING_ENGINE=local
6367
- BAE_CB_DPAS_CLIENT_API_URL=http://host.docker.internal:4201/api/payment-start
6468
- BAE_SERVICE_HOST=http://localhost:4200/
@@ -70,4 +74,3 @@ services:
7074
networks:
7175
main:
7276
external: true
73-

cypress.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ export default defineConfig({
1313
requestTimeout: 30000,
1414
responseTimeout: 60000,
1515
env: {
16-
PAYMENT_METHOD: process.env.BAE_CB_PAYMENT_METHOD || 'stripe'
16+
PAYMENT_METHOD: process.env.BAE_CB_PAYMENT_METHOD || 'redsys',
17+
REDSYS_ORIGIN: process.env.BAE_CB_REDSYS_ORIGIN || 'https://sis-t.redsys.es:25443',
18+
REDSYS_AUTH_ORIGIN: process.env.BAE_CB_REDSYS_AUTH_ORIGIN || 'https://sis-d.redsys.es'
1719
}
1820
}
1921
})

cypress/e2e/01-happy-journey.cy.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,16 @@ describe('Happy Journey E2E', {
166166

167167
cy.wait('@saveBilling')
168168
cy.wait('@getBilling')
169+
// Wait for the billing address to be processed and selected
170+
cy.wait(2000)
171+
cy.deferPaymentRedirect()
169172
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
170173
cy.wait('@createOrder')
171-
cy.wait('@getOrders')
174+
cy.waitForOrdersBeforePayment()
172175
cy.intercept('**/charging/api/orderManagement/orders/confirm/').as('checkin')
173176
cy.completePayment()
174177
cy.wait('@checkin')
178+
cy.waitForOrdersAfterPayment()
175179
cy.wait('@getBilling')
176180

177181
// ============================================

cypress/e2e/04-billing-and-payment-scheduler.cy.ts

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import {
1717
*
1818
* After each billing scheduler run, also triggers the payment scheduler
1919
* cron job and verifies that the resulting CustomerBill ends up 'settled'
20-
* once the recurring charge against the stored Stripe payment method succeeds.
20+
* once the recurring charge against the stored payment method succeeds.
2121
*
2222
* Requires BAE_CB_BILLING_HTTP_ENABLED=true in the charging container.
2323
*/
2424
const CHARGING_URL = 'http://localhost:8006'
2525
const TMF_URL = 'http://localhost:8633'
26+
const SCORPIO_URL = 'http://localhost:1026'
2627
const BILLING_SERVER_URL = 'http://localhost:4201'
28+
const IS_REDSYS = Cypress.env('PAYMENT_METHOD') === 'redsys'
2729

2830
const runPaymentScheduler = () => {
2931
cy.request({ url: `${CHARGING_URL}/charging/api/test/paymentScheduler`, method: 'POST' }).then((res) => {
@@ -41,6 +43,65 @@ const expectCustomerBillState = (billId: string, expectedState: string) => {
4143
})
4244
}
4345

46+
const patchCustomerBillAmountInScorpio = (
47+
billId: string,
48+
taxIncludedAmount: { unit: string, value: number | string }
49+
) => {
50+
const value = Number(taxIncludedAmount.value)
51+
52+
cy.request({
53+
url: `${SCORPIO_URL}/ngsi-ld/v1/entities/${encodeURIComponent(billId)}/attrs`,
54+
method: 'PATCH',
55+
headers: { 'Content-Type': 'application/ld+json' },
56+
body: {
57+
taxIncludedAmount: {
58+
type: 'Property',
59+
tmfValue: { type: 'Property', value },
60+
unit: { type: 'Property', value: taxIncludedAmount.unit },
61+
value: { tmfValue: value, unit: taxIncludedAmount.unit },
62+
},
63+
'@context': ['https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld'],
64+
},
65+
}).then((res) => {
66+
expect(res.status).to.eq(204)
67+
})
68+
}
69+
70+
const expectRecurringPaymentToRetry = (
71+
billId: string,
72+
stripeStatus: 'processing' | 'requires_payment_method'
73+
) => {
74+
const runAndVerifyRetry = (restorePayment: () => void = () => {}) => {
75+
runPaymentScheduler()
76+
expectCustomerBillState(billId, 'new')
77+
78+
restorePayment()
79+
runPaymentScheduler()
80+
expectCustomerBillState(billId, 'settled')
81+
}
82+
83+
if (!IS_REDSYS) {
84+
cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/${stripeStatus}`)
85+
runAndVerifyRetry()
86+
return
87+
}
88+
89+
cy.request({
90+
url: `${TMF_URL}/tmf-api/customerBillManagement/v4/customerBill/${billId}`,
91+
method: 'GET',
92+
}).then((res) => {
93+
expect(res.status).to.eq(200)
94+
const originalAmount = res.body.taxIncludedAmount
95+
const rejectedAmount = {
96+
...originalAmount,
97+
value: `${Math.floor(Number(originalAmount.value))}.96`,
98+
}
99+
100+
patchCustomerBillAmountInScorpio(billId, rejectedAmount)
101+
runAndVerifyRetry(() => patchCustomerBillAmountInScorpio(billId, originalAmount))
102+
})
103+
}
104+
44105
describe('Billing Scheduler Period Coverage', {
45106
viewportHeight: 1080,
46107
viewportWidth: 1920,
@@ -66,6 +127,7 @@ describe('Billing Scheduler Period Coverage', {
66127
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
67128
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')
68129
cy.intercept('GET', '**/shoppingCart/item/').as('cartItem')
130+
cy.intercept('GET', '**/paymentInfo').as('getPaymentInfo')
69131

70132
// ============================================
71133
// Verify catalog and product spec exist (from happy journey)
@@ -140,7 +202,12 @@ describe('Billing Scheduler Period Coverage', {
140202
cy.getBySel('savePricePlan').click()
141203
cy.getBySel('offerNext').click()
142204

143-
cy.getBySel('procurement').select('automatic')
205+
cy.wait('@getPaymentInfo')
206+
.its('response.body.gatewaysCount')
207+
.should('be.greaterThan', 0)
208+
cy.getBySel('procurement')
209+
.select('automatic')
210+
.should('have.value', 'automatic')
144211
cy.getBySel('offerNext').click()
145212

146213
waitForInitialPaginatedList('**/catalog/productOffering?*', () => {
@@ -189,16 +256,19 @@ describe('Billing Scheduler Period Coverage', {
189256
cy.getBySel('cartPurchase').click()
190257

191258
cy.wait('@getBilling')
259+
cy.wait(2000)
260+
cy.deferPaymentRedirect()
192261
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
193262
cy.wait('@createOrder')
194-
cy.wait('@getOrders')
263+
cy.waitForOrdersBeforePayment()
195264

196265
// ============================================
197266
// Step 4: Complete payment (activation)
198267
// ============================================
199268
cy.intercept('**/charging/api/orderManagement/orders/confirm/').as('checkin')
200-
cy.completePayment()
269+
cy.completePayment({ recurring: true })
201270
cy.wait('@checkin')
271+
cy.waitForOrdersAfterPayment()
202272

203273
cy.getBySel('ordersTable').should('be.visible')
204274
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
@@ -409,18 +479,12 @@ describe('Billing Scheduler Period Coverage', {
409479
expect(new Date(acbrs[0].periodCoverage.endDateTime).getTime()).to.equal(week2End.getTime())
410480

411481
// ============================================
412-
// Payment scheduler: recurring charge comes back pending on the
413-
// first attempt, so the CB must be left untouched ('new'), then
414-
// settles on the next scheduler run once the charge resolves.
482+
// Stripe leaves the first charge pending; Redsys uses a sandbox
483+
// rejection amount. Both must leave the bill 'new' and then settle.
415484
// ============================================
416485
const cbId = acbrs[0].bill.id
417486

418-
cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/processing`)
419-
runPaymentScheduler()
420-
expectCustomerBillState(cbId, 'new')
421-
422-
runPaymentScheduler()
423-
expectCustomerBillState(cbId, 'settled')
487+
expectRecurringPaymentToRetry(cbId, 'processing')
424488
})
425489

426490
// — Week 3 —
@@ -465,18 +529,12 @@ describe('Billing Scheduler Period Coverage', {
465529
expect(new Date(acbrs[0].periodCoverage.endDateTime).getTime()).to.equal(week4End.getTime())
466530

467531
// ============================================
468-
// Payment scheduler: recurring charge fails on the first attempt,
469-
// so the CB must be left untouched ('new'), then settles on the
470-
// next scheduler run once the charge succeeds.
532+
// Stripe requires a new payment method; Redsys uses a sandbox
533+
// rejection amount. Both must leave the bill 'new' and then settle.
471534
// ============================================
472535
const cbId = acbrs[0].bill.id
473536

474-
cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/requires_payment_method`)
475-
runPaymentScheduler()
476-
expectCustomerBillState(cbId, 'new')
477-
478-
runPaymentScheduler()
479-
expectCustomerBillState(cbId, 'settled')
537+
expectRecurringPaymentToRetry(cbId, 'requires_payment_method')
480538
})
481539

482540
// ============================================

cypress/e2e/edges/global-states/test-2.cy.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ describe('Check order global states - Reverse test (auto and semi failed, iterat
4545
cy.intercept('POST', '**/shoppingCart/item/').as('postCart')
4646
cy.intercept('POST', '**/ordering/productOrder').as('createOrder')
4747
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
48+
cy.intercept('GET', '**/ordering/productOrder?*relatedParty.role=seller*').as('getProviderOrders')
4849
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')
50+
cy.intercept('GET', '**/catalog/productOffering?*', (request) => {
51+
delete request.headers['if-none-match']
52+
})
4953
cy.intercept('PATCH', '**/ordering/productOrder/**').as('patchOrder')
5054

5155
cy.loginAsAdmin()
@@ -103,13 +107,14 @@ describe('Check order global states - Reverse test (auto and semi failed, iterat
103107
cy.getBySel('shoppingCart').click()
104108
cy.getBySel('cartPurchase').click()
105109

106-
cy.intercept('POST', '**/ordering/productOrder').as('createOrder')
110+
cy.deferPaymentRedirect()
107111
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
108112
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')
109113

110114
cy.wait('@getBilling')
111115
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
112116
cy.wait('@createOrder')
117+
cy.waitForOrdersBeforePayment()
113118

114119
// Fail the auto and semi offering
115120
cy.intercept('**/charging/api/orderManagement/orders/confirm/').as('checkin')
@@ -120,8 +125,9 @@ describe('Check order global states - Reverse test (auto and semi failed, iterat
120125
// Navigate to product orders as provider
121126
cy.visit('/product-orders')
122127
cy.wait('@getOrders')
128+
cy.getBySel('ordersTable').should('be.visible')
123129
cy.getBySel('asProviderTab').should('be.visible').click()
124-
cy.wait('@getOrders')
130+
cy.wait('@getProviderOrders')
125131
cy.getBySel('ordersTable').should('be.visible')
126132

127133
// Find the most recent order and set auto and semi to failed

cypress/e2e/edges/global-states/test1.cy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ describe('Check order global states', {
5858
cy.changeSessionTo('SELLER ORG')
5959
cy.visit('/product-orders')
6060
cy.wait('@getOrders')
61+
cy.getBySel('ordersTable').should('be.visible')
6162
cy.getBySel('asProviderTab').should('be.visible').click()
62-
cy.wait('@getOrders')
63+
cy.wait('@getProviderOrders')
6364
cy.getBySel('ordersTable').should('be.visible')
6465
// check global state
6566
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
@@ -119,8 +120,9 @@ describe('Check order global states', {
119120
cy.changeSessionTo('SELLER ORG')
120121
cy.visit('/product-orders')
121122
cy.wait('@getOrders')
123+
cy.getBySel('ordersTable').should('be.visible')
122124
cy.getBySel('asProviderTab').should('be.visible').click()
123-
cy.wait('@getOrders')
125+
cy.wait('@getProviderOrders')
124126
cy.getBySel('ordersTable').should('be.visible')
125127
// check global state
126128
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {

cypress/e2e/edges/global-states/test2.cy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ describe('Check order global states', {
5858
cy.changeSessionTo('SELLER ORG')
5959
cy.visit('/product-orders')
6060
cy.wait('@getOrders')
61+
cy.getBySel('ordersTable').should('be.visible')
6162
cy.getBySel('asProviderTab').should('be.visible').click()
62-
cy.wait('@getOrders')
63+
cy.wait('@getProviderOrders')
6364
cy.getBySel('ordersTable').should('be.visible')
6465
// check global state
6566
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
@@ -119,8 +120,9 @@ describe('Check order global states', {
119120
cy.changeSessionTo('SELLER ORG')
120121
cy.visit('/product-orders')
121122
cy.wait('@getOrders')
123+
cy.getBySel('ordersTable').should('be.visible')
122124
cy.getBySel('asProviderTab').should('be.visible').click()
123-
cy.wait('@getOrders')
125+
cy.wait('@getProviderOrders')
124126
cy.getBySel('ordersTable').should('be.visible')
125127
// check global state
126128
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {

cypress/e2e/edges/global-states/test3.cy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ describe('Check order global states', {
5858
cy.changeSessionTo('SELLER ORG')
5959
cy.visit('/product-orders')
6060
cy.wait('@getOrders')
61+
cy.getBySel('ordersTable').should('be.visible')
6162
cy.getBySel('asProviderTab').should('be.visible').click()
62-
cy.wait('@getOrders')
63+
cy.wait('@getProviderOrders')
6364
cy.getBySel('ordersTable').should('be.visible')
6465
// check global state
6566
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
@@ -120,8 +121,9 @@ describe('Check order global states', {
120121
cy.changeSessionTo('SELLER ORG')
121122
cy.visit('/product-orders')
122123
cy.wait('@getOrders')
124+
cy.getBySel('ordersTable').should('be.visible')
123125
cy.getBySel('asProviderTab').should('be.visible').click()
124-
cy.wait('@getOrders')
126+
cy.wait('@getProviderOrders')
125127

126128
cy.getBySel('ordersTable').should('be.visible')
127129
// check global state

cypress/e2e/edges/global-states/test4.cy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ describe('Check order global states', {
5858
cy.changeSessionTo('SELLER ORG')
5959
cy.visit('/product-orders')
6060
cy.wait('@getOrders')
61+
cy.getBySel('ordersTable').should('be.visible')
6162
cy.getBySel('asProviderTab').should('be.visible').click()
62-
cy.wait('@getOrders')
63+
cy.wait('@getProviderOrders')
6364
cy.getBySel('ordersTable').should('be.visible')
6465
// check global state
6566
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
@@ -121,8 +122,9 @@ describe('Check order global states', {
121122
cy.changeSessionTo('SELLER ORG')
122123
cy.visit('/product-orders')
123124
cy.wait('@getOrders')
125+
cy.getBySel('ordersTable').should('be.visible')
124126
cy.getBySel('asProviderTab').should('be.visible').click()
125-
cy.wait('@getOrders')
127+
cy.wait('@getProviderOrders')
126128
cy.getBySel('ordersTable').should('be.visible')
127129
// check global state
128130
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {

0 commit comments

Comments
 (0)