Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion billing-server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ app.post('/v1/checkout/sessions', (req, res) => {
status: 'open',
customer: customerId,
payment_intent: paymentIntentId,
url: `http://localhost:${PORT}/stripe-checkout?session_id=${sessionId}`,
url: `http://localhost:${PORT}/stripe/checkin`,
line_items: lineItems,
}
stripeSessions.set(sessionId, session)
Expand Down
7 changes: 5 additions & 2 deletions charging-docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ services:
# Service Configuration
- BAE_SERVICE_HOST=http://proxy.docker:8004/
- BAE_CB_LOCAL_SITE=http://charging.docker:8006/
- GUNICORN_CMD_ARGS=--timeout 120

# TMForum APIs - all-in-one (port 8633)
- BAE_CB_CATALOG=http://host.docker.internal:8633/tmf-api/productCatalogManagement/v4
Expand All @@ -58,7 +59,10 @@ services:
- BAE_LP_OAUTH2_CUSTOMER_ROLE=customer
- BAE_LP_OAUTH2_ORG_ADMIN_ROLE=orgAdmin
# Payment Configuration
- BAE_CB_PAYMENT_METHOD=stripe
- BAE_CB_PAYMENT_METHOD=redsys
- BAE_CB_REDSYS_MERCHANT_CODE=263100000
- BAE_CB_REDSYS_TERMINAL=005
- BAE_CB_REDSYS_SECRET_KEY=sq7HjrUOBfKmC576ILgskD5srU870gJ7
- BAE_CB_BILLING_ENGINE=local
- BAE_CB_DPAS_CLIENT_API_URL=http://host.docker.internal:4201/api/payment-start
- BAE_SERVICE_HOST=http://localhost:4200/
Expand All @@ -70,4 +74,3 @@ services:
networks:
main:
external: true

4 changes: 3 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export default defineConfig({
requestTimeout: 30000,
responseTimeout: 60000,
env: {
PAYMENT_METHOD: process.env.BAE_CB_PAYMENT_METHOD || 'stripe'
PAYMENT_METHOD: process.env.BAE_CB_PAYMENT_METHOD || 'redsys',
REDSYS_ORIGIN: process.env.BAE_CB_REDSYS_ORIGIN || 'https://sis-t.redsys.es:25443',
REDSYS_AUTH_ORIGIN: process.env.BAE_CB_REDSYS_AUTH_ORIGIN || 'https://sis-d.redsys.es'
}
}
})
6 changes: 5 additions & 1 deletion cypress/e2e/01-happy-journey.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,16 @@ describe('Happy Journey E2E', {

cy.wait('@saveBilling')
cy.wait('@getBilling')
// Wait for the billing address to be processed and selected
cy.wait(2000)
cy.deferPaymentRedirect()
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
cy.wait('@createOrder')
cy.wait('@getOrders')
cy.waitForOrdersBeforePayment()
cy.intercept('**/charging/api/orderManagement/orders/confirm/').as('checkin')
cy.completePayment()
cy.wait('@checkin')
cy.waitForOrdersAfterPayment()
cy.wait('@getBilling')

// ============================================
Expand Down
102 changes: 80 additions & 22 deletions cypress/e2e/04-billing-and-payment-scheduler.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import {
*
* After each billing scheduler run, also triggers the payment scheduler
* cron job and verifies that the resulting CustomerBill ends up 'settled'
* once the recurring charge against the stored Stripe payment method succeeds.
* once the recurring charge against the stored payment method succeeds.
*
* Requires BAE_CB_BILLING_HTTP_ENABLED=true in the charging container.
*/
const CHARGING_URL = 'http://localhost:8006'
const TMF_URL = 'http://localhost:8633'
const SCORPIO_URL = 'http://localhost:1026'
const BILLING_SERVER_URL = 'http://localhost:4201'
const IS_REDSYS = Cypress.env('PAYMENT_METHOD') === 'redsys'

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

const patchCustomerBillAmountInScorpio = (
billId: string,
taxIncludedAmount: { unit: string, value: number | string }
) => {
const value = Number(taxIncludedAmount.value)

cy.request({
url: `${SCORPIO_URL}/ngsi-ld/v1/entities/${encodeURIComponent(billId)}/attrs`,
method: 'PATCH',
headers: { 'Content-Type': 'application/ld+json' },
body: {
taxIncludedAmount: {
type: 'Property',
tmfValue: { type: 'Property', value },
unit: { type: 'Property', value: taxIncludedAmount.unit },
value: { tmfValue: value, unit: taxIncludedAmount.unit },
},
'@context': ['https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld'],
},
}).then((res) => {
expect(res.status).to.eq(204)
})
}

const expectRecurringPaymentToRetry = (
billId: string,
stripeStatus: 'processing' | 'requires_payment_method'
) => {
const runAndVerifyRetry = (restorePayment: () => void = () => {}) => {
runPaymentScheduler()
expectCustomerBillState(billId, 'new')

restorePayment()
runPaymentScheduler()
expectCustomerBillState(billId, 'settled')
}

if (!IS_REDSYS) {
cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/${stripeStatus}`)
runAndVerifyRetry()
return
}

cy.request({
url: `${TMF_URL}/tmf-api/customerBillManagement/v4/customerBill/${billId}`,
method: 'GET',
}).then((res) => {
expect(res.status).to.eq(200)
const originalAmount = res.body.taxIncludedAmount
const rejectedAmount = {
...originalAmount,
value: `${Math.floor(Number(originalAmount.value))}.96`,
}

patchCustomerBillAmountInScorpio(billId, rejectedAmount)
runAndVerifyRetry(() => patchCustomerBillAmountInScorpio(billId, originalAmount))
})
}

describe('Billing Scheduler Period Coverage', {
viewportHeight: 1080,
viewportWidth: 1920,
Expand All @@ -66,6 +127,7 @@ describe('Billing Scheduler Period Coverage', {
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')
cy.intercept('GET', '**/shoppingCart/item/').as('cartItem')
cy.intercept('GET', '**/paymentInfo').as('getPaymentInfo')

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

cy.getBySel('procurement').select('automatic')
cy.wait('@getPaymentInfo')
.its('response.body.gatewaysCount')
.should('be.greaterThan', 0)
cy.getBySel('procurement')
.select('automatic')
.should('have.value', 'automatic')
cy.getBySel('offerNext').click()

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

cy.wait('@getBilling')
cy.wait(2000)
cy.deferPaymentRedirect()
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
cy.wait('@createOrder')
cy.wait('@getOrders')
cy.waitForOrdersBeforePayment()

// ============================================
// Step 4: Complete payment (activation)
// ============================================
cy.intercept('**/charging/api/orderManagement/orders/confirm/').as('checkin')
cy.completePayment()
cy.completePayment({ recurring: true })
cy.wait('@checkin')
cy.waitForOrdersAfterPayment()

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

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

cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/processing`)
runPaymentScheduler()
expectCustomerBillState(cbId, 'new')

runPaymentScheduler()
expectCustomerBillState(cbId, 'settled')
expectRecurringPaymentToRetry(cbId, 'processing')
})

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

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

cy.request(`${BILLING_SERVER_URL}/stripe/set-recurring-status/requires_payment_method`)
runPaymentScheduler()
expectCustomerBillState(cbId, 'new')

runPaymentScheduler()
expectCustomerBillState(cbId, 'settled')
expectRecurringPaymentToRetry(cbId, 'requires_payment_method')
})

// ============================================
Expand Down
10 changes: 8 additions & 2 deletions cypress/e2e/edges/global-states/test-2.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ describe('Check order global states - Reverse test (auto and semi failed, iterat
cy.intercept('POST', '**/shoppingCart/item/').as('postCart')
cy.intercept('POST', '**/ordering/productOrder').as('createOrder')
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
cy.intercept('GET', '**/ordering/productOrder?*relatedParty.role=seller*').as('getProviderOrders')
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')
cy.intercept('GET', '**/catalog/productOffering?*', (request) => {
delete request.headers['if-none-match']
})
cy.intercept('PATCH', '**/ordering/productOrder/**').as('patchOrder')

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

cy.intercept('POST', '**/ordering/productOrder').as('createOrder')
cy.deferPaymentRedirect()
cy.intercept('GET', '**/ordering/productOrder*').as('getOrders')
cy.intercept('GET', '**/account/billingAccount*').as('getBilling')

cy.wait('@getBilling')
cy.getBySel('checkout').should('be.visible').should('not.be.disabled').click()
cy.wait('@createOrder')
cy.waitForOrdersBeforePayment()

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

// Find the most recent order and set auto and semi to failed
Expand Down
6 changes: 4 additions & 2 deletions cypress/e2e/edges/global-states/test1.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down Expand Up @@ -119,8 +120,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down
6 changes: 4 additions & 2 deletions cypress/e2e/edges/global-states/test2.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down Expand Up @@ -119,8 +120,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down
6 changes: 4 additions & 2 deletions cypress/e2e/edges/global-states/test3.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down Expand Up @@ -120,8 +121,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')

cy.getBySel('ordersTable').should('be.visible')
// check global state
Expand Down
6 changes: 4 additions & 2 deletions cypress/e2e/edges/global-states/test4.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down Expand Up @@ -121,8 +122,9 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable').should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
Expand Down
10 changes: 6 additions & 4 deletions cypress/e2e/edges/global-states/test5.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable', { timeout: 200000 }).should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
cy.contains(/inprogress/i)
Expand Down Expand Up @@ -121,9 +122,10 @@ describe('Check order global states', {
cy.changeSessionTo('SELLER ORG')
cy.visit('/product-orders')
cy.wait('@getOrders')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getOrders')
cy.getBySel('ordersTable').should('be.visible')
cy.getBySel('asProviderTab').should('be.visible').click()
cy.wait('@getProviderOrders')
cy.getBySel('ordersTable', { timeout: 200000 }).should('be.visible')
// check global state
cy.getBySel('ordersTable').find('tbody tr').first().within(() => {
cy.contains(/partial/i)
Expand Down
Loading
Loading